2026-05-08 19:53:21 -04:00
|
|
|
import { useTheme } from '../hooks/useTheme'
|
|
|
|
|
import { useLocalStorage } from '../hooks/useLocalStorage'
|
2026-05-13 18:10:38 -04:00
|
|
|
import { useSSEContext } from '../contexts/SSEContext'
|
|
|
|
|
import { Sun, Moon, Monitor, Zap, Radio } from 'lucide-react'
|
2026-05-08 19:53:21 -04:00
|
|
|
|
2026-05-13 18:10:38 -04:00
|
|
|
const SSE_STATUS_COPY: Record<string, { label: string; description: string; color: string }> = {
|
|
|
|
|
connected: {
|
|
|
|
|
label: 'Connected',
|
|
|
|
|
description: 'Real-time updates are active. Agent status, tasks, and progress stream live.',
|
|
|
|
|
color: 'text-green-500',
|
|
|
|
|
},
|
|
|
|
|
connecting: {
|
|
|
|
|
label: 'Connecting…',
|
|
|
|
|
description: 'Establishing SSE connection to the backend.',
|
|
|
|
|
color: 'text-yellow-500',
|
|
|
|
|
},
|
|
|
|
|
reconnecting: {
|
|
|
|
|
label: 'Reconnecting…',
|
|
|
|
|
description: 'Connection lost. Retrying with exponential back-off.',
|
|
|
|
|
color: 'text-yellow-500',
|
|
|
|
|
},
|
|
|
|
|
error: {
|
|
|
|
|
label: 'Disconnected',
|
|
|
|
|
description: 'Could not connect to the SSE endpoint. Check that the backend is reachable.',
|
|
|
|
|
color: 'text-red-500',
|
|
|
|
|
},
|
|
|
|
|
}
|
2026-05-08 19:53:21 -04:00
|
|
|
|
2026-05-07 20:15:30 -04:00
|
|
|
export default function SettingsPage() {
|
2026-05-08 19:53:21 -04:00
|
|
|
const { isDark, toggleTheme } = useTheme()
|
|
|
|
|
const [gatewayUrl, setGatewayUrl] = useLocalStorage('cc-gateway-url', '')
|
2026-05-13 18:10:38 -04:00
|
|
|
const { sseStatus } = useSSEContext()
|
|
|
|
|
const sseInfo = SSE_STATUS_COPY[sseStatus] ?? SSE_STATUS_COPY.error
|
2026-05-08 19:53:21 -04:00
|
|
|
|
2026-05-07 20:15:30 -04:00
|
|
|
return (
|
2026-05-08 19:53:21 -04:00
|
|
|
<div className="space-y-8 max-w-2xl">
|
|
|
|
|
<header>
|
|
|
|
|
<h1 className="text-2xl font-bold">Settings</h1>
|
|
|
|
|
<p className="text-on-surface-variant">Application preferences</p>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
{/* Appearance */}
|
|
|
|
|
<section className="space-y-4">
|
|
|
|
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
|
|
|
|
<Monitor size={20} className="text-primary" />
|
|
|
|
|
Appearance
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
<div className="p-5 rounded-xl border border-surface-light bg-surface-dark">
|
|
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-medium">Dark Mode</p>
|
|
|
|
|
<p className="text-sm text-on-surface-variant">Toggle between dark and light themes</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button
|
|
|
|
|
onClick={toggleTheme}
|
|
|
|
|
className={`relative w-14 h-8 rounded-full transition-colors duration-200 ${
|
|
|
|
|
isDark ? 'bg-primary' : 'bg-surface-lighter'
|
|
|
|
|
}`}
|
|
|
|
|
aria-label="Toggle dark mode"
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
className={`absolute top-1 h-6 w-6 rounded-full bg-white transition-transform duration-200 flex items-center justify-center ${
|
|
|
|
|
isDark ? 'translate-x-7' : 'translate-x-1'
|
|
|
|
|
}`}
|
|
|
|
|
>
|
|
|
|
|
{isDark ? <Moon size={14} className="text-surface-darkest" /> : <Sun size={14} className="text-yellow-500" />}
|
|
|
|
|
</div>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
{/* Connection */}
|
|
|
|
|
<section className="space-y-4">
|
|
|
|
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
|
|
|
|
<Zap size={20} className="text-primary" />
|
|
|
|
|
Connection
|
|
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
<div className="p-5 rounded-xl border border-surface-light bg-surface-dark space-y-3">
|
|
|
|
|
<div>
|
|
|
|
|
<label htmlFor="gateway-url" className="block text-sm font-medium mb-1">
|
|
|
|
|
Gateway URL
|
|
|
|
|
</label>
|
|
|
|
|
<input
|
|
|
|
|
id="gateway-url"
|
|
|
|
|
type="url"
|
|
|
|
|
value={gatewayUrl}
|
|
|
|
|
onChange={(e) => setGatewayUrl(e.target.value)}
|
|
|
|
|
placeholder="http://localhost:8080"
|
|
|
|
|
className="w-full px-3 py-2 rounded-lg border border-surface-light bg-surface-darkest text-on-surface placeholder:text-on-surface-muted focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors"
|
|
|
|
|
/>
|
|
|
|
|
<p className="text-xs text-on-surface-muted mt-1">
|
|
|
|
|
The backend Gateway address for API requests
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
2026-05-13 18:10:38 -04:00
|
|
|
{/* Real-time connection status */}
|
2026-05-08 19:53:21 -04:00
|
|
|
<section className="space-y-4">
|
|
|
|
|
<h2 className="text-lg font-semibold flex items-center gap-2">
|
2026-05-13 18:10:38 -04:00
|
|
|
<Radio size={20} className="text-primary" />
|
|
|
|
|
Real-time Updates
|
2026-05-08 19:53:21 -04:00
|
|
|
</h2>
|
|
|
|
|
|
|
|
|
|
<div className="p-5 rounded-xl border border-surface-light bg-surface-dark space-y-3">
|
2026-05-13 18:10:38 -04:00
|
|
|
<div className="flex items-center justify-between">
|
|
|
|
|
<div>
|
|
|
|
|
<p className="font-medium">SSE Connection</p>
|
|
|
|
|
<p className="text-sm text-on-surface-variant mt-0.5">{sseInfo.description}</p>
|
2026-05-08 19:53:21 -04:00
|
|
|
</div>
|
2026-05-13 18:10:38 -04:00
|
|
|
<span className={`text-sm font-semibold whitespace-nowrap ${sseInfo.color}`}>
|
|
|
|
|
{sseInfo.label}
|
|
|
|
|
</span>
|
2026-05-08 19:53:21 -04:00
|
|
|
</div>
|
2026-05-13 18:10:38 -04:00
|
|
|
<p className="text-xs text-on-surface-muted">
|
|
|
|
|
Endpoint: <code className="bg-surface-light px-1.5 py-0.5 rounded text-on-surface-variant">/api/events</code>
|
|
|
|
|
· Events: agent.status, agent.task, agent.progress, fleet.update
|
|
|
|
|
</p>
|
|
|
|
|
<p className="text-xs text-on-surface-muted">
|
|
|
|
|
Polling is disabled. All status updates are pushed from the server over a persistent SSE connection.
|
|
|
|
|
The client reconnects automatically with exponential back-off on drop.
|
|
|
|
|
</p>
|
2026-05-08 19:53:21 -04:00
|
|
|
</div>
|
|
|
|
|
</section>
|
2026-05-07 20:15:30 -04:00
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
}
|