📝 (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:
parent
0db67b8c6a
commit
6dcb65debd
4 changed files with 495 additions and 180 deletions
|
|
@ -91,8 +91,10 @@ function ChatPage() {
|
|||
addConversationDoc,
|
||||
forkFromResponse,
|
||||
refreshConversations,
|
||||
refreshConversationsSilent,
|
||||
previousResponseIds,
|
||||
setPreviousResponseIds,
|
||||
placeholderConversation,
|
||||
} = useChat();
|
||||
const [messages, setMessages] = useState<Message[]>([
|
||||
{
|
||||
|
|
@ -133,6 +135,7 @@ function ChatPage() {
|
|||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const streamAbortRef = useRef<AbortController | null>(null);
|
||||
const streamIdRef = useRef(0);
|
||||
const lastLoadedConversationRef = useRef<string | null>(null);
|
||||
const { addTask, isMenuOpen } = useTask();
|
||||
const { selectedFilter, parsedFilterData, isPanelOpen, setSelectedFilter } =
|
||||
useKnowledgeFilter();
|
||||
|
|
@ -241,11 +244,16 @@ function ChatPage() {
|
|||
...prev,
|
||||
[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 {
|
||||
throw new Error(`Upload failed: ${response.status}`);
|
||||
}
|
||||
|
|
@ -406,6 +414,7 @@ function ChatPage() {
|
|||
setExpandedFunctionCalls(new Set());
|
||||
setIsFilterHighlighted(false);
|
||||
setLoading(false);
|
||||
lastLoadedConversationRef.current = null;
|
||||
};
|
||||
|
||||
const handleFocusInput = () => {
|
||||
|
|
@ -420,25 +429,19 @@ function ChatPage() {
|
|||
};
|
||||
}, []);
|
||||
|
||||
// Load conversation when conversationData changes
|
||||
// Load conversation only when user explicitly selects a conversation
|
||||
useEffect(() => {
|
||||
const now = Date.now();
|
||||
|
||||
// Don't reset messages if user is in the middle of an interaction (like forking)
|
||||
if (isUserInteracting || isForkingInProgress) {
|
||||
console.log(
|
||||
"Skipping conversation load due to user interaction or forking"
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Only load conversation data when:
|
||||
// 1. conversationData exists AND
|
||||
// 2. It's different from the last loaded conversation AND
|
||||
// 3. User is not in the middle of an interaction
|
||||
if (
|
||||
conversationData &&
|
||||
conversationData.messages &&
|
||||
lastLoadedConversationRef.current !== conversationData.response_id &&
|
||||
!isUserInteracting &&
|
||||
!isForkingInProgress
|
||||
) {
|
||||
console.log(
|
||||
"Loading conversation with",
|
||||
conversationData.messages.length,
|
||||
|
|
@ -460,6 +463,7 @@ function ChatPage() {
|
|||
);
|
||||
|
||||
setMessages(convertedMessages);
|
||||
lastLoadedConversationRef.current = conversationData.response_id;
|
||||
|
||||
// Set the previous response ID for this conversation
|
||||
setPreviousResponseIds((prev) => ({
|
||||
|
|
@ -467,14 +471,16 @@ function ChatPage() {
|
|||
[conversationData.endpoint]: conversationData.response_id,
|
||||
}));
|
||||
}
|
||||
// Reset messages when starting a new conversation (but not during forking)
|
||||
else if (
|
||||
currentConversationId === null &&
|
||||
!isUserInteracting &&
|
||||
!isForkingInProgress &&
|
||||
now - lastForkTimestamp > 1000
|
||||
) {
|
||||
console.log("Resetting to default message for new conversation");
|
||||
}, [
|
||||
conversationData,
|
||||
isUserInteracting,
|
||||
isForkingInProgress,
|
||||
]);
|
||||
|
||||
// Handle new conversation creation - only reset messages when placeholderConversation is set
|
||||
useEffect(() => {
|
||||
if (placeholderConversation && currentConversationId === null) {
|
||||
console.log("Starting new conversation");
|
||||
setMessages([
|
||||
{
|
||||
role: "assistant",
|
||||
|
|
@ -482,15 +488,9 @@ function ChatPage() {
|
|||
timestamp: new Date(),
|
||||
},
|
||||
]);
|
||||
lastLoadedConversationRef.current = null;
|
||||
}
|
||||
}, [
|
||||
conversationData,
|
||||
currentConversationId,
|
||||
isUserInteracting,
|
||||
isForkingInProgress,
|
||||
lastForkTimestamp,
|
||||
setPreviousResponseIds,
|
||||
]);
|
||||
}, [placeholderConversation, currentConversationId]);
|
||||
|
||||
// Listen for file upload events from navigation
|
||||
useEffect(() => {
|
||||
|
|
@ -1280,14 +1280,16 @@ function ChatPage() {
|
|||
...prev,
|
||||
[endpoint]: newResponseId,
|
||||
}));
|
||||
|
||||
// If this is a new conversation (no currentConversationId), set it now
|
||||
if (!currentConversationId) {
|
||||
setCurrentConversationId(newResponseId);
|
||||
refreshConversations(true);
|
||||
} else {
|
||||
// For existing conversations, do a silent refresh to keep backend in sync
|
||||
refreshConversationsSilent();
|
||||
}
|
||||
}
|
||||
|
||||
// Trigger sidebar refresh to include this conversation (with small delay to ensure backend has processed)
|
||||
setTimeout(() => {
|
||||
try {
|
||||
refreshConversations();
|
||||
} catch {}
|
||||
}, 100);
|
||||
} catch (error) {
|
||||
// If stream was aborted (e.g., starting new conversation), do not append errors or final messages
|
||||
if (streamAbortRef.current?.signal.aborted) {
|
||||
|
|
@ -1390,13 +1392,16 @@ function ChatPage() {
|
|||
...prev,
|
||||
[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 {
|
||||
console.error("Chat failed:", result.error);
|
||||
const errorMessage: Message = {
|
||||
|
|
@ -2013,9 +2018,6 @@ function ChatPage() {
|
|||
// Clear filter highlight when user starts typing
|
||||
if (isFilterHighlighted) {
|
||||
setIsFilterHighlighted(false);
|
||||
try {
|
||||
refreshConversations();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// Find if there's an @ at the start of the last word
|
||||
|
|
|
|||
230
frontend/src/components/navigation.tsx
Normal file
230
frontend/src/components/navigation.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
|
|
@ -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 {
|
||||
filename: string
|
||||
uploadTime: Date
|
||||
filename: string;
|
||||
uploadTime: Date;
|
||||
}
|
||||
|
||||
interface ConversationMessage {
|
||||
role: string
|
||||
content: string
|
||||
timestamp?: string
|
||||
response_id?: string
|
||||
role: string;
|
||||
content: string;
|
||||
timestamp?: string;
|
||||
response_id?: string;
|
||||
}
|
||||
|
||||
interface ConversationData {
|
||||
messages: ConversationMessage[]
|
||||
endpoint: EndpointType
|
||||
response_id: string
|
||||
title: string
|
||||
[key: string]: unknown
|
||||
messages: ConversationMessage[];
|
||||
endpoint: EndpointType;
|
||||
response_id: string;
|
||||
title: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface ChatContextType {
|
||||
endpoint: EndpointType
|
||||
setEndpoint: (endpoint: EndpointType) => void
|
||||
currentConversationId: string | null
|
||||
setCurrentConversationId: (id: string | null) => void
|
||||
endpoint: EndpointType;
|
||||
setEndpoint: (endpoint: EndpointType) => void;
|
||||
currentConversationId: string | null;
|
||||
setCurrentConversationId: (id: string | null) => void;
|
||||
previousResponseIds: {
|
||||
chat: 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
|
||||
refreshConversations: () => void
|
||||
refreshTrigger: 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
|
||||
chat: 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;
|
||||
refreshConversations: (force?: boolean) => void;
|
||||
refreshConversationsSilent: () => Promise<void>;
|
||||
refreshTrigger: number;
|
||||
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 {
|
||||
children: ReactNode
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
export function ChatProvider({ children }: ChatProviderProps) {
|
||||
const [endpoint, setEndpoint] = useState<EndpointType>('langflow')
|
||||
const [currentConversationId, setCurrentConversationId] = useState<string | null>(null)
|
||||
const [endpoint, setEndpoint] = useState<EndpointType>("langflow");
|
||||
const [currentConversationId, setCurrentConversationId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
const [previousResponseIds, setPreviousResponseIds] = useState<{
|
||||
chat: string | null
|
||||
langflow: string | null
|
||||
}>({ chat: null, langflow: null })
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0)
|
||||
const [conversationData, setConversationData] = useState<ConversationData | null>(null)
|
||||
const [conversationDocs, setConversationDocs] = useState<ConversationDocument[]>([])
|
||||
const [placeholderConversation, setPlaceholderConversation] = useState<ConversationData | null>(null)
|
||||
chat: string | null;
|
||||
langflow: string | null;
|
||||
}>({ chat: null, langflow: null });
|
||||
const [refreshTrigger, setRefreshTrigger] = useState(0);
|
||||
const [refreshTriggerSilent, setRefreshTriggerSilent] = useState(0);
|
||||
const [conversationData, setConversationData] =
|
||||
useState<ConversationData | null>(null);
|
||||
const [conversationDocs, setConversationDocs] = useState<
|
||||
ConversationDocument[]
|
||||
>([]);
|
||||
const [placeholderConversation, setPlaceholderConversation] =
|
||||
useState<ConversationData | null>(null);
|
||||
|
||||
const refreshConversations = () => {
|
||||
setRefreshTrigger(prev => prev + 1)
|
||||
}
|
||||
// Debounce refresh requests to prevent excessive reloads
|
||||
const refreshTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const loadConversation = (conversation: ConversationData) => {
|
||||
setCurrentConversationId(conversation.response_id)
|
||||
setEndpoint(conversation.endpoint)
|
||||
// Store the full conversation data for the chat page to use
|
||||
// We'll pass it through a ref or state that the chat page can access
|
||||
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()
|
||||
const refreshConversations = useCallback((force = false) => {
|
||||
if (force) {
|
||||
// Immediate refresh for important updates like new conversations
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
setCurrentConversationId(null)
|
||||
setPreviousResponseIds({ chat: null, langflow: null })
|
||||
setConversationData(null)
|
||||
setConversationDocs([])
|
||||
setPlaceholderConversation(placeholderConversation)
|
||||
// Force a refresh to ensure sidebar shows correct state
|
||||
setRefreshTrigger(prev => prev + 1)
|
||||
}
|
||||
|
||||
const addConversationDoc = (filename: string) => {
|
||||
setConversationDocs(prev => [...prev, { filename, uploadTime: new Date() }])
|
||||
}
|
||||
// Clear any existing timeout
|
||||
if (refreshTimeoutRef.current) {
|
||||
clearTimeout(refreshTimeoutRef.current);
|
||||
}
|
||||
|
||||
const clearConversationDocs = () => {
|
||||
setConversationDocs([])
|
||||
}
|
||||
// Set a new timeout to debounce multiple rapid refresh calls
|
||||
refreshTimeoutRef.current = setTimeout(() => {
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
}, 250); // 250ms debounce
|
||||
}, []);
|
||||
|
||||
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
|
||||
setConversationData(null) // Clear conversation data to prevent reloading
|
||||
// Set the response ID that we're forking from as the previous response ID
|
||||
setPreviousResponseIds(prev => ({
|
||||
// Cleanup timeout on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (refreshTimeoutRef.current) {
|
||||
clearTimeout(refreshTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Silent refresh - updates data without loading states
|
||||
const refreshConversationsSilent = useCallback(async () => {
|
||||
// Trigger silent refresh that updates conversation data without showing loading states
|
||||
setRefreshTriggerSilent((prev) => prev + 1);
|
||||
}, []);
|
||||
|
||||
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,
|
||||
[endpoint]: responseId
|
||||
}))
|
||||
// Clear placeholder when forking
|
||||
setPlaceholderConversation(null)
|
||||
// The messages are already set by the chat page component before calling this
|
||||
}
|
||||
{ filename, uploadTime: new Date() },
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const value: ChatContextType = {
|
||||
endpoint,
|
||||
setEndpoint,
|
||||
currentConversationId,
|
||||
setCurrentConversationId,
|
||||
previousResponseIds,
|
||||
setPreviousResponseIds,
|
||||
refreshConversations,
|
||||
refreshTrigger,
|
||||
loadConversation,
|
||||
startNewConversation,
|
||||
conversationData,
|
||||
forkFromResponse,
|
||||
conversationDocs,
|
||||
addConversationDoc,
|
||||
clearConversationDocs,
|
||||
placeholderConversation,
|
||||
setPlaceholderConversation,
|
||||
}
|
||||
const clearConversationDocs = useCallback(() => {
|
||||
setConversationDocs([]);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ChatContext.Provider value={value}>
|
||||
{children}
|
||||
</ChatContext.Provider>
|
||||
)
|
||||
const forkFromResponse = useCallback(
|
||||
(responseId: string) => {
|
||||
// Start a new conversation with the messages up to the fork point
|
||||
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 {
|
||||
const context = useContext(ChatContext)
|
||||
const context = useContext(ChatContext);
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -328,7 +328,7 @@ class ChatService:
|
|||
# 2. Get historical conversations from Langflow database
|
||||
# (works with both Google-bound users and direct Langflow users)
|
||||
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"):
|
||||
for conversation in langflow_history["conversations"]:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue