From 5197269c43a14ba00ffd0a7776595b4ae5a1f13c Mon Sep 17 00:00:00 2001 From: Utkarsh-Patel-13 Date: Fri, 25 Jul 2025 15:40:29 -0700 Subject: [PATCH] Added Further question component and streaming fix --- .../app/services/streaming_service.py | 9 ++- .../components/chat_v2/ChatCitation.tsx | 56 ++++++++++++++++ .../chat_v2/ChatFurtherQuestions.tsx | 18 +++++ .../components/chat_v2/ChatInterface.tsx | 66 +++---------------- .../components/chat_v2/ChatTerminal.tsx | 4 +- 5 files changed, 92 insertions(+), 61 deletions(-) create mode 100644 surfsense_web/components/chat_v2/ChatCitation.tsx create mode 100644 surfsense_web/components/chat_v2/ChatFurtherQuestions.tsx diff --git a/surfsense_backend/app/services/streaming_service.py b/surfsense_backend/app/services/streaming_service.py index 2163f56..10f6fb7 100644 --- a/surfsense_backend/app/services/streaming_service.py +++ b/surfsense_backend/app/services/streaming_service.py @@ -132,7 +132,14 @@ class StreamingService: self.message_annotations[3]["content"] = further_questions # Return only the delta annotation - annotation = {"type": "FURTHER_QUESTIONS", "content": further_questions} + annotation = { + "type": "FURTHER_QUESTIONS", + "data": [ + question.get("question", "") + for question in further_questions + if question.get("question", "") != "" + ], + } return f"8:[{json.dumps(annotation)}]\n" def format_text_chunk(self, text: str) -> str: diff --git a/surfsense_web/components/chat_v2/ChatCitation.tsx b/surfsense_web/components/chat_v2/ChatCitation.tsx new file mode 100644 index 0000000..c5570b1 --- /dev/null +++ b/surfsense_web/components/chat_v2/ChatCitation.tsx @@ -0,0 +1,56 @@ +"use client" + +import React from "react"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { ExternalLink } from "lucide-react"; + +export const CitationDisplay: React.FC<{index: number, node: any}> = ({index, node}) => { + + const truncateText = (text: string, maxLength: number = 200) => { + if (text.length <= maxLength) return text; + return text.substring(0, maxLength) + '...'; + }; + + const handleUrlClick = (e: React.MouseEvent, url: string) => { + e.preventDefault(); + e.stopPropagation(); + window.open(url, '_blank', 'noopener,noreferrer'); + }; + + return ( + + + + {index + 1} + + + + {/* External Link Button - Top Right */} + {node?.url && ( + + )} + + {/* Heading */} +
+ {node?.metadata?.group_name || 'Source'} +
+ + {/* Source */} +
+ {node?.metadata?.title || 'Untitled'} +
+ + {/* Body */} +
+ {truncateText(node?.text || 'No content available')} +
+
+
+ ); +} \ No newline at end of file diff --git a/surfsense_web/components/chat_v2/ChatFurtherQuestions.tsx b/surfsense_web/components/chat_v2/ChatFurtherQuestions.tsx new file mode 100644 index 0000000..ddfb3ea --- /dev/null +++ b/surfsense_web/components/chat_v2/ChatFurtherQuestions.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { SuggestedQuestions } from "@llamaindex/chat-ui/widgets"; +import { getAnnotationData, Message, useChatUI } from "@llamaindex/chat-ui"; + +export const ChatFurtherQuestions: React.FC<{message: Message}> = ({message}) => { + const annotations: string[][] = getAnnotationData(message, "FURTHER_QUESTIONS"); + const { append, requestData } = useChatUI(); + + console.log('🔥 annotations', annotations); + + + if (annotations.length !== 1 || annotations[0].length === 0) { + return <>; + } + + return ; +}; \ No newline at end of file diff --git a/surfsense_web/components/chat_v2/ChatInterface.tsx b/surfsense_web/components/chat_v2/ChatInterface.tsx index fba1ee0..90bb111 100644 --- a/surfsense_web/components/chat_v2/ChatInterface.tsx +++ b/surfsense_web/components/chat_v2/ChatInterface.tsx @@ -9,15 +9,14 @@ import { useChatUI, ChatMessage, Message, - getAnnotationData, } from "@llamaindex/chat-ui"; import { Document } from "@/hooks/use-documents"; import { CustomChatInput } from "@/components/chat_v2/ChatInputGroup"; import { ResearchMode } from "@/components/chat"; import TerminalDisplay from "@/components/chat_v2/ChatTerminal"; import ChatSourcesDisplay from "@/components/chat_v2/ChatSources"; -import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; -import { ExternalLink } from "lucide-react"; +import { CitationDisplay } from "@/components/chat_v2/ChatCitation"; +import { ChatFurtherQuestions } from "@/components/chat_v2/ChatFurtherQuestions"; interface ChatInterfaceProps { handler: ChatHandler; @@ -30,59 +29,7 @@ interface ChatInterfaceProps { researchMode?: ResearchMode; onResearchModeChange?: (mode: ResearchMode) => void; } - -const CitationDisplay: React.FC<{index: number, node: any}> = ({index, node}) => { - - - const truncateText = (text: string, maxLength: number = 200) => { - if (text.length <= maxLength) return text; - return text.substring(0, maxLength) + '...'; - }; - - const handleUrlClick = (e: React.MouseEvent, url: string) => { - e.preventDefault(); - e.stopPropagation(); - window.open(url, '_blank', 'noopener,noreferrer'); - }; - - return ( - - - - {index + 1} - - - - {/* External Link Button - Top Right */} - {node?.url && ( - - )} - - {/* Heading */} -
- {node?.metadata?.group_name || 'Source'} -
- - {/* Source */} -
- {node?.metadata?.title || 'Untitled'} -
- - {/* Body */} -
- {truncateText(node?.text || 'No content available')} -
-
-
- ); -} - + function ChatMessageDisplay({ message, @@ -99,12 +46,15 @@ function ChatMessageDisplay({ > {message.role === "assistant" ? (
- + - +
+ {isLast && } + +
) : ( diff --git a/surfsense_web/components/chat_v2/ChatTerminal.tsx b/surfsense_web/components/chat_v2/ChatTerminal.tsx index 61d1a9b..0300147 100644 --- a/surfsense_web/components/chat_v2/ChatTerminal.tsx +++ b/surfsense_web/components/chat_v2/ChatTerminal.tsx @@ -3,8 +3,8 @@ import React from "react"; import { getAnnotationData, Message } from "@llamaindex/chat-ui"; -export default function TerminalDisplay({ message }: { message: Message }) { - const [isCollapsed, setIsCollapsed] = React.useState(true); +export default function TerminalDisplay({ message, open }: { message: Message, open: boolean }) { + const [isCollapsed, setIsCollapsed] = React.useState(!open); // Get the last assistant message that's not being typed if (!message) {