diff --git a/frontend/components/navigation.tsx b/frontend/components/navigation.tsx index 41ba5216..a2d6e08b 100644 --- a/frontend/components/navigation.tsx +++ b/frontend/components/navigation.tsx @@ -4,15 +4,17 @@ import Link from "next/link" import { usePathname } from "next/navigation" import { Library, MessageSquare, Settings2, Plus, FileText } from "lucide-react" import { cn } from "@/lib/utils" -import { useState, useEffect, useRef } from "react" +import { useState, useEffect, useRef, useCallback } from "react" import { useChat } from "@/contexts/chat-context" -interface ChatConversation { +import { EndpointType } from "@/contexts/chat-context" + +interface RawConversation { response_id: string title: string - endpoint: 'chat' | 'langflow' + endpoint: string messages: Array<{ - role: 'user' | 'assistant' + role: string content: string timestamp?: string response_id?: string @@ -21,6 +23,24 @@ interface ChatConversation { last_activity?: string previous_response_id?: string total_messages: number + [key: string]: unknown +} + +interface ChatConversation { + response_id: string + title: string + endpoint: EndpointType + messages: Array<{ + role: string + content: string + timestamp?: string + response_id?: string + }> + created_at?: string + last_activity?: string + previous_response_id?: string + total_messages: number + [key: string]: unknown } @@ -128,14 +148,7 @@ export function Navigation() { const isOnChatPage = pathname === "/" || pathname === "/chat" - // Fetch chat conversations when on chat page, endpoint changes, or refresh is triggered - useEffect(() => { - if (isOnChatPage) { - fetchConversations() - } - }, [isOnChatPage, endpoint, refreshTrigger]) - - const fetchConversations = async () => { + const fetchConversations = useCallback(async () => { setLoadingConversations(true) try { // Fetch from the selected endpoint only @@ -144,7 +157,13 @@ export function Navigation() { const response = await fetch(apiEndpoint) if (response.ok) { const history = await response.json() - const conversations = history.conversations || [] + const rawConversations = history.conversations || [] + + // Cast conversations to proper type and ensure endpoint is correct + const conversations: ChatConversation[] = rawConversations.map((conv: RawConversation) => ({ + ...conv, + endpoint: conv.endpoint as EndpointType + })) // Sort conversations by last activity (most recent first) conversations.sort((a: ChatConversation, b: ChatConversation) => { @@ -166,7 +185,14 @@ export function Navigation() { } finally { setLoadingConversations(false) } - } + }, [endpoint]) + + // Fetch chat conversations when on chat page, endpoint changes, or refresh is triggered + useEffect(() => { + if (isOnChatPage) { + fetchConversations() + } + }, [isOnChatPage, endpoint, refreshTrigger, fetchConversations]) return (
diff --git a/frontend/src/app/chat/page.tsx b/frontend/src/app/chat/page.tsx index 82040e53..3c489999 100644 --- a/frontend/src/app/chat/page.tsx +++ b/frontend/src/app/chat/page.tsx @@ -70,7 +70,7 @@ interface RequestBody { function ChatPage() { const isDebugMode = process.env.NODE_ENV === 'development' || process.env.NEXT_PUBLIC_OPENRAG_DEBUG === 'true' const { user } = useAuth() - const { endpoint, setEndpoint, refreshConversations, currentConversationId, conversationData, setCurrentConversationId, addConversationDoc, startNewConversation } = useChat() + const { endpoint, setEndpoint, currentConversationId, conversationData, setCurrentConversationId, addConversationDoc, forkFromResponse } = useChat() const [messages, setMessages] = useState([ { role: "assistant", @@ -370,8 +370,13 @@ function ChatPage() { if (conversationData && conversationData.messages) { console.log("Loading conversation with", conversationData.messages.length, "messages") // Convert backend message format to frontend Message interface - const convertedMessages: Message[] = conversationData.messages.map((msg: any) => ({ - role: msg.role, + const convertedMessages: Message[] = conversationData.messages.map((msg: { + role: string; + content: string; + timestamp?: string; + response_id?: string; + }) => ({ + role: msg.role as "user" | "assistant", content: msg.content, timestamp: new Date(msg.timestamp || new Date()), // Add any other necessary properties @@ -1097,19 +1102,23 @@ function ChatPage() { // This means we're continuing the conversation thread from that point const responseIdToForkFrom = currentConversationId || previousResponseIds[endpoint] - // Create a new conversation by clearing the current conversation ID - // but keeping the messages truncated to the fork point + // Create a new conversation by properly forking setMessages(messagesToKeep) - setCurrentConversationId(null) // This creates a new conversation thread - // We'll handle clearing conversation data differently - - // Set the response_id we want to continue from as the previous response ID - // This tells the backend to continue the conversation from this point - setPreviousResponseIds(prev => ({ - ...prev, - [endpoint]: responseIdToForkFrom - })) + // Use the chat context's fork method which handles creating a new conversation properly + if (forkFromResponse) { + forkFromResponse(responseIdToForkFrom || '') + } else { + // Fallback to manual approach + setCurrentConversationId(null) // This creates a new conversation thread + + // Set the response_id we want to continue from as the previous response ID + // This tells the backend to continue the conversation from this point + setPreviousResponseIds(prev => ({ + ...prev, + [endpoint]: responseIdToForkFrom + })) + } console.log("Forked conversation with", messagesToKeep.length, "messages") diff --git a/frontend/src/contexts/chat-context.tsx b/frontend/src/contexts/chat-context.tsx index 9965a06f..0ff7839d 100644 --- a/frontend/src/contexts/chat-context.tsx +++ b/frontend/src/contexts/chat-context.tsx @@ -9,6 +9,20 @@ interface ConversationDocument { uploadTime: Date } +interface ConversationMessage { + role: string + content: string + timestamp?: string + response_id?: string +} + +interface ConversationData { + messages: ConversationMessage[] + endpoint: EndpointType + response_id: string + [key: string]: unknown +} + interface ChatContextType { endpoint: EndpointType setEndpoint: (endpoint: EndpointType) => void @@ -21,10 +35,10 @@ interface ChatContextType { setPreviousResponseIds: (ids: { chat: string | null; langflow: string | null }) => void refreshConversations: () => void refreshTrigger: number - loadConversation: (conversation: any) => void + loadConversation: (conversation: ConversationData) => void startNewConversation: () => void - conversationData: any - forkFromResponse: (responseId: string, messagesToKeep: any[]) => void + conversationData: ConversationData | null + forkFromResponse: (responseId: string) => void conversationDocs: ConversationDocument[] addConversationDoc: (filename: string) => void clearConversationDocs: () => void @@ -44,14 +58,14 @@ export function ChatProvider({ children }: ChatProviderProps) { langflow: string | null }>({ chat: null, langflow: null }) const [refreshTrigger, setRefreshTrigger] = useState(0) - const [conversationData, setConversationData] = useState(null) + const [conversationData, setConversationData] = useState(null) const [conversationDocs, setConversationDocs] = useState([]) const refreshConversations = () => { setRefreshTrigger(prev => prev + 1) } - const loadConversation = (conversation: any) => { + const loadConversation = (conversation: ConversationData) => { setCurrentConversationId(conversation.response_id) setEndpoint(conversation.endpoint) // Store the full conversation data for the chat page to use @@ -74,10 +88,10 @@ export function ChatProvider({ children }: ChatProviderProps) { setConversationDocs([]) } - const forkFromResponse = (responseId: string, messagesToKeep: any[]) => { + const forkFromResponse = (responseId: string) => { // Start a new conversation with the messages up to the fork point setCurrentConversationId(null) // Clear current conversation to indicate new conversation - // Don't clear conversation data - let the chat page manage the messages + setConversationData(null) // Clear conversation data to prevent reloading // Set the response ID that we're forking from as the previous response ID setPreviousResponseIds(prev => ({ ...prev,