diff --git a/server/src/web/routes/sessions.ts b/server/src/web/routes/sessions.ts index b03bda01..0502194b 100644 --- a/server/src/web/routes/sessions.ts +++ b/server/src/web/routes/sessions.ts @@ -8,15 +8,18 @@ type SessionSummaryMetadata = { name?: string path: string summary?: { text: string } + flavor?: string | null } type SessionSummary = { id: string active: boolean + activeAt: number updatedAt: number metadata: SessionSummaryMetadata | null todoProgress: { completed: number; total: number } | null pendingRequestsCount: number + modelMode?: 'default' | 'sonnet' | 'opus' | null } function toSessionSummary(session: Session): SessionSummary { @@ -25,7 +28,8 @@ function toSessionSummary(session: Session): SessionSummary { const metadata: SessionSummaryMetadata | null = session.metadata ? { name: session.metadata.name, path: session.metadata.path, - summary: session.metadata.summary ? { text: session.metadata.summary.text } : undefined + summary: session.metadata.summary ? { text: session.metadata.summary.text } : undefined, + flavor: session.metadata.flavor ?? null } : null const todoProgress = session.todos?.length ? { @@ -36,10 +40,12 @@ function toSessionSummary(session: Session): SessionSummary { return { id: session.id, active: session.active, + activeAt: session.activeAt, updatedAt: session.updatedAt, metadata, todoProgress, - pendingRequestsCount + pendingRequestsCount, + modelMode: session.modelMode ?? null } } diff --git a/web/src/components/SessionList.tsx b/web/src/components/SessionList.tsx index 1ee38413..561b7476 100644 --- a/web/src/components/SessionList.tsx +++ b/web/src/components/SessionList.tsx @@ -1,5 +1,3 @@ -import { Badge } from '@/components/ui/badge' -import { Card, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' import type { SessionSummary } from '@/types/api' function PlusIcon(props: { className?: string }) { @@ -63,6 +61,37 @@ function getTodoProgress(session: SessionSummary): { completed: number; total: n return session.todoProgress } +function getAgentLabel(session: SessionSummary): string { + const flavor = session.metadata?.flavor?.trim() + if (flavor) return flavor + return 'unknown' +} + +function getModelLabel(session: SessionSummary): string { + return session.modelMode ?? 'default' +} + +function formatRelativeTime(value: number): string | null { + const ms = value < 1_000_000_000_000 ? value * 1000 : value + if (!Number.isFinite(ms)) return null + const delta = Date.now() - ms + if (delta < 60_000) return 'just now' + const minutes = Math.floor(delta / 60_000) + if (minutes < 60) return `${minutes}m ago` + const hours = Math.floor(minutes / 60) + if (hours < 24) return `${hours}h ago` + const days = Math.floor(hours / 24) + if (days < 7) return `${days}d ago` + return new Date(ms).toLocaleDateString() +} + +function getLastSeenLabel(session: SessionSummary): string | null { + if (session.active) return null + const lastSeen = formatRelativeTime(session.activeAt ?? session.updatedAt) + if (!lastSeen) return null + return `last seen ${lastSeen}` +} + export function SessionList(props: { sessions: SessionSummary[] onSelect: (sessionId: string) => void @@ -71,54 +100,70 @@ export function SessionList(props: { isLoading: boolean }) { return ( -
-
-
+
+
+
{props.sessions.length} sessions
-
+
{props.sessions.map((s) => ( - props.onSelect(s.id)}> - -
- {getSessionTitle(s)} -
- {(() => { - const progress = getTodoProgress(s) - if (!progress) return null - return ( - - - {progress.completed}/{progress.total} - - ) - })()} - {s.active ? ( - s.pendingRequestsCount > 0 ? ( - {s.pendingRequestsCount} pending - ) : ( - active - ) - ) : ( - inactive - )} + ))}
diff --git a/web/src/index.css b/web/src/index.css index 8cc0c7b8..53ba1c68 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -104,6 +104,13 @@ body { height: 100%; } +@media (hover: hover) and (pointer: fine) { + .session-list-item:hover, + .session-list-new-button:hover { + background-color: var(--app-subtle-bg); + } +} + /* Markdown styles */ .markdown-content a { color: var(--app-link); text-decoration: underline; } .markdown-content code { background: var(--app-inline-code-bg); padding: 0.1em 0.3em; border-radius: 4px; font-size: 0.9em; } diff --git a/web/src/types/api.ts b/web/src/types/api.ts index 31c9cafb..79ef3324 100644 --- a/web/src/types/api.ts +++ b/web/src/types/api.ts @@ -62,15 +62,18 @@ export type SessionSummaryMetadata = { name?: string path: string summary?: { text: string } + flavor?: string | null } export type SessionSummary = { id: string active: boolean + activeAt: number updatedAt: number metadata: SessionSummaryMetadata | null todoProgress: { completed: number; total: number } | null pendingRequestsCount: number + modelMode?: ModelMode } export type MessageStatus = 'sending' | 'sent' | 'failed'