diff --git a/web/src/components/SessionList.tsx b/web/src/components/SessionList.tsx index 5131b98ae..8abb19343 100644 --- a/web/src/components/SessionList.tsx +++ b/web/src/components/SessionList.tsx @@ -11,8 +11,10 @@ import { getSessionModelLabel } from '@/lib/sessionModelLabel' import { useTranslation } from '@/lib/use-translation' type SessionGroup = { + key: string directory: string displayName: string + machineId: string | null sessions: SessionSummary[] latestUpdatedAt: number hasActiveSession: boolean @@ -26,33 +28,49 @@ function getGroupDisplayName(directory: string): string { return `${parts[parts.length - 2]}/${parts[parts.length - 1]}` } +export const UNKNOWN_MACHINE_ID = '__unknown__' + function groupSessionsByDirectory(sessions: SessionSummary[]): SessionGroup[] { - const groups = new Map() + const groups = new Map() sessions.forEach(session => { const path = session.metadata?.worktree?.basePath ?? session.metadata?.path ?? 'Other' - if (!groups.has(path)) { - groups.set(path, []) + const machineId = session.metadata?.machineId ?? null + const key = `${machineId ?? UNKNOWN_MACHINE_ID}::${path}` + if (!groups.has(key)) { + groups.set(key, { + directory: path, + machineId, + sessions: [] + }) } - groups.get(path)!.push(session) + groups.get(key)!.sessions.push(session) }) return Array.from(groups.entries()) - .map(([directory, groupSessions]) => { - const sortedSessions = [...groupSessions].sort((a, b) => { + .map(([key, group]) => { + const sortedSessions = [...group.sessions].sort((a, b) => { const rankA = a.active ? (a.pendingRequestsCount > 0 ? 0 : 1) : 2 const rankB = b.active ? (b.pendingRequestsCount > 0 ? 0 : 1) : 2 if (rankA !== rankB) return rankA - rankB return b.updatedAt - a.updatedAt }) - const latestUpdatedAt = groupSessions.reduce( + const latestUpdatedAt = group.sessions.reduce( (max, s) => (s.updatedAt > max ? s.updatedAt : max), -Infinity ) - const hasActiveSession = groupSessions.some(s => s.active) - const displayName = getGroupDisplayName(directory) + const hasActiveSession = group.sessions.some(s => s.active) + const displayName = getGroupDisplayName(group.directory) - return { directory, displayName, sessions: sortedSessions, latestUpdatedAt, hasActiveSession } + return { + key, + directory: group.directory, + displayName, + machineId: group.machineId, + sessions: sortedSessions, + latestUpdatedAt, + hasActiveSession + } }) .sort((a, b) => { if (a.hasActiveSession !== b.hasActiveSession) { @@ -148,6 +166,27 @@ function getAgentLabel(session: SessionSummary): string { return 'unknown' } +function MachineIcon(props: { className?: string }) { + return ( + + + + + + ) +} + function formatRelativeTime(value: number, t: (key: string, params?: Record) => string): string | null { const ms = value < 1_000_000_000_000 ? value * 1000 : value if (!Number.isFinite(ms)) return null @@ -203,6 +242,7 @@ function SessionItem(props: { const statusDotClass = s.active ? (s.thinking ? 'bg-[#007AFF]' : 'bg-[var(--app-badge-success-text)]') : 'bg-[var(--app-hint)]' + const todoProgress = getTodoProgress(s) return ( <> {!isCollapsed ? ( -
+
{group.sessions.map((s) => ( { void refetch() }, [refetch]) - const projectCount = new Set(sessions.map(s => s.metadata?.worktree?.basePath ?? s.metadata?.path ?? 'Other')).size + const projectCount = useMemo(() => new Set(sessions.map(s => + s.metadata?.worktree?.basePath ?? s.metadata?.path ?? 'Other' + )).size, [sessions]) + const machineLabelsById = useMemo(() => { + const labels: Record = {} + for (const machine of machines) { + labels[machine.id] = getMachineTitle(machine) + } + return labels + }, [machines]) const sessionMatch = matchRoute({ to: '/sessions/$sessionId', fuzzy: true }) const selectedSessionId = sessionMatch && sessionMatch.sessionId !== 'new' ? sessionMatch.sessionId : null const isSessionsIndex = pathname === '/sessions' || pathname === '/sessions/' @@ -160,6 +177,7 @@ function SessionsPage() { isLoading={isLoading} renderHeader={false} api={api} + machineLabelsById={machineLabelsById} />