diff --git a/src/components/ChatWindow.tsx b/src/components/ChatWindow.tsx index f0a3273..18fea8f 100644 --- a/src/components/ChatWindow.tsx +++ b/src/components/ChatWindow.tsx @@ -420,6 +420,9 @@ const ChatWindow = ({ id }: { id?: string }) => { let sources: Document[] | undefined = undefined; let recievedMessage = ''; + let messageBuffer = ''; + let tokenCount = 0; + const bufferThreshold = 10; let added = false; let messageChatHistory = chatHistory; @@ -542,50 +545,55 @@ const ChatWindow = ({ id }: { id?: string }) => { } if (data.type === 'response') { - if (!added) { - setMessages((prevMessages) => [ - ...prevMessages, - { - content: data.data, - messageId: data.messageId, // Use the AI message ID from the backend - chatId: chatId!, - role: 'assistant', - sources: sources, - createdAt: new Date(), - }, - ]); - added = true; - } else { - setMessages((prev) => - prev.map((message) => { - if (message.messageId === data.messageId) { - return { ...message, content: message.content + data.data }; - } - return message; - }), - ); - } - + // Add to buffer instead of immediately updating UI + messageBuffer += data.data; recievedMessage += data.data; - setScrollTrigger((prev) => prev + 1); + tokenCount++; + + // Only update UI every bufferThreshold tokens + if (tokenCount >= bufferThreshold) { + if (!added) { + setMessages((prevMessages) => [ + ...prevMessages, + { + content: messageBuffer, + messageId: data.messageId, // Use the AI message ID from the backend + chatId: chatId!, + role: 'assistant', + sources: sources, + createdAt: new Date(), + }, + ]); + added = true; + } else { + setMessages((prev) => + prev.map((message) => { + if (message.messageId === data.messageId) { + return { ...message, content: recievedMessage }; + } + return message; + }), + ); + } + + // Reset buffer and counter + messageBuffer = ''; + tokenCount = 0; + setScrollTrigger((prev) => prev + 1); + } } if (data.type === 'messageEnd') { // Clear analysis progress setAnalysisProgress(null); - setChatHistory((prevHistory) => [ - ...prevHistory, - ['human', message], - ['assistant', recievedMessage], - ]); - - // Always update the message, adding modelStats if available + // Ensure final message content is displayed (flush any remaining buffer) setMessages((prev) => prev.map((message) => { if (message.messageId === data.messageId) { return { ...message, + content: recievedMessage, // Use the complete received message // Include model stats if available, otherwise null modelStats: data.modelStats || null, // Make sure the searchQuery is preserved (if available in the message data) @@ -597,6 +605,12 @@ const ChatWindow = ({ id }: { id?: string }) => { }), ); + setChatHistory((prevHistory) => [ + ...prevHistory, + ['human', message], + ['assistant', recievedMessage], + ]); + setLoading(false); setScrollTrigger((prev) => prev + 1); diff --git a/src/components/CitationLink.tsx b/src/components/CitationLink.tsx index 3428acb..c3f0a02 100644 --- a/src/components/CitationLink.tsx +++ b/src/components/CitationLink.tsx @@ -1,5 +1,6 @@ import { Document } from '@langchain/core/documents'; -import { useState } from 'react'; +import { useState, useRef } from 'react'; +import { createPortal } from 'react-dom'; import MessageSource from './MessageSource'; interface CitationLinkProps { @@ -10,6 +11,9 @@ interface CitationLinkProps { const CitationLink = ({ number, source, url }: CitationLinkProps) => { const [showTooltip, setShowTooltip] = useState(false); + const [tooltipPosition, setTooltipPosition] = useState({ x: 0, y: 0 }); + const spanRef = useRef(null); + const linkContent = ( { ); + const handleMouseEnter = () => { + if (spanRef.current) { + const rect = spanRef.current.getBoundingClientRect(); + setTooltipPosition({ + x: rect.left + rect.width / 2, + y: rect.top, + }); + setShowTooltip(true); + } + }; + + const handleMouseLeave = () => { + setShowTooltip(false); + }; + // If we have source data, wrap with tooltip if (source) { return ( -
-
setShowTooltip(true)} - onMouseLeave={() => setShowTooltip(false)} + <> + {linkContent} -
+ - {showTooltip && ( -
-
- -
- {/* Tooltip arrow */} -
-
- )} -
+ {showTooltip && + typeof window !== 'undefined' && + createPortal( +
+
+ +
+ {/* Tooltip arrow */} +
+
, + document.body, + )} + ); } diff --git a/src/components/MessageTabs.tsx b/src/components/MessageTabs.tsx index 71a1a5c..4a41882 100644 --- a/src/components/MessageTabs.tsx +++ b/src/components/MessageTabs.tsx @@ -100,7 +100,6 @@ const MessageTabs = ({ // Process message content useEffect(() => { - const citationRegex = /\[([^\]]+)\]/g; const regex = /\[(\d+)\]/g; let processedMessage = message.content; @@ -119,35 +118,32 @@ const MessageTabs = ({ message.sources.length > 0 ) { setParsedMessage( - processedMessage.replace( - citationRegex, - (_, capturedContent: string) => { - const numbers = capturedContent - .split(',') - .map((numStr) => numStr.trim()); + processedMessage.replace(regex, (_, capturedContent: string) => { + const numbers = capturedContent + .split(',') + .map((numStr) => numStr.trim()); - const linksHtml = numbers - .map((numStr) => { - const number = parseInt(numStr); + const linksHtml = numbers + .map((numStr) => { + const number = parseInt(numStr); - if (isNaN(number) || number <= 0) { - return `[${numStr}]`; - } + if (isNaN(number) || number <= 0) { + return `[${numStr}]`; + } - const source = message.sources?.[number - 1]; - const url = source?.metadata?.url; + const source = message.sources?.[number - 1]; + const url = source?.metadata?.url; - if (url) { - return `${numStr}`; - } else { - return `[${numStr}]`; - } - }) - .join(''); + if (url) { + return `${numStr}`; + } else { + return `[${numStr}]`; + } + }) + .join(''); - return linksHtml; - }, - ), + return linksHtml; + }), ); setSpeechMessage(message.content.replace(regex, '')); return; diff --git a/src/lib/search/simplifiedAgent.ts b/src/lib/search/simplifiedAgent.ts index db4847a..dd0fa85 100644 --- a/src/lib/search/simplifiedAgent.ts +++ b/src/lib/search/simplifiedAgent.ts @@ -58,11 +58,8 @@ function normalizeUsageMetadata(usageData: any): { } /** - * Simplified Agent using createReactAgent - * - * This agent replaces the complex LangGraph supervisor pattern with a single - * tool-calling agent that handles analysis and synthesis internally while - * using specialized tools for search, file processing, and URL summarization. + * SimplifiedAgent class that provides a streamlined interface for creating and managing an AI agent + * with customizable focus modes and tools. */ export class SimplifiedAgent { private llm: BaseChatModel; @@ -95,7 +92,6 @@ export class SimplifiedAgent { // Select appropriate tools based on focus mode and available files const tools = this.getToolsForFocusMode(focusMode, fileIds); - // Create the enhanced system prompt that includes analysis and synthesis instructions const enhancedSystemPrompt = this.createEnhancedSystemPrompt( focusMode, fileIds, @@ -159,9 +155,6 @@ export class SimplifiedAgent { } } - /** - * Create enhanced system prompt that includes analysis and synthesis capabilities - */ private createEnhancedSystemPrompt( focusMode: string, fileIds: string[] = [], @@ -348,15 +341,16 @@ Your task is to provide answers that are: - Passing true is **required** to include images or links within the page content. - You will receive a summary of the content from each URL if the content of the page is long. If the content of the page is short, you will receive the full content. - You may request up to 5 URLs per turn. + - If you recieve a request to summarize a specific URL you **must** use this tool to retrieve it. 5. **Analyze**: Examine the retrieved information for relevance, accuracy, and completeness. - - If you have sufficient information, you can move on to the synthesis stage. + - If you have sufficient information, you can move on to the respond stage. - If you need to gather more information, consider revisiting the search or supplement stages.${ fileIds.length > 0 ? ` - Consider both web search results and file content when analyzing information completeness.` : '' } -6. **Synthesize**: Combine all information into a coherent, well-cited response +6. **Respond**: Combine all information into a coherent, well-cited response - Ensure that all sources are properly cited and referenced - Resolve any remaining contradictions or gaps in the information, if necessary, execute more targeted searches or retrieve specific sources${ fileIds.length > 0 @@ -457,13 +451,14 @@ Your task is to provide answers that are: - You will receive relevant excerpts from documents that match your search criteria. - Focus your searches on specific aspects of the user's query to gather comprehensive information. 3. **Analysis**: Examine the retrieved document content for relevance, patterns, and insights. - - If you have sufficient information from the documents, you can move on to the synthesis stage. + - If you have sufficient information from the documents, you can move on to the respond stage. - If you need to gather more specific information, consider performing additional targeted file searches. - Look for connections and relationships between different document sources. -4. **Synthesize**: Combine all document insights into a coherent, well-cited response +4. **Respond**: Combine all document insights into a coherent, well-cited response - Ensure that all sources are properly cited and referenced - Resolve any contradictions or gaps in the document information - Provide comprehensive analysis based on the available document content + - Only respond with your final answer once you've gathered all relevant information and are done with tool use ## Current Context - Today's Date: ${formatDateForLLM(new Date())}