From 551a7a050e8cd25e795026a1060c9d5b5ea028bf Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 10 Oct 2026 01:36:39 +0900 Subject: perf(chat): fast open of long in-flight turns - Batch live chat events: fold a burst in one pass per animation frame (rAF + timeout fallback for hidden tabs) instead of one reactive cycle per WS frame. Flushed before send/resync to keep ordering. - De-dup the late-join replay against committed history: provisional chunks of the in-flight turn already persisted (CR-6 per-step persistence) are hidden by aligned-prefix matching (fail-safe: mismatch shows, never hides). - Memoize chunks/turnMetrics getters by state identity. - LazyCollapse: tool/thinking card bodies mount only on first open. - content-visibility:auto on transcript rows. - scripts/probe-replay.ts + bench-pure.ts for measuring the replay path. --- src/features/chat/ui/ChatView.svelte | 144 +++++++++++++---------------------- 1 file changed, 54 insertions(+), 90 deletions(-) (limited to 'src/features/chat/ui/ChatView.svelte') diff --git a/src/features/chat/ui/ChatView.svelte b/src/features/chat/ui/ChatView.svelte index 8f153bf..5e4513c 100644 --- a/src/features/chat/ui/ChatView.svelte +++ b/src/features/chat/ui/ChatView.svelte @@ -10,6 +10,7 @@ type TurnMetricsEntry, } from "../../../core/metrics"; import { Markdown } from "../../markdown"; + import LazyCollapse from "./LazyCollapse.svelte"; const badgeClass = { success: "badge-success", @@ -102,6 +103,22 @@ }); +{#snippet wrench()} + +{/snippet} + {#snippet chunkRow(rendered: RenderedChunk)} {#if rendered.role === "user"}
-
- -
+ + {#snippet title()} {rendered.streaming ? "Thinking" : "Thoughts"} {#if rendered.streaming} {/if} -
-
-

{rendered.chunk.text}

-
-
+ {/snippet} +

{rendered.chunk.type === "thinking" ? rendered.chunk.text : ""}

+
{:else if rendered.chunk.type === "tool-call" || rendered.chunk.type === "tool-result"} @@ -151,61 +165,26 @@
{#if rendered.chunk.type === "tool-call"} -
- -
- - {rendered.chunk.toolName} -
-
-
{JSON.stringify(
-                  rendered.chunk.input,
-                  null,
-                  2,
-                )}
-
-
+ {@const call = rendered.chunk} + + {#snippet title()} + {@render wrench()} + {call.toolName} + {/snippet} +
{JSON.stringify(call.input, null, 2)}
+
{:else} -
- -
- - {rendered.chunk.toolName} - {#if rendered.chunk.isError} + {@const res = rendered.chunk} + + {#snippet title()} + {@render wrench()} + {res.toolName} + {#if res.isError} error {/if} -
-
-
{rendered.chunk.content}
-
-
+ {/snippet} +
{res.content}
+ {/if}
@@ -246,6 +225,9 @@ {/if} {#each keyedRows as { row, key } (key)} + +
{#if row.kind === "step-metrics"} {@const sv = viewStepMetrics(row.step, row.index)} {@const stepCache = viewCacheRate(row.step.usage)} @@ -311,47 +293,29 @@
{#each row.group.entries as entry, entryIndex (entryIndex)} -
- -
- + + {#snippet title()} + {@render wrench()} {entry.call.toolName} {#if entry.result?.isError} error {:else if entry.result === null} {/if} -
-
-
{JSON.stringify(
-                      entry.call.input,
-                      null,
-                      2,
-                    )}
- {#if entry.result} -
{entry.result.content}
- {/if} -
-
+ {/snippet} +
{JSON.stringify(entry.call.input, null, 2)}
+ {#if entry.result} +
{entry.result.content}
+ {/if} + {/each}
{/if} + {/each} {#if providerRetry} {@const rv = viewProviderRetry(providerRetry)} -- cgit v1.2.3