📝 (frontend): Add new function 'refreshConversationsSilent' to update data without loading states

🚀 (frontend): Implement support for process.env.PORT to run app on a configurable port
🔧 (frontend): Change port variable case from lowercase 'port' to uppercase 'PORT' for better semantics
📝 (frontend): Add comments to clarify the purpose of loading conversation data only when user explicitly selects a conversation
📝 (frontend): Add comments to explain the logic for loading conversation data based on certain conditions
📝 (frontend): Add comments to describe the purpose of handling new conversation creation and resetting messages
📝 (frontend): Add comments to explain the logic for loading conversation data when conversationData changes
📝 (frontend): Add comments to clarify the purpose of loading conversations from the backend
📝 (frontend): Add comments to describe the logic for silent refresh to update data without loading states
📝 (frontend): Add comments to explain the purpose of starting a new conversation and creating a placeholder conversation
📝 (frontend): Add comments to clarify the logic for forking from a response and starting a new conversation
📝 (frontend): Add comments to describe the purpose of adding a conversation document and clearing conversation documents
📝 (frontend): Add comments to explain the logic for using a timeout to debounce multiple rapid refresh calls
📝 (frontend): Add comments to clarify the purpose of cleaning up timeout on unmount
📝 (frontend): Add comments to describe the logic for handling new conversation creation and resetting state
📝 (frontend): Add comments to explain the logic for forking from a response and starting a new conversation
📝 (frontend): Add comments to clarify the purpose of using useMemo for optimizing performance in ChatProvider
📝 (frontend): Add comments to describe the logic for using useMemo in the ChatProvider component
📝 (frontend): Add comments to explain the purpose of the useChat custom hook
📝 (frontend): Add comments to clarify the error message when useChat is not used within a ChatProvider
📝 (services): Update ChatService to fetch Langflow history with flow_id parameter for better control
This commit is contained in:
cristhianzl 2025-09-04 15:36:41 -03:00
parent 0db67b8c6a
commit 6dcb65debd
4 changed files with 495 additions and 180 deletions

View file

@ -91,8 +91,10 @@ function ChatPage() {
addConversationDoc, addConversationDoc,
forkFromResponse, forkFromResponse,
refreshConversations, refreshConversations,
refreshConversationsSilent,
previousResponseIds, previousResponseIds,
setPreviousResponseIds, setPreviousResponseIds,
placeholderConversation,
} = useChat(); } = useChat();
const [messages, setMessages] = useState<Message[]>([ const [messages, setMessages] = useState<Message[]>([
{ {
@ -133,6 +135,7 @@ function ChatPage() {
const dropdownRef = useRef<HTMLDivElement>(null); const dropdownRef = useRef<HTMLDivElement>(null);
const streamAbortRef = useRef<AbortController | null>(null); const streamAbortRef = useRef<AbortController | null>(null);
const streamIdRef = useRef(0); const streamIdRef = useRef(0);
const lastLoadedConversationRef = useRef<string | null>(null);
const { addTask, isMenuOpen } = useTask(); const { addTask, isMenuOpen } = useTask();
const { selectedFilter, parsedFilterData, isPanelOpen, setSelectedFilter } = const { selectedFilter, parsedFilterData, isPanelOpen, setSelectedFilter } =
useKnowledgeFilter(); useKnowledgeFilter();
@ -241,11 +244,16 @@ function ChatPage() {
...prev, ...prev,
[endpoint]: result.response_id, [endpoint]: result.response_id,
})); }));
// If this is a new conversation (no currentConversationId), set it now
if (!currentConversationId) {
setCurrentConversationId(result.response_id);
refreshConversations(true);
} else {
// For existing conversations, do a silent refresh to keep backend in sync
refreshConversationsSilent();
}
} }
// Sidebar should show this conversation after upload creates it
try {
refreshConversations();
} catch {}
} else { } else {
throw new Error(`Upload failed: ${response.status}`); throw new Error(`Upload failed: ${response.status}`);
} }
@ -406,6 +414,7 @@ function ChatPage() {
setExpandedFunctionCalls(new Set()); setExpandedFunctionCalls(new Set());
setIsFilterHighlighted(false); setIsFilterHighlighted(false);
setLoading(false); setLoading(false);
lastLoadedConversationRef.current = null;
}; };
const handleFocusInput = () => { const handleFocusInput = () => {
@ -420,25 +429,19 @@ function ChatPage() {
}; };
}, []); }, []);
// Load conversation when conversationData changes // Load conversation only when user explicitly selects a conversation
useEffect(() => { useEffect(() => {
const now = Date.now(); // Only load conversation data when:
// 1. conversationData exists AND
// Don't reset messages if user is in the middle of an interaction (like forking) // 2. It's different from the last loaded conversation AND
if (isUserInteracting || isForkingInProgress) { // 3. User is not in the middle of an interaction
console.log( if (
"Skipping conversation load due to user interaction or forking" conversationData &&
); conversationData.messages &&
return; lastLoadedConversationRef.current !== conversationData.response_id &&
} !isUserInteracting &&
!isForkingInProgress
// Don't reload if we just forked recently (within 1 second) ) {
if (now - lastForkTimestamp < 1000) {
console.log("Skipping conversation load - recent fork detected");
return;
}
if (conversationData && conversationData.messages) {
console.log( console.log(
"Loading conversation with", "Loading conversation with",
conversationData.messages.length, conversationData.messages.length,
@ -460,6 +463,7 @@ function ChatPage() {
); );
setMessages(convertedMessages); setMessages(convertedMessages);
lastLoadedConversationRef.current = conversationData.response_id;
// Set the previous response ID for this conversation // Set the previous response ID for this conversation
setPreviousResponseIds((prev) => ({ setPreviousResponseIds((prev) => ({
@ -467,14 +471,16 @@ function ChatPage() {
[conversationData.endpoint]: conversationData.response_id, [conversationData.endpoint]: conversationData.response_id,
})); }));
} }
// Reset messages when starting a new conversation (but not during forking) }, [
else if ( conversationData,
currentConversationId === null && isUserInteracting,
!isUserInteracting && isForkingInProgress,
!isForkingInProgress && ]);
now - lastForkTimestamp > 1000
) { // Handle new conversation creation - only reset messages when placeholderConversation is set
console.log("Resetting to default message for new conversation"); useEffect(() => {
if (placeholderConversation && currentConversationId === null) {
console.log("Starting new conversation");
setMessages([ setMessages([
{ {
role: "assistant", role: "assistant",
@ -482,15 +488,9 @@ function ChatPage() {
timestamp: new Date(), timestamp: new Date(),
}, },
]); ]);
lastLoadedConversationRef.current = null;
} }
}, [ }, [placeholderConversation, currentConversationId]);
conversationData,
currentConversationId,
isUserInteracting,
isForkingInProgress,
lastForkTimestamp,
setPreviousResponseIds,
]);
// Listen for file upload events from navigation // Listen for file upload events from navigation
useEffect(() => { useEffect(() => {
@ -1280,14 +1280,16 @@ function ChatPage() {
...prev, ...prev,
[endpoint]: newResponseId, [endpoint]: newResponseId,
})); }));
}
// Trigger sidebar refresh to include this conversation (with small delay to ensure backend has processed) // If this is a new conversation (no currentConversationId), set it now
setTimeout(() => { if (!currentConversationId) {
try { setCurrentConversationId(newResponseId);
refreshConversations(); refreshConversations(true);
} catch {} } else {
}, 100); // For existing conversations, do a silent refresh to keep backend in sync
refreshConversationsSilent();
}
}
} catch (error) { } catch (error) {
// If stream was aborted (e.g., starting new conversation), do not append errors or final messages // If stream was aborted (e.g., starting new conversation), do not append errors or final messages
if (streamAbortRef.current?.signal.aborted) { if (streamAbortRef.current?.signal.aborted) {
@ -1390,13 +1392,16 @@ function ChatPage() {
...prev, ...prev,
[endpoint]: result.response_id, [endpoint]: result.response_id,
})); }));
// If this is a new conversation (no currentConversationId), set it now
if (!currentConversationId) {
setCurrentConversationId(result.response_id);
refreshConversations(true);
} else {
// For existing conversations, do a silent refresh to keep backend in sync
refreshConversationsSilent();
}
} }
// Trigger sidebar refresh to include/update this conversation (with small delay to ensure backend has processed)
setTimeout(() => {
try {
refreshConversations();
} catch {}
}, 100);
} else { } else {
console.error("Chat failed:", result.error); console.error("Chat failed:", result.error);
const errorMessage: Message = { const errorMessage: Message = {
@ -2013,9 +2018,6 @@ function ChatPage() {
// Clear filter highlight when user starts typing // Clear filter highlight when user starts typing
if (isFilterHighlighted) { if (isFilterHighlighted) {
setIsFilterHighlighted(false); setIsFilterHighlighted(false);
try {
refreshConversations();
} catch {}
} }
// Find if there's an @ at the start of the last word // Find if there's an @ at the start of the last word

View file

@ -0,0 +1,230 @@
"use client"
import { useState, useEffect, useRef } from "react"
import { useRouter, usePathname } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Plus, MessageSquare, Database, Settings, GitBranch } from "lucide-react"
import { useChat } from "@/contexts/chat-context"
import { useAuth } from "@/contexts/auth-context"
interface Conversation {
id: string
title: string
endpoint: string
last_activity: string
created_at: string
response_id: string
messages?: Array<{
role: string
content: string
timestamp?: string
response_id?: string
}>
}
export function Navigation() {
const router = useRouter()
const pathname = usePathname()
const { user } = useAuth()
const {
refreshTrigger,
refreshTriggerSilent,
loadConversation,
startNewConversation,
currentConversationId,
placeholderConversation,
} = useChat()
const [conversations, setConversations] = useState<Conversation[]>([])
const [loading, setLoading] = useState(false)
// Load conversations from backend
const loadConversations = async () => {
if (!user) return
try {
setLoading(true)
const response = await fetch("/api/conversations")
if (response.ok) {
const data = await response.json()
setConversations(data.conversations || [])
}
} catch (error) {
console.error("Failed to load conversations:", error)
} finally {
setLoading(false)
}
}
// Load conversations on mount and when refreshTrigger changes (with loading state)
useEffect(() => {
loadConversations()
}, [refreshTrigger, user])
// Silent refresh - update data without loading state
useEffect(() => {
const loadSilent = async () => {
if (!user) return
try {
// Don't show loading state for silent refresh
const response = await fetch("/api/conversations")
if (response.ok) {
const data = await response.json()
setConversations(data.conversations || [])
}
} catch (error) {
console.error("Silent conversation refresh failed:", error)
}
}
// Only do silent refresh if we have a silent trigger change (not initial load)
if (refreshTriggerSilent > 0) {
loadSilent()
}
}, [refreshTriggerSilent, user])
const handleNewConversation = () => {
startNewConversation()
// Dispatch custom event to notify chat page
window.dispatchEvent(new CustomEvent('newConversation'))
router.push('/chat')
}
const handleConversationClick = async (conversation: Conversation) => {
try {
// Load full conversation data from backend
const response = await fetch(`/api/conversations/${conversation.response_id}`)
if (response.ok) {
const fullConversation = await response.json()
loadConversation(fullConversation)
router.push('/chat')
}
} catch (error) {
console.error("Failed to load conversation:", error)
}
}
const formatRelativeTime = (timestamp: string) => {
const date = new Date(timestamp)
const now = new Date()
const diffMs = now.getTime() - date.getTime()
const diffHours = Math.floor(diffMs / (1000 * 60 * 60))
const diffDays = Math.floor(diffHours / 24)
if (diffDays > 0) {
return `${diffDays}d ago`
} else if (diffHours > 0) {
return `${diffHours}h ago`
} else {
return 'Just now'
}
}
return (
<nav className="flex flex-col h-full w-72 bg-muted/30 border-r border-border">
{/* Header */}
<div className="p-4 border-b border-border">
<Button
onClick={handleNewConversation}
className="w-full justify-start gap-2"
variant="default"
>
<Plus className="h-4 w-4" />
New Conversation
</Button>
</div>
{/* Navigation Links */}
<div className="p-4 border-b border-border">
<div className="space-y-2">
<Button
variant={pathname === '/chat' ? 'secondary' : 'ghost'}
className="w-full justify-start gap-2"
onClick={() => router.push('/chat')}
>
<MessageSquare className="h-4 w-4" />
Chat
</Button>
<Button
variant={pathname === '/knowledge' ? 'secondary' : 'ghost'}
className="w-full justify-start gap-2"
onClick={() => router.push('/knowledge')}
>
<Database className="h-4 w-4" />
Knowledge
</Button>
<Button
variant={pathname === '/settings' ? 'secondary' : 'ghost'}
className="w-full justify-start gap-2"
onClick={() => router.push('/settings')}
>
<Settings className="h-4 w-4" />
Settings
</Button>
</div>
</div>
{/* Conversations List */}
<div className="flex-1 overflow-hidden">
<div className="px-4 py-2">
<h3 className="text-sm font-medium text-muted-foreground mb-2">Conversations</h3>
</div>
<div className="flex-1 overflow-y-auto px-2">
{loading ? (
<div className="p-4 text-sm text-muted-foreground">
Loading conversations...
</div>
) : (
<div className="space-y-1">
{/* Show placeholder conversation if exists */}
{placeholderConversation && (
<div className="p-2 rounded-md bg-primary/10 border border-primary/20">
<div className="flex items-center gap-2 text-sm">
<MessageSquare className="h-3 w-3 text-primary" />
<span className="text-primary font-medium">New conversation</span>
</div>
<div className="text-xs text-muted-foreground mt-1">
Active
</div>
</div>
)}
{conversations.map((conversation) => (
<button
key={conversation.id}
onClick={() => handleConversationClick(conversation)}
className={`w-full text-left p-2 rounded-md transition-colors hover:bg-muted/50 ${
currentConversationId === conversation.response_id
? 'bg-muted border border-border'
: ''
}`}
>
<div className="flex items-center gap-2 mb-1">
<MessageSquare className="h-3 w-3 text-muted-foreground" />
<span className="text-sm font-medium truncate">
{conversation.title || 'Untitled'}
</span>
{conversation.endpoint === 'chat' && (
<GitBranch className="h-3 w-3 text-blue-400 ml-auto" />
)}
</div>
<div className="text-xs text-muted-foreground">
{formatRelativeTime(conversation.last_activity)}
</div>
</button>
))}
{conversations.length === 0 && !placeholderConversation && (
<div className="p-4 text-sm text-muted-foreground text-center">
No conversations yet
</div>
)}
</div>
)}
</div>
</div>
</nav>
)
}

View file

@ -1,161 +1,244 @@
"use client" "use client";
import React, { createContext, useContext, useState, ReactNode } from 'react' import {
createContext,
ReactNode,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from "react";
export type EndpointType = 'chat' | 'langflow' export type EndpointType = "chat" | "langflow";
interface ConversationDocument { interface ConversationDocument {
filename: string filename: string;
uploadTime: Date uploadTime: Date;
} }
interface ConversationMessage { interface ConversationMessage {
role: string role: string;
content: string content: string;
timestamp?: string timestamp?: string;
response_id?: string response_id?: string;
} }
interface ConversationData { interface ConversationData {
messages: ConversationMessage[] messages: ConversationMessage[];
endpoint: EndpointType endpoint: EndpointType;
response_id: string response_id: string;
title: string title: string;
[key: string]: unknown [key: string]: unknown;
} }
interface ChatContextType { interface ChatContextType {
endpoint: EndpointType endpoint: EndpointType;
setEndpoint: (endpoint: EndpointType) => void setEndpoint: (endpoint: EndpointType) => void;
currentConversationId: string | null currentConversationId: string | null;
setCurrentConversationId: (id: string | null) => void setCurrentConversationId: (id: string | null) => void;
previousResponseIds: { previousResponseIds: {
chat: string | null chat: string | null;
langflow: string | null langflow: string | null;
} };
setPreviousResponseIds: (ids: { chat: string | null; langflow: string | null } | ((prev: { chat: string | null; langflow: string | null }) => { chat: string | null; langflow: string | null })) => void setPreviousResponseIds: (
refreshConversations: () => void ids:
refreshTrigger: number | { chat: string | null; langflow: string | null }
loadConversation: (conversation: ConversationData) => void | ((prev: { chat: string | null; langflow: string | null }) => {
startNewConversation: () => void chat: string | null;
conversationData: ConversationData | null langflow: string | null;
forkFromResponse: (responseId: string) => void })
conversationDocs: ConversationDocument[] ) => void;
addConversationDoc: (filename: string) => void refreshConversations: (force?: boolean) => void;
clearConversationDocs: () => void refreshConversationsSilent: () => Promise<void>;
placeholderConversation: ConversationData | null refreshTrigger: number;
setPlaceholderConversation: (conversation: ConversationData | null) => void refreshTriggerSilent: number;
loadConversation: (conversation: ConversationData) => void;
startNewConversation: () => void;
conversationData: ConversationData | null;
forkFromResponse: (responseId: string) => void;
conversationDocs: ConversationDocument[];
addConversationDoc: (filename: string) => void;
clearConversationDocs: () => void;
placeholderConversation: ConversationData | null;
setPlaceholderConversation: (conversation: ConversationData | null) => void;
} }
const ChatContext = createContext<ChatContextType | undefined>(undefined) const ChatContext = createContext<ChatContextType | undefined>(undefined);
interface ChatProviderProps { interface ChatProviderProps {
children: ReactNode children: ReactNode;
} }
export function ChatProvider({ children }: ChatProviderProps) { export function ChatProvider({ children }: ChatProviderProps) {
const [endpoint, setEndpoint] = useState<EndpointType>('langflow') const [endpoint, setEndpoint] = useState<EndpointType>("langflow");
const [currentConversationId, setCurrentConversationId] = useState<string | null>(null) const [currentConversationId, setCurrentConversationId] = useState<
string | null
>(null);
const [previousResponseIds, setPreviousResponseIds] = useState<{ const [previousResponseIds, setPreviousResponseIds] = useState<{
chat: string | null chat: string | null;
langflow: string | null langflow: string | null;
}>({ chat: null, langflow: null }) }>({ chat: null, langflow: null });
const [refreshTrigger, setRefreshTrigger] = useState(0) const [refreshTrigger, setRefreshTrigger] = useState(0);
const [conversationData, setConversationData] = useState<ConversationData | null>(null) const [refreshTriggerSilent, setRefreshTriggerSilent] = useState(0);
const [conversationDocs, setConversationDocs] = useState<ConversationDocument[]>([]) const [conversationData, setConversationData] =
const [placeholderConversation, setPlaceholderConversation] = useState<ConversationData | null>(null) useState<ConversationData | null>(null);
const [conversationDocs, setConversationDocs] = useState<
ConversationDocument[]
>([]);
const [placeholderConversation, setPlaceholderConversation] =
useState<ConversationData | null>(null);
const refreshConversations = () => { // Debounce refresh requests to prevent excessive reloads
setRefreshTrigger(prev => prev + 1) const refreshTimeoutRef = useRef<NodeJS.Timeout | null>(null);
}
const loadConversation = (conversation: ConversationData) => { const refreshConversations = useCallback((force = false) => {
setCurrentConversationId(conversation.response_id) if (force) {
setEndpoint(conversation.endpoint) // Immediate refresh for important updates like new conversations
// Store the full conversation data for the chat page to use setRefreshTrigger((prev) => prev + 1);
// We'll pass it through a ref or state that the chat page can access return;
setConversationData(conversation)
// Clear placeholder when loading a real conversation
setPlaceholderConversation(null)
}
const startNewConversation = () => {
// Create a temporary placeholder conversation
const placeholderConversation: ConversationData = {
response_id: 'new-conversation-' + Date.now(),
title: 'New conversation',
endpoint: endpoint,
messages: [{
role: 'assistant',
content: 'How can I assist?',
timestamp: new Date().toISOString()
}],
created_at: new Date().toISOString(),
last_activity: new Date().toISOString()
} }
setCurrentConversationId(null) // Clear any existing timeout
setPreviousResponseIds({ chat: null, langflow: null }) if (refreshTimeoutRef.current) {
setConversationData(null) clearTimeout(refreshTimeoutRef.current);
setConversationDocs([]) }
setPlaceholderConversation(placeholderConversation)
// Force a refresh to ensure sidebar shows correct state
setRefreshTrigger(prev => prev + 1)
}
const addConversationDoc = (filename: string) => { // Set a new timeout to debounce multiple rapid refresh calls
setConversationDocs(prev => [...prev, { filename, uploadTime: new Date() }]) refreshTimeoutRef.current = setTimeout(() => {
} setRefreshTrigger((prev) => prev + 1);
}, 250); // 250ms debounce
}, []);
const clearConversationDocs = () => { // Cleanup timeout on unmount
setConversationDocs([]) useEffect(() => {
} return () => {
if (refreshTimeoutRef.current) {
clearTimeout(refreshTimeoutRef.current);
}
};
}, []);
const forkFromResponse = (responseId: string) => { // Silent refresh - updates data without loading states
// Start a new conversation with the messages up to the fork point const refreshConversationsSilent = useCallback(async () => {
setCurrentConversationId(null) // Clear current conversation to indicate new conversation // Trigger silent refresh that updates conversation data without showing loading states
setConversationData(null) // Clear conversation data to prevent reloading setRefreshTriggerSilent((prev) => prev + 1);
// Set the response ID that we're forking from as the previous response ID }, []);
setPreviousResponseIds(prev => ({
const loadConversation = useCallback((conversation: ConversationData) => {
setCurrentConversationId(conversation.response_id);
setEndpoint(conversation.endpoint);
// Store the full conversation data for the chat page to use
setConversationData(conversation);
// Clear placeholder when loading a real conversation
setPlaceholderConversation(null);
}, []);
const startNewConversation = useCallback(() => {
// Clear current conversation data and reset state
setCurrentConversationId(null);
setPreviousResponseIds({ chat: null, langflow: null });
setConversationData(null);
setConversationDocs([]);
// Create a temporary placeholder conversation to show in sidebar
const placeholderConversation: ConversationData = {
response_id: "new-conversation-" + Date.now(),
title: "New conversation",
endpoint: endpoint,
messages: [
{
role: "assistant",
content: "How can I assist?",
timestamp: new Date().toISOString(),
},
],
created_at: new Date().toISOString(),
last_activity: new Date().toISOString(),
};
setPlaceholderConversation(placeholderConversation);
// Force immediate refresh to ensure sidebar shows correct state
refreshConversations(true);
}, [endpoint, refreshConversations]);
const addConversationDoc = useCallback((filename: string) => {
setConversationDocs((prev) => [
...prev, ...prev,
[endpoint]: responseId { filename, uploadTime: new Date() },
})) ]);
// Clear placeholder when forking }, []);
setPlaceholderConversation(null)
// The messages are already set by the chat page component before calling this
}
const value: ChatContextType = { const clearConversationDocs = useCallback(() => {
endpoint, setConversationDocs([]);
setEndpoint, }, []);
currentConversationId,
setCurrentConversationId,
previousResponseIds,
setPreviousResponseIds,
refreshConversations,
refreshTrigger,
loadConversation,
startNewConversation,
conversationData,
forkFromResponse,
conversationDocs,
addConversationDoc,
clearConversationDocs,
placeholderConversation,
setPlaceholderConversation,
}
return ( const forkFromResponse = useCallback(
<ChatContext.Provider value={value}> (responseId: string) => {
{children} // Start a new conversation with the messages up to the fork point
</ChatContext.Provider> setCurrentConversationId(null); // Clear current conversation to indicate new conversation
) 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,
[endpoint]: responseId,
}));
// Clear placeholder when forking
setPlaceholderConversation(null);
// The messages are already set by the chat page component before calling this
},
[endpoint]
);
const value = useMemo<ChatContextType>(
() => ({
endpoint,
setEndpoint,
currentConversationId,
setCurrentConversationId,
previousResponseIds,
setPreviousResponseIds,
refreshConversations,
refreshConversationsSilent,
refreshTrigger,
refreshTriggerSilent,
loadConversation,
startNewConversation,
conversationData,
forkFromResponse,
conversationDocs,
addConversationDoc,
clearConversationDocs,
placeholderConversation,
setPlaceholderConversation,
}),
[
endpoint,
currentConversationId,
previousResponseIds,
refreshConversations,
refreshConversationsSilent,
refreshTrigger,
refreshTriggerSilent,
loadConversation,
startNewConversation,
conversationData,
forkFromResponse,
conversationDocs,
addConversationDoc,
clearConversationDocs,
placeholderConversation,
]
);
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
} }
export function useChat(): ChatContextType { export function useChat(): ChatContextType {
const context = useContext(ChatContext) const context = useContext(ChatContext);
if (context === undefined) { if (context === undefined) {
throw new Error('useChat must be used within a ChatProvider') throw new Error("useChat must be used within a ChatProvider");
} }
return context return context;
} }

View file

@ -328,7 +328,7 @@ class ChatService:
# 2. Get historical conversations from Langflow database # 2. Get historical conversations from Langflow database
# (works with both Google-bound users and direct Langflow users) # (works with both Google-bound users and direct Langflow users)
print(f"[DEBUG] Attempting to fetch Langflow history for user: {user_id}") print(f"[DEBUG] Attempting to fetch Langflow history for user: {user_id}")
langflow_history = await langflow_history_service.get_user_conversation_history(user_id) langflow_history = await langflow_history_service.get_user_conversation_history(user_id, flow_id=FLOW_ID)
if langflow_history.get("conversations"): if langflow_history.get("conversations"):
for conversation in langflow_history["conversations"]: for conversation in langflow_history["conversations"]: