Long chats stay fast; search in a chat; media overview
Performance (measured with 5,900 messages: open 9 s -> 0.7 s, typing 440 ms -> 25 ms)
- GET /sessions/{id}?limit=N loads the end of a chat (from the last summary
on, at most N); /sessions/{id}/messages?before|after|around loads more
(scrolling up stops at a summary unless asked). Without parameters the
full history as before (terminal client).
- Chat view: memoised message list (typing, streaming and status changes no
longer re-render the history), finished turns fold into "N steps · M files"
with the answer visible, tool card bodies and summaries are built only when
opened, scroll-to-bottom only when content changes.
Search: /sessions/{id}/search over the whole chat; a hit loads its part
(around) and marks the message, "back to the latest messages" returns.
Media: /sessions/{id}/media lists images, videos and audio of a chat and its
subagents (generated images, tool output, files written, media in answers,
attachments); ⋯ -> Media shows them with filters, a large view and a jump to
the message.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6e9eda28b5
commit
3b0764450a
14 files changed
+1278
-99
No files matched your search
@@ -1,5 +1,6 @@
|
||||
import { Icon } from "./Icon";
|
||||
import { FileFigure, imageSize, imageSrc } from "./FileFigure";
|
||||
import { useState } from "react";
|
||||
import { useT } from "../i18n";
|
||||
import { navigate } from "../router";
|
||||
import { useStore } from "../store";
|
||||
@@ -68,6 +69,8 @@ export function TodoList({ todos }: { todos: Todo[] }) {
|
||||
|
||||
export function ToolCard({ call, result, running, baseDir }: { call: ToolCall; result?: ToolResult; running: boolean; baseDir?: string }) {
|
||||
const t = useT();
|
||||
// The body (diff, output) is only built while the card is open – long chats have thousands.
|
||||
const [toggled, setToggled] = useState<boolean | null>(null);
|
||||
const progress = useStore((s) => s.progress[call.id]);
|
||||
const status = result ? (result.is_error ? "err" : "ok") : running ? "run" : "err";
|
||||
const statusText = result ? <Icon name={result.is_error ? "x-mark" : "check"} /> : running ? "…" : "–";
|
||||
@@ -119,9 +122,13 @@ export function ToolCard({ call, result, running, baseDir }: { call: ToolCall; r
|
||||
const openByDefault =
|
||||
(status === "err" && !!result) ||
|
||||
(!!result && !result.is_error && ((diffLines > 0 && diffLines <= 40) || call.name === "generate_image"));
|
||||
const open = toggled ?? openByDefault;
|
||||
|
||||
return (
|
||||
<details className={`tool ${status === "run" ? "running" : ""}`} open={openByDefault}>
|
||||
<details className={`tool ${status === "run" ? "running" : ""}`} open={open} onToggle={(e) => {
|
||||
const now = (e.currentTarget as HTMLDetailsElement).open;
|
||||
if (now !== open) setToggled(now);
|
||||
}}>
|
||||
<summary>
|
||||
<span className="tool-name">{call.name}</span>
|
||||
<span className="tool-title">{toolTitle(call.name, call.input)}</span>
|
||||
@@ -130,6 +137,7 @@ export function ToolCard({ call, result, running, baseDir }: { call: ToolCall; r
|
||||
{call.name === "task" && call.input?.background && <span className="chip">{t("tool.background")}</span>}
|
||||
{status === "run" ? <Spinner small /> : <span className={`status ${status}`}>{statusText}</span>}
|
||||
</summary>
|
||||
{open && (
|
||||
<div className="tool-body">
|
||||
{call.name === "bash" && <pre className="mono">$ {call.input?.command}</pre>}
|
||||
{(call.name === "edit" || call.name === "write") && meta?.diff && <Diff diff={meta.diff} />}
|
||||
@@ -186,6 +194,7 @@ export function ToolCard({ call, result, running, baseDir }: { call: ToolCall; r
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</details>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user