diff --git a/web/src/components/chat/ChatEventThumbnailsRow.tsx b/web/src/components/chat/ChatEventThumbnailsRow.tsx new file mode 100644 index 000000000..bf2c5e88f --- /dev/null +++ b/web/src/components/chat/ChatEventThumbnailsRow.tsx @@ -0,0 +1,42 @@ +import { useApiHost } from "@/api"; + +type ChatEventThumbnailsRowProps = { + events: { id: string }[]; +}; + +/** + * Horizontal scroll row of event thumbnail images for chat (e.g. after search_objects). + * Renders nothing when events is empty. + */ +export function ChatEventThumbnailsRow({ + events, +}: ChatEventThumbnailsRowProps) { + const apiHost = useApiHost(); + + if (events.length === 0) return null; + + return ( +
+
+
+ {events.map((event) => ( + + + + ))} +
+
+
+ ); +} diff --git a/web/src/pages/Chat.tsx b/web/src/pages/Chat.tsx index 01912b77e..ac812b58e 100644 --- a/web/src/pages/Chat.tsx +++ b/web/src/pages/Chat.tsx @@ -4,10 +4,14 @@ import { FaArrowUpLong } from "react-icons/fa6"; import { useTranslation } from "react-i18next"; import { useState, useCallback } from "react"; import axios from "axios"; +import { ChatEventThumbnailsRow } from "@/components/chat/ChatEventThumbnailsRow"; import { MessageBubble } from "@/components/chat/ChatMessage"; import { ToolCallBubble } from "@/components/chat/ToolCallBubble"; import type { ChatMessage } from "@/types/chat"; -import { streamChatCompletion } from "@/utils/chatUtil"; +import { + getEventIdsFromSearchObjectsToolCalls, + streamChatCompletion, +} from "@/utils/chatUtil"; export default function ChatPage() { const { t } = useTranslation(["views/chat"]); @@ -118,6 +122,15 @@ export default function ChatPage() { msg.role === "user" || !isLoading || i < messages.length - 1 } /> + {msg.role === "assistant" && + (() => { + const isComplete = !isLoading || i < messages.length - 1; + if (!isComplete) return null; + const events = getEventIdsFromSearchObjectsToolCalls( + msg.toolCalls, + ); + return ; + })()} ); })} diff --git a/web/src/utils/chatUtil.ts b/web/src/utils/chatUtil.ts index 5eb26bb72..323475a21 100644 --- a/web/src/utils/chatUtil.ts +++ b/web/src/utils/chatUtil.ts @@ -161,3 +161,33 @@ export async function streamChatCompletion( onDone(); } } + +/** + * Parse search_objects tool call response(s) into event ids for thumbnails. + */ +export function getEventIdsFromSearchObjectsToolCalls( + toolCalls: ToolCall[] | undefined, +): { id: string }[] { + if (!toolCalls?.length) return []; + const results: { id: string }[] = []; + for (const tc of toolCalls) { + if (tc.name !== "search_objects" || !tc.response?.trim()) continue; + try { + const parsed = JSON.parse(tc.response) as unknown; + if (!Array.isArray(parsed)) continue; + for (const item of parsed) { + if ( + item && + typeof item === "object" && + "id" in item && + typeof (item as { id: unknown }).id === "string" + ) { + results.push({ id: (item as { id: string }).id }); + } + } + } catch { + // ignore parse errors + } + } + return results; +}