perf(panel): cached info and history paint first, lazy markdown up the thread, background prefetch

This commit is contained in:
hh committed 2026-09-02 05:16:13 +02:00
1 parent 20568915db
commit 9080eb7dee
10 files changed
+192 -16

No files matched your search

+14 -7
View File
@@ -8,6 +8,7 @@
import { cn } from "$lib/utils";
import type { ActivityModel } from "./activity.svelte";
import { summarizeInput, toolLabel } from "./activity.svelte";
import { cacheableHistory, historyKey } from "./history-cache";
import { usePanelHost } from "./host";
import Markdown from "./markdown.svelte";
import QuestionCard from "./question-card.svelte";
@@ -26,7 +27,9 @@
} = $props();
const RESULT_CLIP = 600;
const CACHED_MESSAGES = 80;
// Markdown for the last few screens renders at once; the rest waits
// until scrolled near, so a long thread paints as fast as a short one.
const EAGER_MARKDOWN = 40;
const host = usePanelHost();
const NEAR_BOTTOM_PX = 120;
const TICK_MS = 1000;
@@ -59,7 +62,7 @@
failure = null;
if (messages === null) {
const cached = host.cache?.get<HistoryMessage[]>(
`history:${conversationId}`
historyKey(conversationId)
);
if (cached) {
messages = cached;
@@ -69,10 +72,7 @@
({ messages } = await client.history(conversationId));
loadedAt = new Date().toISOString();
settle();
host.cache?.set(
`history:${conversationId}`,
messages.slice(-CACHED_MESSAGES)
);
host.cache?.set(historyKey(conversationId), cacheableHistory(messages));
} catch (cause) {
failure = cause instanceof Error ? cause.message : String(cause);
}
@@ -181,6 +181,12 @@
openSystem = next;
}
// Keys count from the end: when the network copy grows the head past
// what the cache held, the tail keeps its nodes and its rendered markdown.
function keyOf(message: HistoryMessage, index: number, total: number) {
return `${index - total}:${message.role}:${message.ts ?? ""}`;
}
function blocks(message: HistoryMessage): ContentBlock[] {
return typeof message.content === "string"
? [{ text: message.content, type: "text" }]
@@ -237,7 +243,7 @@
title="Empty conversation"
/>
{:else}
{#each messages as message, index (index)}
{#each messages as message, index (keyOf(message, index, messages.length))}
{@const parts = blocks(message)}
{@const isResultOnly = parts.every((b) => b.type === "tool_result")}
{@const system = systemText(message)}
@@ -303,6 +309,7 @@
? "rounded-2xl rounded-br-md bg-primary/10 px-3.5 py-2"
: "px-1 py-1"
)}
eager={index >= messages.length - EAGER_MARKDOWN}
text={block.text}
/>
{:else if block.type === "tool_use"}