From 3d985accf50153dec024af7f3724d3fa51d050a9 Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Fri, 13 Mar 2026 08:31:23 -0500 Subject: [PATCH] feat(web): settings page with profile, providers, and models Add settings page displaying user profile, configured LLM providers with status badges, and available models table with context window size, cost, and reasoning capability indicators. Refs #31 Co-Authored-By: Claude Opus 4.6 --- .../web/src/app/(dashboard)/settings/page.tsx | 150 ++++++++++++++++++ docs/TASKS.md | 4 +- 2 files changed, 152 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/app/(dashboard)/settings/page.tsx diff --git a/apps/web/src/app/(dashboard)/settings/page.tsx b/apps/web/src/app/(dashboard)/settings/page.tsx new file mode 100644 index 0000000..cc6c925 --- /dev/null +++ b/apps/web/src/app/(dashboard)/settings/page.tsx @@ -0,0 +1,150 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import { api } from '@/lib/api'; +import { useSession } from '@/lib/auth-client'; + +interface ProviderInfo { + name: string; + enabled: boolean; + modelCount: number; +} + +interface ModelInfo { + id: string; + name: string; + provider: string; + contextWindow: number; + reasoning: boolean; + cost: { input: number; output: number }; +} + +export default function SettingsPage(): React.ReactElement { + const { data: session } = useSession(); + const [providers, setProviders] = useState([]); + const [models, setModels] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + Promise.all([ + api('/api/providers').catch(() => []), + api('/api/providers/models').catch(() => []), + ]) + .then(([p, m]) => { + setProviders(p); + setModels(m); + }) + .finally(() => setLoading(false)); + }, []); + + return ( +
+

Settings

+ + {/* Profile */} +
+

Profile

+
+ {session?.user ? ( +
+ + + +
+ ) : ( +

Not signed in

+ )} +
+
+ + {/* Providers */} +
+

LLM Providers

+ {loading ? ( +

Loading providers...

+ ) : providers.length === 0 ? ( +
+

No providers configured

+
+ ) : ( +
+ {providers.map((p) => ( +
+
+

{p.name}

+

{p.modelCount} models available

+
+ + {p.enabled ? 'Active' : 'Disabled'} + +
+ ))} +
+ )} +
+ + {/* Models */} +
+

Available Models

+ {loading ? ( +

Loading models...

+ ) : models.length === 0 ? ( +
+

No models available

+
+ ) : ( +
+ + + + + + + + + + + {models.map((m) => ( + + + + + + + ))} + +
ModelProviderContextCost (in/out)
+ {m.name} + {m.reasoning && ( + reasoning + )} + {m.provider} + {(m.contextWindow / 1000).toFixed(0)}k + + ${m.cost.input} / ${m.cost.output} +
+
+ )} +
+
+ ); +} + +function Field({ label, value }: { label: string; value: string }): React.ReactElement { + return ( +
+ {label} + {value} +
+ ); +} diff --git a/docs/TASKS.md b/docs/TASKS.md index dc4feda..7728607 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -33,8 +33,8 @@ | P3-002 | done | Phase 3 | Auth pages — login, registration, SSO redirect | #83 | #27 | | P3-003 | done | Phase 3 | Chat UI — conversations, messages, streaming | #84 | #28 | | P3-004 | done | Phase 3 | Task management — list view + kanban board | #86 | #29 | -| P3-005 | in-progress | Phase 3 | Project & mission views — dashboard + PRD viewer | — | #30 | -| P3-006 | not-started | Phase 3 | Settings — provider config, profile, integrations | — | #31 | +| P3-005 | done | Phase 3 | Project & mission views — dashboard + PRD viewer | #87 | #30 | +| P3-006 | in-progress | Phase 3 | Settings — provider config, profile, integrations | — | #31 | | P3-007 | not-started | Phase 3 | Admin panel — user management, RBAC | — | #32 | | P3-008 | not-started | Phase 3 | Verify Phase 3 — web dashboard functional E2E | — | #33 | | P4-001 | not-started | Phase 4 | @mosaic/memory — preference + insight stores | — | #34 |