update chat page to use a new filter for every conversation
This commit is contained in:
parent
230081e591
commit
15701f8b16
2 changed files with 1620 additions and 1558 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -1,255 +1,304 @@
|
|||
"use client";
|
||||
|
||||
import {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
createContext,
|
||||
type ReactNode,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
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 KnowledgeFilter {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
query_data: string;
|
||||
owner: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
interface ConversationData {
|
||||
messages: ConversationMessage[];
|
||||
endpoint: EndpointType;
|
||||
response_id: string;
|
||||
title: string;
|
||||
[key: string]: unknown;
|
||||
messages: ConversationMessage[];
|
||||
endpoint: EndpointType;
|
||||
response_id: string;
|
||||
title: string;
|
||||
filter?: KnowledgeFilter | null;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
interface ChatContextType {
|
||||
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: (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;
|
||||
conversationLoaded: boolean;
|
||||
setConversationLoaded: (loaded: boolean) => 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: (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;
|
||||
conversationLoaded: boolean;
|
||||
setConversationLoaded: (loaded: boolean) => void;
|
||||
conversationFilter: KnowledgeFilter | null;
|
||||
setConversationFilter: (filter: KnowledgeFilter | null) => void;
|
||||
}
|
||||
|
||||
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 [previousResponseIds, setPreviousResponseIds] = useState<{
|
||||
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 [conversationLoaded, setConversationLoaded] = useState(false);
|
||||
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 [refreshTriggerSilent, setRefreshTriggerSilent] = useState(0);
|
||||
const [conversationData, setConversationData] =
|
||||
useState<ConversationData | null>(null);
|
||||
const [conversationDocs, setConversationDocs] = useState<
|
||||
ConversationDocument[]
|
||||
>([]);
|
||||
const [placeholderConversation, setPlaceholderConversation] =
|
||||
useState<ConversationData | null>(null);
|
||||
const [conversationLoaded, setConversationLoaded] = useState(false);
|
||||
const [conversationFilter, setConversationFilterState] =
|
||||
useState<KnowledgeFilter | null>(null);
|
||||
|
||||
// Debounce refresh requests to prevent excessive reloads
|
||||
const refreshTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
// Debounce refresh requests to prevent excessive reloads
|
||||
const refreshTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const refreshConversations = useCallback((force = false) => {
|
||||
if (force) {
|
||||
// Immediate refresh for important updates like new conversations
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
return;
|
||||
}
|
||||
const refreshConversations = useCallback((force = false) => {
|
||||
if (force) {
|
||||
// Immediate refresh for important updates like new conversations
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear any existing timeout
|
||||
if (refreshTimeoutRef.current) {
|
||||
clearTimeout(refreshTimeoutRef.current);
|
||||
}
|
||||
// Clear any existing timeout
|
||||
if (refreshTimeoutRef.current) {
|
||||
clearTimeout(refreshTimeoutRef.current);
|
||||
}
|
||||
|
||||
// Set a new timeout to debounce multiple rapid refresh calls
|
||||
refreshTimeoutRef.current = setTimeout(() => {
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
}, 250); // 250ms debounce
|
||||
}, []);
|
||||
// Set a new timeout to debounce multiple rapid refresh calls
|
||||
refreshTimeoutRef.current = setTimeout(() => {
|
||||
setRefreshTrigger((prev) => prev + 1);
|
||||
}, 250); // 250ms debounce
|
||||
}, []);
|
||||
|
||||
// Cleanup timeout on unmount
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (refreshTimeoutRef.current) {
|
||||
clearTimeout(refreshTimeoutRef.current);
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
// 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);
|
||||
}, []);
|
||||
// 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);
|
||||
setConversationLoaded(true);
|
||||
// Clear conversation docs to prevent duplicates when switching conversations
|
||||
setConversationDocs([]);
|
||||
}, []);
|
||||
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);
|
||||
// Load the filter if one exists for this conversation
|
||||
// Only update the filter if this is a different conversation (to preserve user's filter selection)
|
||||
setConversationFilterState((currentFilter) => {
|
||||
// If we're loading a different conversation, load its filter
|
||||
// Otherwise keep the current filter (don't reset it when conversation refreshes)
|
||||
const isDifferentConversation =
|
||||
conversation.response_id !== conversationData?.response_id;
|
||||
return isDifferentConversation
|
||||
? conversation.filter || null
|
||||
: currentFilter;
|
||||
});
|
||||
// Clear placeholder when loading a real conversation
|
||||
setPlaceholderConversation(null);
|
||||
setConversationLoaded(true);
|
||||
// Clear conversation docs to prevent duplicates when switching conversations
|
||||
setConversationDocs([]);
|
||||
},
|
||||
[conversationData?.response_id],
|
||||
);
|
||||
|
||||
const startNewConversation = useCallback(() => {
|
||||
// Clear current conversation data and reset state
|
||||
setCurrentConversationId(null);
|
||||
setPreviousResponseIds({ chat: null, langflow: null });
|
||||
setConversationData(null);
|
||||
setConversationDocs([]);
|
||||
setConversationLoaded(false);
|
||||
const startNewConversation = useCallback(() => {
|
||||
// Clear current conversation data and reset state
|
||||
setCurrentConversationId(null);
|
||||
setPreviousResponseIds({ chat: null, langflow: null });
|
||||
setConversationData(null);
|
||||
setConversationDocs([]);
|
||||
setConversationLoaded(false);
|
||||
// Clear the filter when starting a new conversation
|
||||
setConversationFilterState(null);
|
||||
|
||||
// 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(),
|
||||
};
|
||||
// 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]);
|
||||
setPlaceholderConversation(placeholderConversation);
|
||||
// Force immediate refresh to ensure sidebar shows correct state
|
||||
refreshConversations(true);
|
||||
}, [endpoint, refreshConversations]);
|
||||
|
||||
const addConversationDoc = useCallback((filename: string) => {
|
||||
setConversationDocs((prev) => [
|
||||
...prev,
|
||||
{ filename, uploadTime: new Date() },
|
||||
]);
|
||||
}, []);
|
||||
const addConversationDoc = useCallback((filename: string) => {
|
||||
setConversationDocs((prev) => [
|
||||
...prev,
|
||||
{ filename, uploadTime: new Date() },
|
||||
]);
|
||||
}, []);
|
||||
|
||||
const clearConversationDocs = useCallback(() => {
|
||||
setConversationDocs([]);
|
||||
}, []);
|
||||
const clearConversationDocs = useCallback(() => {
|
||||
setConversationDocs([]);
|
||||
}, []);
|
||||
|
||||
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 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,
|
||||
conversationLoaded,
|
||||
setConversationLoaded,
|
||||
}),
|
||||
[
|
||||
endpoint,
|
||||
currentConversationId,
|
||||
previousResponseIds,
|
||||
refreshConversations,
|
||||
refreshConversationsSilent,
|
||||
refreshTrigger,
|
||||
refreshTriggerSilent,
|
||||
loadConversation,
|
||||
startNewConversation,
|
||||
conversationData,
|
||||
forkFromResponse,
|
||||
conversationDocs,
|
||||
addConversationDoc,
|
||||
clearConversationDocs,
|
||||
placeholderConversation,
|
||||
conversationLoaded,
|
||||
setConversationLoaded,
|
||||
],
|
||||
);
|
||||
const setConversationFilter = useCallback(
|
||||
(filter: KnowledgeFilter | null) => {
|
||||
setConversationFilterState(filter);
|
||||
// Update the conversation data to include the filter
|
||||
setConversationData((prev) => {
|
||||
if (!prev) return prev;
|
||||
return {
|
||||
...prev,
|
||||
filter,
|
||||
};
|
||||
});
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
||||
const value = useMemo<ChatContextType>(
|
||||
() => ({
|
||||
endpoint,
|
||||
setEndpoint,
|
||||
currentConversationId,
|
||||
setCurrentConversationId,
|
||||
previousResponseIds,
|
||||
setPreviousResponseIds,
|
||||
refreshConversations,
|
||||
refreshConversationsSilent,
|
||||
refreshTrigger,
|
||||
refreshTriggerSilent,
|
||||
loadConversation,
|
||||
startNewConversation,
|
||||
conversationData,
|
||||
forkFromResponse,
|
||||
conversationDocs,
|
||||
addConversationDoc,
|
||||
clearConversationDocs,
|
||||
placeholderConversation,
|
||||
setPlaceholderConversation,
|
||||
conversationLoaded,
|
||||
setConversationLoaded,
|
||||
conversationFilter,
|
||||
setConversationFilter,
|
||||
}),
|
||||
[
|
||||
endpoint,
|
||||
currentConversationId,
|
||||
previousResponseIds,
|
||||
refreshConversations,
|
||||
refreshConversationsSilent,
|
||||
refreshTrigger,
|
||||
refreshTriggerSilent,
|
||||
loadConversation,
|
||||
startNewConversation,
|
||||
conversationData,
|
||||
forkFromResponse,
|
||||
conversationDocs,
|
||||
addConversationDoc,
|
||||
clearConversationDocs,
|
||||
placeholderConversation,
|
||||
conversationLoaded,
|
||||
conversationFilter,
|
||||
setConversationFilter,
|
||||
],
|
||||
);
|
||||
|
||||
return <ChatContext.Provider value={value}>{children}</ChatContext.Provider>;
|
||||
}
|
||||
|
||||
export function useChat(): ChatContextType {
|
||||
const context = useContext(ChatContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useChat must be used within a ChatProvider");
|
||||
}
|
||||
return context;
|
||||
const context = useContext(ChatContext);
|
||||
if (context === undefined) {
|
||||
throw new Error("useChat must be used within a ChatProvider");
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue