-
-
{selectedProject.name}
-
- {selectedProject.priority}
-
+
+ {/* Tiger's PROJECTS.md — primary */}
+
+
+
+ Tiger's Projects
+ PROJECTS.md
+ {fileProjects.length}
+
+ {fileProjects.length === 0 ? (
+
+
+
No projects in PROJECTS.md yet.
+
+ ) : (
+
+ {fileProjects.map((p) => )}
+
+ )}
- {/* Simple Kanban - Tasks by Status */}
- {loadingTasks ? (
-
-
-
- ) : (
-
- {(["backlog", "ready", "in-progress", "review", "done"] as const).map((status) => (
-
-
- {status.replace("-", " ")} ({tasksByStatus[status].length})
-
-
- {tasksByStatus[status].map((task) => (
-
- {task.title}
- {task.description && (
-
- {task.description}
-
- )}
- {task.assigned_agent && (
-
- {task.assigned_agent}
-
- )}
-
- ))}
-
-
- ))}
+ {/* Dashboard queue — secondary */}
+ {dbProjects.length > 0 && (
+
+
+
+ Dashboard Queue
+ {dbProjects.length}
+
+
+ Projects you've queued — Tiger will add them to PROJECTS.md when he processes them.
+
+
+ {dbProjects.map((p) => )}
+
)}
)}
)
-}
\ No newline at end of file
+}
diff --git a/dashboard/src/app/settings/page.tsx b/dashboard/src/app/settings/page.tsx
index 9301d3c..d770778 100644
--- a/dashboard/src/app/settings/page.tsx
+++ b/dashboard/src/app/settings/page.tsx
@@ -1,12 +1,14 @@
"use client"
+
/**
- * settings/page.tsx — Tiger configuration
+ * settings/page.tsx — Tiger configuration + API key management
*
- * Sections:
- * 1. Model — primary model dropdown + fallback models
- * 2. Session — dmScope, compaction mode
- * 3. Telegram — enabled toggle, streaming mode
- * 4. Commands — native commands, ownerDisplay
+ * Sections (in order):
+ * 0. API Keys & Router — ANTHROPIC, OPENROUTER, TELEGRAM keys + TIGER_ROUTER_MODEL
+ * 1. Model — OpenClaw global model dropdown + fallbacks + compaction
+ * 2. Session — dmScope
+ * 3. Telegram — enabled toggle, streaming mode
+ * 4. Commands — native commands, ownerDisplay, restart
*/
import * as React from "react"
@@ -14,6 +16,7 @@ import useSWR from "swr"
import {
Settings2, Save, Loader2, RefreshCw,
Bot, MessageSquare, Terminal, Cpu, AlertCircle, Check,
+ Key, RotateCcw,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
@@ -31,12 +34,22 @@ interface ModelInfo {
}
interface OpenClawConfig {
- agents?: { defaults?: { model?: { primary?: string; fallbacks?: string[] }; compaction?: { mode?: string } } }
+ agents?: {
+ defaults?: {
+ model?: { primary?: string; fallbacks?: string[] }
+ compaction?: { mode?: string }
+ }
+ }
session?: { dmScope?: string }
channels?: { telegram?: { enabled?: boolean; streaming?: string } }
commands?: { native?: string; ownerDisplay?: string; restart?: boolean }
}
+interface KeyPresence {
+ isSet: boolean
+ preview?: string
+}
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
const fetcher = (url: string) => fetch(url).then(r => r.json())
@@ -57,7 +70,7 @@ function set(obj: any, path: string, value: any): any {
return result
}
-// ─── Sub-components ───────────────────────────────────────────────────────────
+// ─── Shared sub-components ────────────────────────────────────────────────────
function SettingRow({ label, hint, children }: { label: string; hint?: string; children: React.ReactNode }) {
return (
@@ -107,26 +120,17 @@ function SelectInput({ value, options, onChange }: {
)
}
-// ─── Model dropdown component ─────────────────────────────────────────────────
-
function ModelSelect({ value, models, onChange }: {
value: string
models: ModelInfo[]
onChange: (v: string) => void
}) {
- // Group by provider
const grouped = models.reduce
>((acc, m) => {
if (!acc[m.provider]) acc[m.provider] = []
acc[m.provider].push(m)
return acc
}, {})
- const providerLabels: Record = {
- minimax: "MiniMax",
- "minimax-portal": "MiniMax Portal",
- openrouter: "OpenRouter",
- }
-
const current = models.find(m => m.id === value)
return (
@@ -137,15 +141,13 @@ function ModelSelect({ value, models, onChange }: {
className="h-9 rounded-md border border-input bg-background px-3 py-1 text-sm font-mono focus:outline-none focus:ring-1 focus:ring-ring w-full max-w-sm"
>
{Object.entries(grouped).map(([prov, mods]) => (
-
)}
+ {/* ── 0. API Keys (always rendered — has its own data fetching) ─── */}
+