From 50628816e3cc6e2ed95860200694412d211be8a4 Mon Sep 17 00:00:00 2001 From: "guangfei.zhao" Date: Thu, 16 Oct 2025 18:52:20 +0800 Subject: [PATCH] feat(knowledge): refactor knowledge creation form and improve form handling --- .env | 4 +- src/hooks/knowledge-hooks.ts | 9 +- .../knowledge/components/ChunkMethodForm.tsx | 66 +++- .../knowledge/components/GeneralForm.tsx | 75 ++++- src/pages/knowledge/create.tsx | 308 ++++++++++-------- 5 files changed, 312 insertions(+), 150 deletions(-) diff --git a/.env b/.env index e7fdc6e..7e5f5d5 100644 --- a/.env +++ b/.env @@ -1,5 +1,5 @@ -# VITE_API_BASE_URL = http://150.158.121.95 -VITE_API_BASE_URL = http://154.9.253.114:9380 +VITE_API_BASE_URL = http://150.158.121.95 +# VITE_API_BASE_URL = http://154.9.253.114:9380 VITE_RSA_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArq9XTUSeYr2+N1h3Afl/z8Dse/2yD0ZGrKwx+EEEcdsBLca9Ynmx3nIB5obmLlSfmskLpBo0UACBmB5rEjBp2Q2f3AG3Hjd4B+gNCG6BDaawuDlgANIhGnaTLrIqWrrcm4EMzJOnAOI1fgzJRsOOUEfaS318Eq9OVO3apEyCCt0lOQK6PuksduOjVxtltDav+guVAA068NrPYmRNabVKRNLJpL8w4D44sfth5RvZ3q9t+6RTArpEtc5sh5ChzvqPOzKGMXW83C95TxmXqpbK6olN4RevSfVjEAgCydH6HN6OhtOQEcnrU97r9H0iZOWwbw3pVrZiUkuRD1R56Wzs2wIDAQAB diff --git a/src/hooks/knowledge-hooks.ts b/src/hooks/knowledge-hooks.ts index fd077b7..481d298 100644 --- a/src/hooks/knowledge-hooks.ts +++ b/src/hooks/knowledge-hooks.ts @@ -272,7 +272,9 @@ export const useKnowledgeOperations = () => { * 更新知识库基础信息 * 包括名称、描述、语言等基本信息 */ - const updateKnowledgeBasicInfo = useCallback(async (data: IKnowledge) => { + const updateKnowledgeBasicInfo = useCallback(async (data: { + kb_id: string; + } & Partial) => { try { setLoading(true); setError(null); @@ -300,10 +302,7 @@ export const useKnowledgeOperations = () => { */ const updateKnowledgeModelConfig = useCallback(async (data: { kb_id: string; - embd_id?: string; - parser_config?: Partial; - parser_id?: string; - }) => { + } & Partial) => { try { setLoading(true); setError(null); diff --git a/src/pages/knowledge/components/ChunkMethodForm.tsx b/src/pages/knowledge/components/ChunkMethodForm.tsx index 134742d..10d524f 100644 --- a/src/pages/knowledge/components/ChunkMethodForm.tsx +++ b/src/pages/knowledge/components/ChunkMethodForm.tsx @@ -1,8 +1,9 @@ import React, { useMemo } from 'react'; -import { useFormContext, useWatch } from 'react-hook-form'; +import { useFormContext, useWatch, type UseFormReturn } from 'react-hook-form'; import { Box, Typography, + Button, } from '@mui/material'; import { DOCUMENT_PARSER_TYPES, type DocumentParserType } from '@/constants/knowledge'; import { type IParserConfig } from '@/interfaces/database/knowledge'; @@ -59,8 +60,45 @@ export interface ConfigFormData extends IParserConfig { parser_id: DocumentParserType; } -function ChunkMethodForm() { - const { control } = useFormContext(); +interface ChunkMethodFormProps { + form?: UseFormReturn; + onSubmit?: (data: any) => void; + isSubmitting?: boolean; + onCancel?: () => void; + submitButtonText?: string; + cancelButtonText?: string; +} + +function ChunkMethodForm({ + form: propForm, + onSubmit, + isSubmitting, + onCancel, + submitButtonText = '保存', + cancelButtonText = '取消', +}: ChunkMethodFormProps = {}) { + // 优先使用props传递的form,否则使用FormProvider的context + let contextForm; + try { + contextForm = useFormContext(); + } catch (error) { + contextForm = null; + } + + const form = propForm || contextForm; + + if (!form) { + console.error('ChunkMethodForm: No form context found. Component must be used within a FormProvider or receive a form prop.'); + return ( + + + 表单配置错误:请确保组件在FormProvider中使用或传递form参数 + + + ); + } + + const { control } = form; // 监听parser_id变化 const parser_id = useWatch({ @@ -79,6 +117,28 @@ function ChunkMethodForm() { {/* 动态配置内容 */} + + {/* 表单操作按钮 - 仅在有onSubmit回调时显示 */} + {onSubmit && ( + + {onCancel && ( + + )} + + + )} ); } diff --git a/src/pages/knowledge/components/GeneralForm.tsx b/src/pages/knowledge/components/GeneralForm.tsx index f69c94b..953dec5 100644 --- a/src/pages/knowledge/components/GeneralForm.tsx +++ b/src/pages/knowledge/components/GeneralForm.tsx @@ -1,5 +1,5 @@ import React, { useRef } from 'react'; -import { useFormContext, Controller } from 'react-hook-form'; +import { useFormContext, Controller, type UseFormReturn } from 'react-hook-form'; import { Box, Typography, @@ -18,30 +18,69 @@ import { Delete as DeleteIcon, } from '@mui/icons-material'; -function GeneralForm() { - const { control, watch, setValue } = useFormContext(); +interface GeneralFormProps { + form?: UseFormReturn; + onSubmit?: (data: any) => void; + isSubmitting?: boolean; + onCancel?: () => void; + submitButtonText?: string; + cancelButtonText?: string; +} + +function GeneralForm({ + form: propForm, + onSubmit, + isSubmitting, + onCancel, + submitButtonText = '保存', + cancelButtonText = '取消', +}: GeneralFormProps = {}) { + // 优先使用props传递的form,否则使用FormProvider的context + let contextForm: UseFormReturn | null = null; + try { + contextForm = useFormContext(); + } catch (error) { + contextForm = null; + } + + const form = propForm || contextForm; + + if (!form) { + console.error('GeneralForm: No form context found. Component must be used within a FormProvider or receive a form prop.'); + return ( + + + 表单配置错误:请确保组件在FormProvider中使用或传递form参数 + + + ); + } + + const { control, watch, setValue, handleSubmit } = form; const fileInputRef = useRef(null); const handleAvatarUpload = (event: React.ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { + if (file && form) { const reader = new FileReader(); reader.onload = (e) => { - setValue('avatar', e.target?.result as string); + form.setValue('avatar', e.target?.result as string); }; reader.readAsDataURL(file); } }; const handleAvatarDelete = () => { - setValue('avatar', undefined); + if (form) { + form.setValue('avatar', undefined); + } }; const handleAvatarClick = () => { fileInputRef.current?.click(); }; - const avatar = watch('avatar'); + const avatar = watch('avatar', ''); return ( @@ -146,6 +185,28 @@ function GeneralForm() { + + {/* 表单操作按钮 - 仅在有onSubmit回调时显示 */} + {onSubmit && ( + + {onCancel && ( + + )} + + + )} ); } diff --git a/src/pages/knowledge/create.tsx b/src/pages/knowledge/create.tsx index 3c2469c..f6cf8f4 100644 --- a/src/pages/knowledge/create.tsx +++ b/src/pages/knowledge/create.tsx @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useForm, type UseFormReturn } from 'react-hook-form'; +import { useForm, FormProvider, useWatch, Form } from 'react-hook-form'; import { Box, Typography, @@ -9,46 +9,29 @@ import { Button, Alert, Divider, - CircularProgress, Stepper, Step, StepLabel, } from '@mui/material'; import { ArrowBack as ArrowBackIcon, - Settings as SettingsIcon, CheckCircle as CheckCircleIcon, SkipNext as SkipNextIcon, } from '@mui/icons-material'; -import { useKnowledgeOperations } from '@/hooks/knowledge-hooks'; +import { useKnowledgeOperations, useKnowledgeDetail } from '@/hooks/knowledge-hooks'; import GeneralForm from './components/GeneralForm'; import ChunkMethodForm from './components/ChunkMethodForm'; +import { useSnackbar } from '@/components/Provider/SnackbarProvider'; +import { DOCUMENT_PARSER_TYPES } from '@/constants/knowledge'; -// 基础信息表单数据 -interface BasicFormData { +// 统一表单数据类型 +interface BaseFormData { name: string; description: string; permission: string; avatar?: string; } -// 配置表单数据 -interface ConfigFormData { - parser_id: string; - embd_id?: string; - chunk_token_num?: number; - layout_recognize?: string; - delimiter?: string; - auto_keywords?: number; - auto_questions?: number; - html4excel?: boolean; - topn_tags?: number; - use_raptor?: boolean; - use_graphrag?: boolean; - graphrag_method?: string; - pagerank?: number; -} - const steps = ['基础信息', '配置设置']; function KnowledgeBaseCreate() { @@ -57,85 +40,119 @@ function KnowledgeBaseCreate() { const [createdKbId, setCreatedKbId] = useState(null); // 使用知识库操作 hooks - const { + const { loading: isSubmitting, - error, - createKnowledge, + error, + createKnowledge, updateKnowledgeModelConfig, - clearError + clearError } = useKnowledgeOperations(); - // 基础信息表单 - const basicForm = useForm({ + const { showMessage } = useSnackbar(); + + // 获取知识库详情的hook + const { knowledge: knowledgeDetail, refresh: refreshKnowledgeDetail } = useKnowledgeDetail(createdKbId || ''); + + // 统一表单管理 + const form = useForm({ defaultValues: { name: '', description: '', permission: 'me', - avatar: '', - }, - }); - - // 配置表单 - const configForm = useForm({ - defaultValues: { - parser_id: 'naive', + avatar: undefined, + parser_id: DOCUMENT_PARSER_TYPES.Naive, embd_id: 'text-embedding-v3@Tongyi-Qianwen', - chunk_token_num: 512, - layout_recognize: 'DeepDOC', - delimiter: '\n', - auto_keywords: 0, - auto_questions: 0, - html4excel: false, - topn_tags: 10, - use_raptor: false, - use_graphrag: false, - graphrag_method: 'light', - pagerank: 0.3, + parser_config: { + chunk_token_num: 512, + delimiter: '\n', + auto_keywords: 0, + auto_questions: 0, + html4excel: false, + topn_tags: 10, + raptor: { + use_raptor: false, + prompt: '请总结以下段落。小心数字,不要编造。段落如下:\n {cluster_content}\n以上就是你需要总结的内容。', + max_token: 256, + threshold: 0.1, + max_cluster: 64, + random_seed: 0, + }, + graphrag: { + use_graphrag: false, + entity_types: ['organization', 'person', 'geo', 'event', 'category'], + method: 'light', + }, + }, }, }); - // 处理基础信息提交 - const handleBasicSubmit = async (data: BasicFormData) => { + // 处理表单提交 + const handleSubmit = async ({ data }: { data: any }) => { clearError(); + console.log('提交数据:', data); try { - const result = await createKnowledge(data); - setCreatedKbId(result.kb_id); - setActiveStep(1); + if (activeStep === 0) { + // 第一步:创建知识库基础信息 + const basicData = { + name: data.name, + description: data.description, + permission: data.permission, + avatar: data.avatar, + }; + + const result = await createKnowledge(basicData); + setCreatedKbId(result.kb_id); + + // 获取创建后的知识库详情,确保获取到正确的名称 + setTimeout(async () => { + await refreshKnowledgeDetail(); + }, 500); + + setActiveStep(1); + showMessage.success('知识库创建成功,请配置解析设置'); + } else { + // 第二步:配置知识库解析设置 + if (!createdKbId) return; + + // 使用知识库详情中的正确名称,如果没有则使用表单中的名称 + const correctName = knowledgeDetail?.name || data.name; + + const configData = { + kb_id: createdKbId, + name: correctName, // 使用正确的名称 + description: data.description, + permission: data.permission, + avatar: data.avatar, + parser_id: data.parser_id, + embd_id: data.embd_id, + parser_config: data.parser_config, + }; + + await updateKnowledgeModelConfig(configData); + showMessage.success('知识库配置完成'); + navigate('/knowledge'); + } } catch (err) { - console.error('创建知识库失败:', err); + console.error('操作失败:', err); + showMessage.error(activeStep === 0 ? '创建知识库失败' : '配置知识库失败'); } }; - // 处理配置提交 - const handleConfigSubmit = async (data: ConfigFormData) => { - if (!createdKbId) return; - - clearError(); - + // 跳过配置,直接完成创建 + const handleSkipConfig = async () => { try { - await updateKnowledgeModelConfig({ - id: createdKbId, - ...data, - }); + showMessage.success('知识库创建完成,您可以稍后在设置页面配置解析参数'); navigate('/knowledge'); } catch (err) { - console.error('配置知识库失败:', err); + console.error('跳过配置失败:', err); + showMessage.error('操作失败'); } }; - // 跳过配置 - const handleSkipConfig = () => { - navigate('/knowledge'); - }; - - // 返回上一步 - const handleBack = () => { - setActiveStep(0); - }; return ( - - {/* 页面标题 */} + + {/* 页面头部 */} - - - )} - - + {/* 操作按钮 */} + + + {activeStep === 0 && ( + + )} + + {/* 第二步显示跳过配置按钮 */} + {activeStep === 1 && ( + + )} + + + + + + + + ); }