feat(FN-2152): render collapsed tool call previews in chat

- Emit tool_start/tool_end SSE events from dashboard chat backend and parse them in streaming client helpers
- Track in-flight and completed tool calls in useChat/useQuickChat to preserve tool output summaries alongside assistant messages
- Render collapsed tool call preview blocks in ChatView and QuickChatFAB with dedicated tokenized styles for compact output summaries
- Expand frontend and backend test coverage for SSE tool events, hook state transitions, and collapsed preview rendering behavior
- Add a changeset for @gsxdsm/fusion documenting the new tool-call display behavior
This commit is contained in:
Fusion
2026-04-19 23:47:12 -07:00
committed by gsxdsm
parent eaf99e6279
commit 7b8bbaaa0a
12 changed files with 1021 additions and 84 deletions

View File

@@ -94,6 +94,7 @@ const defaultChatState = {
isStreaming: false,
streamingText: "",
streamingThinking: "",
streamingToolCalls: [],
selectSession: vi.fn(),
createSession: vi.fn().mockResolvedValue({ id: "session-new", agentId: "__fn_agent__" }),
archiveSession: vi.fn(),
@@ -398,6 +399,143 @@ describe("ChatView", () => {
expect(screen.getByText("Hi there!")).toBeInTheDocument();
});
it("renders tool calls from persisted messages", () => {
setupMockChat({
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Tool Chat", updatedAt: "2026-04-08T00:00:00.000Z" },
messages: [
{
id: "msg-002",
sessionId: "session-001",
role: "assistant",
content: "I used a tool",
toolCalls: [
{
toolName: "read",
args: { path: "foo.ts" },
isError: false,
result: "contents",
status: "completed",
},
],
createdAt: "2026-04-08T00:01:00.000Z",
},
],
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
expect(screen.getByText("read")).toBeInTheDocument();
const preview = document.querySelector(".chat-tool-call-preview");
expect(preview).toHaveTextContent("result: contents");
});
it("renders streaming tool calls", () => {
setupMockChat({
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Tool Chat", updatedAt: "2026-04-08T00:00:00.000Z" },
messages: [{ id: "msg-001", sessionId: "session-001", role: "user", content: "Use tools", createdAt: "2026-04-08T00:00:00.000Z" }],
isStreaming: true,
streamingText: "Working...",
streamingToolCalls: [
{
toolName: "read",
args: { path: "foo.ts" },
isError: false,
status: "running",
},
],
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const streamingBubble = document.querySelector(".chat-message--streaming");
expect(streamingBubble).toBeInTheDocument();
expect(within(streamingBubble as HTMLElement).getByText("read")).toBeInTheDocument();
const preview = (streamingBubble as HTMLElement).querySelector(".chat-tool-call-preview");
expect(preview).toHaveTextContent("path=foo.ts");
});
it("completed tool calls are collapsed by default", () => {
setupMockChat({
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Tool Chat", updatedAt: "2026-04-08T00:00:00.000Z" },
messages: [
{
id: "msg-002",
sessionId: "session-001",
role: "assistant",
content: "Done",
toolCalls: [
{
toolName: "read",
isError: false,
result: "contents",
status: "completed",
},
],
createdAt: "2026-04-08T00:01:00.000Z",
},
],
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
const details = document.querySelector(".chat-tool-call") as HTMLDetailsElement | null;
expect(details).toBeInTheDocument();
expect(details?.open).toBe(false);
});
it("running tool calls show running indicator", () => {
setupMockChat({
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Tool Chat", updatedAt: "2026-04-08T00:00:00.000Z" },
messages: [
{
id: "msg-002",
sessionId: "session-001",
role: "assistant",
content: "Running",
toolCalls: [
{
toolName: "read",
isError: false,
status: "running",
},
],
createdAt: "2026-04-08T00:01:00.000Z",
},
],
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
expect(document.querySelector(".chat-tool-call--running")).toBeInTheDocument();
});
it("error tool calls show error styling", () => {
setupMockChat({
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Tool Chat", updatedAt: "2026-04-08T00:00:00.000Z" },
messages: [
{
id: "msg-002",
sessionId: "session-001",
role: "assistant",
content: "Error",
toolCalls: [
{
toolName: "read",
isError: true,
result: "failed",
status: "completed",
},
],
createdAt: "2026-04-08T00:01:00.000Z",
},
],
});
render(<ChatView projectId="proj-123" addToast={vi.fn()} />);
expect(document.querySelector(".chat-tool-call--error")).toBeInTheDocument();
});
it("shows resolved agent name in assistant message avatar", async () => {
setupMockChat({
activeSession: { id: "session-001", agentId: "agent-001", status: "active", title: "Agent Chat", updatedAt: "2026-04-08T00:00:00.000Z" },