diff --git a/src/lib/components/Graph/Graph.svelte b/src/lib/components/Graph/Graph.svelte index 7c0af8e..2cac2c2 100644 --- a/src/lib/components/Graph/Graph.svelte +++ b/src/lib/components/Graph/Graph.svelte @@ -17,6 +17,8 @@ import GraphCircleNode from './GraphCircleNode.svelte'; import * as d3Force from 'd3-force'; + import type { SessionAgentState } from '$lib/session.svelte'; + import { cn } from '$lib/utils'; import { Button, @@ -42,6 +44,7 @@ type DraggingNode = { id: string; position: { x: number; y: number } }; let { + class: className, agents, groups, selectedAgent = $bindable(undefined), @@ -51,7 +54,8 @@ viewOnly = false, fitDefault = true }: { - agents: z.infer['agents']; + class?: string; + agents: z.infer['agents'] | SessionAgentState[]; groups: z.infer['groups']; selectedAgent?: number | null; onSelect?: (idx: number) => void; @@ -258,7 +262,7 @@ bind:nodes bind:edges {nodeTypes} - class=" [&_.svelte-flow__edge-wrapper]:z-10!" + class={cn('[&_.svelte-flow__edge-wrapper]:z-10!', className)} fitView onnodedragstart={handleNodeDragStart} onnodedrag={handleNodeDrag} diff --git a/src/lib/components/SessionSwitcher.svelte b/src/lib/components/SessionSwitcher.svelte index 402a03f..fc672de 100644 --- a/src/lib/components/SessionSwitcher.svelte +++ b/src/lib/components/SessionSwitcher.svelte @@ -13,6 +13,8 @@ let ctx = appContext.get(); + let { class: className }: { class?: string } = $props(); + let sessionSearcherOpen = $state(false); let triggerRef = $state(null!); @@ -27,12 +29,12 @@ -
+
- {#snippet child({ props }: {props: any})} + {#snippet child({ props }: { props: any })}
{/if} + { + return dollarFmt.format(microcents / 100_000_000); +}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any export type WithoutChild = T extends { child?: any } ? Omit : T; // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/src/routes/(app)/session/overview/+page.svelte b/src/routes/(app)/session/overview/+page.svelte new file mode 100644 index 0000000..83f7927 --- /dev/null +++ b/src/routes/(app)/session/overview/+page.svelte @@ -0,0 +1,238 @@ + + +
+ +{#if session} +
+
+
+

Session Overview

+

Here's what's happening in your session.

+
+ +
+

Current Session

+ +
+ +
+ { + session?.kill(); + }}>Kill session +
+ +
+ +
+ +

Agents

+

{agents.length}

+

+ {agents.filter( + (a) => a.status.type === 'running' && a.status.connectionStatus.type === 'connected' + ).length} running, {agents.filter((a) => a.status.type === 'waiting').length} waiting, {agents.filter( + (a) => a.status.type === 'stopped' + ).length} + stopped +

+
+ + +

Threads

+

{sessions.length}

+
+ + +

Messages

+

+ {threads.reduce((acc, t) => acc + t.messages.length, 0)} +

+

total over all threads

+
+ + +

Session Budget

+ {#if session.extended} + {@const budget = session.extended.runningBudget} +

+ 0 && 'text-destructive')} + >{fmtMicrocents(budget.startBudget - budget.remaining + budget.overclaim)} + / {fmtMicrocents(budget.startBudget)} +

+ {:else} + + {/if} +

current usage / session budget

+
+
+ +
+ + + Threads + + + + + + Thread + Status + Created + Closed + Messages + + + + {#each threads as thread} + + + + {thread.name}{thread.name} + + + {thread.state.state} + + {#if thread.state.state === 'closed'} + "{thread.state.summary}" + {:else} + Thread is open - when closed, the close summary will show here. + {/if} + + + + + + {(tick || true) && + formatDistanceToNow(thread.timestamp, { + addSuffix: true + })} + + {format(thread.timestamp, 'PPPppp')} + + + {#if thread.state.state === 'closed'} + + {(tick || true) && + formatDistanceToNow(thread.state.timestamp, { + addSuffix: true + })} + + {format(thread.timestamp, 'PPPppp')} + + {:else} + + {/if} + + {thread.messages.length} + + {:else} + + + No threads in this session. + + + {/each} + + + + + + + + Agent Graph + + + + a.name)]} class="size-full" controls /> + + + + + + + Messages (last hour) + + +
+ Chart placeholder +
+
+
+
+
+{/if}