import { forwardRef, useEffect, useImperativeHandle, useMemo, useState } from 'react'; import { Form, Input, Select, Modal, Tabs, Switch, Radio, Button,message } from 'antd'; import { useTranslation } from 'react-i18next'; import type { KnowledgeBaseListItem, KnowledgeBaseFormData, CreateModalRef, CreateModalRefProps } from '@/views/KnowledgeBase/types'; import { getModelTypeList, getModelList, createKnowledgeBase, updateKnowledgeBase, getKnowledgeGraphEntityTypes, deleteKnowledgeGraph, rebuildKnowledgeGraph } from '@/api/knowledgeBase' import RbModal from '@/components/RbModal' const { TextArea } = Input; const { confirm } = Modal // 全局模型数据常量 let models: any = null; const CreateModal = forwardRef(({ refreshTable }, ref) => { const { t } = useTranslation(); const [messageApi, contextHolder] = message.useMessage(); const [visible, setVisible] = useState(false); const [modelTypeList, setModelTypeList] = useState([]); const [modelOptionsByType, setModelOptionsByType] = useState>({}); const [datasets, setDatasets] = useState(null); const [currentType, setCurrentType] = useState<'General' | 'Web' | 'Third-party' | 'Folder'>('General'); const [form] = Form.useForm(); const [loading, setLoading] = useState(false); const [activeTab, setActiveTab] = useState('basic'); const [generatingEntityTypes, setGeneratingEntityTypes] = useState(false); const [isRebuildMode, setIsRebuildMode] = useState(false); const [originalType, setOriginalType] = useState(''); // 保存原始的 type 参数 // 监听 parser_config.graphrag 相关字段的变化 const parserConfig = Form.useWatch('parser_config', form); const graphragConfig = parserConfig?.graphrag; const enableKnowledgeGraph = graphragConfig?.use_graphrag || false; const entityTypes = graphragConfig?.entity_types || ''; const entityNormalization = graphragConfig?.resolution || false; const communityReportGeneration = graphragConfig?.community || false; // 封装取消方法,添加关闭弹窗逻辑 const handleClose = () => { setDatasets(null); form.resetFields(); setLoading(false); setActiveTab('basic'); setIsRebuildMode(false); // 重置重建模式标识 setOriginalType(''); // 重置原始 type setVisible(false); }; // 生成实体类型的函数 const generateEntityTypes = async () => { const sceneName = form.getFieldValue(['parser_config', 'graphrag', 'scene_name']); if (!sceneName) { // 可以添加提示用户输入场景名称 messageApi.error(t('knowledgeBase.enterScenarioName')); return; } // 检查是否选择了 LLM 模型 const llmId = form.getFieldValue('llm_id'); if (!llmId) { // 跳转到基础配置页 setActiveTab('basic'); messageApi.error(t('knowledgeBase.pleaseSelectLLMModel')); return; } setGeneratingEntityTypes(true); try { // 这里应该调用实际的API接口 // const user = JSON.parse(localStorage.getItem('user') as any); //datasets?.id || datasets?.parent_id || user?.current_workspace_id, const params = { scenario: sceneName, llm_id: llmId }; const response = await getKnowledgeGraphEntityTypes(params); // 模拟API调用 // await new Promise(resolve => setTimeout(resolve, 1000)); // 处理API响应数据 console.log('API Response:', response); // 调试日志 // 检查响应结构 - API直接返回字符串 if (response && typeof response === 'string' && response.trim()) { // 将逗号分隔的字符串转换为换行分隔的格式以便在TextArea中显示 const entityTypesString = response.replace(/,\s*/g, '\n'); console.log('Converted entity types:', entityTypesString); // 调试日志 const currentGraphrag = form.getFieldValue(['parser_config', 'graphrag']) || {}; const updatedGraphrag = { ...currentGraphrag, entity_types: entityTypesString }; console.log('Updating form with:', updatedGraphrag); // 调试日志 // 使用更直接的方式更新表单字段 form.setFieldValue(['parser_config', 'graphrag', 'entity_types'], entityTypesString); // 强制触发表单重新渲染 form.validateFields([['parser_config', 'graphrag', 'entity_types']]); // 额外的强制更新机制 setTimeout(() => { form.setFieldValue(['parser_config', 'graphrag', 'entity_types'], entityTypesString); }, 100); messageApi.success(t('knowledgeBase.generateEntityTypesSuccess')); } else { messageApi.error(t('knowledgeBase.generateEntityTypesFailed') + ':' + t('knowledgeBase.unknownError')); } } catch (error) { console.error(t('knowledgeBase.generateEntityTypesFailed') + ':', error); } finally { setGeneratingEntityTypes(false); } }; const typeToFieldKey = (type: string): string => { switch ((type || '').toLowerCase()) { case 'embedding': return 'embedding_id'; case 'llm': return 'llm_id'; case 'image2text': return 'image2text_id'; case 'rerank': case 'reranker': return 'reranker_id'; case 'chat': return 'chat_id'; default: return `${type.toLowerCase()}_id`; } }; const fetchModelLists = async (types: string[]) => { // 如果还没有获取过全部模型数据,则获取一次 if (!models) { try { models = await getModelList({ page: 1, pagesize: 100 }); } catch (error) { console.error('Failed to fetch models:', error); models = { items: [] }; } } // 从全部模型数据中过滤出需要的类型 const typesToFetch = types.includes('llm') ? [...types, 'chat'] : types; const next: Record = {}; typesToFetch.forEach((tp) => { const targetType = tp === 'image2text' ? 'chat' : tp; const filteredModels = (models?.items || []).filter((m: any) => m.type === targetType); next[tp] = filteredModels.map((m: any) => ({ label: m.name, value: m.id })); }); setModelOptionsByType(next); // 如果不是编辑模式,为每个类型的下拉框设置默认值为第一条数据 if (!datasets?.id) { const defaultValues: Record = {}; types.forEach((tp) => { const fieldKey = typeToFieldKey(tp); const options = tp.toLowerCase() === 'llm' ? [...(next['llm'] || []), ...(next['chat'] || [])] : next[tp] || []; // 如果有选项且当前字段没有值,设置第一个选项为默认值 if (options.length > 0 && !form.getFieldValue(fieldKey)) { defaultValues[fieldKey] = options[0].value; } }); if (Object.keys(defaultValues).length > 0) { form.setFieldsValue(defaultValues as Partial); } } }; const setBaseFields = (record: KnowledgeBaseListItem | null, type?: string) => { if (!record) { form.resetFields(); const defaults: Partial = { permission_id: 'Private', type: type || currentType, }; form.setFieldsValue(defaults); return; } const baseValues: Partial = { name: record.name, description: record.description, permission_id: record.permission_id || 'Private', type: type || record.type || currentType, status: record.status, }; // 处理 parser_config 配置数据,如果没有则设置默认值 baseValues.parser_config = record.parser_config || { graphrag: { use_graphrag: false, scene_name: '', entity_types: [] as any, method: 'general', resolution: false, community: false, } }; // 如果存在 entity_types,转换为换行分隔格式用于 TextArea 显示 if (baseValues.parser_config.graphrag.entity_types) { if (Array.isArray(baseValues.parser_config.graphrag.entity_types)) { // 如果是数组格式,转换为换行分隔字符串 (baseValues.parser_config.graphrag as any).entity_types = baseValues.parser_config.graphrag.entity_types.join('\n'); } else if (typeof baseValues.parser_config.graphrag.entity_types === 'string') { // 如果是逗号分隔字符串格式,转换为换行分隔字符串(兼容旧数据) (baseValues.parser_config.graphrag as any).entity_types = (baseValues.parser_config.graphrag.entity_types as string).replace(/,\s*/g, '\n'); } } form.setFieldsValue(baseValues); }; const setDynamicModelFields = (record: KnowledgeBaseListItem | null, types: string[]) => { if (!record || !types.length) return; const dynamicValues: Record = {}; const source = record as unknown as Record; types.forEach((tp) => { const fieldKey = typeToFieldKey(tp); const fieldValue = source[fieldKey]; if (typeof fieldValue === 'string') { dynamicValues[fieldKey] = fieldValue; } }); if (Object.keys(dynamicValues).length) { form.setFieldsValue(dynamicValues as Partial); } }; const handleOpen = (record?: KnowledgeBaseListItem | null, type?: string) => { setDatasets(record || null); // 如果是重建模式,使用记录的实际类型,否则使用传入的类型 const actualType = type === 'rebuild' ? (record?.type || 'General') : (type || currentType); setCurrentType(actualType as any); setIsRebuildMode(type === 'rebuild'); // 设置重建模式标识 setOriginalType(type || ''); // 保存原始的 type 参数 // 如果是重建模式,默认切换到知识图谱标签页 if (type === 'rebuild') { setActiveTab('knowledgeGraph'); } else { setActiveTab('basic'); } setBaseFields(record || null, actualType); getTypeList(record || null); setVisible(true); }; const getTypeList = async (record: KnowledgeBaseListItem | null) => { const response = await getModelTypeList(); const types = Array.isArray(response) ? [...response.filter(type => type !== 'chat'),'image2text'] : []; setModelTypeList(types); if (types.length) { await fetchModelLists(types); setDynamicModelFields(record, types); } else { setModelOptionsByType({}); } }; useEffect(() => { if (!visible) return; setBaseFields(datasets, currentType); setDynamicModelFields(datasets, modelTypeList); }, [visible, datasets, currentType, modelTypeList]); // 封装保存方法,添加提交逻辑 const handleSave = () => { // 获取当前表单中的知识图谱开启状态 const currentFormValues = form.getFieldsValue(); const isGraphragEnabled = currentFormValues?.parser_config?.graphrag?.use_graphrag || false; // 如果原始 type 是 'rebuild' 并且知识图谱开启为true,显示确认弹框 if (originalType === 'rebuild' && isGraphragEnabled) { confirm({ title: t('knowledgeBase.rebuildConfirmTitle'), content: t('knowledgeBase.rebuildConfirmContent'), onOk: async() => { handleDeleteGraph() performSave(); await rebuildKnowledgeGraph(datasets?.id || '') }, onCancel: () => { // 用户取消,不执行任何操作 }, }); } else { // 非重建模式或知识图谱未开启,直接保存 performSave(); } }; const handleDeleteGraph = () => { try{ deleteKnowledgeGraph(datasets?.id || '') console.log(t('knowledgeBase.deleteGraphSuccess')) }catch(e){ messageApi.error(t('knowledgeBase.deleteGraphFailed')) } }; // 实际的保存逻辑 const performSave = () => { form .validateFields() .then(() => { setLoading(true) const formValues = form.getFieldsValue(); // 处理 entity_types 格式转换:从换行分隔字符串转换为字符串数组 if (formValues.parser_config && formValues.parser_config.graphrag && formValues.parser_config.graphrag.entity_types) { const entityTypesString = formValues.parser_config.graphrag.entity_types as any as string; const entityTypesArray = entityTypesString .split('\n') .map((item: string) => item.trim()) .filter((item: string) => item.length > 0); formValues.parser_config.graphrag.entity_types = entityTypesArray; } // 确保保存时使用正确的类型(不是 'rebuild') const saveType = originalType === 'rebuild' ? currentType : (formValues.type || currentType); const payload: KnowledgeBaseFormData = { ...formValues, type: saveType, permission_id: formValues.permission_id || 'Private', parent_id: datasets?.parent_id || undefined, }; console.log('Saving payload:', payload); // 调试日志 const submit = datasets?.id ? updateKnowledgeBase(datasets.id, payload) : createKnowledgeBase(payload); submit .then(() => { if (refreshTable) { refreshTable(); } handleClose(); }) .catch(() => { setLoading(false); }); }).catch((err) => { console.log('Validation failed:', err) }); } const handleChange = (_value: string, tp: string) => { // 只在编辑模式且类型为 embedding 时触发提示 if (datasets?.id && tp.toLowerCase() === 'embedding') { const fieldKey = typeToFieldKey(tp); // 从原始 datasets 对象中获取之前的值 const previousValue = (datasets as any)[fieldKey]; confirm({ title: t('common.updateWarning'), content: t('knowledgeBase.updateEmbeddingContent'), onOk: () => { // 确定时什么也不做,保持新值 }, onCancel: () => { // 取消时恢复之前的值 form.setFieldsValue({ [fieldKey]: previousValue } as any); }, }); } } // 暴露给父组件的方法 useImperativeHandle(ref, () => ({ handleOpen, handleClose })); // 根据 type 获取标题 const getTitle = () => { if (isRebuildMode) { return t('knowledgeBase.rebuildGraph') + ' - ' + (datasets?.name || ''); } if (datasets?.id) { return t('knowledgeBase.edit') + ' ' + datasets.name; } if (currentType === 'Folder') { return t('knowledgeBase.createA') + ' ' + t('knowledgeBase.folder'); } return t('knowledgeBase.createA') + ' ' + t('knowledgeBase.knowledgeBase'); }; const dynamicTypeList = useMemo(() => modelTypeList.filter((tp) => (modelOptionsByType[tp] || []).length), [modelTypeList, modelOptionsByType]); // 基础配置表单内容 const renderBasicConfig = () => ( <> {!datasets?.id && ( )}