diff --git a/.cursor/rules/frontend/ui_standards_rules.mdc b/.cursor/rules/frontend/ui_standards_rules.mdc new file mode 100644 index 000000000..1663d5036 --- /dev/null +++ b/.cursor/rules/frontend/ui_standards_rules.mdc @@ -0,0 +1,123 @@ +--- +globs: frontend/app/**,frontend/components/** +alwaysApply: false +--- +# Frontend UI Standards Rules + +## Principle +Use Ant Design as primary UI library with minimal Tailwind CSS. Prioritize mature Ant Design solutions for responsive layouts. Avoid secondary encapsulation unless necessary. + +## Technology Usage Guidelines +- **Ant Design**: Forms, data display, complex interactions (` + } + width={1000} + styles={{ + body: { padding: "20px" } + }} + > +
+
+ { + setEditContent(e.target.value); + }} + style={{ + width: "100%", + minHeight: "400px", + resize: "vertical" + }} + bordered={true} + /> +
+
+ + ); +} \ No newline at end of file diff --git a/frontend/app/[locale]/agents/page.tsx b/frontend/app/[locale]/agents/page.tsx index b259f3584..da6e85420 100644 --- a/frontend/app/[locale]/agents/page.tsx +++ b/frontend/app/[locale]/agents/page.tsx @@ -1,16 +1,32 @@ "use client"; import { Card, Row, Col, Flex } from "antd"; +import { useSearchParams } from "next/navigation"; +import { useEffect } from "react"; import { useSetupFlow } from "@/hooks/useSetupFlow"; import { motion } from "framer-motion"; import AgentManageComp from "./components/AgentManageComp"; import AgentConfigComp from "./components/AgentConfigComp"; import AgentInfoComp from "./components/AgentInfoComp"; +import { useAgentConfigStore } from "@/stores/agentConfigStore"; export default function AgentSetupOrchestrator() { const { pageVariants, pageTransition, canAccessProtectedData } = useSetupFlow(); + const searchParams = useSearchParams(); + const enterCreateMode = useAgentConfigStore((state) => state.enterCreateMode); + + // Handle auto-create mode from URL params + useEffect(() => { + const create = searchParams.get('create'); + if (create === 'true') { + // Small delay to ensure component is fully mounted + setTimeout(() => { + enterCreateMode(); + }, 100); + } + }, [searchParams, enterCreateMode]); return ( <> diff --git a/frontend/app/[locale]/knowledges/KnowledgeBaseConfiguration.tsx b/frontend/app/[locale]/knowledges/KnowledgeBaseConfiguration.tsx index e189caf52..36a2e0f16 100644 --- a/frontend/app/[locale]/knowledges/KnowledgeBaseConfiguration.tsx +++ b/frontend/app/[locale]/knowledges/KnowledgeBaseConfiguration.tsx @@ -4,7 +4,7 @@ import type React from "react"; import { useState, useEffect, useRef, useLayoutEffect } from "react"; import { useTranslation } from "react-i18next"; -import { App, Modal, Row, Col, theme } from "antd"; +import { App, Modal, Row, Col, theme, Button, Input, Form } from "antd"; import { ExclamationCircleFilled, WarningFilled, @@ -21,6 +21,8 @@ import knowledgeBasePollingService from "@/services/knowledgeBasePollingService" import { API_ENDPOINTS } from "@/services/api"; import { KnowledgeBase } from "@/types/knowledgeBase"; import { useConfig } from "@/hooks/useConfig"; +import { ConfigStore } from "@/lib/config"; +import { configService } from "@/services/configService"; import { SETUP_PAGE_CONTAINER, TWO_COLUMN_LAYOUT, @@ -126,8 +128,71 @@ function DataConfig({ isActive }: DataConfigProps) { useEffect(() => { localStorage.removeItem("preloaded_kb_data"); localStorage.removeItem("kb_cache"); + + // Load DataMate URL configuration + const loadConfig = async () => { + try { + await loadDataMateConfig(); + } catch (error) { + console.error("Failed to load DataMate configuration on mount:", error); + // Set default values if loading fails + setDataMateUrl(""); + } + }; + loadConfig(); }, []); + // Load DataMate URL configuration + const loadDataMateConfig = async () => { + try { + const response = await fetch(API_ENDPOINTS.config.load, { + method: "GET", + headers: getAuthHeaders(), + }); + + if (response.ok) { + const result = await response.json(); + const config = result.config; + console.log("Loaded config:", config); + // DataMate URL would be in the app config section + if ( + config && + config.app && + typeof config.app.datamateUrl === "string" + ) { + console.log("Setting DataMate URL to:", config.app.datamateUrl); + setDataMateUrl(config.app.datamateUrl); + } else { + console.log("No DataMate URL found in config, setting to empty"); + setDataMateUrl(""); + } + + if ( + config && + config.app && + typeof config.app.modelEngineEnabled === "boolean" + ) { + setModelEngineEnabled(config.app.modelEngineEnabled); + } + + // Return the DataMate URL for verification + return config.app?.datamateUrl || ""; + } else { + console.error( + "Failed to load config, response status:", + response.status, + response.statusText + ); + throw new Error( + `Failed to load config: ${response.status} ${response.statusText}` + ); + } + } catch (error) { + log.error("Failed to load DataMate configuration:", error); + throw error; // Re-throw so the calling function can handle it + } + }; + // Get context values const { state: kbState, @@ -137,7 +202,9 @@ function DataConfig({ isActive }: DataConfigProps) { selectKnowledgeBase, setActiveKnowledgeBase, isKnowledgeBaseSelectable, + hasKnowledgeBaseModelMismatch, refreshKnowledgeBaseData, + refreshKnowledgeBaseDataWithDataMate, loadUserSelectedKnowledgeBases, saveUserSelectedKnowledgeBases, dispatch: kbDispatch, @@ -153,6 +220,9 @@ function DataConfig({ isActive }: DataConfigProps) { const { state: uiState, setDragging, dispatch: uiDispatch } = useUIContext(); + // Check if ModelEngine is enabled (from config API) + const [modelEngineEnabled, setModelEngineEnabled] = useState(false); + // Create mode state const [isCreatingMode, setIsCreatingMode] = useState(false); const [newKbName, setNewKbName] = useState(""); @@ -177,7 +247,7 @@ function DataConfig({ isActive }: DataConfigProps) { setIsCreatingMode(false); setHasClickedUpload(false); setActiveKnowledgeBase(knowledgeBase); - fetchDocuments(knowledgeBase.id); + fetchDocuments(knowledgeBase.id, false, knowledgeBase.source); } }; @@ -275,9 +345,20 @@ function DataConfig({ isActive }: DataConfigProps) { // When component unmounts, if previously active and user has interacted, execute save if (prevIsActiveRef.current === true && hasUserInteractedRef.current) { // Use saved state instead of current potentially cleared state - const selectedKbNames = savedKnowledgeBasesRef.current - .filter((kb) => savedSelectedIdsRef.current.includes(kb.id)) - .map((kb) => kb.id); + const selectedKnowledgeBases = savedKnowledgeBasesRef.current.filter( + (kb) => savedSelectedIdsRef.current.includes(kb.id) + ); + + // Group knowledge bases by source + const knowledgeBySource: { nexent?: string[]; datamate?: string[] } = + {}; + selectedKnowledgeBases.forEach((kb) => { + const source = kb.source as keyof typeof knowledgeBySource; + if (!knowledgeBySource[source]) { + knowledgeBySource[source] = []; + } + knowledgeBySource[source]!.push(kb.id); + }); try { // Use fetch with keepalive to ensure request can be sent during page unload @@ -287,7 +368,7 @@ function DataConfig({ isActive }: DataConfigProps) { "Content-Type": "application/json", ...getAuthHeaders(), }, - body: JSON.stringify(selectedKbNames), + body: JSON.stringify(knowledgeBySource), keepalive: true, }).catch((error) => { log.error("卸载时保存失败:", error); @@ -358,6 +439,8 @@ function DataConfig({ isActive }: DataConfigProps) { const filtered = currentSelected.filter((id) => { const kb = kbState.knowledgeBases.find((k) => k.id === id); if (!kb) return false; + // DataMate knowledge bases are always allowed (skip model check) + if (kb.source === "datamate") return true; return allowedModels.has(kb.embeddingModel); }); @@ -375,7 +458,6 @@ function DataConfig({ isActive }: DataConfigProps) { }, [ isActive, kbState.isLoading, - kbState.selectedIds, kbState.knowledgeBases, modelConfig?.embedding?.modelName, modelConfig?.multiEmbedding?.modelName, @@ -443,7 +525,10 @@ function DataConfig({ isActive }: DataConfigProps) { }); // Get latest document data - const documents = await knowledgeBaseService.getAllFiles(kb.id); + const documents = await knowledgeBaseService.getAllFiles( + kb.id, + kb.source + ); // Trigger document update event knowledgeBasePollingService.triggerDocumentsUpdate(kb.id, documents); @@ -482,6 +567,16 @@ function DataConfig({ isActive }: DataConfigProps) { setDragging(false); // If in creation mode or has active knowledge base, process files + // Do not allow uploads when active KB source is datamate + if ( + kbState.activeKnowledgeBase && + kbState.activeKnowledgeBase.source === "datamate" && + !isCreatingMode + ) { + message.warning(t("document.message.uploadDisabledForDataMate")); + return; + } + if (isCreatingMode || kbState.activeKnowledgeBase) { const files = Array.from(e.dataTransfer.files); if (files.length > 0) { @@ -496,6 +591,22 @@ function DataConfig({ isActive }: DataConfigProps) { // Handle knowledge base deletion const handleDelete = (id: string) => { hasUserInteractedRef.current = true; // Mark user interaction + + // Find the knowledge base to check its source + const kb = kbState.knowledgeBases.find((kb) => kb.id === id); + + if (kb?.source === "datamate") { + // Show informational message for DataMate knowledge bases + Modal.info({ + title: t("knowledgeBase.modal.deleteDataMate.title", { name: kb.name }), + content: t("knowledgeBase.modal.deleteDataMate.content"), + okText: t("common.confirm"), + centered: true, + }); + return; + } + + // Normal delete confirmation for local knowledge bases confirm({ title: t("knowledgeBase.modal.deleteConfirm.title"), content: t("knowledgeBase.modal.deleteConfirm.content"), @@ -521,20 +632,104 @@ function DataConfig({ isActive }: DataConfigProps) { }); }; - // Handle knowledge base sync - const handleSync = () => { - // When manually syncing, force fetch latest data from server - refreshKnowledgeBaseData(true) - .then(() => { - message.success(t("knowledgeBase.message.syncSuccess")); - }) - .catch((error) => { - message.error( - t("knowledgeBase.message.syncError", { - error: error.message || t("common.unknownError"), - }) - ); + // Handle knowledge base sync (includes both indices and DataMate sync and create records) + const handleSync = async () => { + // Set sync loading state + kbDispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.SET_SYNC_LOADING, + payload: true, + }); + + try { + // Check if ModelEngine is enabled to determine sync behavior + if (modelEngineEnabled) { + // When ModelEngine is enabled, sync both local and DataMate knowledge bases + await refreshKnowledgeBaseDataWithDataMate(); + } else { + // When ModelEngine is disabled, only sync local knowledge bases + await refreshKnowledgeBaseData(true); + } + + // Use unified success message + message.success(t("knowledgeBase.message.syncSuccess")); + } catch (error) { + // Use unified error message + message.error( + t("knowledgeBase.message.syncError", { + error: (error as Error)?.message || t("common.unknownError"), + }) + ); + } finally { + // Clear sync loading state + kbDispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.SET_SYNC_LOADING, + payload: false, }); + } + }; + + // Handle DataMate configuration + const [showDataMateConfigModal, setShowDataMateConfigModal] = useState(false); + const [dataMateUrl, setDataMateUrl] = useState(""); + const configStore = ConfigStore.getInstance(); + + // Monitor DataMate URL changes + useEffect(() => { + console.log("DataMate URL changed to:", dataMateUrl); + }, [dataMateUrl]); + + const handleDataMateConfig = () => { + setShowDataMateConfigModal(true); + }; + + const handleDataMateConfigSave = async () => { + try { + console.log("Saving DataMate URL:", dataMateUrl); + + // Update global configuration with DataMate URL + const currentConfig = configStore.getConfig(); + const updatedConfig = { + ...currentConfig, + app: { + ...currentConfig.app, + datamateUrl: dataMateUrl, + }, + }; + + // Save to backend using global config API + const success = await configService.saveConfigToBackend(updatedConfig); + + if (!success) { + throw new Error("Failed to save configuration to backend"); + } + + message.success(t("knowledgeBase.message.dataMateConfigSaved")); + + // Update local config store + configStore.updateConfig(updatedConfig); + + // Update local state + setDataMateUrl(dataMateUrl); + + try { + await configService.loadConfigToFrontend(); + console.log("Configuration reloaded from backend successfully"); + } catch (reloadError) { + console.warn( + "Failed to reload configuration from backend, but save was successful:", + reloadError + ); + // Don't fail the entire operation if reload fails + } + + // Trigger knowledge base sync with the new configuration + await handleSync(); + + setShowDataMateConfigModal(false); + } catch (error) { + log.error("Failed to save DataMate configuration:", error); + message.error(t("knowledgeBase.message.dataMateConfigError")); + } }; // Handle new knowledge base creation @@ -846,11 +1041,14 @@ function DataConfig({ isActive }: DataConfigProps) { activeKnowledgeBase={kbState.activeKnowledgeBase} currentEmbeddingModel={kbState.currentEmbeddingModel} isLoading={kbState.isLoading} + syncLoading={kbState.syncLoading} onSelect={handleSelectKnowledgeBase} onClick={handleKnowledgeBaseClick} onDelete={handleDelete} onSync={handleSync} onCreateNew={handleCreateNew} + onDataMateConfig={handleDataMateConfig} + showDataMateConfig={modelEngineEnabled} isSelectable={isKnowledgeBaseSelectable} getModelDisplayName={(modelId) => modelId} containerHeight={SETUP_PAGE_CONTAINER.MAIN_CONTENT_HEIGHT} @@ -870,6 +1068,7 @@ function DataConfig({ isActive }: DataConfigProps) { {}} + knowledgeBaseSource={""} isCreatingMode={true} knowledgeBaseId={""} knowledgeBaseName={newKbName} @@ -889,15 +1088,16 @@ function DataConfig({ isActive }: DataConfigProps) { + + { + setShowDataMateConfigModal(false); + // Reload config to ensure we have the latest values + loadDataMateConfig(); + }} + okText={t("common.save")} + cancelText={t("common.cancel")} + centered + getContainer={() => contentRef.current || document.body} + confirmLoading={kbState.syncLoading} + > +
+
+ {t("knowledgeBase.modal.dataMateConfig.description")} +
+ + + setDataMateUrl(e.target.value)} + placeholder={t( + "knowledgeBase.modal.dataMateConfig.urlPlaceholder" + )} + /> + + +
+
); } diff --git a/frontend/app/[locale]/knowledges/components/document/DocumentList.tsx b/frontend/app/[locale]/knowledges/components/document/DocumentList.tsx index c99100958..d8b09f9e7 100644 --- a/frontend/app/[locale]/knowledges/components/document/DocumentList.tsx +++ b/frontend/app/[locale]/knowledges/components/document/DocumentList.tsx @@ -47,6 +47,8 @@ const TITLE_BAR_HEIGHT_CLASS_MAP: Record = { interface DocumentListProps { documents: Document[]; onDelete: (id: string) => void; + // Knowledge base source, e.g. "nexent" or "datamate" + knowledgeBaseSource?: string; // User-facing knowledge base name (display name) knowledgeBaseName?: string; // Internal knowledge base ID / Elasticsearch index name @@ -80,6 +82,7 @@ const DocumentListContainer = forwardRef( { documents, onDelete, + knowledgeBaseSource = "", knowledgeBaseId = "", knowledgeBaseName = "", modelMismatch = false, @@ -163,6 +166,7 @@ const DocumentListContainer = forwardRef( const [isLoadingModels, setIsLoadingModels] = useState(false); const {} = useKnowledgeBaseContext(); const { t } = useTranslation(); + const isDataMate = (knowledgeBaseSource || "").toLowerCase() === "datamate"; // Reset showDetail and showChunk state when knowledge base name changes React.useEffect(() => { @@ -390,7 +394,7 @@ const DocumentListContainer = forwardRef( )} {/* Right: overview and detail buttons */} - {!isCreatingMode && ( + {!isCreatingMode && !isDataMate && (
{/* Document list */} +
{ @@ -577,21 +582,25 @@ const DocumentListContainer = forwardRef( > {t("document.table.header.status")} - - {t("document.table.header.size")} - + {!isDataMate && ( + + {t("document.table.header.size")} + + )} {t("document.table.header.date")} - - {t("document.table.header.action")} - + {!isDataMate && ( + + {t("document.table.header.action")} + + )} @@ -624,30 +633,34 @@ const DocumentListContainer = forwardRef( />
- - {formatFileSize(doc.size)} - + {!isDataMate && ( + + {formatFileSize(doc.size)} + + )} {new Date(doc.create_time).toLocaleString()} - - - + {!isDataMate && ( + + + + )} ))} @@ -661,30 +674,38 @@ const DocumentListContainer = forwardRef( {/* Upload area */} - {!showDetail && !showChunk && ( - {})} - isUploading={isUploading} - isDragging={isDragging} - onDragOver={onDragOver} - onDragLeave={onDragLeave} - onDrop={onDrop} - disabled={!isCreatingMode && !knowledgeBaseId} - componentHeight={uploadHeight} - isCreatingMode={isCreatingMode} - // Use internal ID for backend operations; fall back to name in creation mode - indexName={knowledgeBaseId || knowledgeBaseName} - newKnowledgeBaseName={isCreatingMode ? knowledgeBaseName : ""} - modelMismatch={modelMismatch} - /> - )} + {!showDetail && + !showChunk && + (isDataMate ? ( +
+ + {t("knowledgeBase.datamate.editDisabled")} + +
+ ) : ( + {})} + isUploading={isUploading} + isDragging={isDragging} + onDragOver={onDragOver} + onDragLeave={onDragLeave} + onDrop={onDrop} + disabled={!isCreatingMode && !knowledgeBaseId} + componentHeight={uploadHeight} + isCreatingMode={isCreatingMode} + // Use internal ID for backend operations; fall back to name in creation mode + indexName={knowledgeBaseId || knowledgeBaseName} + newKnowledgeBaseName={isCreatingMode ? knowledgeBaseName : ""} + modelMismatch={modelMismatch} + /> + ))} ); } diff --git a/frontend/app/[locale]/knowledges/components/knowledge/KnowledgeBaseList.tsx b/frontend/app/[locale]/knowledges/components/knowledge/KnowledgeBaseList.tsx index f0594b042..baa2933c0 100644 --- a/frontend/app/[locale]/knowledges/components/knowledge/KnowledgeBaseList.tsx +++ b/frontend/app/[locale]/knowledges/components/knowledge/KnowledgeBaseList.tsx @@ -2,7 +2,7 @@ import React from "react"; import { useTranslation } from "react-i18next"; import { Button, Checkbox, ConfigProvider } from "antd"; -import { SyncOutlined, PlusOutlined } from "@ant-design/icons"; +import { SyncOutlined, PlusOutlined, SettingOutlined } from "@ant-design/icons"; import { KnowledgeBase } from "@/types/knowledgeBase"; @@ -44,11 +44,14 @@ interface KnowledgeBaseListProps { activeKnowledgeBase: KnowledgeBase | null; currentEmbeddingModel: string | null; isLoading?: boolean; + syncLoading?: boolean; onSelect: (id: string) => void; onClick: (kb: KnowledgeBase) => void; onDelete: (id: string) => void; onSync: () => void; onCreateNew: () => void; + onDataMateConfig?: () => void; + showDataMateConfig?: boolean; // Control whether to show DataMate config button isSelectable: (kb: KnowledgeBase) => boolean; getModelDisplayName: (modelId: string) => string; containerHeight?: string; // Container total height, consistent with DocumentList @@ -61,11 +64,14 @@ const KnowledgeBaseList: React.FC = ({ activeKnowledgeBase, currentEmbeddingModel, isLoading = false, + syncLoading = false, onSelect, onClick, onDelete, onSync, onCreateNew, + onDataMateConfig, + showDataMateConfig = false, isSelectable, getModelDisplayName, containerHeight = "70vh", // Default container height consistent with DocumentList @@ -160,10 +166,30 @@ const KnowledgeBaseList: React.FC = ({ height: "14px", }} > - + {t("knowledgeBase.button.sync")} + {showDataMateConfig && ( + + )} @@ -340,19 +366,19 @@ const KnowledgeBaseList: React.FC = ({ })} - {/* Only show source, creation date, and model tags when there are valid documents or chunks */} + {/* Always show source tag regardless of document/chunk count */} + + {t("knowledgeBase.tag.source", { + source: kb.source, + })} + + + {/* Only show creation date, model tags when there are valid documents or chunks */} {((kb.documentCount || 0) > 0 || (kb.chunkCount || 0) > 0) && ( <> - {/* Knowledge base source tag */} - - {t("knowledgeBase.tag.source", { - source: kb.source, - })} - - {/* Creation date tag - only show date */} = ({ )} {kb.embeddingModel !== "unknown" && - kb.embeddingModel !== currentEmbeddingModel && ( + kb.embeddingModel !== currentEmbeddingModel && + kb.source !== "datamate" && ( diff --git a/frontend/app/[locale]/knowledges/contexts/DocumentContext.tsx b/frontend/app/[locale]/knowledges/contexts/DocumentContext.tsx index 4e0b33967..b956dd919 100644 --- a/frontend/app/[locale]/knowledges/contexts/DocumentContext.tsx +++ b/frontend/app/[locale]/knowledges/contexts/DocumentContext.tsx @@ -111,7 +111,7 @@ const documentReducer = (state: DocumentState, action: DocumentAction): Document export const DocumentContext = createContext<{ state: DocumentState; dispatch: React.Dispatch; - fetchDocuments: (kbId: string, forceRefresh?: boolean) => Promise; + fetchDocuments: (kbId: string, forceRefresh?: boolean, kbSource?: string) => Promise; uploadDocuments: (kbId: string, files: File[]) => Promise; deleteDocument: (kbId: string, docId: string) => Promise; }>({ @@ -175,23 +175,23 @@ export const DocumentProvider: React.FC = ({ children }) }, []); // Fetch documents for a knowledge base - const fetchDocuments = useCallback(async (kbId: string, forceRefresh?: boolean) => { + const fetchDocuments = useCallback(async (kbId: string, forceRefresh?: boolean, kbSource?: string) => { // Skip if already loading this kb if (state.loadingKbIds.has(kbId)) return; - + // If forceRefresh is false and we have cached data, return directly if (!forceRefresh && state.documentsMap[kbId] && state.documentsMap[kbId].length > 0) { return; // If we have cached data and don't need force refresh, return directly without server request } - + dispatch({ type: DOCUMENT_ACTION_TYPES.SET_LOADING_KB_ID, payload: { kbId, isLoading: true } }); - + try { // Use getAllFiles() to get documents including those not yet in ES - const documents = await knowledgeBaseService.getAllFiles(kbId); - dispatch({ - type: DOCUMENT_ACTION_TYPES.FETCH_SUCCESS, - payload: { kbId, documents } + const documents = await knowledgeBaseService.getAllFiles(kbId, kbSource); + dispatch({ + type: DOCUMENT_ACTION_TYPES.FETCH_SUCCESS, + payload: { kbId, documents } }); } catch (error) { log.error(t('document.error.fetch'), error); diff --git a/frontend/app/[locale]/knowledges/contexts/KnowledgeBaseContext.tsx b/frontend/app/[locale]/knowledges/contexts/KnowledgeBaseContext.tsx index c866600fd..d96c8fa33 100644 --- a/frontend/app/[locale]/knowledges/contexts/KnowledgeBaseContext.tsx +++ b/frontend/app/[locale]/knowledges/contexts/KnowledgeBaseContext.tsx @@ -1,67 +1,90 @@ -"use client" +"use client"; -import { createContext, useReducer, useEffect, useContext, ReactNode, useCallback, useMemo } from "react" -import { useTranslation } from 'react-i18next' +import { + createContext, + useReducer, + useEffect, + useContext, + ReactNode, + useCallback, + useMemo, +} from "react"; +import { useTranslation } from "react-i18next"; -import knowledgeBaseService from "@/services/knowledgeBaseService" -import { userConfigService } from "@/services/userConfigService" +import knowledgeBaseService from "@/services/knowledgeBaseService"; +import { userConfigService } from "@/services/userConfigService"; -import { KnowledgeBase, KnowledgeBaseState, KnowledgeBaseAction } from "@/types/knowledgeBase" -import { KNOWLEDGE_BASE_ACTION_TYPES } from "@/const/knowledgeBase" +import { + KnowledgeBase, + KnowledgeBaseState, + KnowledgeBaseAction, +} from "@/types/knowledgeBase"; +import { KNOWLEDGE_BASE_ACTION_TYPES } from "@/const/knowledgeBase"; -import { configStore } from "@/lib/config" +import { configStore } from "@/lib/config"; import log from "@/lib/logger"; - - // Reducer function -const knowledgeBaseReducer = (state: KnowledgeBaseState, action: KnowledgeBaseAction): KnowledgeBaseState => { +const knowledgeBaseReducer = ( + state: KnowledgeBaseState, + action: KnowledgeBaseAction +): KnowledgeBaseState => { switch (action.type) { case KNOWLEDGE_BASE_ACTION_TYPES.FETCH_SUCCESS: return { ...state, knowledgeBases: action.payload, - error: null + error: null, }; case KNOWLEDGE_BASE_ACTION_TYPES.SELECT_KNOWLEDGE_BASE: return { ...state, - selectedIds: action.payload + selectedIds: action.payload, }; case KNOWLEDGE_BASE_ACTION_TYPES.SET_ACTIVE: return { ...state, - activeKnowledgeBase: action.payload + activeKnowledgeBase: action.payload, }; case KNOWLEDGE_BASE_ACTION_TYPES.SET_MODEL: return { ...state, - currentEmbeddingModel: action.payload + currentEmbeddingModel: action.payload, }; case KNOWLEDGE_BASE_ACTION_TYPES.DELETE_KNOWLEDGE_BASE: return { ...state, - knowledgeBases: state.knowledgeBases.filter(kb => kb.id !== action.payload), - selectedIds: state.selectedIds.filter(id => id !== action.payload), - activeKnowledgeBase: state.activeKnowledgeBase?.id === action.payload ? null : state.activeKnowledgeBase + knowledgeBases: state.knowledgeBases.filter( + (kb) => kb.id !== action.payload + ), + selectedIds: state.selectedIds.filter((id) => id !== action.payload), + activeKnowledgeBase: + state.activeKnowledgeBase?.id === action.payload + ? null + : state.activeKnowledgeBase, }; case KNOWLEDGE_BASE_ACTION_TYPES.ADD_KNOWLEDGE_BASE: - if (state.knowledgeBases.some(kb => kb.id === action.payload.id)) { + if (state.knowledgeBases.some((kb) => kb.id === action.payload.id)) { return state; // If the knowledge base already exists, do not insert it } return { ...state, - knowledgeBases: [...state.knowledgeBases, action.payload] + knowledgeBases: [...state.knowledgeBases, action.payload], }; case KNOWLEDGE_BASE_ACTION_TYPES.LOADING: return { ...state, - isLoading: action.payload + isLoading: action.payload, + }; + case KNOWLEDGE_BASE_ACTION_TYPES.SET_SYNC_LOADING: + return { + ...state, + syncLoading: action.payload, }; case KNOWLEDGE_BASE_ACTION_TYPES.ERROR: return { ...state, - error: action.payload + error: action.payload, }; default: return state; @@ -72,13 +95,22 @@ const knowledgeBaseReducer = (state: KnowledgeBaseState, action: KnowledgeBaseAc export const KnowledgeBaseContext = createContext<{ state: KnowledgeBaseState; dispatch: React.Dispatch; - fetchKnowledgeBases: (skipHealthCheck?: boolean, shouldLoadSelected?: boolean) => Promise; - createKnowledgeBase: (name: string, description: string, source?: string) => Promise; + fetchKnowledgeBases: ( + skipHealthCheck?: boolean, + shouldLoadSelected?: boolean + ) => Promise; + createKnowledgeBase: ( + name: string, + description: string, + source?: string + ) => Promise; deleteKnowledgeBase: (id: string) => Promise; selectKnowledgeBase: (id: string) => void; setActiveKnowledgeBase: (kb: KnowledgeBase) => void; isKnowledgeBaseSelectable: (kb: KnowledgeBase) => boolean; + hasKnowledgeBaseModelMismatch: (kb: KnowledgeBase) => boolean; refreshKnowledgeBaseData: (forceRefresh?: boolean) => Promise; + refreshKnowledgeBaseDataWithDataMate: () => Promise; loadUserSelectedKnowledgeBases: () => Promise; saveUserSelectedKnowledgeBases: () => Promise; }>({ @@ -88,7 +120,8 @@ export const KnowledgeBaseContext = createContext<{ activeKnowledgeBase: null, currentEmbeddingModel: null, isLoading: false, - error: null + syncLoading: false, + error: null, }, dispatch: () => {}, fetchKnowledgeBases: async () => {}, @@ -97,7 +130,9 @@ export const KnowledgeBaseContext = createContext<{ selectKnowledgeBase: () => {}, setActiveKnowledgeBase: () => {}, isKnowledgeBaseSelectable: () => false, + hasKnowledgeBaseModelMismatch: () => false, refreshKnowledgeBaseData: async () => {}, + refreshKnowledgeBaseDataWithDataMate: async () => {}, loadUserSelectedKnowledgeBases: async () => {}, saveUserSelectedKnowledgeBases: async () => false, }); @@ -110,7 +145,9 @@ interface KnowledgeBaseProviderProps { children: ReactNode; } -export const KnowledgeBaseProvider: React.FC = ({ children }) => { +export const KnowledgeBaseProvider: React.FC = ({ + children, +}) => { const { t } = useTranslation(); const [state, dispatch] = useReducer(knowledgeBaseReducer, { knowledgeBases: [], @@ -118,69 +155,175 @@ export const KnowledgeBaseProvider: React.FC = ({ ch activeKnowledgeBase: null, currentEmbeddingModel: null, isLoading: false, - error: null + syncLoading: false, + error: null, }); - + // Check if knowledge base is selectable - memoized with useCallback - const isKnowledgeBaseSelectable = useCallback((kb: KnowledgeBase): boolean => { - // If no current embedding model is set, not selectable - if (!state.currentEmbeddingModel) { - return false; - } - // Only selectable when knowledge base model exactly matches current model - return kb.embeddingModel === "unknown" || kb.embeddingModel === state.currentEmbeddingModel; - }, [state.currentEmbeddingModel]); + const isKnowledgeBaseSelectable = useCallback( + (kb: KnowledgeBase): boolean => { + // If no current embedding model is set, not selectable + if (!state.currentEmbeddingModel) { + return false; + } - // Load knowledge base data (supports force fetch from server and load selected status) - optimized with useCallback - const fetchKnowledgeBases = useCallback(async (skipHealthCheck = true) => { - // If already loading, return directly - if (state.isLoading) { - return; - } + // Check if knowledge base has content (documents or chunks) + const hasContent = + (kb.documentCount || 0) > 0 || (kb.chunkCount || 0) > 0; + + // Empty knowledge bases cannot be selected + if (!hasContent) { + return false; + } + + // DataMate knowledge bases are selectable if they have content (even if model doesn't match) + if (kb.source === "datamate") { + return true; + } + + // For local knowledge bases, only selectable when model exactly matches current model + return ( + kb.embeddingModel === "unknown" || + kb.embeddingModel === state.currentEmbeddingModel + ); + }, + [state.currentEmbeddingModel] + ); - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.LOADING, payload: true }); + // Check if knowledge base has model mismatch (for display purposes) + const hasKnowledgeBaseModelMismatch = useCallback( + (kb: KnowledgeBase): boolean => { + if (!state.currentEmbeddingModel || kb.embeddingModel === "unknown") { + return false; + } + // DataMate knowledge bases don't report model mismatch (they are always selectable) + if (kb.source === "datamate") { + return false; + } + return kb.embeddingModel !== state.currentEmbeddingModel; + }, + [state.currentEmbeddingModel] + ); + + // Load user selected knowledge bases from backend + const loadUserSelectedKnowledgeBases = useCallback(async () => { try { - // Clear possible cache interference - localStorage.removeItem('preloaded_kb_data'); - localStorage.removeItem('kb_cache'); - - // Get knowledge base list data directly from server - const kbs = await knowledgeBaseService.getKnowledgeBasesInfo(skipHealthCheck); - - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.FETCH_SUCCESS, payload: kbs }); - + const userConfig = await userConfigService.loadKnowledgeList(); + if (userConfig) { + let allSelectedNames: string[] = []; + + // Handle new format (selectedKbNames array) + if ( + userConfig.selectedKbNames && + userConfig.selectedKbNames.length > 0 + ) { + allSelectedNames = userConfig.selectedKbNames; + } + // Fallback to legacy grouped format for backward compatibility + else if (userConfig.nexent || userConfig.datamate) { + allSelectedNames = [ + ...(userConfig.nexent || []), + ...(userConfig.datamate || []), + ]; + } + + if (allSelectedNames.length > 0) { + // Find matching knowledge base IDs based on index names + const selectedIds = state.knowledgeBases + .filter((kb) => allSelectedNames.includes(kb.id)) + .map((kb) => kb.id); + + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.SELECT_KNOWLEDGE_BASE, + payload: selectedIds, + }); + } + } } catch (error) { - log.error(t('knowledgeBase.error.fetchList'), error); - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, payload: t('knowledgeBase.error.fetchListRetry') }); - } finally { - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.LOADING, payload: false }); + log.error(t("knowledgeBase.error.loadSelected"), error); + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, + payload: t("knowledgeBase.error.loadSelectedRetry"), + }); } - }, [state.isLoading, t]); + }, [state.knowledgeBases]); + + // Load knowledge base data (supports force fetch from server and load selected status) - optimized with useCallback + const fetchKnowledgeBases = useCallback( + async ( + skipHealthCheck = true, + shouldLoadSelected = true, + includeDataMateSync = true + ) => { + // If already loading, return directly + if (state.isLoading) { + return; + } + + dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.LOADING, payload: true }); + try { + // Clear possible cache interference + localStorage.removeItem("preloaded_kb_data"); + localStorage.removeItem("kb_cache"); + + // Get knowledge base list data directly from server + const kbs = await knowledgeBaseService.getKnowledgeBasesInfo( + skipHealthCheck, + includeDataMateSync + ); + + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.FETCH_SUCCESS, + payload: kbs, + }); + + // After loading knowledge bases, automatically load user's selected knowledge bases if requested + if (shouldLoadSelected && kbs.length > 0) { + await loadUserSelectedKnowledgeBases(); + } + } catch (error) { + log.error(t("knowledgeBase.error.fetchList"), error); + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, + payload: t("knowledgeBase.error.fetchListRetry"), + }); + } finally { + dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.LOADING, payload: false }); + } + }, + [state.isLoading, t, loadUserSelectedKnowledgeBases] + ); // Select knowledge base - memoized with useCallback - const selectKnowledgeBase = useCallback((id: string) => { - const kb = state.knowledgeBases.find((kb) => kb.id === id); - if (!kb) return; + const selectKnowledgeBase = useCallback( + (id: string) => { + const kb = state.knowledgeBases.find((kb) => kb.id === id); + if (!kb) return; - const isSelected = state.selectedIds.includes(id); + const isSelected = state.selectedIds.includes(id); - // If trying to select an item, check for model compatibility. Deselection is always allowed. - if (!isSelected && !isKnowledgeBaseSelectable(kb)) { - log.warn(`Cannot select knowledge base ${kb.name}, model mismatch`); - return; - } + // If trying to select an item, check for model compatibility. Deselection is always allowed. + if (!isSelected && !isKnowledgeBaseSelectable(kb)) { + log.warn(`Cannot select knowledge base ${kb.name}, model mismatch`); + return; + } - // Toggle selection status - const newSelectedIds = isSelected - ? state.selectedIds.filter(kbId => kbId !== id) - : [...state.selectedIds, id]; + // Toggle selection status + const newSelectedIds = isSelected + ? state.selectedIds.filter((kbId) => kbId !== id) + : [...state.selectedIds, id]; - // Update state - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.SELECT_KNOWLEDGE_BASE, payload: newSelectedIds }); - - // Note: removed logic for saving selection status to config - // This feature is no longer needed as we don't store data config - }, [state.knowledgeBases, state.selectedIds, isKnowledgeBaseSelectable]); + // Update state + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.SELECT_KNOWLEDGE_BASE, + payload: newSelectedIds, + }); + + // Note: removed logic for saving selection status to config + // This feature is no longer needed as we don't store data config + }, + [state.knowledgeBases, state.selectedIds, isKnowledgeBaseSelectable] + ); // Set current active knowledge base - memoized with useCallback const setActiveKnowledgeBase = useCallback((kb: KnowledgeBase) => { @@ -188,93 +331,105 @@ export const KnowledgeBaseProvider: React.FC = ({ ch }, []); // Create knowledge base - memoized with useCallback - const createKnowledgeBase = useCallback(async (name: string, description: string, source: string = "elasticsearch") => { - try { - const newKB = await knowledgeBaseService.createKnowledgeBase({ - name, - description, - source, - embeddingModel: state.currentEmbeddingModel || "text-embedding-3-small" - }); - return newKB; - } catch (error) { - log.error(t('knowledgeBase.error.create'), error); - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, payload: t('knowledgeBase.error.createRetry') }); - return null; - } - }, [state.currentEmbeddingModel, t]); + const createKnowledgeBase = useCallback( + async ( + name: string, + description: string, + source: string = "elasticsearch" + ) => { + try { + const newKB = await knowledgeBaseService.createKnowledgeBase({ + name, + description, + source, + embeddingModel: + state.currentEmbeddingModel || "text-embedding-3-small", + }); + return newKB; + } catch (error) { + log.error(t("knowledgeBase.error.create"), error); + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, + payload: t("knowledgeBase.error.createRetry"), + }); + return null; + } + }, + [state.currentEmbeddingModel, t] + ); // Delete knowledge base - memoized with useCallback - const deleteKnowledgeBase = useCallback(async (id: string) => { - try { - await knowledgeBaseService.deleteKnowledgeBase(id); - - // Update knowledge base list - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.DELETE_KNOWLEDGE_BASE, payload: id }); - - // If current active knowledge base is deleted, clear active state - if (state.activeKnowledgeBase?.id === id) { - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.SET_ACTIVE, payload: null }); - } - - // Update selected knowledge base list - const newSelectedIds = state.selectedIds.filter(kbId => kbId !== id); - - if (newSelectedIds.length !== state.selectedIds.length) { - // Update state - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.SELECT_KNOWLEDGE_BASE, payload: newSelectedIds }); - } - - return true; - } catch (error) { - log.error(t('knowledgeBase.error.delete'), error); - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, payload: t('knowledgeBase.error.deleteRetry') }); - return false; - } - }, [state.knowledgeBases, state.selectedIds, state.activeKnowledgeBase]); + const deleteKnowledgeBase = useCallback( + async (id: string) => { + try { + await knowledgeBaseService.deleteKnowledgeBase(id); - // Load user selected knowledge bases from backend - const loadUserSelectedKnowledgeBases = useCallback(async () => { - try { - const userConfig = await userConfigService.loadKnowledgeList(); - if (userConfig && userConfig.selectedKbNames.length > 0) { - // Find matching knowledge base IDs based on index names - const selectedIds = state.knowledgeBases - .filter((kb) => userConfig.selectedKbNames.includes(kb.id)) - .map((kb) => kb.id); + // Update knowledge base list + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.DELETE_KNOWLEDGE_BASE, + payload: id, + }); + + // If current active knowledge base is deleted, clear active state + if (state.activeKnowledgeBase?.id === id) { + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.SET_ACTIVE, + payload: null, + }); + } + + // Update selected knowledge base list + const newSelectedIds = state.selectedIds.filter((kbId) => kbId !== id); + + if (newSelectedIds.length !== state.selectedIds.length) { + // Update state + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.SELECT_KNOWLEDGE_BASE, + payload: newSelectedIds, + }); + } + return true; + } catch (error) { + log.error(t("knowledgeBase.error.delete"), error); dispatch({ - type: KNOWLEDGE_BASE_ACTION_TYPES.SELECT_KNOWLEDGE_BASE, - payload: selectedIds, + type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, + payload: t("knowledgeBase.error.deleteRetry"), }); + return false; } - } catch (error) { - log.error(t("knowledgeBase.error.loadSelected"), error); - dispatch({ - type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, - payload: t("knowledgeBase.error.loadSelectedRetry"), - }); - } - }, [state.knowledgeBases]); + }, + [state.knowledgeBases, state.selectedIds, state.activeKnowledgeBase] + ); // Save user selected knowledge bases to backend const saveUserSelectedKnowledgeBases = useCallback(async () => { try { - // Get selected knowledge base index names (globally unique identifiers) - const selectedKbNames = state.knowledgeBases - .filter((kb) => state.selectedIds.includes(kb.id)) - .map((kb) => kb.id); - - const success = await userConfigService.updateKnowledgeList( - selectedKbNames + // Get selected knowledge bases grouped by source + const selectedKnowledgeBases = state.knowledgeBases.filter((kb) => + state.selectedIds.includes(kb.id) ); - if (!success) { + + // Group knowledge bases by source + const knowledgeBySource: { nexent?: string[]; datamate?: string[] } = {}; + selectedKnowledgeBases.forEach((kb) => { + const source = kb.source as keyof typeof knowledgeBySource; + if (!knowledgeBySource[source]) { + knowledgeBySource[source] = []; + } + knowledgeBySource[source]!.push(kb.id); + }); + + const result = + await userConfigService.updateKnowledgeList(knowledgeBySource); + if (!result) { dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, payload: t("knowledgeBase.error.saveSelected"), }); + return false; } - return success; + return true; } catch (error) { log.error(t("knowledgeBase.error.saveSelected"), error); dispatch({ @@ -286,30 +441,78 @@ export const KnowledgeBaseProvider: React.FC = ({ ch }, [state.knowledgeBases, state.selectedIds, t]); // Add a function to refresh the knowledge base data - const refreshKnowledgeBaseData = useCallback(async () => { + const refreshKnowledgeBaseData = useCallback( + async (forceRefresh = false) => { + try { + // Get latest knowledge base data directly from server, but don't reload user selections, include DataMate sync to prevent DataMate KBs from disappearing + await fetchKnowledgeBases(false, false, true); + + // If there is an active knowledge base, also refresh its document information + if (state.activeKnowledgeBase) { + // Publish document update event to notify document list component to refresh document data + try { + const documents = await knowledgeBaseService.getAllFiles( + state.activeKnowledgeBase.id, + state.activeKnowledgeBase.source + ); + log.log("documents", documents); + window.dispatchEvent( + new CustomEvent("documentsUpdated", { + detail: { + kbId: state.activeKnowledgeBase.id, + documents, + }, + }) + ); + } catch (error) { + log.error("Failed to refresh document information:", error); + } + } + } catch (error) { + log.error("Failed to refresh knowledge base data:", error); + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, + payload: "Failed to refresh knowledge base data", + }); + } + }, + [fetchKnowledgeBases, state.activeKnowledgeBase] + ); + + // Add a function to refresh the knowledge base data with DataMate sync and create records + const refreshKnowledgeBaseDataWithDataMate = useCallback(async () => { try { - // Get latest knowledge base data directly from server - await fetchKnowledgeBases(false); + // Get latest knowledge base data directly from server, which includes DataMate sync + // The getKnowledgeBasesInfo method already handles syncDataMateAndCreateRecords internally + await fetchKnowledgeBases(false, false, true); // If there is an active knowledge base, also refresh its document information if (state.activeKnowledgeBase) { // Publish document update event to notify document list component to refresh document data try { - const documents = await knowledgeBaseService.getAllFiles(state.activeKnowledgeBase.id); + const documents = await knowledgeBaseService.getAllFiles( + state.activeKnowledgeBase.id, + state.activeKnowledgeBase.source + ); log.log("documents", documents); - window.dispatchEvent(new CustomEvent('documentsUpdated', { - detail: { - kbId: state.activeKnowledgeBase.id, - documents - } - })); + window.dispatchEvent( + new CustomEvent("documentsUpdated", { + detail: { + kbId: state.activeKnowledgeBase.id, + documents, + }, + }) + ); } catch (error) { log.error("Failed to refresh document information:", error); } } } catch (error) { - log.error("Failed to refresh knowledge base data:", error); - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, payload: 'Failed to refresh knowledge base data' }); + log.error("Failed to refresh knowledge base data with DataMate:", error); + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.ERROR, + payload: "Failed to refresh knowledge base data with DataMate", + }); } }, [fetchKnowledgeBases, state.activeKnowledgeBase]); @@ -322,92 +525,126 @@ export const KnowledgeBaseProvider: React.FC = ({ ch const loadInitialData = async () => { const modelConfig = configStore.getModelConfig(); if (modelConfig.embedding?.modelName) { - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.SET_MODEL, payload: modelConfig.embedding.modelName }); + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.SET_MODEL, + payload: modelConfig.embedding.modelName, + }); } - + // Don't load knowledge base list here, wait for knowledgeBaseDataUpdated event }; - + loadInitialData(); - + // Listen for embedding model change event const handleEmbeddingModelChange = (e: CustomEvent) => { const newModel = e.detail.model || null; - + // If model changes if (newModel !== state.currentEmbeddingModel) { - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.SET_MODEL, payload: newModel }); - + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.SET_MODEL, + payload: newModel, + }); + // Reload knowledge base list when model changes - fetchKnowledgeBases(true); + fetchKnowledgeBases(true, true, true); } }; - + // Listen for env config change event const handleEnvConfigChanged = () => { // Reload env related config const newModelConfig = configStore.getModelConfig(); if (newModelConfig.embedding?.modelName !== state.currentEmbeddingModel) { - dispatch({ type: KNOWLEDGE_BASE_ACTION_TYPES.SET_MODEL, payload: newModelConfig.embedding?.modelName || null }); - + dispatch({ + type: KNOWLEDGE_BASE_ACTION_TYPES.SET_MODEL, + payload: newModelConfig.embedding?.modelName || null, + }); + // Reload knowledge base list when model changes - fetchKnowledgeBases(true); + fetchKnowledgeBases(true, true, true); } }; - + // Listen for knowledge base data update event const handleKnowledgeBaseDataUpdated = (e: Event) => { // Check if need to force fetch data from server const customEvent = e as CustomEvent; const forceRefresh = customEvent.detail?.forceRefresh === true; - + // If first time loading data or force refresh, get from server if (!initialDataLoaded || forceRefresh) { - fetchKnowledgeBases(false); + // For force refresh, don't reload user selections to preserve current state + fetchKnowledgeBases(false, !forceRefresh, true); initialDataLoaded = true; } }; - - window.addEventListener("embeddingModelChanged", handleEmbeddingModelChange as EventListener); - window.addEventListener("configChanged", handleEnvConfigChanged as EventListener); - window.addEventListener("knowledgeBaseDataUpdated", handleKnowledgeBaseDataUpdated as EventListener); - + + window.addEventListener( + "embeddingModelChanged", + handleEmbeddingModelChange as EventListener + ); + window.addEventListener( + "configChanged", + handleEnvConfigChanged as EventListener + ); + window.addEventListener( + "knowledgeBaseDataUpdated", + handleKnowledgeBaseDataUpdated as EventListener + ); + return () => { - window.removeEventListener("embeddingModelChanged", handleEmbeddingModelChange as EventListener); - window.removeEventListener("configChanged", handleEnvConfigChanged as EventListener); - window.removeEventListener("knowledgeBaseDataUpdated", handleKnowledgeBaseDataUpdated as EventListener); + window.removeEventListener( + "embeddingModelChanged", + handleEmbeddingModelChange as EventListener + ); + window.removeEventListener( + "configChanged", + handleEnvConfigChanged as EventListener + ); + window.removeEventListener( + "knowledgeBaseDataUpdated", + handleKnowledgeBaseDataUpdated as EventListener + ); }; }, [fetchKnowledgeBases, state.currentEmbeddingModel]); // Memoized context value to prevent unnecessary re-renders - const contextValue = useMemo(() => ({ - state, - dispatch, - fetchKnowledgeBases, - createKnowledgeBase, - deleteKnowledgeBase, - selectKnowledgeBase, - setActiveKnowledgeBase, - isKnowledgeBaseSelectable, - refreshKnowledgeBaseData, - loadUserSelectedKnowledgeBases, - saveUserSelectedKnowledgeBases - }), [ - state, - fetchKnowledgeBases, - createKnowledgeBase, - deleteKnowledgeBase, - selectKnowledgeBase, - setActiveKnowledgeBase, - isKnowledgeBaseSelectable, - refreshKnowledgeBaseData, - loadUserSelectedKnowledgeBases, - saveUserSelectedKnowledgeBases - ]); - + const contextValue = useMemo( + () => ({ + state, + dispatch, + fetchKnowledgeBases, + createKnowledgeBase, + deleteKnowledgeBase, + selectKnowledgeBase, + setActiveKnowledgeBase, + isKnowledgeBaseSelectable, + hasKnowledgeBaseModelMismatch, + refreshKnowledgeBaseData, + refreshKnowledgeBaseDataWithDataMate, + loadUserSelectedKnowledgeBases, + saveUserSelectedKnowledgeBases, + }), + [ + state, + fetchKnowledgeBases, + createKnowledgeBase, + deleteKnowledgeBase, + selectKnowledgeBase, + setActiveKnowledgeBase, + isKnowledgeBaseSelectable, + refreshKnowledgeBaseData, + refreshKnowledgeBaseDataWithDataMate, + loadUserSelectedKnowledgeBases, + saveUserSelectedKnowledgeBases, + ] + ); + return ( {children} ); -}; \ No newline at end of file +}; diff --git a/frontend/app/[locale]/models/components/modelConfig.tsx b/frontend/app/[locale]/models/components/modelConfig.tsx index 08ea8af20..628f52392 100644 --- a/frontend/app/[locale]/models/components/modelConfig.tsx +++ b/frontend/app/[locale]/models/components/modelConfig.tsx @@ -837,20 +837,22 @@ export const ModelConfigSection = forwardRef< }} > - - - + {modelEngineEnable && ( + + + + )}