diff --git a/web/src/App.svelte b/web/src/App.svelte --- a/web/src/App.svelte +++ b/web/src/App.svelte @@ -16,6 +16,9 @@ import ProjectDetail from './views/ProjectDetail.svelte'; import TaskTree from './views/TaskTree.svelte'; import DecisionGraph from './views/DecisionGraph.svelte'; + import AgentMonitor from './views/AgentMonitor.svelte'; + import SessionHistory from './views/SessionHistory.svelte'; + import GraphSearch from './views/GraphSearch.svelte'; import Placeholder from './views/Placeholder.svelte'; let wsConnection: ReturnType | null = null; @@ -63,11 +66,11 @@ {:else if currentRoute.name === 'decision-graph'} {:else if currentRoute.name === 'agent-monitor'} - + {:else if currentRoute.name === 'session-history'} - + {:else if currentRoute.name === 'graph-search'} - + {:else} {/if} diff --git a/web/src/lib/date-formatting.ts b/web/src/lib/date-formatting.ts --- a/web/src/lib/date-formatting.ts +++ b/web/src/lib/date-formatting.ts @@ -19,3 +19,35 @@ return dateStr; } } + +/** + * Format a date string into relative time format (e.g., "2s ago", "1m ago"). + * @param dateStr - ISO date string + * @returns Relative time string or "unknown" if parsing fails + */ +export function formatRelativeTime(dateStr: string): string { + try { + const date = new Date(dateStr); + const now = new Date(); + const seconds = Math.floor((now.getTime() - date.getTime()) / 1000); + + if (seconds < 60) { + return `${seconds}s ago`; + } + + const minutes = Math.floor(seconds / 60); + 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); + return `${days}d ago`; + } catch { + return 'unknown'; + } +} diff --git a/web/src/views/AgentMonitor.svelte b/web/src/views/AgentMonitor.svelte new file mode 100644 --- /dev/null +++ b/web/src/views/AgentMonitor.svelte @@ -0,0 +1,291 @@ + + +
+
+

Agent Monitor

+
+ +
+ {#if agentsState.loading} + + {:else if goalId} +
+
+ {agentsState.activeAgents.length} + {agentsState.activeAgents.length === 1 ? 'agent' : 'agents'} running +
+
+ {#each agentsState.activeAgents as agent (agent.agent_id)} + + {/each} +
+
+ +
+ + +
+ +
+ {#if agentsState.eventFeed.length === 0} +
+

No agent activity yet. Events will appear here as agents run.

+
+ {/if} + {#each agentsState.eventFeed as event (event.type + Math.random())} +
+ {getEventTime(event)} + {formatEventMessage(event)} +
+ {/each} +
+ {:else} +
+

No goal ID provided. Navigate to a goal first.

+
+ {/if} +
+
+ +