diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 8114ee2b2..9a8c4e32d 100644 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -271,6 +271,56 @@ export default { saveFailed: 'Failed to save configuration: {message}' } }, + graphSettings: { + title: 'Knowledge Graph Configuration', + description: 'Configure entity-relationship extraction to automatically build knowledge graphs from text', + enableLabel: 'Enable Entity-Relationship Extraction', + enableDescription: 'Automatically extract entities and relationships from text when enabled', + tagsLabel: 'Relationship Types', + tagsDescription: 'Define relationship type tags to extract, separated by commas', + tagsPlaceholder: 'Enter relationship types, e.g., works_at, colleague, friend', + generateRandomTags: 'Generate Random Tags', + sampleTextLabel: 'Sample Text', + sampleTextDescription: 'Sample text for testing entity-relationship extraction', + sampleTextPlaceholder: 'Enter text containing entities and relationships...', + generateRandomText: 'Generate Random Text', + entityListLabel: 'Entity List', + entityListDescription: 'Entities and their attributes extracted from text', + nodeNamePlaceholder: 'Enter entity name', + attributePlaceholder: 'Enter attribute value', + addAttribute: 'Add Attribute', + manageEntitiesLabel: 'Manage Entities', + manageEntitiesDescription: 'Add or remove entity nodes', + addEntity: 'Add Entity', + relationListLabel: 'Relationship List', + relationListDescription: 'Define relationship connections between entities', + selectEntity: 'Select Entity', + selectRelationType: 'Select Relationship Type', + manageRelationsLabel: 'Manage Relationships', + manageRelationsDescription: 'Add or remove relationships between entities', + addRelation: 'Add Relationship', + extractActionsLabel: 'Extraction Actions', + extractActionsDescription: 'Perform entity-relationship extraction or manage sample data', + startExtraction: 'Start Extraction', + extracting: 'Extracting...', + defaultExample: 'Default Example', + clearExample: 'Clear Example', + completeModelConfig: 'Please complete model configuration first', + tagsGenerated: 'Tags generated successfully', + tagsGenerateFailed: 'Failed to generate tags', + textGenerated: 'Text generated successfully', + textGenerateFailed: 'Failed to generate text', + pleaseInputText: 'Please enter sample text first', + extractSuccess: 'Entity-relationship extraction successful', + extractFailed: 'Entity-relationship extraction failed', + exampleLoaded: 'Example loaded', + exampleCleared: 'Example cleared', + saveSuccess: 'Graph configuration saved', + saveFailed: 'Failed to save configuration: {message}', + errors: { + unknown: 'Unknown error', + }, + }, initialization: { title: 'Initialization', welcome: 'Welcome to WeKnora', @@ -770,7 +820,8 @@ export default { models: 'Model Configuration', chunking: 'Chunking Settings', advanced: 'Advanced Settings', - faq: 'FAQ Settings' + faq: 'FAQ Settings', + graph: 'Knowledge Graph' }, basic: { title: 'Basic Information', @@ -936,7 +987,7 @@ export default { }, advanced: { title: 'Advanced Settings', - description: 'Configure multimodal and knowledge graph features', + description: 'Configure multimodal features', multimodal: { label: 'Multimodal Feature', description: 'Enable understanding of multimodal content such as images and videos', @@ -981,40 +1032,6 @@ export default { pathPrefixDescription: 'Optional prefix for stored file paths', pathPrefixPlaceholder: 'Enter path prefix' } - }, - graph: { - label: 'Knowledge Graph Extraction', - description: 'Automatically extract entities and relationships to build a knowledge graph', - configTitle: 'Graph Configuration', - promptLabel: 'Prompt Text', - promptDescription: 'Prompt used to guide the model when extracting entities and relationships', - promptPlaceholder: 'Enter prompt text', - tagsLabel: 'Tags', - tagsDescription: 'Predefined entity tags (separate multiple tags with commas)', - tagsPlaceholder: 'Enter a tag and press Enter', - nodesLabel: 'Node Configuration', - nodesDescription: 'Define entity node types and attributes to extract', - addNode: 'Add Node', - nodeNameLabel: 'Node Name', - nodeNamePlaceholder: 'e.g., Person, Organization', - nodeChunksLabel: 'Associated Chunks', - nodeChunksPlaceholder: 'Enter chunk ID and press Enter', - nodeAttributesLabel: 'Node Attributes', - nodeAttributesPlaceholder: 'Enter attribute name and press Enter', - relationsLabel: 'Relation Configuration', - relationsDescription: 'Define relationship types between nodes', - addRelation: 'Add Relation', - relationNode1Label: 'Source Node', - relationNode1Placeholder: 'e.g., Person', - relationNode2Label: 'Target Node', - relationNode2Placeholder: 'e.g., Organization', - relationTypeLabel: 'Relation Type', - relationTypePlaceholder: 'e.g., WORKS_FOR, BELONGS_TO', - deleteNode: 'Delete Node', - deleteRelation: 'Delete Relation', - disabledWarning: - 'Graph database is not enabled. Please follow the "Enable Knowledge Graph" guide before using this feature.', - howToEnable: 'View guide' } } }, diff --git a/frontend/src/i18n/locales/ru-RU.ts b/frontend/src/i18n/locales/ru-RU.ts index c98e33688..3b64857bc 100644 --- a/frontend/src/i18n/locales/ru-RU.ts +++ b/frontend/src/i18n/locales/ru-RU.ts @@ -270,6 +270,56 @@ export default { saveFailed: 'Не удалось сохранить настройки: {message}' } }, + graphSettings: { + title: 'Настройки графа знаний', + description: 'Настройте извлечение сущностей и отношений для автоматического построения графа знаний из текста', + enableLabel: 'Включить извлечение сущностей и отношений', + enableDescription: 'Автоматически извлекать сущности и отношения из текста при включении', + tagsLabel: 'Типы отношений', + tagsDescription: 'Определите теги типов отношений для извлечения, разделённые запятыми', + tagsPlaceholder: 'Введите типы отношений, например: работает_в, коллега, друг', + generateRandomTags: 'Сгенерировать случайные теги', + sampleTextLabel: 'Образец текста', + sampleTextDescription: 'Образец текста для тестирования извлечения сущностей и отношений', + sampleTextPlaceholder: 'Введите текст, содержащий сущности и отношения...', + generateRandomText: 'Сгенерировать случайный текст', + entityListLabel: 'Список сущностей', + entityListDescription: 'Сущности и их атрибуты, извлечённые из текста', + nodeNamePlaceholder: 'Введите имя сущности', + attributePlaceholder: 'Введите значение атрибута', + addAttribute: 'Добавить атрибут', + manageEntitiesLabel: 'Управление сущностями', + manageEntitiesDescription: 'Добавить или удалить узлы сущностей', + addEntity: 'Добавить сущность', + relationListLabel: 'Список отношений', + relationListDescription: 'Определите связи отношений между сущностями', + selectEntity: 'Выберите сущность', + selectRelationType: 'Выберите тип отношения', + manageRelationsLabel: 'Управление отношениями', + manageRelationsDescription: 'Добавить или удалить отношения между сущностями', + addRelation: 'Добавить отношение', + extractActionsLabel: 'Действия извлечения', + extractActionsDescription: 'Выполнить извлечение сущностей и отношений или управлять образцами данных', + startExtraction: 'Начать извлечение', + extracting: 'Извлечение...', + defaultExample: 'Пример по умолчанию', + clearExample: 'Очистить пример', + completeModelConfig: 'Пожалуйста, сначала завершите настройку модели', + tagsGenerated: 'Теги успешно сгенерированы', + tagsGenerateFailed: 'Не удалось сгенерировать теги', + textGenerated: 'Текст успешно сгенерирован', + textGenerateFailed: 'Не удалось сгенерировать текст', + pleaseInputText: 'Пожалуйста, сначала введите образец текста', + extractSuccess: 'Извлечение сущностей и отношений выполнено успешно', + extractFailed: 'Не удалось извлечь сущности и отношения', + exampleLoaded: 'Пример загружен', + exampleCleared: 'Пример очищен', + saveSuccess: 'Настройки графа сохранены', + saveFailed: 'Не удалось сохранить настройки: {message}', + errors: { + unknown: 'Неизвестная ошибка', + }, + }, initialization: { title: 'Инициализация', welcome: 'Добро пожаловать в WeKnora', @@ -858,7 +908,8 @@ export default { models: 'Конфигурация моделей', chunking: 'Настройки разбиения', advanced: 'Дополнительные настройки', - faq: 'FAQ настройки' + faq: 'FAQ настройки', + graph: 'Граф знаний' }, basic: { title: 'Основная информация', @@ -1021,7 +1072,7 @@ export default { }, advanced: { title: 'Расширенные настройки', - description: 'Настройте мультимодальные возможности и извлечение знаний', + description: 'Настройте мультимодальные возможности', multimodal: { label: 'Мультимодальная функция', description: 'Включите понимание мультимедийного контента, такого как изображения и видео', @@ -1066,19 +1117,6 @@ export default { pathPrefixDescription: 'Необязательный префикс для путей хранения файлов', pathPrefixPlaceholder: 'Введите префикс пути' } - }, - graph: { - label: 'Извлечение знаний', - description: 'Автоматически извлекайте сущности и связи для построения графа знаний', - configTitle: 'Настройки графа', - promptLabel: 'Текст подсказки', - promptDescription: 'Текст подсказки, который помогает модели извлекать сущности и связи', - promptPlaceholder: 'Введите текст подсказки', - tagsLabel: 'Теги', - tagsDescription: 'Предопределённые теги сущностей (несколько тегов разделяйте запятыми)', - tagsPlaceholder: 'Введите тег и нажмите Enter', - disabledWarning: 'Графовая база данных не включена. Пожалуйста, сначала включите графовую базу данных, чтобы использовать эту функцию.', - howToEnable: 'Как включить' } } }, diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index 8c71d7056..78bfea272 100644 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -373,6 +373,56 @@ export default { saveFailed: "保存配置失败: {message}", }, }, + graphSettings: { + title: "知识图谱配置", + description: "配置实体关系提取功能,自动从文本中提取实体和关系构建知识图谱", + enableLabel: "启用实体关系提取", + enableDescription: "开启后将自动从文本中提取实体和关系", + tagsLabel: "关系类型", + tagsDescription: "定义要提取的关系类型标签,多个标签用逗号分隔", + tagsPlaceholder: "输入关系类型,如:工作于、同事、朋友等", + generateRandomTags: "生成随机标签", + sampleTextLabel: "示例文本", + sampleTextDescription: "用于测试实体关系提取的示例文本", + sampleTextPlaceholder: "输入一段包含实体和关系的文本...", + generateRandomText: "生成随机文本", + entityListLabel: "实体列表", + entityListDescription: "从文本中提取的实体及其属性", + nodeNamePlaceholder: "输入实体名称", + attributePlaceholder: "输入属性值", + addAttribute: "添加属性", + manageEntitiesLabel: "管理实体", + manageEntitiesDescription: "添加或删除实体节点", + addEntity: "添加实体", + relationListLabel: "关系列表", + relationListDescription: "定义实体之间的关系连接", + selectEntity: "选择实体", + selectRelationType: "选择关系类型", + manageRelationsLabel: "管理关系", + manageRelationsDescription: "添加或删除实体间的关系", + addRelation: "添加关系", + extractActionsLabel: "提取操作", + extractActionsDescription: "执行实体关系提取或管理示例数据", + startExtraction: "开始提取", + extracting: "提取中...", + defaultExample: "默认示例", + clearExample: "清除示例", + completeModelConfig: "请先完成模型配置", + tagsGenerated: "标签生成成功", + tagsGenerateFailed: "标签生成失败", + textGenerated: "文本生成成功", + textGenerateFailed: "文本生成失败", + pleaseInputText: "请先输入示例文本", + extractSuccess: "实体关系提取成功", + extractFailed: "实体关系提取失败", + exampleLoaded: "示例已加载", + exampleCleared: "示例已清除", + saveSuccess: "图谱配置已保存", + saveFailed: "保存配置失败: {message}", + errors: { + unknown: "未知错误", + }, + }, initialization: { title: "初始化", welcome: "欢迎使用WeKnora", @@ -1094,6 +1144,7 @@ export default { chunking: "分块设置", advanced: "高级设置", faq: "FAQ 设置", + graph: "知识图谱", }, basic: { title: "基本信息", @@ -1260,7 +1311,7 @@ export default { }, advanced: { title: "高级设置", - description: "配置多模态、知识图谱等高级功能", + description: "配置多模态等高级功能", multimodal: { label: "多模态功能", description: "启用图片、视频等多模态内容的理解能力", @@ -1307,40 +1358,6 @@ export default { pathPrefixPlaceholder: "请输入路径前缀", }, }, - graph: { - label: "知识图谱提取", - description: "从文档中自动提取实体和关系,构建知识图谱", - configTitle: "图谱配置", - promptLabel: "提示文本", - promptDescription: "用于引导模型提取实体和关系的提示文本", - promptPlaceholder: "请输入提示文本", - tagsLabel: "标签", - tagsDescription: "预定义的实体标签(多个标签用逗号分隔)", - tagsPlaceholder: "输入标签后按回车", - nodesLabel: "节点配置", - nodesDescription: "定义需要提取的实体节点类型及其属性", - addNode: "添加节点", - nodeNameLabel: "节点名称", - nodeNamePlaceholder: "如:Person、Organization", - nodeChunksLabel: "关联文档块", - nodeChunksPlaceholder: "输入 chunk ID 后按回车", - nodeAttributesLabel: "节点属性", - nodeAttributesPlaceholder: "输入属性名后按回车", - relationsLabel: "关系配置", - relationsDescription: "定义节点之间的关系类型", - addRelation: "添加关系", - relationNode1Label: "起始节点", - relationNode1Placeholder: "如:Person", - relationNode2Label: "目标节点", - relationNode2Placeholder: "如:Organization", - relationTypeLabel: "关系类型", - relationTypePlaceholder: "如:WORKS_FOR、BELONGS_TO", - deleteNode: "删除节点", - deleteRelation: "删除关系", - disabledWarning: - "图数据库未启用,无法使用知识图谱功能。请先根据《开启知识图谱功能指南》完成配置。", - howToEnable: "查看启用说明", - }, }, }, input: { diff --git a/frontend/src/views/knowledge/KnowledgeBaseEditorModal.vue b/frontend/src/views/knowledge/KnowledgeBaseEditorModal.vue index 6d0a736ea..40206cddc 100644 --- a/frontend/src/views/knowledge/KnowledgeBaseEditorModal.vue +++ b/frontend/src/views/knowledge/KnowledgeBaseEditorModal.vue @@ -128,16 +128,24 @@ /> + +
+ +
+
@@ -169,6 +177,7 @@ import { useUIStore } from '@/stores/ui' import KBModelConfig from './settings/KBModelConfig.vue' import KBChunkingSettings from './settings/KBChunkingSettings.vue' import KBAdvancedSettings from './settings/KBAdvancedSettings.vue' +import GraphSettings from './settings/GraphSettings.vue' import { useI18n } from 'vue-i18n' const uiStore = useUIStore() @@ -204,6 +213,7 @@ const navItems = computed(() => { } else { items.push( { key: 'chunking', icon: 'file-copy', label: t('knowledgeEditor.sidebar.chunking') }, + { key: 'graph', icon: 'chart-bubble', label: t('knowledgeEditor.sidebar.graph') }, { key: 'advanced', icon: 'setting', label: t('knowledgeEditor.sidebar.advanced') } ) } @@ -272,13 +282,12 @@ const initFormData = (type: 'document' | 'faq' = 'document') => { pathPrefix: '' } }, - nodeExtractConfig: { + extractConfig: { enabled: false, text: '', tags: [] as string[], nodes: [] as Array<{ name: string - chunks: string[] attributes: string[] }>, relations: [] as Array<{ @@ -286,7 +295,7 @@ const initFormData = (type: 'document' | 'faq' = 'document') => { node2: string type: string }> - } + }, } } @@ -358,13 +367,16 @@ const loadKBData = async () => { pathPrefix: kb.cos_config?.path_prefix || '' } }, - nodeExtractConfig: { + extractConfig: { enabled: kb.extract_config?.enabled || false, text: kb.extract_config?.text || '', tags: kb.extract_config?.tags || [], - nodes: kb.extract_config?.nodes || [], + nodes: (kb.extract_config?.nodes || []).map((node: any) => ({ + name: node.name, + attributes: node.attributes || [] + })), relations: kb.extract_config?.relations || [] - } + }, } } catch (error) { console.error('Failed to load knowledge base data:', error) @@ -396,7 +408,7 @@ const handleMultimodalUpdate = (config: any) => { const handleNodeExtractUpdate = (config: any) => { if (formData.value) { - formData.value.nodeExtractConfig = { ...config } + formData.value.extractConfig = { ...config } } } @@ -492,13 +504,13 @@ const buildSubmitData = () => { } // 添加知识图谱配置 - if (formData.value.nodeExtractConfig.enabled) { + if (formData.value.extractConfig.enabled) { data.extract_config = { enabled: true, - text: formData.value.nodeExtractConfig.text, - tags: formData.value.nodeExtractConfig.tags, - nodes: formData.value.nodeExtractConfig.nodes, - relations: formData.value.nodeExtractConfig.relations + text: formData.value.extractConfig.text, + tags: formData.value.extractConfig.tags, + nodes: formData.value.extractConfig.nodes, + relations: formData.value.extractConfig.relations } } @@ -584,8 +596,8 @@ const handleSubmit = async () => { enabled: data.extract_config?.enabled || false, text: data.extract_config?.text || '', tags: data.extract_config?.tags || [], - nodes: [], - relations: [] + nodes: data.extract_config?.nodes || [], + relations: data.extract_config?.relations || [] } } diff --git a/frontend/src/views/knowledge/settings/GraphSettings.vue b/frontend/src/views/knowledge/settings/GraphSettings.vue new file mode 100644 index 000000000..28c78b9fe --- /dev/null +++ b/frontend/src/views/knowledge/settings/GraphSettings.vue @@ -0,0 +1,746 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/views/knowledge/settings/KBAdvancedSettings.vue b/frontend/src/views/knowledge/settings/KBAdvancedSettings.vue index 71ec0fba9..a59f5ff90 100644 --- a/frontend/src/views/knowledge/settings/KBAdvancedSettings.vue +++ b/frontend/src/views/knowledge/settings/KBAdvancedSettings.vue @@ -210,198 +210,6 @@ - -
-
- -

{{ $t('knowledgeEditor.advanced.graph.description') }}

- - - - -
-
- -
-
- - -
-
-

{{ $t('knowledgeEditor.advanced.graph.configTitle') }}

-
- -
-
- -

{{ $t('knowledgeEditor.advanced.graph.promptDescription') }}

-
-
- -
-
- -
-
- -

{{ $t('knowledgeEditor.advanced.graph.tagsDescription') }}

-
-
- -
-
- - -
-

{{ $t('knowledgeEditor.advanced.graph.nodesLabel') }}

-

{{ $t('knowledgeEditor.advanced.graph.nodesDescription') }}

-
- -
-
- 节点 #{{ index + 1 }} - - {{ $t('knowledgeEditor.advanced.graph.deleteNode') }} - -
- -
-
- - -
- -
- - -
- -
- - -
-
-
- - - - {{ $t('knowledgeEditor.advanced.graph.addNode') }} - - - -
-

{{ $t('knowledgeEditor.advanced.graph.relationsLabel') }}

-

{{ $t('knowledgeEditor.advanced.graph.relationsDescription') }}

-
- -
-
- 关系 #{{ index + 1 }} - - {{ $t('knowledgeEditor.advanced.graph.deleteRelation') }} - -
- -
-
- - -
- -
- - -
- -
- - -
-
-
- - - - {{ $t('knowledgeEditor.advanced.graph.addRelation') }} - -
@@ -433,25 +241,8 @@ interface MultimodalConfig { } } -interface NodeExtractConfig { - enabled: boolean - text: string - tags: string[] - nodes: Array<{ - name: string - chunks: string[] - attributes: string[] - }> - relations: Array<{ - node1: string - node2: string - type: string - }> -} - interface Props { multimodal: MultimodalConfig - nodeExtract: NodeExtractConfig allModels?: any[] } @@ -459,18 +250,11 @@ const props = defineProps() const emit = defineEmits<{ 'update:multimodal': [value: MultimodalConfig] - 'update:nodeExtract': [value: NodeExtractConfig] }>() const localMultimodal = ref({ ...props.multimodal }) -const localNodeExtract = ref({ - ...props.nodeExtract, - nodes: props.nodeExtract.nodes || [], - relations: props.nodeExtract.relations || [] -}) const vllmSelectorRef = ref() -const isGraphDatabaseEnabled = ref(false) const isMinioEnabled = ref(false) // Check system status on mount @@ -478,28 +262,6 @@ onMounted(async () => { try { const systemInfo = await getSystemInfo() - // Check graph database status - if (systemInfo.data?.graph_database_engine) { - // Check if graph database is enabled - // Enabled if it's "Neo4j" or any other non-empty value that's not a disabled indicator - const engine = systemInfo.data.graph_database_engine.trim() - const disabledIndicators = ['未启用', '未配置', 'Unknown', 'Неизвестно', ''] - isGraphDatabaseEnabled.value = !disabledIndicators.includes(engine) && engine.length > 0 - - // If graph database is disabled, also disable node extract - if (!isGraphDatabaseEnabled.value && localNodeExtract.value.enabled) { - localNodeExtract.value.enabled = false - emit('update:nodeExtract', localNodeExtract.value) - } - } else { - // No graph database engine info, assume disabled - isGraphDatabaseEnabled.value = false - if (localNodeExtract.value.enabled) { - localNodeExtract.value.enabled = false - emit('update:nodeExtract', localNodeExtract.value) - } - } - // Check MinIO status isMinioEnabled.value = systemInfo.data?.minio_enabled === true @@ -511,12 +273,7 @@ onMounted(async () => { } catch (error) { console.error('Failed to fetch system info:', error) // Default to disabled if we can't fetch the info - isGraphDatabaseEnabled.value = false isMinioEnabled.value = false - if (localNodeExtract.value.enabled) { - localNodeExtract.value.enabled = false - emit('update:nodeExtract', localNodeExtract.value) - } // If MinIO status unknown and storage type is minio, switch to cos if (localMultimodal.value.storageType === 'minio') { localMultimodal.value.storageType = 'cos' @@ -530,14 +287,6 @@ watch(() => props.multimodal, (newVal) => { localMultimodal.value = { ...newVal } }, { deep: true }) -watch(() => props.nodeExtract, (newVal) => { - localNodeExtract.value = { - ...newVal, - nodes: newVal.nodes || [], - relations: newVal.relations || [] - } -}, { deep: true }) - // Handle multimodal toggle const handleMultimodalToggle = () => { // Reset related configuration when multimodal is disabled @@ -580,67 +329,9 @@ const handleAddModel = (subSection: string) => { uiStore.openSettings('models', subSection) } -const graphGuideUrl = - import.meta.env.VITE_KG_GUIDE_URL || - 'https://github.com/Tencent/WeKnora/blob/main/docs/%E5%BC%80%E5%90%AF%E7%9F%A5%E8%AF%86%E5%9B%BE%E8%B0%B1%E5%8A%9F%E8%83%BD.md' - -// Handle knowledge graph toggle -const handleNodeExtractToggle = () => { - // Prevent enabling if graph database is not enabled - if (!isGraphDatabaseEnabled.value) { - localNodeExtract.value.enabled = false - return - } - emit('update:nodeExtract', localNodeExtract.value) -} - -// Open guide documentation to show how to enable graph database -const handleOpenGraphGuide = () => { - window.open(graphGuideUrl, '_blank', 'noopener') -} - // Handle configuration change const handleConfigChange = () => { emit('update:multimodal', localMultimodal.value) - emit('update:nodeExtract', localNodeExtract.value) -} - -// Add a new node -const addNode = () => { - if (!localNodeExtract.value.nodes) { - localNodeExtract.value.nodes = [] - } - localNodeExtract.value.nodes.push({ - name: '', - chunks: [], - attributes: [] - }) - handleConfigChange() -} - -// Remove a node -const removeNode = (index: number) => { - localNodeExtract.value.nodes.splice(index, 1) - handleConfigChange() -} - -// Add a new relation -const addRelation = () => { - if (!localNodeExtract.value.relations) { - localNodeExtract.value.relations = [] - } - localNodeExtract.value.relations.push({ - node1: '', - node2: '', - type: '' - }) - handleConfigChange() -} - -// Remove a relation -const removeRelation = (index: number) => { - localNodeExtract.value.relations.splice(index, 1) - handleConfigChange() } // The allModels prop keeps model options in sync @@ -716,11 +407,6 @@ const removeRelation = (index: number) => { align-items: center; } -.graph-guide-link { - display: inline-block; - margin-top: 8px; -} - .subsection { padding: 16px 20px; margin: 12px 0 0 0; diff --git a/internal/handler/initialization.go b/internal/handler/initialization.go index 65d594446..27c7fbb11 100644 --- a/internal/handler/initialization.go +++ b/internal/handler/initialization.go @@ -319,13 +319,19 @@ func (h *InitializationHandler) UpdateKBConfig(c *gin.Context) { } kb.ExtractConfig = &types.ExtractConfig{ + Enabled: req.NodeExtract.Enabled, Text: req.NodeExtract.Text, Tags: req.NodeExtract.Tags, Nodes: nodes, Relations: relations, } } else { - kb.ExtractConfig = nil + kb.ExtractConfig = &types.ExtractConfig{Enabled: false} + } + if err := validateExtractConfig(kb.ExtractConfig); err != nil { + logger.Error(ctx, "Invalid extract configuration", err) + c.Error(err) + return } // 保存更新后的知识库 @@ -1811,13 +1817,7 @@ func (h *InitializationHandler) ExtractTextRelations(c *gin.Context) { result, err := h.extractRelationsFromText(ctx, req.Text, req.Tags, req.LLMConfig) if err != nil { logger.Error(ctx, "文本关系提取失败", err) - c.JSON(http.StatusOK, gin.H{ - "success": true, - "data": gin.H{ - "success": false, - "message": err.Error(), - }, - }) + c.Error(errors.NewInternalServerError("文本关系提取失败: " + err.Error())) return } @@ -1885,13 +1885,7 @@ func (h *InitializationHandler) FabriText(c *gin.Context) { result, err := h.fabriText(ctx, req.Tags, req.LLMConfig) if err != nil { logger.Error(ctx, "生成示例文本失败", err) - c.JSON(http.StatusOK, gin.H{ - "success": true, - "data": gin.H{ - "success": false, - "message": err.Error(), - }, - }) + c.Error(errors.NewInternalServerError("生成示例文本失败: " + err.Error())) return } diff --git a/internal/handler/knowledgebase.go b/internal/handler/knowledgebase.go index 86e10b749..653f5d6ea 100644 --- a/internal/handler/knowledgebase.go +++ b/internal/handler/knowledgebase.go @@ -3,6 +3,7 @@ package handler import ( "context" "net/http" + "strconv" "github.com/Tencent/WeKnora/internal/errors" "github.com/Tencent/WeKnora/internal/logger" @@ -78,6 +79,11 @@ func (h *KnowledgeBaseHandler) CreateKnowledgeBase(c *gin.Context) { c.Error(errors.NewBadRequestError("Invalid request parameters").WithDetails(err.Error())) return } + if err := validateExtractConfig(req.ExtractConfig); err != nil { + logger.Error(ctx, "Invalid extract configuration", err) + c.Error(err) + return + } logger.Infof(ctx, "Creating knowledge base, name: %s", req.Name) // Create knowledge base using the service @@ -267,3 +273,70 @@ func (h *KnowledgeBaseHandler) CopyKnowledgeBase(c *gin.Context) { "message": "Knowledge base copy successfully", }) } + +// validateExtractConfig validates the graph configuration parameters +func validateExtractConfig(config *types.ExtractConfig) error { + logger.Errorf(context.Background(), "Validating extract configuration: %+v", config) + if config == nil { + return nil + } + if !config.Enabled { + *config = types.ExtractConfig{Enabled: false} + return nil + } + // Validate text field + if config.Text == "" { + return errors.NewBadRequestError("text cannot be empty") + } + + // Validate tags field + if len(config.Tags) == 0 { + return errors.NewBadRequestError("tags cannot be empty") + } + for i, tag := range config.Tags { + if tag == "" { + return errors.NewBadRequestError("tag cannot be empty at index " + strconv.Itoa(i)) + } + } + + // Validate nodes + if len(config.Nodes) == 0 { + return errors.NewBadRequestError("nodes cannot be empty") + } + nodeNames := make(map[string]bool) + for i, node := range config.Nodes { + if node.Name == "" { + return errors.NewBadRequestError("node name cannot be empty at index " + strconv.Itoa(i)) + } + // Check for duplicate node names + if nodeNames[node.Name] { + return errors.NewBadRequestError("duplicate node name: " + node.Name) + } + nodeNames[node.Name] = true + } + + if len(config.Relations) == 0 { + return errors.NewBadRequestError("relations cannot be empty") + } + // Validate relations + for i, relation := range config.Relations { + if relation.Node1 == "" { + return errors.NewBadRequestError("relation node1 cannot be empty at index " + strconv.Itoa(i)) + } + if relation.Node2 == "" { + return errors.NewBadRequestError("relation node2 cannot be empty at index " + strconv.Itoa(i)) + } + if relation.Type == "" { + return errors.NewBadRequestError("relation type cannot be empty at index " + strconv.Itoa(i)) + } + // Check if referenced nodes exist + if !nodeNames[relation.Node1] { + return errors.NewBadRequestError("relation references non-existent node1: " + relation.Node1) + } + if !nodeNames[relation.Node2] { + return errors.NewBadRequestError("relation references non-existent node2: " + relation.Node2) + } + } + + return nil +} diff --git a/internal/types/knowledgebase.go b/internal/types/knowledgebase.go index 3df1215e5..525efa806 100644 --- a/internal/types/knowledgebase.go +++ b/internal/types/knowledgebase.go @@ -197,10 +197,10 @@ func (c *VLMConfig) Scan(value interface{}) error { type ExtractConfig struct { Enabled bool `yaml:"enabled" json:"enabled"` - Text string `yaml:"text" json:"text"` - Tags []string `yaml:"tags" json:"tags"` - Nodes []*GraphNode `yaml:"nodes" json:"nodes"` - Relations []*GraphRelation `yaml:"relations" json:"relations"` + Text string `yaml:"text" json:"text,omitempty"` + Tags []string `yaml:"tags" json:"tags,omitempty"` + Nodes []*GraphNode `yaml:"nodes" json:"nodes,omitempty"` + Relations []*GraphRelation `yaml:"relations" json:"relations,omitempty"` } // Value implements the driver.Valuer interface, used to convert ExtractConfig to database value