From 289f9498a2a1be90eb0a45540d581ce191a42084 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 6 May 2026 15:03:40 -0700 Subject: [PATCH] feat(ui/agents): add Conversation pane Middle pane unioning the initial conversation snapshot with live SSE events. user_message and assistant_message render as MessageBubble; tool_call as ToolCallCard; file_diff folds into FilesChangedAccordion. --- .../components/three-pane/conversation.tsx | 114 ++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 ui/litellm-dashboard/src/app/(dashboard)/agents/components/three-pane/conversation.tsx diff --git a/ui/litellm-dashboard/src/app/(dashboard)/agents/components/three-pane/conversation.tsx b/ui/litellm-dashboard/src/app/(dashboard)/agents/components/three-pane/conversation.tsx new file mode 100644 index 00000000000..99a90371496 --- /dev/null +++ b/ui/litellm-dashboard/src/app/(dashboard)/agents/components/three-pane/conversation.tsx @@ -0,0 +1,114 @@ +"use client"; + +/** + * Conversation — middle pane of the three-pane Session view. + * + * Renders the union of: + * - the initial conversation snapshot (from /v2/sessions/{sid}/conversation) + * - live events from useSessionEventStream (user_message, assistant_message, + * tool_call, file_diff) + * + * file_diff events fold into a single "N Files Changed" accordion at the + * bottom (cumulative across the run, per the LIT-2881 spec). + */ +import { useMemo } from "react"; +import { Empty, Typography } from "antd"; +import MessageBubble from "@/app/(dashboard)/agents/components/three-pane/message-bubble"; +import ToolCallCard from "@/app/(dashboard)/agents/components/three-pane/tool-call-card"; +import FilesChangedAccordion from "@/app/(dashboard)/agents/components/three-pane/files-changed-accordion"; +import Composer from "@/app/(dashboard)/agents/components/three-pane/composer"; +import type { + CloudAgentConversationMessage, + CloudAgentRunEvent, + FileDiffPayload, + ToolCallPayload, +} from "@/types/cloud-agents"; + +const { Title } = Typography; + +interface ConversationProps { + sessionId: string; + accessToken: string | null; + initialMessages: CloudAgentConversationMessage[]; + events: CloudAgentRunEvent[]; +} + +interface DisplayItem { + key: string; + kind: "message" | "tool_call"; + message?: CloudAgentConversationMessage; + toolCall?: ToolCallPayload; +} + +function buildDisplayItems( + initialMessages: CloudAgentConversationMessage[], + events: CloudAgentRunEvent[], +): { items: DisplayItem[]; diffs: FileDiffPayload[] } { + const items: DisplayItem[] = initialMessages.map((m) => ({ + key: `msg-${m.id}`, + kind: "message", + message: m, + })); + const diffs: FileDiffPayload[] = []; + for (const evt of events) { + if (evt.type === "user_message" || evt.type === "assistant_message") { + const role = evt.type === "user_message" ? "user" : "assistant"; + const content = String((evt.payload as { content?: unknown }).content ?? ""); + items.push({ + key: `evt-${evt.seq}`, + kind: "message", + message: { + id: `evt-${evt.seq}`, + role: role as CloudAgentConversationMessage["role"], + content, + created_at: evt.created_at, + }, + }); + } else if (evt.type === "tool_call") { + items.push({ + key: `evt-${evt.seq}`, + kind: "tool_call", + toolCall: evt.payload as unknown as ToolCallPayload, + }); + } else if (evt.type === "file_diff") { + diffs.push(evt.payload as unknown as FileDiffPayload); + } + } + return { items, diffs }; +} + +export default function Conversation({ sessionId, accessToken, initialMessages, events }: ConversationProps) { + const { items, diffs } = useMemo(() => buildDisplayItems(initialMessages, events), [initialMessages, events]); + + return ( +
+
+ + Conversation + +
+
+ {items.length === 0 && diffs.length === 0 ? ( + + ) : ( + <> + {items.map((it) => + it.kind === "message" && it.message ? ( + + ) : it.kind === "tool_call" && it.toolCall ? ( + + ) : null, + )} + + + )} +
+ +
+ ); +}