"use client"; import React, { createContext, useContext, useReducer, useEffect, useRef, ReactNode, useCallback, } from "react"; // Enums for better type safety export enum ChatbotStatus { IDLE = "idle", LOADING = "loading", READY = "ready", ERROR = "error", DISCONNECTED = "disconnected", } export enum VectorStoreType { QDRANT = "qdrant", PINECONE = "pinecone", SUPABASE = "supabase", } export enum LLMProvider { OPENAI = "openai", ANTHROPIC = "anthropic", GOOGLE = "google", MISTRAL = "mistral", } // Core interfaces export interface ChatbotError { code: string; message: string; details?: any; timestamp: Date; } export interface VectorStoreConfig { type: VectorStoreType; url?: string; apiKey?: string; collection?: string; dimensions?: number; similarity?: "cosine" | "euclidean" | "dot"; namespace?: string; } export interface LLMConfig { provider: LLMProvider; model: string; apiKey?: string; temperature?: number; maxTokens?: number; topP?: number; frequencyPenalty?: number; presencePenalty?: number; systemPrompt?: string; } export interface StorageConfig { bucket: string; url?: string; apiKey?: string; maxFileSize?: number; allowedTypes?: string[]; } export interface ChatbotConfig { systemPrompt: string; welcomeMessage: string; suggestedQuestions: string[]; maxConversationHistory: number; enableRAG: boolean; ragSettings: { similarityThreshold: number; maxRetrievedDocs: number; chunkSize: number; chunkOverlap: number; }; uiSettings: { theme: string; language: string; showTimestamps: boolean; enableTypingIndicator: boolean; }; } export interface Document { id: string; title: string; content: string; metadata: { source?: string; uploadedAt: Date; size: number; type: string; description?: string; tags?: string[]; }; embedding?: number[]; status: "processing" | "ready" | "error"; } export interface ConversationMessage { id: string; content: string; role: "user" | "assistant" | "system"; timestamp: Date; metadata?: { retrievedDocs?: Document[]; confidence?: number; tokens?: number; }; } export interface Conversation { id: string; title?: string; messages: ConversationMessage[]; createdAt: Date; updatedAt: Date; metadata?: { userId?: string; sessionId?: string; tags?: string[]; }; } // State interfaces export interface VectorStoreState { status: ChatbotStatus; isConnected: boolean; config: VectorStoreConfig | null; error: ChatbotError | null; collections: string[]; documentCount: number; } export interface LLMState { status: ChatbotStatus; isConnected: boolean; config: LLMConfig | null; error: ChatbotError | null; availableModels: string[]; currentModel: string | null; } export interface StorageState { status: ChatbotStatus; isConnected: boolean; config: StorageConfig | null; error: ChatbotError | null; uploadProgress: { [fileId: string]: number }; } export interface ChatbotContextState { // Overall system state isInitialized: boolean; globalStatus: ChatbotStatus; initializationProgress: number; // Subsystem states vectorStore: VectorStoreState; llm: LLMState; storage: StorageState; // Configuration config: ChatbotConfig; // Data documents: Document[]; conversations: Conversation[]; currentConversation: Conversation | null; // UI state isProcessing: boolean; lastError: ChatbotError | null; } // Action types export type ChatbotAction = | { type: "INITIALIZE_START" } | { type: "INITIALIZE_SUCCESS" } | { type: "INITIALIZE_ERROR"; payload: ChatbotError } | { type: "SET_INITIALIZATION_PROGRESS"; payload: number } // Vector store actions | { type: "VECTOR_STORE_CONNECTING" } | { type: "VECTOR_STORE_CONNECTED"; payload: { collections: string[]; documentCount: number }; } | { type: "VECTOR_STORE_ERROR"; payload: ChatbotError } | { type: "VECTOR_STORE_CONFIG_UPDATE"; payload: Partial } // LLM actions | { type: "LLM_CONNECTING" } | { type: "LLM_CONNECTED"; payload: { availableModels: string[]; currentModel: string }; } | { type: "LLM_ERROR"; payload: ChatbotError } | { type: "LLM_CONFIG_UPDATE"; payload: Partial } // Storage actions | { type: "STORAGE_CONNECTING" } | { type: "STORAGE_CONNECTED" } | { type: "STORAGE_ERROR"; payload: ChatbotError } | { type: "STORAGE_UPLOAD_PROGRESS"; payload: { fileId: string; progress: number }; } | { type: "STORAGE_CONFIG_UPDATE"; payload: Partial } // Configuration actions | { type: "CONFIG_UPDATE"; payload: Partial } // Document actions | { type: "DOCUMENTS_SET"; payload: Document[] } | { type: "DOCUMENT_ADD"; payload: Document } | { type: "DOCUMENT_UPDATE"; payload: { id: string; updates: Partial }; } | { type: "DOCUMENT_REMOVE"; payload: string } // Conversation actions | { type: "CONVERSATIONS_SET"; payload: Conversation[] } | { type: "CONVERSATION_START"; payload: Conversation } | { type: "CONVERSATION_SELECT"; payload: string } | { type: "CONVERSATION_MESSAGE_ADD"; payload: { conversationId: string; message: ConversationMessage }; } // UI actions | { type: "SET_PROCESSING"; payload: boolean } | { type: "CLEAR_ERROR" } | { type: "SET_ERROR"; payload: ChatbotError }; // Context methods interface export interface ChatbotContextMethods { // Initialization initialize: () => Promise; reset: () => Promise; // Vector store operations initializeVectorStore: (config: VectorStoreConfig) => Promise; addDocuments: (documents: Document[]) => Promise; removeDocuments: (documentIds: string[]) => Promise; searchSimilarDocuments: ( query: string, limit?: number ) => Promise; // LLM operations initializeLLM: (config: LLMConfig) => Promise; generateResponse: (prompt: string, context?: Document[]) => Promise; generateRAGResponse: ( query: string ) => Promise<{ response: string; sources: Document[] }>; // Storage operations initializeStorage: (config: StorageConfig) => Promise; uploadDocument: ( file: File, metadata?: Partial ) => Promise; downloadDocument: (documentId: string) => Promise; deleteDocument: (documentId: string) => Promise; listDocuments: () => Promise; // Configuration management updateConfig: (updates: Partial) => Promise; exportConfig: () => ChatbotConfig; importConfig: (config: ChatbotConfig) => Promise; // Conversation management startConversation: (title?: string) => Promise; selectConversation: (conversationId: string) => Promise; sendMessage: (content: string) => Promise; getConversationHistory: ( conversationId?: string ) => Promise; deleteConversation: (conversationId: string) => Promise; // Utility methods getStatus: () => { overall: ChatbotStatus; subsystems: { vectorStore: ChatbotStatus; llm: ChatbotStatus; storage: ChatbotStatus; }; }; clearError: () => void; healthCheck: () => Promise<{ vectorStore: boolean; llm: boolean; storage: boolean; }>; } // Combined context type export type ChatbotContextType = ChatbotContextState & ChatbotContextMethods; // Default configuration export const defaultChatbotConfig: ChatbotConfig = { systemPrompt: "You are a helpful AI assistant with access to a knowledge base. Use the provided context to answer questions accurately and helpfully.", welcomeMessage: "Hello! I'm here to help you find information. What would you like to know?", suggestedQuestions: [ "What can you help me with?", "How does this system work?", "Tell me about your capabilities", ], maxConversationHistory: 10, enableRAG: true, ragSettings: { similarityThreshold: 0.7, maxRetrievedDocs: 5, chunkSize: 1000, chunkOverlap: 200, }, uiSettings: { theme: "default", language: "en", showTimestamps: true, enableTypingIndicator: true, }, }; // Initial state const initialState: ChatbotContextState = { isInitialized: false, globalStatus: ChatbotStatus.IDLE, initializationProgress: 0, vectorStore: { status: ChatbotStatus.IDLE, isConnected: false, config: null, error: null, collections: [], documentCount: 0, }, llm: { status: ChatbotStatus.IDLE, isConnected: false, config: null, error: null, availableModels: [], currentModel: null, }, storage: { status: ChatbotStatus.IDLE, isConnected: false, config: null, error: null, uploadProgress: {}, }, config: defaultChatbotConfig, documents: [], conversations: [], currentConversation: null, isProcessing: false, lastError: null, }; // Reducer function export function chatbotReducer( state: ChatbotContextState, action: ChatbotAction ): ChatbotContextState { switch (action.type) { case "INITIALIZE_START": return { ...state, globalStatus: ChatbotStatus.LOADING, initializationProgress: 0, lastError: null, }; case "INITIALIZE_SUCCESS": return { ...state, isInitialized: true, globalStatus: ChatbotStatus.READY, initializationProgress: 100, }; case "INITIALIZE_ERROR": return { ...state, globalStatus: ChatbotStatus.ERROR, lastError: action.payload, }; case "SET_INITIALIZATION_PROGRESS": return { ...state, initializationProgress: action.payload, }; // Vector store cases case "VECTOR_STORE_CONNECTING": return { ...state, vectorStore: { ...state.vectorStore, status: ChatbotStatus.LOADING, error: null, }, }; case "VECTOR_STORE_CONNECTED": return { ...state, vectorStore: { ...state.vectorStore, status: ChatbotStatus.READY, isConnected: true, collections: action.payload.collections, documentCount: action.payload.documentCount, }, }; case "VECTOR_STORE_ERROR": return { ...state, vectorStore: { ...state.vectorStore, status: ChatbotStatus.ERROR, isConnected: false, error: action.payload, }, }; case "VECTOR_STORE_CONFIG_UPDATE": return { ...state, vectorStore: { ...state.vectorStore, config: state.vectorStore.config ? { ...state.vectorStore.config, ...action.payload } : null, }, }; // LLM cases case "LLM_CONNECTING": return { ...state, llm: { ...state.llm, status: ChatbotStatus.LOADING, error: null, }, }; case "LLM_CONNECTED": return { ...state, llm: { ...state.llm, status: ChatbotStatus.READY, isConnected: true, availableModels: action.payload.availableModels, currentModel: action.payload.currentModel, }, }; case "LLM_ERROR": return { ...state, llm: { ...state.llm, status: ChatbotStatus.ERROR, isConnected: false, error: action.payload, }, }; case "LLM_CONFIG_UPDATE": return { ...state, llm: { ...state.llm, config: state.llm.config ? { ...state.llm.config, ...action.payload } : null, }, }; // Storage cases case "STORAGE_CONNECTING": return { ...state, storage: { ...state.storage, status: ChatbotStatus.LOADING, error: null, }, }; case "STORAGE_CONNECTED": return { ...state, storage: { ...state.storage, status: ChatbotStatus.READY, isConnected: true, }, }; case "STORAGE_ERROR": return { ...state, storage: { ...state.storage, status: ChatbotStatus.ERROR, isConnected: false, error: action.payload, }, }; case "STORAGE_UPLOAD_PROGRESS": return { ...state, storage: { ...state.storage, uploadProgress: { ...state.storage.uploadProgress, [action.payload.fileId]: action.payload.progress, }, }, }; case "STORAGE_CONFIG_UPDATE": return { ...state, storage: { ...state.storage, config: state.storage.config ? { ...state.storage.config, ...action.payload } : null, }, }; // Configuration cases case "CONFIG_UPDATE": return { ...state, config: { ...state.config, ...action.payload }, }; // Document cases case "DOCUMENTS_SET": return { ...state, documents: action.payload, }; case "DOCUMENT_ADD": return { ...state, documents: [...state.documents, action.payload], }; case "DOCUMENT_UPDATE": return { ...state, documents: state.documents.map((doc) => doc.id === action.payload.id ? { ...doc, ...action.payload.updates } : doc ), }; case "DOCUMENT_REMOVE": return { ...state, documents: state.documents.filter((doc) => doc.id !== action.payload), }; // Conversation cases case "CONVERSATIONS_SET": return { ...state, conversations: action.payload, }; case "CONVERSATION_START": return { ...state, conversations: [action.payload, ...state.conversations], currentConversation: action.payload, }; case "CONVERSATION_SELECT": return { ...state, currentConversation: state.conversations.find((c) => c.id === action.payload) || null, }; case "CONVERSATION_MESSAGE_ADD": const updatedConversations = state.conversations.map((conv) => conv.id === action.payload.conversationId ? { ...conv, messages: [...conv.messages, action.payload.message], updatedAt: new Date(), } : conv ); return { ...state, conversations: updatedConversations, currentConversation: state.currentConversation?.id === action.payload.conversationId ? updatedConversations.find( (c) => c.id === action.payload.conversationId ) || null : state.currentConversation, }; // UI cases case "SET_PROCESSING": return { ...state, isProcessing: action.payload, }; case "CLEAR_ERROR": return { ...state, lastError: null, }; case "SET_ERROR": return { ...state, lastError: action.payload, }; default: return state; } } // Create context export const ChatbotContext = createContext( undefined ); // Provider props export interface ChatbotProviderProps { children: ReactNode; initialConfig?: Partial; autoInitialize?: boolean; } // Custom hook for consuming context export function useChatbot(): ChatbotContextType { const context = useContext(ChatbotContext); if (context === undefined) { throw new Error("useChatbot must be used within a ChatbotProvider"); } return context; } // Provider component (basic structure - will be implemented in subsequent subtasks) export function ChatbotProvider({ children, initialConfig = {}, autoInitialize = false, }: ChatbotProviderProps) { const [state, dispatch] = useReducer(chatbotReducer, { ...initialState, config: { ...defaultChatbotConfig, ...initialConfig }, }); // Vector store instance reference const vectorStoreRef = useRef(null); // LLM instance reference const llmRef = useRef(null); // RAG chain instance reference const ragChainRef = useRef(null); // Storage instance reference const storageRef = useRef(null); // Initialization methods const initialize = useCallback(async () => { dispatch({ type: "INITIALIZE_START" }); dispatch({ type: "SET_INITIALIZATION_PROGRESS", payload: 0 }); try { // Clear any previous errors dispatch({ type: "CLEAR_ERROR" }); // Initialize based on configuration const config = state.config; let initProgress = 10; // Step 1: Initialize Vector Store (if enabled) if (config.enableRAG) { try { // Check if we have vector store configuration const vectorStoreConfig = state.vectorStore.config; if (vectorStoreConfig) { await initializeVectorStore(vectorStoreConfig); initProgress = 40; } else { console.warn( "RAG is enabled but no vector store configuration found" ); } } catch (error) { console.warn("Failed to initialize vector store:", error); // Continue with other initializations } } dispatch({ type: "SET_INITIALIZATION_PROGRESS", payload: initProgress }); // Step 2: Initialize LLM (required for basic functionality) try { const llmConfig = state.llm.config; if (llmConfig) { await initializeLLM(llmConfig); initProgress = Math.max(initProgress, 80); } else { throw new Error("LLM configuration is required"); } } catch (error) { console.error("Failed to initialize LLM:", error); throw error; // LLM is required, so fail initialization } dispatch({ type: "SET_INITIALIZATION_PROGRESS", payload: initProgress }); // Step 3: Initialize Storage (optional) try { const storageConfig = state.storage.config; if (storageConfig) { await initializeStorage(storageConfig); initProgress = 100; } else { console.warn( "No storage configuration found, skipping storage initialization" ); initProgress = 100; } } catch (error) { console.warn("Failed to initialize storage:", error); // Storage is optional, continue initProgress = 100; } dispatch({ type: "SET_INITIALIZATION_PROGRESS", payload: initProgress }); dispatch({ type: "INITIALIZE_SUCCESS" }); } catch (error) { const chatbotError: ChatbotError = { code: "INITIALIZATION_ERROR", message: `Failed to initialize chatbot: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "INITIALIZE_ERROR", payload: chatbotError }); dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } }, [ state.config, state.vectorStore.config, state.llm.config, state.storage.config, ]); const reset = useCallback(async () => { try { dispatch({ type: "SET_PROCESSING", payload: true }); // Disconnect and clear all subsystems if (vectorStoreRef.current) { try { await vectorStoreRef.current.disconnect(); } catch (error) { console.warn("Failed to disconnect vector store:", error); } vectorStoreRef.current = null; } if (llmRef.current) { try { await llmRef.current.disconnect(); } catch (error) { console.warn("Failed to disconnect LLM:", error); } llmRef.current = null; } if (ragChainRef.current) { ragChainRef.current = null; } if (storageRef.current) { try { await storageRef.current.disconnect(); } catch (error) { console.warn("Failed to disconnect storage:", error); } storageRef.current = null; } // Reset state to initial values const resetState: ChatbotContextState = { ...initialState, config: state.config, // Keep the configuration }; // Clear all state except config dispatch({ type: "INITIALIZE_START" }); // This will reset most of the state dispatch({ type: "CLEAR_ERROR" }); dispatch({ type: "SET_PROCESSING", payload: false }); } catch (error) { const chatbotError: ChatbotError = { code: "RESET_ERROR", message: `Failed to reset chatbot: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } }, [state.config]); // Vector store methods const initializeVectorStore = useCallback( async (config: VectorStoreConfig) => { dispatch({ type: "VECTOR_STORE_CONNECTING" }); dispatch({ type: "SET_INITIALIZATION_PROGRESS", payload: 20 }); try { // Dynamic import to avoid bundling issues const { createVectorStore, VectorStoreUtils } = await import( "../services/vectorStore" ); // Validate configuration if (!VectorStoreUtils.validateConfig(config)) { throw new Error("Invalid vector store configuration"); } // Create vector store instance vectorStoreRef.current = createVectorStore(config); // Connect to vector store await vectorStoreRef.current.connect(); // Get initial data const collections = await vectorStoreRef.current.getCollections(); const documentCount = await vectorStoreRef.current.getDocumentCount(); dispatch({ type: "VECTOR_STORE_CONNECTED", payload: { collections, documentCount }, }); dispatch({ type: "VECTOR_STORE_CONFIG_UPDATE", payload: config }); dispatch({ type: "SET_INITIALIZATION_PROGRESS", payload: 40 }); } catch (error) { const chatbotError: ChatbotError = { code: "VECTOR_STORE_INIT_ERROR", message: `Failed to initialize vector store: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "VECTOR_STORE_ERROR", payload: chatbotError }); dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } }, [] ); const addDocuments = useCallback( async (documents: Document[]) => { if (!vectorStoreRef.current) { throw new Error("Vector store is not initialized"); } dispatch({ type: "SET_PROCESSING", payload: true }); try { // Dynamic import for utilities const { VectorStoreUtils } = await import("../services/vectorStore"); // Prepare documents with embeddings const preparedDocuments = await VectorStoreUtils.prepareDocumentsForStorage(documents); // Add to vector store await vectorStoreRef.current.addDocuments(preparedDocuments); // Update local state preparedDocuments.forEach((doc) => { dispatch({ type: "DOCUMENT_ADD", payload: doc }); }); // Update document count const documentCount = await vectorStoreRef.current.getDocumentCount(); dispatch({ type: "VECTOR_STORE_CONNECTED", payload: { collections: state.vectorStore.collections, documentCount, }, }); } catch (error) { const chatbotError: ChatbotError = { code: "ADD_DOCUMENTS_ERROR", message: `Failed to add documents: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } finally { dispatch({ type: "SET_PROCESSING", payload: false }); } }, [state.vectorStore.collections] ); const removeDocuments = useCallback( async (documentIds: string[]) => { if (!vectorStoreRef.current) { throw new Error("Vector store is not initialized"); } dispatch({ type: "SET_PROCESSING", payload: true }); try { // Remove from vector store await vectorStoreRef.current.removeDocuments(documentIds); // Update local state documentIds.forEach((id) => { dispatch({ type: "DOCUMENT_REMOVE", payload: id }); }); // Update document count const documentCount = await vectorStoreRef.current.getDocumentCount(); dispatch({ type: "VECTOR_STORE_CONNECTED", payload: { collections: state.vectorStore.collections, documentCount, }, }); } catch (error) { const chatbotError: ChatbotError = { code: "REMOVE_DOCUMENTS_ERROR", message: `Failed to remove documents: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } finally { dispatch({ type: "SET_PROCESSING", payload: false }); } }, [state.vectorStore.collections] ); const searchSimilarDocuments = useCallback( async (query: string, limit = 5): Promise => { if (!vectorStoreRef.current) { throw new Error("Vector store is not initialized"); } try { const results = await vectorStoreRef.current.similaritySearch( query, limit ); return results; } catch (error) { const chatbotError: ChatbotError = { code: "SIMILARITY_SEARCH_ERROR", message: `Failed to search documents: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } }, [] ); // LLM methods const initializeLLM = useCallback(async (config: LLMConfig) => { dispatch({ type: "LLM_CONNECTING" }); dispatch({ type: "SET_INITIALIZATION_PROGRESS", payload: 60 }); try { // Dynamic import to avoid bundling issues const { createLLM, LLMUtils, RAGChainService } = await import( "../services/llmService" ); // Validate configuration if (!LLMUtils.validateConfig(config)) { throw new Error("Invalid LLM configuration"); } // Create LLM instance llmRef.current = createLLM(config); // Connect to LLM await llmRef.current.connect(); // Get available models and current model const availableModels = await llmRef.current.listModels(); const currentModel = config.model; dispatch({ type: "LLM_CONNECTED", payload: { availableModels, currentModel }, }); dispatch({ type: "LLM_CONFIG_UPDATE", payload: config }); // Create RAG chain if vector store is available if (vectorStoreRef.current && llmRef.current) { ragChainRef.current = new RAGChainService( llmRef.current, vectorStoreRef.current ); } dispatch({ type: "SET_INITIALIZATION_PROGRESS", payload: 80 }); } catch (error) { const chatbotError: ChatbotError = { code: "LLM_INIT_ERROR", message: `Failed to initialize LLM: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "LLM_ERROR", payload: chatbotError }); dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } }, []); const generateResponse = useCallback( async (prompt: string, context?: Document[]): Promise => { if (!llmRef.current) { throw new Error("LLM is not initialized"); } dispatch({ type: "SET_PROCESSING", payload: true }); try { const response = await llmRef.current.generateResponse(prompt, context); return response.content; } catch (error) { const chatbotError: ChatbotError = { code: "LLM_GENERATE_ERROR", message: `Failed to generate response: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } finally { dispatch({ type: "SET_PROCESSING", payload: false }); } }, [] ); const generateRAGResponse = useCallback( async ( query: string ): Promise<{ response: string; sources: Document[] }> => { if (!ragChainRef.current) { // Fallback to regular LLM if RAG chain is not available if (!llmRef.current) { throw new Error("Neither RAG chain nor LLM is initialized"); } dispatch({ type: "SET_PROCESSING", payload: true }); try { const response = await llmRef.current.generateResponse(query); return { response: response.content, sources: [] }; } catch (error) { const chatbotError: ChatbotError = { code: "RAG_FALLBACK_ERROR", message: `Failed to generate response: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } finally { dispatch({ type: "SET_PROCESSING", payload: false }); } } dispatch({ type: "SET_PROCESSING", payload: true }); try { const ragResponse = await ragChainRef.current.generateRAGResponse( query, { maxRetrievedDocs: state.config.ragSettings.maxRetrievedDocs, similarityThreshold: state.config.ragSettings.similarityThreshold, } ); return { response: ragResponse.response, sources: ragResponse.sources, }; } catch (error) { const chatbotError: ChatbotError = { code: "RAG_GENERATE_ERROR", message: `Failed to generate RAG response: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } finally { dispatch({ type: "SET_PROCESSING", payload: false }); } }, [state.config.ragSettings] ); // Storage methods const initializeStorage = useCallback(async (config: StorageConfig) => { dispatch({ type: "STORAGE_CONNECTING" }); dispatch({ type: "SET_INITIALIZATION_PROGRESS", payload: 90 }); try { // Dynamic import to avoid bundling issues const { createStorage, StorageUtils } = await import( "../services/storageService" ); // Validate configuration if (!StorageUtils.validateConfig(config)) { throw new Error("Invalid storage configuration"); } // Create storage instance storageRef.current = createStorage(config); // Connect to storage await storageRef.current.connect(); dispatch({ type: "STORAGE_CONNECTED" }); dispatch({ type: "STORAGE_CONFIG_UPDATE", payload: config }); dispatch({ type: "SET_INITIALIZATION_PROGRESS", payload: 100 }); } catch (error) { const chatbotError: ChatbotError = { code: "STORAGE_INIT_ERROR", message: `Failed to initialize storage: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "STORAGE_ERROR", payload: chatbotError }); dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } }, []); const uploadDocument = useCallback( async ( file: File, metadata?: Partial ): Promise => { if (!storageRef.current) { throw new Error("Storage is not initialized"); } dispatch({ type: "SET_PROCESSING", payload: true }); try { // Setup progress tracking const fileId = `upload_${Date.now()}_${Math.random() .toString(36) .substr(2, 9)}`; if (storageRef.current.setProgressCallback) { storageRef.current.setProgressCallback(fileId, (progress: any) => { dispatch({ type: "STORAGE_UPLOAD_PROGRESS", payload: { fileId: progress.fileId, progress: progress.progress }, }); }); } // Upload file const document = await storageRef.current.uploadFile(file, { description: metadata?.description, tags: metadata?.tags, extractText: true, }); // Add to local state dispatch({ type: "DOCUMENT_ADD", payload: document }); // Add to vector store if available and document has content if (vectorStoreRef.current && document.content) { try { await vectorStoreRef.current.addDocuments([document]); } catch (error) { console.warn("Failed to add document to vector store:", error); // Continue without vector store indexing } } // Clean up progress tracking if (storageRef.current.removeProgressCallback) { storageRef.current.removeProgressCallback(fileId); } return document; } catch (error) { const chatbotError: ChatbotError = { code: "UPLOAD_DOCUMENT_ERROR", message: `Failed to upload document: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } finally { dispatch({ type: "SET_PROCESSING", payload: false }); } }, [] ); const downloadDocument = useCallback( async (documentId: string): Promise => { if (!storageRef.current) { throw new Error("Storage is not initialized"); } try { const blob = await storageRef.current.downloadFile(documentId); return blob; } catch (error) { const chatbotError: ChatbotError = { code: "DOWNLOAD_DOCUMENT_ERROR", message: `Failed to download document: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } }, [] ); const deleteDocument = useCallback( async (documentId: string): Promise => { if (!storageRef.current) { throw new Error("Storage is not initialized"); } dispatch({ type: "SET_PROCESSING", payload: true }); try { // Remove from storage await storageRef.current.deleteFile(documentId); // Remove from vector store if available if (vectorStoreRef.current) { try { await vectorStoreRef.current.removeDocuments([documentId]); } catch (error) { console.warn("Failed to remove document from vector store:", error); // Continue with local removal } } // Remove from local state dispatch({ type: "DOCUMENT_REMOVE", payload: documentId }); } catch (error) { const chatbotError: ChatbotError = { code: "DELETE_DOCUMENT_ERROR", message: `Failed to delete document: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } finally { dispatch({ type: "SET_PROCESSING", payload: false }); } }, [] ); const listDocuments = useCallback(async (): Promise => { if (!storageRef.current) { // Return local documents if storage is not initialized return state.documents; } try { const documents = await storageRef.current.listFiles(); // Update local state dispatch({ type: "DOCUMENTS_SET", payload: documents }); return documents; } catch (error) { const chatbotError: ChatbotError = { code: "LIST_DOCUMENTS_ERROR", message: `Failed to list documents: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); // Return local documents as fallback return state.documents; } }, [state.documents]); // Configuration methods (placeholders) const updateConfig = useCallback(async (updates: Partial) => { dispatch({ type: "CONFIG_UPDATE", payload: updates }); }, []); const exportConfig = useCallback(() => { return state.config; }, [state.config]); const importConfig = useCallback(async (config: ChatbotConfig) => { dispatch({ type: "CONFIG_UPDATE", payload: config }); }, []); // Conversation methods (placeholders) const startConversation = useCallback( async (title?: string): Promise => { // Implementation will be added in subtask 27.5 const conversation: Conversation = { id: `conv_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, title: title || `Conversation ${new Date().toLocaleDateString()}`, messages: [], createdAt: new Date(), updatedAt: new Date(), }; dispatch({ type: "CONVERSATION_START", payload: conversation }); return conversation; }, [] ); const selectConversation = useCallback(async (conversationId: string) => { dispatch({ type: "CONVERSATION_SELECT", payload: conversationId }); }, []); const sendMessage = useCallback( async (content: string): Promise => { if (!state.currentConversation) { throw new Error( "No active conversation. Please start a conversation first." ); } if (!llmRef.current) { throw new Error("LLM is not initialized"); } dispatch({ type: "SET_PROCESSING", payload: true }); try { // Create user message const userMessage: ConversationMessage = { id: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, content, role: "user", timestamp: new Date(), }; // Add user message to conversation dispatch({ type: "CONVERSATION_MESSAGE_ADD", payload: { conversationId: state.currentConversation.id, message: userMessage, }, }); // Generate AI response let aiResponse: string; let retrievedDocs: Document[] = []; if (state.config.enableRAG && ragChainRef.current) { // Use RAG for response generation try { const ragResult = await generateRAGResponse(content); aiResponse = ragResult.response; retrievedDocs = ragResult.sources; } catch (error) { console.warn( "RAG generation failed, falling back to direct LLM:", error ); aiResponse = await generateResponse(content); } } else { // Use direct LLM response aiResponse = await generateResponse(content); } // Create assistant message const assistantMessage: ConversationMessage = { id: `msg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, content: aiResponse, role: "assistant", timestamp: new Date(), metadata: { retrievedDocs: retrievedDocs.length > 0 ? retrievedDocs : undefined, confidence: retrievedDocs.length > 0 ? 0.8 : undefined, // Mock confidence }, }; // Add assistant message to conversation dispatch({ type: "CONVERSATION_MESSAGE_ADD", payload: { conversationId: state.currentConversation.id, message: assistantMessage, }, }); return assistantMessage; } catch (error) { const chatbotError: ChatbotError = { code: "SEND_MESSAGE_ERROR", message: `Failed to send message: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } finally { dispatch({ type: "SET_PROCESSING", payload: false }); } }, [ state.currentConversation, state.config.enableRAG, llmRef.current, ragChainRef.current, generateRAGResponse, generateResponse, ] ); const getConversationHistory = useCallback( async (conversationId?: string): Promise => { const targetId = conversationId || state.currentConversation?.id; if (!targetId) return []; const conversation = state.conversations.find((c) => c.id === targetId); return conversation?.messages || []; }, [state.conversations, state.currentConversation] ); const deleteConversation = useCallback( async (conversationId: string) => { try { // Check if the conversation being deleted is the current one if (state.currentConversation?.id === conversationId) { // Clear current conversation dispatch({ type: "CONVERSATION_SELECT", payload: "" }); } // Remove from conversations list const updatedConversations = state.conversations.filter( (c) => c.id !== conversationId ); dispatch({ type: "CONVERSATIONS_SET", payload: updatedConversations }); } catch (error) { const chatbotError: ChatbotError = { code: "DELETE_CONVERSATION_ERROR", message: `Failed to delete conversation: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); throw error; } }, [state.conversations, state.currentConversation] ); // Utility methods const getStatus = useCallback(() => { return { overall: state.globalStatus, subsystems: { vectorStore: state.vectorStore.status, llm: state.llm.status, storage: state.storage.status, }, }; }, [state]); const clearError = useCallback(() => { dispatch({ type: "CLEAR_ERROR" }); }, []); const healthCheck = useCallback(async () => { const results = { vectorStore: false, llm: false, storage: false, }; try { // Check vector store health if (vectorStoreRef.current) { try { await vectorStoreRef.current.healthCheck(); results.vectorStore = true; } catch (error) { console.warn("Vector store health check failed:", error); } } // Check LLM health if (llmRef.current) { try { await llmRef.current.healthCheck(); results.llm = true; } catch (error) { console.warn("LLM health check failed:", error); } } // Check storage health if (storageRef.current) { try { await storageRef.current.healthCheck(); results.storage = true; } catch (error) { console.warn("Storage health check failed:", error); } } return results; } catch (error) { const chatbotError: ChatbotError = { code: "HEALTH_CHECK_ERROR", message: `Health check failed: ${ error instanceof Error ? error.message : "Unknown error" }`, details: error, timestamp: new Date(), }; dispatch({ type: "SET_ERROR", payload: chatbotError }); return results; // Return partial results even on error } }, []); // Auto-initialize if enabled useEffect(() => { if (autoInitialize && !state.isInitialized) { initialize(); } }, [autoInitialize, state.isInitialized, initialize]); // Context value const contextValue: ChatbotContextType = { ...state, initialize, reset, initializeVectorStore, addDocuments, removeDocuments, searchSimilarDocuments, initializeLLM, generateResponse, generateRAGResponse, initializeStorage, uploadDocument, downloadDocument, deleteDocument, listDocuments, updateConfig, exportConfig, importConfig, startConversation, selectConversation, sendMessage, getConversationHistory, deleteConversation, getStatus, clearError, healthCheck, }; return ( {children} ); } // Re-export everything for convenience export default ChatbotProvider;