From 12ce301277d2a981ef1eeb1f5b4eec412c20fae7 Mon Sep 17 00:00:00 2001 From: Nick Huang Date: Wed, 27 Aug 2025 21:02:42 -0400 Subject: [PATCH 1/4] Citations w truncated URLs --- .../components/ChatMessage.tsx | 12 ++- .../deep-agent-chat/components/Citations.tsx | 80 +++++++++++++++++++ .../web/src/features/deep-agent-chat/utils.ts | 3 + 3 files changed, 93 insertions(+), 2 deletions(-) create mode 100644 apps/web/src/features/deep-agent-chat/components/Citations.tsx diff --git a/apps/web/src/features/deep-agent-chat/components/ChatMessage.tsx b/apps/web/src/features/deep-agent-chat/components/ChatMessage.tsx index 42dcf7a5..af69623e 100644 --- a/apps/web/src/features/deep-agent-chat/components/ChatMessage.tsx +++ b/apps/web/src/features/deep-agent-chat/components/ChatMessage.tsx @@ -5,9 +5,10 @@ import { User, Bot } from "lucide-react"; import { SubAgentIndicator } from "./SubAgentIndicator"; import { ToolCallBox } from "./ToolCallBox"; import { MarkdownContent } from "./MarkdownContent"; +import { Citations } from "./Citations"; import type { SubAgent, ToolCall } from "../types"; import { Message } from "@langchain/langgraph-sdk"; -import { extractStringFromMessageContent } from "../utils"; +import { extractStringFromMessageContent, extractCitationUrls } from "../utils"; import { cn } from "@/lib/utils"; interface ChatMessageProps { @@ -58,6 +59,10 @@ export const ChatMessage = React.memo( } }, [selectedSubAgent, onSelectSubAgent, subAgentsString, subAgents]); + const citations = useMemo(() => { + return extractCitationUrls(messageContent); + }, [messageContent]); + return (
( {messageContent}

) : ( - + <> + + {citations.length > 0 && } + )}
)} diff --git a/apps/web/src/features/deep-agent-chat/components/Citations.tsx b/apps/web/src/features/deep-agent-chat/components/Citations.tsx new file mode 100644 index 00000000..2b01491b --- /dev/null +++ b/apps/web/src/features/deep-agent-chat/components/Citations.tsx @@ -0,0 +1,80 @@ +"use client"; + +import React, { useMemo } from "react"; +import { ExternalLink } from "lucide-react"; + +interface CitationsProps { + urls: string[]; +} + +export const Citations = React.memo(({ urls }) => { + if (urls.length === 0) return null; + + return ( +
+
+ Sources ({urls.length}) +
+
+ {urls.map((url, index) => ( + + ))} +
+
+ ); +}); + +Citations.displayName = "Citations"; + +const CHARACTER_LIMIT = 40; + +interface CitationProps { + url: string; +} +const Citation = React.memo(({ url }) => { + const displayUrl = url.length > CHARACTER_LIMIT + ? url.substring(0, CHARACTER_LIMIT) + '...' + : url; + + const favicon = useMemo(() => { + try { + const urlObj = new URL(url); + const domain = urlObj.hostname; + return `https://www.google.com/s2/favicons?domain=${domain}&sz=32`; + } catch { + return undefined; + } + }, [url]); + + return ( + + <> + {favicon ? ( + { + e.currentTarget.style.display = 'none'; + e.currentTarget.nextElementSibling?.classList.remove('hidden'); + }} + /> + ) : ( + + ) + } + + {displayUrl} + + + + ); +}); + +Citation.displayName = "Citation"; \ No newline at end of file diff --git a/apps/web/src/features/deep-agent-chat/utils.ts b/apps/web/src/features/deep-agent-chat/utils.ts index 1ecaf6fa..9ca3800d 100644 --- a/apps/web/src/features/deep-agent-chat/utils.ts +++ b/apps/web/src/features/deep-agent-chat/utils.ts @@ -68,3 +68,6 @@ export function deploymentSupportsDeepAgents( ) { return deployment?.supportsDeepAgents ?? false; } + +export const extractCitationUrls = (text: string): string[] => + Array.from(text.matchAll(/\[([^\]]*)\]\(([^)]*)\)/g), match => match[2]); \ No newline at end of file From cfbe108e6c225411b025692dc87866042e5859a9 Mon Sep 17 00:00:00 2001 From: Nick Huang Date: Thu, 28 Aug 2025 01:18:11 -0400 Subject: [PATCH 2/4] Add document titles and document view --- .../components/ChatInterface.tsx | 24 ++++ .../components/ChatMessage.tsx | 17 ++- .../deep-agent-chat/components/Citations.tsx | 77 +++++++----- .../components/ToolCallBox.tsx | 119 +++++++++++++++--- .../web/src/features/deep-agent-chat/utils.ts | 30 ++++- 5 files changed, 212 insertions(+), 55 deletions(-) diff --git a/apps/web/src/features/deep-agent-chat/components/ChatInterface.tsx b/apps/web/src/features/deep-agent-chat/components/ChatInterface.tsx index 50ff5616..33b33810 100644 --- a/apps/web/src/features/deep-agent-chat/components/ChatInterface.tsx +++ b/apps/web/src/features/deep-agent-chat/components/ChatInterface.tsx @@ -31,6 +31,8 @@ import { Assistant, Message } from "@langchain/langgraph-sdk"; import { extractStringFromMessageContent, isPreparingToCallTaskTool, + extractDocumentsFromMessage, + Document, } from "../utils"; import { v4 as uuidv4 } from "uuid"; import { useQueryState } from "nuqs"; @@ -166,6 +168,27 @@ export const ChatInterface = React.memo( agentId, ); + const sourceToDocumentsMap = useMemo(() => { + const documents = messages + .filter( + (message) => + message.type === "tool" && typeof message.content === "string", + ) + .map((message) => + extractDocumentsFromMessage(message.content as string), + ) + .flat(); + return documents.reduce( + (acc, document) => { + if (document.source) { + acc[document.source] = document; + } + return acc; + }, + {} as Record, + ); + }, [messages]); + useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages]); @@ -444,6 +467,7 @@ export const ChatInterface = React.memo( selectedSubAgent={selectedSubAgent} onRestartFromAIMessage={handleRestartFromAIMessage} onRestartFromSubTask={handleRestartFromSubTask} + sourceToDocumentsMap={sourceToDocumentsMap} debugMode={debugMode} isLoading={isLoading} isLastMessage={index === processedMessages.length - 1} diff --git a/apps/web/src/features/deep-agent-chat/components/ChatMessage.tsx b/apps/web/src/features/deep-agent-chat/components/ChatMessage.tsx index f114f734..4148ebe4 100644 --- a/apps/web/src/features/deep-agent-chat/components/ChatMessage.tsx +++ b/apps/web/src/features/deep-agent-chat/components/ChatMessage.tsx @@ -8,7 +8,11 @@ import { MarkdownContent } from "./MarkdownContent"; import { Citations } from "./Citations"; import type { SubAgent, ToolCall } from "../types"; import { Message } from "@langchain/langgraph-sdk"; -import { extractStringFromMessageContent, extractCitationUrls } from "../utils"; +import { + extractStringFromMessageContent, + extractCitationUrls, + Document, +} from "../utils"; import { cn } from "@/lib/utils"; interface ChatMessageProps { @@ -19,6 +23,7 @@ interface ChatMessageProps { selectedSubAgent: SubAgent | null; onRestartFromAIMessage: (message: Message) => void; onRestartFromSubTask: (toolCallId: string) => void; + sourceToDocumentsMap: Record; debugMode?: boolean; isLastMessage?: boolean; isLoading?: boolean; @@ -33,6 +38,7 @@ export const ChatMessage = React.memo( selectedSubAgent, onRestartFromAIMessage, onRestartFromSubTask, + sourceToDocumentsMap, debugMode, isLastMessage, isLoading, @@ -122,7 +128,12 @@ export const ChatMessage = React.memo( ) : ( <> - {citations.length > 0 && } + {citations.length > 0 && ( + + )} )} @@ -139,7 +150,7 @@ export const ChatMessage = React.memo( )} {hasToolCalls && ( -
+
{toolCalls.map((toolCall: ToolCall) => { if (toolCall.name === "task") return null; return ( diff --git a/apps/web/src/features/deep-agent-chat/components/Citations.tsx b/apps/web/src/features/deep-agent-chat/components/Citations.tsx index 2b01491b..505e1ab6 100644 --- a/apps/web/src/features/deep-agent-chat/components/Citations.tsx +++ b/apps/web/src/features/deep-agent-chat/components/Citations.tsx @@ -2,27 +2,35 @@ import React, { useMemo } from "react"; import { ExternalLink } from "lucide-react"; +import { Document } from "../utils"; interface CitationsProps { urls: string[]; + sourceToDocumentsMap: Record; } -export const Citations = React.memo(({ urls }) => { - if (urls.length === 0) return null; +export const Citations = React.memo( + ({ urls, sourceToDocumentsMap }) => { + if (urls.length === 0) return null; - return ( -
-
- Sources ({urls.length}) -
-
- {urls.map((url, index) => ( - - ))} + return ( +
+
+ Sources ({urls.length}) +
+
+ {urls.map((url, index) => ( + + ))} +
-
- ); -}); + ); + }, +); Citations.displayName = "Citations"; @@ -30,11 +38,13 @@ const CHARACTER_LIMIT = 40; interface CitationProps { url: string; + document: Document | null; } -const Citation = React.memo(({ url }) => { - const displayUrl = url.length > CHARACTER_LIMIT - ? url.substring(0, CHARACTER_LIMIT) + '...' - : url; +export const Citation = React.memo(({ url, document }) => { + const displayUrl = + url.length > CHARACTER_LIMIT + ? url.substring(0, CHARACTER_LIMIT) + "..." + : url; const favicon = useMemo(() => { try { @@ -51,30 +61,29 @@ const Citation = React.memo(({ url }) => { href={url} target="_blank" rel="noopener noreferrer" - className="inline-flex items-center gap-1.5 px-2 py-1 rounded-md border border-gray-200 hover:border-gray-300 hover:bg-gray-50 transition-colors group" + className="group inline-flex items-center gap-1.5 rounded-md border border-gray-200 px-2 py-1 transition-colors hover:border-gray-300 hover:bg-gray-50" title={url} > <> {favicon ? ( - { - e.currentTarget.style.display = 'none'; - e.currentTarget.nextElementSibling?.classList.remove('hidden'); - }} - /> - ) : ( - - ) - } + { + e.currentTarget.style.display = "none"; + e.currentTarget.nextElementSibling?.classList.remove("hidden"); + }} + /> + ) : ( + + )} - {displayUrl} + {document?.title || displayUrl} ); }); -Citation.displayName = "Citation"; \ No newline at end of file +Citation.displayName = "Citation"; diff --git a/apps/web/src/features/deep-agent-chat/components/ToolCallBox.tsx b/apps/web/src/features/deep-agent-chat/components/ToolCallBox.tsx index 31792c7d..42c72cab 100644 --- a/apps/web/src/features/deep-agent-chat/components/ToolCallBox.tsx +++ b/apps/web/src/features/deep-agent-chat/components/ToolCallBox.tsx @@ -8,9 +8,19 @@ import { CheckCircle, AlertCircle, Loader, + ExternalLink, } from "lucide-react"; import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; import { ToolCall } from "../types"; +import { extractDocumentsFromMessage, Document } from "../utils"; +import { MarkdownContent } from "./MarkdownContent"; +import { Citation } from "./Citations"; interface ToolCallBoxProps { toolCall: ToolCall; @@ -75,6 +85,13 @@ export const ToolCallBox = React.memo(({ toolCall }) => { const hasContent = result || Object.keys(args).length > 0; + const documents = useMemo(() => { + if (result && typeof result === "string") { + return extractDocumentsFromMessage(result); + } + return []; + }, [result]); + return (
(({ toolCall }) => { > Result -
-                {typeof result === "string"
-                  ? result
-                  : JSON.stringify(result, null, 2)}
-              
+ {documents.length > 0 ? ( +
+ {documents.map((document, index) => ( + + ))} +
+ ) : ( +
+                  {typeof result === "string"
+                    ? result
+                    : JSON.stringify(result, null, 2)}
+                
+ )}
)}
@@ -203,3 +231,62 @@ export const ToolCallBox = React.memo(({ toolCall }) => { }); ToolCallBox.displayName = "ToolCallBox"; + +interface DocumentViewProps { + document: Document; +} + +const DocumentView = React.memo(({ document }) => { + const [isOpen, setIsOpen] = useState(false); + + return ( + <> +
setIsOpen(true)} + > +
+ {document.title || "Untitled Document"} +
+
+ {document.content + ? document.content.substring(0, 150) + "..." + : "No content"} +
+
+ + + + + + {document.title || "Document"} + + {document.source && ( +
+ +
+ )} +
+
+ {document.content ? ( + + ) : ( +

No content available

+ )} +
+
+
+ + ); +}); + +DocumentView.displayName = "DocumentView"; diff --git a/apps/web/src/features/deep-agent-chat/utils.ts b/apps/web/src/features/deep-agent-chat/utils.ts index 9ca3800d..1850ed03 100644 --- a/apps/web/src/features/deep-agent-chat/utils.ts +++ b/apps/web/src/features/deep-agent-chat/utils.ts @@ -1,6 +1,12 @@ import { Deployment } from "@/types/deployment"; import { Message } from "@langchain/langgraph-sdk"; +export interface Document { + title: string | null; + content: string | null; + source: string | null; +} + export function extractStringFromMessageContent(message: Message): string { return typeof message.content === "string" ? message.content @@ -69,5 +75,25 @@ export function deploymentSupportsDeepAgents( return deployment?.supportsDeepAgents ?? false; } -export const extractCitationUrls = (text: string): string[] => - Array.from(text.matchAll(/\[([^\]]*)\]\(([^)]*)\)/g), match => match[2]); \ No newline at end of file +export function extractCitationUrls(text: string): string[] { + return Array.from( + text.matchAll(/\[([^\]]*)\]\(([^)]*)\)/g), + (match) => match[2], + ); +} + +export function extractDocumentsFromMessage(content: string): Document[] { + try { + const toolResultContent = JSON.parse(content); + return toolResultContent["documents"].map((document: any) => { + return { + title: document.title, + content: document.page_content, + source: document.source, + } as Document; + }); + } catch (error) { + console.error("Failed to parse tool call result:", error); + return []; + } +} From e1a67f020cbf7a43be75a293df545a6cd2200992 Mon Sep 17 00:00:00 2001 From: Nick Huang Date: Thu, 28 Aug 2025 10:01:33 -0400 Subject: [PATCH 3/4] Fix styling --- .../src/features/deep-agent-chat/components/ToolCallBox.tsx | 3 +-- apps/web/src/features/deep-agent-chat/utils.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/apps/web/src/features/deep-agent-chat/components/ToolCallBox.tsx b/apps/web/src/features/deep-agent-chat/components/ToolCallBox.tsx index 42c72cab..77535bcf 100644 --- a/apps/web/src/features/deep-agent-chat/components/ToolCallBox.tsx +++ b/apps/web/src/features/deep-agent-chat/components/ToolCallBox.tsx @@ -8,7 +8,6 @@ import { CheckCircle, AlertCircle, Loader, - ExternalLink, } from "lucide-react"; import { Button } from "@/components/ui/button"; import { @@ -262,7 +261,7 @@ const DocumentView = React.memo(({ document }) => { open={isOpen} onOpenChange={setIsOpen} > - + {document.title || "Document"} diff --git a/apps/web/src/features/deep-agent-chat/utils.ts b/apps/web/src/features/deep-agent-chat/utils.ts index 1850ed03..69d76e44 100644 --- a/apps/web/src/features/deep-agent-chat/utils.ts +++ b/apps/web/src/features/deep-agent-chat/utils.ts @@ -92,8 +92,7 @@ export function extractDocumentsFromMessage(content: string): Document[] { source: document.source, } as Document; }); - } catch (error) { - console.error("Failed to parse tool call result:", error); + } catch { return []; } } From 813fe08caec29dc00919e28a5ba37e56a32093b9 Mon Sep 17 00:00:00 2001 From: Nick Huang Date: Thu, 28 Aug 2025 12:30:06 -0400 Subject: [PATCH 4/4] Deduplicate sources --- apps/web/src/features/deep-agent-chat/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/features/deep-agent-chat/utils.ts b/apps/web/src/features/deep-agent-chat/utils.ts index 69d76e44..02c19e0a 100644 --- a/apps/web/src/features/deep-agent-chat/utils.ts +++ b/apps/web/src/features/deep-agent-chat/utils.ts @@ -79,7 +79,7 @@ export function extractCitationUrls(text: string): string[] { return Array.from( text.matchAll(/\[([^\]]*)\]\(([^)]*)\)/g), (match) => match[2], - ); + ).filter((url, index, self) => self.indexOf(url) === index); } export function extractDocumentsFromMessage(content: string): Document[] {