From 22412d2fa3e062f9288c553f55b36d3a307fc829 Mon Sep 17 00:00:00 2001 From: dtoro Date: Thu, 9 Jul 2026 09:48:21 +0200 Subject: [PATCH] feat: group tool calls per turn + session entity graph in chat rail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two chat UX changes (share Chat.svelte, committed together). Tool-call grouping (ToolCallGroup.svelte): - Replaced the one-
-per-tool-call list with a single collapsible group per assistant turn, headed by tool count + a name preview and a status icon (spinning wrench in progress, check done, X on error). - The group auto-collapses the instant its turn finishes streaming, so a completed round shows as one compact pill; historical/loaded turns start collapsed. The auto-collapse fires once at the streaming→done transition, leaving manual toggles alone afterward. Session graph rail (SessionGraph.svelte) — replaces the old ContextRail (fleet health / pending approvals / live events), which is deleted: - A force-directed graph that starts empty (animated constellation empty state) and grows as the conversation references entities. Slugs are extracted from message text and tool *arguments* only — never bulk result rows, so a single get_health_summary doesn't dump all 168 entities — then validated against the backend via fetchGraph (cached) with check/execution probe entities excluded. Nodes are colored by health; edges appear once both endpoints are present. - Clicking a node highlights it and its neighbors and opens an inline detail panel below: slug/type/state, health + freshness, top attributes, in-graph relations (clickable to hop), and a Full detail button opening the entity sheet. - The rail is resizable via a drag handle (260–620px, persisted to localStorage). The header's global fleet-health dots are unchanged; only the right-rail content was replaced. Risk: reversible_low (UI-only). The slug extractor is scoped to focused mentions by design; edges may be slightly incomplete since only root-fetched entities contribute edges, which is acceptable for a session overview. Verification: verified in the browser preview — loading a real session built a 4-node graph (hubris/caddy/netbird-vps/strong) with the hubris→caddy relationship edge; clicking hubris showed "proxmox-host · active · healthy · checked 40s ago" with attributes and relations; dragging the handle resized 320→440px and persisted; a 34-tool historical turn renders as one collapsed pill that expands on click. tsc clean. Co-Authored-By: Claude Opus 4.8 --- web/src/lib/components/ContextRail.svelte | 109 ----- web/src/lib/components/SessionGraph.svelte | 441 ++++++++++++++++++++ web/src/lib/components/ToolCallGroup.svelte | 79 ++++ web/src/pages/Chat.svelte | 87 ++-- 4 files changed, 569 insertions(+), 147 deletions(-) delete mode 100644 web/src/lib/components/ContextRail.svelte create mode 100644 web/src/lib/components/SessionGraph.svelte create mode 100644 web/src/lib/components/ToolCallGroup.svelte diff --git a/web/src/lib/components/ContextRail.svelte b/web/src/lib/components/ContextRail.svelte deleted file mode 100644 index cc06bcf..0000000 --- a/web/src/lib/components/ContextRail.svelte +++ /dev/null @@ -1,109 +0,0 @@ - - - diff --git a/web/src/lib/components/SessionGraph.svelte b/web/src/lib/components/SessionGraph.svelte new file mode 100644 index 0000000..adfde18 --- /dev/null +++ b/web/src/lib/components/SessionGraph.svelte @@ -0,0 +1,441 @@ + + + + + diff --git a/web/src/lib/components/ToolCallGroup.svelte b/web/src/lib/components/ToolCallGroup.svelte new file mode 100644 index 0000000..0fb42d5 --- /dev/null +++ b/web/src/lib/components/ToolCallGroup.svelte @@ -0,0 +1,79 @@ + + +{#if tools.length} +
+ + {#if inProgress} + + {:else if hasError} + + {:else} + + {/if} + {tools.length} tool{tools.length === 1 ? '' : 's'} + {names} + + +
+ {#each tools as tool (tool.id)} +
+
+ {#if tool.type === 'tool_result' && tool.error} + + {:else if tool.type === 'tool_result'} + + {:else} + + {/if} + {tool.name} + {toolSummary(tool.args)} +
+
+ {#if tool.args} +
{JSON.stringify(tool.args, null, 2)}
+ {/if} + {#if tool.type === 'tool_result'} +
{tool.error ?? JSON.stringify(tool.result, null, 2)}
+ {/if} +
+
+ {/each} +
+
+{/if} diff --git a/web/src/pages/Chat.svelte b/web/src/pages/Chat.svelte index eda5a3a..a3ddbca 100644 --- a/web/src/pages/Chat.svelte +++ b/web/src/pages/Chat.svelte @@ -1,14 +1,12 @@
@@ -87,35 +106,13 @@
{/if} - {#each $messages as msg (msg.id)} + {#each $messages as msg, i (msg.id)}
{#if msg.role === 'user'}
{msg.text}
{:else}
- {#each msg.tools as tool (tool.id)} -
- - {#if tool.type === 'tool_result' && tool.error} - - {:else if tool.type === 'tool_result'} - - {:else} - - {/if} - {tool.name} - {toolSummary(tool.args)} - -
- {#if tool.args} -
{JSON.stringify(tool.args, null, 2)}
- {/if} - {#if tool.type === 'tool_result'} -
{tool.error ?? JSON.stringify(tool.result, null, 2)}
- {/if} -
-
- {/each} + {#if msg.text}
@@ -174,8 +171,22 @@
{#if showRail} -