diff --git a/src/components/FileUploadDialog.tsx b/src/components/FileUploadDialog.tsx index dc8e821..ef6df20 100644 --- a/src/components/FileUploadDialog.tsx +++ b/src/components/FileUploadDialog.tsx @@ -21,6 +21,7 @@ import { InsertDriveFile as FileIcon, Delete as DeleteIcon, } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; interface FileUploadDialogProps { open: boolean; @@ -46,24 +47,27 @@ const FileUploadDialog: React.FC = ({ acceptedFileTypes = ['.pdf', '.docx', '.txt', '.md', '.png', '.jpg', '.jpeg', '.mp4', '.wav'], maxFileSize = 100, // 100MB maxFiles = 10, - title = '上传文件', + title, }) => { + const { t } = useTranslation(); const [files, setFiles] = useState([]); const [isDragOver, setIsDragOver] = useState(false); const [uploading, setUploading] = useState(false); const [error, setError] = useState(null); const fileInputRef = useRef(null); + const dialogTitle = title || t('fileUpload.uploadFiles'); + const validateFile = (file: File): string | null => { // 检查文件大小 if (file.size > maxFileSize * 1024 * 1024) { - return `文件大小不能超过 ${maxFileSize}MB`; + return t('fileUpload.fileSizeExceeded', { maxSize: maxFileSize }); } // 检查文件类型 const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase(); if (!acceptedFileTypes.includes(fileExtension)) { - return `不支持的文件类型: ${fileExtension}`; + return t('fileUpload.unsupportedFileType', { extension: fileExtension }); } return null; @@ -76,7 +80,7 @@ const FileUploadDialog: React.FC = ({ // 检查文件数量限制 if (files.length + fileArray.length > maxFiles) { - setError(`最多只能上传 ${maxFiles} 个文件`); + setError(t('fileUpload.maxFilesExceeded', { maxFiles })); return; } @@ -91,7 +95,7 @@ const FileUploadDialog: React.FC = ({ // 检查是否已存在同名文件 const isDuplicate = files.some(f => f.file.name === file.name); if (isDuplicate) { - setError(`文件 "${file.name}" 已存在`); + setError(t('fileUpload.duplicateFile', { fileName: file.name })); hasError = true; return; } @@ -162,7 +166,7 @@ const FileUploadDialog: React.FC = ({ setFiles([]); onClose(); } catch (err) { - setError(err instanceof Error ? err.message : '上传失败'); + setError(err instanceof Error ? err.message : t('fileUpload.uploadFailed')); } finally { setUploading(false); } @@ -186,7 +190,7 @@ const FileUploadDialog: React.FC = ({ return ( - {title} + {dialogTitle} {error && ( @@ -217,13 +221,13 @@ const FileUploadDialog: React.FC = ({ > - {isDragOver ? '释放文件到此处' : '拖拽文件到此处或点击上传'} + {isDragOver ? t('fileUpload.dropFilesHere') : t('fileUpload.dragOrClickToUpload')} - 支持格式: {acceptedFileTypes.join(', ')} + {t('fileUpload.supportedFormats')}: {acceptedFileTypes.join(', ')} - 最大文件大小: {maxFileSize}MB,最多 {maxFiles} 个文件 + {t('fileUpload.fileLimits', { maxSize: maxFileSize, maxFiles })} @@ -240,7 +244,7 @@ const FileUploadDialog: React.FC = ({ {files.length > 0 && ( - 已选择的文件 ({files.length}/{maxFiles}) + {t('fileUpload.selectedFiles', { current: files.length, max: maxFiles })} {files.map((uploadFile) => ( @@ -270,7 +274,7 @@ const FileUploadDialog: React.FC = ({ {uploading && ( - 正在上传... + {t('fileUpload.uploading')} @@ -278,14 +282,14 @@ const FileUploadDialog: React.FC = ({ diff --git a/src/components/FormField/ChipListFormField.tsx b/src/components/FormField/ChipListFormField.tsx index c6fe612..52834b3 100644 --- a/src/components/FormField/ChipListFormField.tsx +++ b/src/components/FormField/ChipListFormField.tsx @@ -9,6 +9,7 @@ import { } from '@mui/material'; import { Add as AddIcon } from '@mui/icons-material'; import { Controller, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; export interface ChipListFormFieldProps { name: string; @@ -35,13 +36,16 @@ export const ChipListFormField: React.FC = ({ allowAdd = true, allowDelete = true, maxChips, - placeholder = '输入后按回车添加', + placeholder, chipColor = 'default', chipVariant = 'outlined', }) => { + const { t } = useTranslation(); const { control, setValue, getValues } = useFormContext(); const [inputValue, setInputValue] = useState(''); + const defaultPlaceholder = placeholder || t('form.chipListPlaceholder'); + const handleAddChip = (field: any) => { if (inputValue.trim() && (!maxChips || field.value.length < maxChips)) { const newChips = [...field.value, inputValue.trim()]; @@ -75,10 +79,10 @@ export const ChipListFormField: React.FC = ({ control={control} defaultValue={defaultValue} rules={{ - required: required ? `${label}是必填项` : false, + required: required ? t('form.fieldRequired', { field: label }) : false, validate: (value) => { if (maxChips && value.length > maxChips) { - return `${label}最多只能有${maxChips}个标签`; + return t('form.maxChipsExceeded', { field: label, max: maxChips }); } return true; }, @@ -99,7 +103,7 @@ export const ChipListFormField: React.FC = ({ ))} {field.value.length === 0 && ( - 暂无标签 + {t('form.noChips')} )} @@ -111,7 +115,7 @@ export const ChipListFormField: React.FC = ({ value={inputValue} onChange={(e) => setInputValue(e.target.value)} onKeyPress={(e) => handleKeyPress(e, field)} - placeholder={placeholder} + placeholder={defaultPlaceholder} size="small" fullWidth disabled={disabled} diff --git a/src/components/FormField/MultilineTextFormField.tsx b/src/components/FormField/MultilineTextFormField.tsx index e9b5276..8e9544e 100644 --- a/src/components/FormField/MultilineTextFormField.tsx +++ b/src/components/FormField/MultilineTextFormField.tsx @@ -5,6 +5,7 @@ import { TextField, } from '@mui/material'; import { Controller, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; export interface MultilineTextFormFieldProps { name: string; @@ -38,6 +39,7 @@ export const MultilineTextFormField: React.FC = ({ maxLength, }) => { const { control } = useFormContext(); + const { t } = useTranslation(); return ( @@ -50,10 +52,10 @@ export const MultilineTextFormField: React.FC = ({ control={control} defaultValue={defaultValue} rules={{ - required: required ? `${label}是必填项` : false, + required: required ? t('form.fieldRequired', { field: label }) : false, maxLength: maxLength ? { value: maxLength, - message: `${label}不能超过${maxLength}个字符` + message: t('form.maxLengthExceeded', { field: label, maxLength }) } : undefined, }} render={({ field, fieldState: { error } }) => ( diff --git a/src/components/FormField/NumberInputFormField.tsx b/src/components/FormField/NumberInputFormField.tsx index 7841400..19b4689 100644 --- a/src/components/FormField/NumberInputFormField.tsx +++ b/src/components/FormField/NumberInputFormField.tsx @@ -7,6 +7,7 @@ import { } from '@mui/material'; import { Add as AddIcon } from '@mui/icons-material'; import { Controller, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; export interface NumberInputFormFieldProps { name: string; @@ -42,6 +43,7 @@ export const NumberInputFormField: React.FC = ({ onRandomClick, }) => { const { control } = useFormContext(); + const { t } = useTranslation(); const handleRandomClick = () => { if (onRandomClick) { @@ -64,14 +66,14 @@ export const NumberInputFormField: React.FC = ({ control={control} defaultValue={defaultValue} rules={{ - required: required ? `${label}是必填项` : false, + required: required ? t('form.fieldRequired', { field: label }) : false, min: min !== undefined ? { value: min, - message: `${label}不能小于${min}` + message: t('form.minValueExceeded', { field: label, min }) } : undefined, max: max !== undefined ? { value: max, - message: `${label}不能大于${max}` + message: t('form.maxValueExceeded', { field: label, max }) } : undefined, }} render={({ field, fieldState: { error } }) => ( diff --git a/src/components/FormField/SelectFormField.tsx b/src/components/FormField/SelectFormField.tsx index b4115ad..9c68300 100644 --- a/src/components/FormField/SelectFormField.tsx +++ b/src/components/FormField/SelectFormField.tsx @@ -9,6 +9,7 @@ import { InputLabel, } from '@mui/material'; import { Controller, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; export interface SelectOption { value: string | number; @@ -42,10 +43,13 @@ export const SelectFormField: React.FC = ({ fullWidth = true, size = 'medium', displayEmpty = true, - emptyLabel = '请选择', + emptyLabel, multiple = false, }) => { const { control } = useFormContext(); + const { t } = useTranslation(); + + const defaultEmptyLabel = emptyLabel || t('common.pleaseSelect'); return ( @@ -58,7 +62,7 @@ export const SelectFormField: React.FC = ({ control={control} defaultValue={multiple ? [] : defaultValue} rules={{ - required: required ? `${label}是必填项` : false, + required: required ? t('form.fieldRequired', { field: label }) : false, }} render={({ field, fieldState: { error } }) => ( @@ -70,7 +74,7 @@ export const SelectFormField: React.FC = ({ > {displayEmpty && !multiple && ( - {emptyLabel} + {defaultEmptyLabel} )} {options.map((option) => ( diff --git a/src/components/FormField/SwitchFormField.tsx b/src/components/FormField/SwitchFormField.tsx index 61ec977..4dd8287 100644 --- a/src/components/FormField/SwitchFormField.tsx +++ b/src/components/FormField/SwitchFormField.tsx @@ -7,6 +7,7 @@ import { FormHelperText, } from '@mui/material'; import { Controller, useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; export interface SwitchFormFieldProps { name: string; @@ -30,6 +31,7 @@ export const SwitchFormField: React.FC = ({ color = 'primary', }) => { const { control } = useFormContext(); + const { t } = useTranslation(); return ( @@ -38,7 +40,7 @@ export const SwitchFormField: React.FC = ({ control={control} defaultValue={defaultValue} rules={{ - required: required ? `${label}是必填项` : false, + required: required ? t('form.fieldRequired', { field: label }) : false, }} render={({ field, fieldState: { error } }) => ( <> diff --git a/src/components/Layout/Sidebar.tsx b/src/components/Layout/Sidebar.tsx index 059950e..a69bbd2 100644 --- a/src/components/Layout/Sidebar.tsx +++ b/src/components/Layout/Sidebar.tsx @@ -10,10 +10,10 @@ import ExtensionOutlinedIcon from '@mui/icons-material/ExtensionOutlined'; const navItems = [ // { text: 'Overview', path: '/', icon: DashboardOutlinedIcon }, { text: 'Knowledge Bases', path: '/', icon: LibraryBooksOutlinedIcon }, - { text: 'RAG Pipeline', path: '/pipeline-config', icon: AccountTreeOutlinedIcon }, - { text: 'Operations', path: '/dashboard', icon: SettingsOutlinedIcon }, - { text: 'Models & Resources', path: '/models-resources', icon: StorageOutlinedIcon }, - { text: 'MCP', path: '/mcp', icon: ExtensionOutlinedIcon }, + // { text: 'RAG Pipeline', path: '/pipeline-config', icon: AccountTreeOutlinedIcon }, + // { text: 'Operations', path: '/dashboard', icon: SettingsOutlinedIcon }, + // { text: 'Models & Resources', path: '/models-resources', icon: StorageOutlinedIcon }, + // { text: 'MCP', path: '/mcp', icon: ExtensionOutlinedIcon }, ]; const Sidebar = () => { diff --git a/src/components/Provider/DialogComponent.tsx b/src/components/Provider/DialogComponent.tsx index f517bbe..6644779 100644 --- a/src/components/Provider/DialogComponent.tsx +++ b/src/components/Provider/DialogComponent.tsx @@ -17,6 +17,7 @@ import { Error as ErrorIcon, Help as ConfirmIcon, } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; import { type IDialogInstance } from '../../interfaces/common'; interface DialogComponentProps { @@ -26,6 +27,7 @@ interface DialogComponentProps { const DialogComponent: React.FC = ({ dialog, onClose }) => { const [loading, setLoading] = useState(false); + const { t } = useTranslation(); const { config } = dialog; // 获取对话框图标 @@ -118,7 +120,7 @@ const DialogComponent: React.FC = ({ dialog, onClose }) => {getDialogIcon()} - {config.title || '提示'} + {config.title || t('dialog.defaultTitle')} = ({ dialog, onClose }) => variant="outlined" disabled={loading} > - {config.cancelText || '取消'} + {config.cancelText || t('common.cancel')} )} diff --git a/src/components/Provider/DialogProvider.tsx b/src/components/Provider/DialogProvider.tsx index 76fabf3..b0490f4 100644 --- a/src/components/Provider/DialogProvider.tsx +++ b/src/components/Provider/DialogProvider.tsx @@ -1,4 +1,5 @@ import React, { createContext, useContext, useState, useCallback, type PropsWithChildren } from 'react'; +import { useTranslation } from 'react-i18next'; import type { IDialogConfig, IDialogInstance, IDialogContextValue } from '../../interfaces/common'; import DialogComponent from './DialogComponent'; @@ -10,6 +11,7 @@ const generateId = () => `dialog_${Date.now()}_${Math.random().toString(36).subs export const DialogProvider: React.FC = ({ children }) => { const [dialogs, setDialogs] = useState([]); + const { t } = useTranslation(); // 打开对话框的通用方法 const openDialog = useCallback((config: IDialogConfig): Promise => { @@ -43,10 +45,10 @@ export const DialogProvider: React.FC = ({ children }) => { ...config, type: 'confirm', showCancel: true, - confirmText: config.confirmText || '确定', - cancelText: config.cancelText || '取消', + confirmText: config.confirmText || t('common.confirm'), + cancelText: config.cancelText || t('common.cancel'), }); - }, [openDialog]); + }, [openDialog, t]); // 信息对话框 const info = useCallback((config: Omit): Promise => { @@ -54,9 +56,9 @@ export const DialogProvider: React.FC = ({ children }) => { ...config, type: 'info', showCancel: false, - confirmText: config.confirmText || '确定', + confirmText: config.confirmText || t('common.confirm'), }); - }, [openDialog]); + }, [openDialog, t]); // 成功对话框 const success = useCallback((config: Omit): Promise => { @@ -64,9 +66,9 @@ export const DialogProvider: React.FC = ({ children }) => { ...config, type: 'success', showCancel: false, - confirmText: config.confirmText || '确定', + confirmText: config.confirmText || t('common.confirm'), }); - }, [openDialog]); + }, [openDialog, t]); // 警告对话框 const warning = useCallback((config: Omit): Promise => { @@ -74,9 +76,9 @@ export const DialogProvider: React.FC = ({ children }) => { ...config, type: 'warning', showCancel: false, - confirmText: config.confirmText || '确定', + confirmText: config.confirmText || t('common.confirm'), }); - }, [openDialog]); + }, [openDialog, t]); // 错误对话框 const error = useCallback((config: Omit): Promise => { @@ -84,9 +86,9 @@ export const DialogProvider: React.FC = ({ children }) => { ...config, type: 'error', showCancel: false, - confirmText: config.confirmText || '确定', + confirmText: config.confirmText || t('common.confirm'), }); - }, [openDialog]); + }, [openDialog, t]); const contextValue: IDialogContextValue = { dialogs, diff --git a/src/components/knowledge/KnowledgeCard.tsx b/src/components/knowledge/KnowledgeCard.tsx index 95900cb..75ddc87 100644 --- a/src/components/knowledge/KnowledgeCard.tsx +++ b/src/components/knowledge/KnowledgeCard.tsx @@ -18,6 +18,7 @@ import { ArrowForward as ArrowForwardIcon, Add as AddIcon, } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; import type { IKnowledge } from '@/interfaces/database/knowledge'; interface KnowledgeCardProps { @@ -27,14 +28,16 @@ interface KnowledgeCardProps { } const KnowledgeCard: React.FC = ({ knowledge, onMenuClick, onViewKnowledge }) => { + const { t } = useTranslation(); + const getStatusInfo = (permission: string) => { switch (permission) { case 'me': - return { label: '私有', color: '#E3F2FD', textColor: '#1976D2' }; + return { label: t('common.private'), color: '#E3F2FD', textColor: '#1976D2' }; case 'team': - return { label: '团队', color: '#E8F5E8', textColor: '#388E3C' }; + return { label: t('common.team'), color: '#E8F5E8', textColor: '#388E3C' }; default: - return { label: '公开', color: '#FFF3E0', textColor: '#F57C00' }; + return { label: t('common.public'), color: '#FFF3E0', textColor: '#F57C00' }; } }; @@ -42,7 +45,7 @@ const KnowledgeCard: React.FC = ({ knowledge, onMenuClick, o // 格式化更新时间 const formatUpdateTime = (timestamp: number) => { - if (!timestamp) return '未知'; + if (!timestamp) return t('common.unknown'); const date = new Date(timestamp); return date.toLocaleDateString('zh-CN', { year: 'numeric', @@ -123,7 +126,7 @@ const KnowledgeCard: React.FC = ({ knowledge, onMenuClick, o overflow: 'hidden', }} > - {knowledge.description || '暂无描述'} + {knowledge.description || t('common.noDescription')} = ({ knowledge, onMenuClick, o {knowledge.doc_num || 0} - 文档数量 + {t('knowledge.documentCount')} @@ -155,7 +158,7 @@ const KnowledgeCard: React.FC = ({ knowledge, onMenuClick, o {knowledge.chunk_num || 0} - 分块数量 + {t('knowledge.chunkCount')} @@ -166,27 +169,27 @@ const KnowledgeCard: React.FC = ({ knowledge, onMenuClick, o {formatTokenNum(knowledge.token_num || 0)} - Token数量 + {t('knowledge.tokenCount')} {/* 显示更新时间 */} - 最后更新: {formatUpdateTime(knowledge.update_time)} + {t('knowledge.lastUpdate')}: {formatUpdateTime(knowledge.update_time)} {/* 显示创建者 */} {knowledge.nickname && ( - 创建者: {knowledge.nickname} + {t('knowledge.creator')}: {knowledge.nickname} )} {/* 显示语言 */} {knowledge.language && ( - 语言: {knowledge.language} + {t('common.language')}: {knowledge.language} )} diff --git a/src/components/knowledge/KnowledgeGridView.tsx b/src/components/knowledge/KnowledgeGridView.tsx index 89aee1e..638aca2 100644 --- a/src/components/knowledge/KnowledgeGridView.tsx +++ b/src/components/knowledge/KnowledgeGridView.tsx @@ -18,6 +18,7 @@ import { ArrowForward as ArrowForwardIcon, Add as AddIcon, } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; import type { IKnowledge } from '@/interfaces/database/knowledge'; import KnowledgeCard from './KnowledgeCard'; @@ -50,6 +51,7 @@ const KnowledgeGridView: React.FC = ({ teamFilter = 'all', onCreateKnowledge, }) => { + const { t } = useTranslation(); const [anchorEl, setAnchorEl] = React.useState(null); const [selectedKB, setSelectedKB] = React.useState(null); @@ -90,7 +92,7 @@ const KnowledgeGridView: React.FC = ({ if (loading) { return ( - 加载中... + {t('common.loading')} ); } @@ -100,12 +102,12 @@ const KnowledgeGridView: React.FC = ({ - {searchTerm || teamFilter !== 'all' ? '没有找到匹配的知识库' : '暂无知识库'} + {searchTerm || teamFilter !== 'all' ? t('knowledge.noMatchingKnowledgeBases') : t('knowledge.noKnowledgeBases')} {searchTerm || teamFilter !== 'all' - ? '尝试调整搜索条件或筛选器' - : '创建您的第一个知识库开始使用' + ? t('knowledge.tryAdjustingFilters') + : t('knowledge.createFirstKnowledgeBase') } {(!searchTerm && teamFilter === 'all' && onCreateKnowledge) && ( @@ -114,7 +116,7 @@ const KnowledgeGridView: React.FC = ({ startIcon={} onClick={onCreateKnowledge} > - 新建知识库 + {t('knowledge.createKnowledgeBase')} )} @@ -139,7 +141,7 @@ const KnowledgeGridView: React.FC = ({ onClick={onSeeAll} sx={{ borderRadius: 2 }} > - 查看全部 ({knowledgeBases.length}) + {t('common.viewAll')} ({knowledgeBases.length}) )} @@ -149,9 +151,9 @@ const KnowledgeGridView: React.FC = ({ open={Boolean(anchorEl)} onClose={handleMenuClose} > - 查看详情 + {t('common.viewDetails')} - 删除 + {t('common.delete')} diff --git a/src/hooks/knowledge-hooks.ts b/src/hooks/knowledge-hooks.ts index 01e6c2d..324b231 100644 --- a/src/hooks/knowledge-hooks.ts +++ b/src/hooks/knowledge-hooks.ts @@ -1,4 +1,5 @@ import { useState, useEffect, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; import knowledgeService from '@/services/knowledge_service'; import type { IKnowledge, IKnowledgeGraph, IKnowledgeResult } from '@/interfaces/database/knowledge'; import type { IFetchKnowledgeListRequestBody, IFetchKnowledgeListRequestParams } from '@/interfaces/request/knowledge'; @@ -80,6 +81,7 @@ export interface UseKnowledgeListReturn extends UseKnowledgeListState { export const useKnowledgeList = ( initialParams?: IFetchKnowledgeListRequestParams & IFetchKnowledgeListRequestBody ): UseKnowledgeListReturn => { + const { t } = useTranslation(); const [knowledgeBases, setKnowledgeBases] = useState([]); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); @@ -137,10 +139,10 @@ export const useKnowledgeList = ( setKnowledgeBases(data.kbs || []); setTotal(data.total || 0); } else { - throw new Error(response.data.message || '获取知识库列表失败'); + throw new Error(response.data.message || t('knowledgeHooks.fetchKnowledgeListFailed')); } } catch (err: any) { - const errorMessage = err.response?.data?.message || err.message || '获取知识库列表失败'; + const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.fetchKnowledgeListFailed'); setError(errorMessage); console.error('Failed to fetch knowledge bases:', err); } finally { @@ -210,6 +212,7 @@ export const useKnowledgeList = ( * 知识库详情Hook */ export const useKnowledgeDetail = (kbId: string) => { + const { t } = useTranslation(); const [knowledge, setKnowledge] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -232,10 +235,10 @@ export const useKnowledgeDetail = (kbId: string) => { if (response.data.code === 0) { setKnowledge(response.data.data); } else { - throw new Error(response.data.message || '获取知识库详情失败'); + throw new Error(response.data.message || t('knowledgeHooks.fetchKnowledgeDetailFailed')); } } catch (err: any) { - const errorMessage = err.response?.data?.message || err.message || '获取知识库详情失败'; + const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.fetchKnowledgeDetailFailed'); setError(errorMessage); console.error('Failed to fetch knowledge detail:', err); } finally { @@ -255,10 +258,10 @@ export const useKnowledgeDetail = (kbId: string) => { if (response.data.code === 0) { setKnowledgeGraph(response.data.data); } else { - throw new Error(response.data.message || '获取知识库图失败'); + throw new Error(response.data.message || t('knowledgeHooks.fetchKnowledgeGraphFailed')); } } catch (err: any) { - const errorMessage = err.response?.data?.message || err.message || '获取知识库图失败'; + const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.fetchKnowledgeGraphFailed'); setError(errorMessage); console.error('Failed to fetch knowledge graph:', err); } finally { @@ -286,6 +289,7 @@ export const useKnowledgeDetail = (kbId: string) => { * 提供创建、更新、删除知识库的功能 */ export const useKnowledgeOperations = () => { + const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -302,10 +306,10 @@ export const useKnowledgeOperations = () => { if (response.data.code === 0) { return response.data.data; } else { - throw new Error(response.data.message || '创建知识库失败'); + throw new Error(response.data.message || t('knowledgeHooks.createKnowledgeFailed')); } } catch (err: any) { - const errorMessage = err.response?.data?.message || err.message || '创建知识库失败'; + const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.createKnowledgeFailed'); setError(errorMessage); console.error('Failed to create knowledge:', err); throw err; @@ -330,10 +334,10 @@ export const useKnowledgeOperations = () => { if (response.data.code === 0) { return response.data.data; } else { - throw new Error(response.data.message || '更新知识库基础信息失败'); + throw new Error(response.data.message || t('knowledgeHooks.updateKnowledgeBasicInfoFailed')); } } catch (err: any) { - const errorMessage = err.response?.data?.message || err.message || '更新知识库基础信息失败'; + const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.updateKnowledgeBasicInfoFailed'); setError(errorMessage); console.error('Failed to update knowledge basic info:', err); throw err; @@ -362,10 +366,10 @@ export const useKnowledgeOperations = () => { if (response.data.code === 0) { return response.data.data; } else { - throw new Error(response.data.message || '更新知识库模型配置失败'); + throw new Error(response.data.message || t('knowledgeHooks.updateKnowledgeModelConfigFailed')); } } catch (err: any) { - const errorMessage = err.response?.data?.message || err.message || '更新知识库模型配置失败'; + const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.updateKnowledgeModelConfigFailed'); setError(errorMessage); console.error('Failed to update knowledge model config:', err); throw err; @@ -387,10 +391,10 @@ export const useKnowledgeOperations = () => { if (response.data.code === 0) { return response.data.data; } else { - throw new Error(response.data.message || '删除知识库失败'); + throw new Error(response.data.message || t('knowledgeHooks.deleteKnowledgeFailed')); } } catch (err: any) { - const errorMessage = err.response?.data?.message || err.message || '删除知识库失败'; + const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.deleteKnowledgeFailed'); setError(errorMessage); console.error('Failed to delete knowledge:', err); throw err; @@ -422,6 +426,7 @@ export const useKnowledgeOperations = () => { * 提供批量删除等功能 */ export const useKnowledgeBatchOperations = () => { + const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); @@ -443,12 +448,12 @@ export const useKnowledgeBatchOperations = () => { .map(({ index }) => kbIds[index]); if (failures.length > 0) { - throw new Error(`删除失败的知识库: ${failures.join(', ')}`); + throw new Error(`${t('knowledgeHooks.batchDeleteFailedKnowledgeBases')}: ${failures.join(', ')}`); } return results; } catch (err: any) { - const errorMessage = err.response?.data?.message || err.message || '批量删除知识库失败'; + const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.batchDeleteKnowledgeFailed'); setError(errorMessage); console.error('Failed to batch delete knowledge:', err); throw err; diff --git a/src/locales/en.ts b/src/locales/en.ts index ac74b90..a1c9fae 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -1,6 +1,9 @@ export default { translation: { + common: { + back: 'Back', + operationFailed: 'Operation failed', noResults: 'No results.', selectPlaceholder: 'select value', selectAll: 'Select All', @@ -26,12 +29,13 @@ export default { traditionalChinese: 'Traditional Chinese', language: 'Language', languageMessage: 'Please input your language!', + loading: 'Loading...', + close: 'Close', languagePlaceholder: 'select your language', copy: 'Copy', copied: 'Copied', comingSoon: 'Coming soon', download: 'Download', - close: 'Close', preview: 'Preview', move: 'Move', warn: 'Warn', @@ -50,6 +54,24 @@ export default { noDataFound: 'No data found.', noData: 'No data', promptPlaceholder: `Please input or use / to quickly insert variables.`, + all: 'All', + refresh: 'Refresh', + submitting: 'Submitting...', + description: 'Description', + confirm: 'Confirm', + enabled: 'Enabled', + clearFilter: 'Clear Filter', + confirmFilter: 'Confirm Filter', + private: 'Private', + moreActions: 'More Actions', + disable: 'Disable', + enable: 'Enable', + team: 'Team', + public: 'Public', + unknown: 'Unknown', + noDescription: 'No description', + viewAll: 'View All', + viewDetails: 'View Details', mcp: { namePlaceholder: 'My MCP Server', nameRequired: @@ -58,6 +80,172 @@ export default { tokenPlaceholder: 'e.g. eyJhbGciOiJIUzI1Ni...', }, }, + + knowledgeSettings: { + submitData: 'Submit data:', + basicInfoUpdateSuccess: 'Basic information updated successfully', + parseConfigUpdateSuccess: 'Parse configuration updated successfully', + updateFailed: '{{type}} update failed', + basicInfo: 'Basic Information', + parseConfig: 'Parse Configuration', + knowledgeBase: 'Knowledge Base', + knowledgeBaseDetail: 'Knowledge Base Detail', + settings: 'Settings', + knowledgeBaseSettings: 'Knowledge Base Settings', + settingsTabs: 'Settings Tabs', + backToKnowledgeDetail: 'Back to Knowledge Base Detail', + }, + + form: { + fieldRequired: '{{field}} is required', + minValueExceeded: '{{field}} cannot be less than {{min}}', + maxValueExceeded: '{{field}} cannot be greater than {{max}}', + configurationError: 'Form configuration error', + chipListPlaceholder: 'Type and press Enter to add', + maxChipsExceeded: '{{field}} can have at most {{max}} tags', + noChips: 'No tags', + formConfigError: 'Form configuration error', + }, + + knowledge: { + basicInfo: 'Basic Information', + uploadAvatar: 'Upload Avatar', + nameRequired: 'Knowledge base name cannot be empty', + knowledgeBaseName: 'Knowledge Base Name', + descriptionPlaceholder: 'Please enter knowledge base description...', + permissionSettings: 'Permission Settings', + onlyMe: 'Only Me', + teamMembers: 'Team Members', + retrievalTest: 'Retrieval Test', + configSettings: 'Configuration Settings', + knowledgeBaseActions: 'Knowledge Base Actions', + filter: 'Filter', + runStatus: { + unstart: 'Not Started', + running: 'Running', + cancel: 'Cancelled', + done: 'Done', + fail: 'Failed', + unknown: 'Unknown', + parsing: 'Parsing' + }, + runStatusFilter: 'Run Status', + fileName: 'File Name', + actions: 'Actions', + fileType: 'File Type', + fileTypeFilter: 'File Type', + searchFiles: 'Search Files', + uploadFile: 'Upload File', + fileCount: 'File Count', + chunkCount: 'Chunk Count', + tokenCount: 'Token Count', + size: 'Size', + createTime: 'Create Time', + updateTime: 'Update Time', + language: 'Language', + permission: 'Permission', + embeddingModel: 'Embedding Model', + parser: 'Parser', + type: 'Type', + status: 'Status', + parseStatus: 'Parse Status', + uploadTime: 'Upload Time', + documentCount: 'Document Count', + lastUpdate: 'Last Update', + creator: 'Creator', + testPrompt: 'Test Prompt', + testResultOverview: 'Test Result Overview', + matchedChunks: 'Matched Chunks', + relatedDocuments: 'Related Documents', + returnedChunks: 'Returned Chunks', + documentFilter: 'Document Filter', + selectDocuments: 'Select Documents', + noContent: 'No content', + keywords: 'Keywords', + relatedDocumentStats: 'Related Document Statistics', + selectParserMethod: 'Select Parser Method', + unknown: 'Unknown', + description: 'Description', + pageRank: 'Page Rank', + edge: 'Edge', + missingIds: 'missing IDs', + nodeNotExists: 'node not exists', + noGraphData: 'No graph data available', + legend: 'Legend', + graphStats: 'Graph Statistics', + reparse: 'Reparse', + cancelRun: 'Cancel Run', + renameFile: 'Rename File', + viewDetails: 'View Details', + viewFileDetails: 'View File Details', + matchedChunksCount: 'Matched Chunks Count', + matchedChunksTitle: 'Matched Chunks Title', + totalMatchedChunks: 'Total Matched Chunks', + similarity: 'Similarity', + vectorSimilarity: 'Vector Similarity', + termSimilarity: 'Term Similarity', + nodeCount: 'Node Count', + edgeCount: 'Edge Count', + noMatchingKnowledgeBases: 'No matching knowledge bases', + noKnowledgeBases: 'No knowledge bases', + tryAdjustingFilters: 'Try adjusting your filters', + createFirstKnowledgeBase: 'Create your first knowledge base', + createKnowledgeBase: 'Create Knowledge Base', + noDescription: 'No description', + config: { + basicConfig: 'Basic Configuration', + pageRankAndAutoExtract: 'PageRank and Auto Extract', + raptorStrategy: 'RAPTOR Strategy', + knowledgeGraph: 'Knowledge Graph', + chunkMethod: 'Chunk Method', + selectChunkMethod: 'Select chunk method', + pageRank: 'Page Rank', + enterPageRank: 'Enter page rank', + autoKeywords: 'Auto Keywords Extraction', + enterKeywordCount: 'Enter keyword count', + autoQuestions: 'Auto Questions Extraction', + enterQuestionCount: 'Enter question count', + pdfParser: 'PDF Parser', + plainText: 'Plain Text', + experimental: 'Experimental', + delimiter: 'Delimiter', + enterDelimiter: 'Enter delimiter', + embeddingModel: 'Embedding Model', + pageRankConfigTodo: 'PageRank Configuration - To be implemented', + entityTypeConfigTodo: 'Entity Type Configuration - To be implemented', + maxTokenConfigTodo: 'Max Token Configuration (Max: 16384) - To be implemented', + delimiterConfigTodo: 'Delimiter Configuration - To be implemented', + chunkTokenSize: 'Suggested chunk token size', + htmlForExcel: 'Table to HTML', + tags: 'Tags', + useRaptorStrategy: 'Use Retrieval-Augmented RAPTOR Strategy', + prompt: 'Prompt', + raptorPromptDefault: 'Please summarize the following paragraphs. Be careful with numbers and do not make up information. Paragraphs:\n{cluster_content}\nThe above is the content you need to summarize.', + maxTokens: 'Max tokens', + threshold: 'Threshold', + maxClusterCount: 'Max cluster count', + randomSeed: 'Random seed', + extractKnowledgeGraph: 'Extract knowledge graph', + entityTypes: '*Entity types', + method: 'Method', + entityNormalization: 'Entity normalization', + communityReportGeneration: 'Community report generation', + parser: { + general: 'General Parser', + qa: 'Q&A Parser', + resume: 'Resume Parser', + manual: 'Manual Parser', + table: 'Table Parser', + paper: 'Paper Parser', + book: 'Book Parser', + laws: 'Laws Parser', + presentation: 'Presentation Parser', + one: 'One Parser', + tag: 'Tag Parser' + } + } + }, + login: { login: 'Sign in', signUp: 'Sign up', @@ -80,7 +268,7 @@ export default { review: 'from 500+ reviews', }, header: { - knowledgeBase: 'Dataset', + knowledgeBase: 'Knowledge Bases', chat: 'Chat', register: 'Register', signin: 'Sign in', @@ -91,17 +279,136 @@ export default { flow: 'Agent', search: 'Search', welcome: 'Welcome to', - dataset: 'Dataset', + }, + knowledgeHooks: { + fetchKnowledgeListFailed: 'Failed to fetch knowledge base list', + fetchKnowledgeDetailFailed: 'Failed to fetch knowledge base details', + fetchKnowledgeGraphFailed: 'Failed to fetch knowledge graph', + createKnowledgeFailed: 'Failed to create knowledge base', + updateKnowledgeBasicInfoFailed: 'Failed to update knowledge base basic information', + updateKnowledgeModelConfigFailed: 'Failed to update knowledge base model configuration', + deleteKnowledgeFailed: 'Failed to delete knowledge base', + batchDeleteKnowledgeFailed: 'Failed to batch delete knowledge bases', + batchDeleteFailedKnowledgeBases: 'Failed to delete knowledge bases', + }, + knowledgeTesting: { + retrievalTestComplete: 'Retrieval test completed', + retrievalTestFailed: 'Retrieval test failed', + paginationRequestFailed: 'Pagination request failed', + knowledgeBase: 'Knowledge Base', + knowledgeBaseDetail: 'Knowledge Base Detail', + testing: 'Testing', + knowledgeBaseTesting: 'Knowledge Base Testing', + testConfiguration: 'Test Configuration', + testQuestion: 'Test Question', + pleaseEnterTestQuestion: 'Please enter test question', + testQuestionPlaceholder: 'Please enter the question you want to test...', + similarityThreshold: 'Similarity Threshold', + vectorSimilarityWeight: 'Vector Similarity Weight', + rerankModel: 'Rerank Model (Optional)', + noRerank: 'No reranking', + pleaseEnterResultCount: 'Please enter result count', + minValue1: 'Minimum value is 1', + maxValue2048: 'Maximum value is 2048', + useWithRerankModel: 'Use with Rerank model', + crossLanguageSearch: 'Cross-language Search', + useKnowledgeGraph: 'Use Knowledge Graph', + startTest: 'Start Test', + languages: { + english: 'English', + chinese: 'Chinese', + japanese: 'Japanese', + korean: 'Korean', + french: 'French', + german: 'German', + spanish: 'Spanish', + italian: 'Italian', + portuguese: 'Portuguese', + russian: 'Russian', + arabic: 'Arabic', + hindi: 'Hindi', + thai: 'Thai', + vietnamese: 'Vietnamese', + indonesian: 'Indonesian', + malay: 'Malay', + filipino: 'Filipino', + turkish: 'Turkish', + polish: 'Polish', + dutch: 'Dutch', + swedish: 'Swedish', + danish: 'Danish', + norwegian: 'Norwegian', + finnish: 'Finnish', + hebrew: 'Hebrew', + czech: 'Czech', + slovak: 'Slovak', + hungarian: 'Hungarian', + romanian: 'Romanian', + bulgarian: 'Bulgarian', + croatian: 'Croatian', + serbian: 'Serbian', + slovenian: 'Slovenian', + estonian: 'Estonian', + latvian: 'Latvian', + lithuanian: 'Lithuanian', + maltese: 'Maltese', + irish: 'Irish', + welsh: 'Welsh', + basque: 'Basque', + catalan: 'Catalan', + galician: 'Galician', + icelandic: 'Icelandic', + macedonian: 'Macedonian', + albanian: 'Albanian', + belarusian: 'Belarusian', + ukrainian: 'Ukrainian', + kazakh: 'Kazakh', + kyrgyz: 'Kyrgyz', + uzbek: 'Uzbek', + tajik: 'Tajik', + mongolian: 'Mongolian', + georgian: 'Georgian', + armenian: 'Armenian', + azerbaijani: 'Azerbaijani', + persian: 'Persian', + urdu: 'Urdu', + bengali: 'Bengali', + tamil: 'Tamil', + telugu: 'Telugu', + malayalam: 'Malayalam', + kannada: 'Kannada', + gujarati: 'Gujarati', + punjabi: 'Punjabi', + odia: 'Odia', + assamese: 'Assamese', + nepali: 'Nepali', + sinhala: 'Sinhala', + burmese: 'Burmese', + khmer: 'Khmer', + lao: 'Lao', + swahili: 'Swahili', + amharic: 'Amharic', + yoruba: 'Yoruba', + igbo: 'Igbo', + hausa: 'Hausa', + zulu: 'Zulu', + xhosa: 'Xhosa', + afrikaans: 'Afrikaans', + } }, knowledgeList: { welcome: 'Welcome back', description: 'Which knowledge bases will you use today?', - createKnowledgeBase: 'Create Dataset', + createKnowledgeBase: 'Create Knowledge Base', name: 'Name', namePlaceholder: 'Please input name!', doc: 'Docs', searchKnowledgePlaceholder: 'Search', noMoreData: `That's all. Nothing more.`, + confirmDeleteKnowledge: 'Are you sure to delete knowledge base', + teamFilter: 'Team Filter', + loadError: 'Failed to load knowledge base list', + paginationInfo: 'Total {{total}} knowledge bases, page {{current}} of {{totalPages}}', }, knowledgeDetails: { fileSize: 'File Size', @@ -151,6 +458,36 @@ export default { knowledgeGraph: 'Knowledge Graph', name: 'Name', namePlaceholder: 'Please input name!', + // 新增的字段 + deleteFileFailed: 'Delete file failed', + uploadFiles: 'Upload files:', + uploadFileFailed: 'Upload file failed', + reparseFailed: 'Reparse failed', + renameFailed: 'Rename failed', + changeStatusFailed: 'Change status failed', + viewDetails: 'View Details', + viewProcessDetails: 'View Process Details', + knowledgeBase: 'Knowledge Base', + knowledgeBaseDetail: 'Knowledge Base Detail', + documents: 'Documents', + graph: 'Graph', + deleteFiles: 'Delete files:', + newSelectionModel: 'New selection model:', + uploadFilesToKnowledge: 'Upload files to knowledge base', + confirmDelete: 'Confirm Delete', + confirmDeleteMessage: 'Are you sure you want to delete the selected {{count}} files? This action cannot be undone.', + documentProcessDetails: 'Document Process Details', + basicInfo: 'Basic Information', + parserId: 'Parser ID', + notSpecified: 'Not specified', + processStatus: 'Process Status', + startTime: 'Start Time', + notStarted: 'Not started', + processingTime: 'Processing Time', + notCompleted: 'Not completed', + progress: 'Progress', + processDetails: 'Process Details', + seconds: 'seconds', doc: 'Docs', datasetDescription: 'Please wait for your files to finish parsing before starting an AI-powered chat.', @@ -271,6 +608,20 @@ export default { reRankModelWaring: 'Re-rank model is very time consuming.', }, knowledgeConfiguration: { + basicInfo: 'Basic Information', + configSettings: 'Configuration Settings', + createSuccess: 'Knowledge base created successfully, please configure parsing settings', + configComplete: 'Knowledge base configuration completed', + createFailed: 'Failed to create knowledge base', + configFailed: 'Failed to configure knowledge base', + skipConfigSuccess: 'Knowledge base created successfully, you can configure parsing parameters later in the settings page', + createSuccessConfig: 'Knowledge base created successfully, now you can configure parsing settings', + configLaterTip: 'You can configure these settings now, or modify them later in the knowledge base details page', + skipConfig: 'Skip Configuration', + creating: 'Creating...', + configuring: 'Configuring...', + createAndNext: 'Create and Next', + completeCreate: 'Complete Creation', deleteGenerateModalContent: `

Deleting the generated {{type}} results will remove all derived entities and relationships from this dataset. @@ -917,21 +1268,6 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s destinationFolder: 'Destination folder', pleaseUploadAtLeastOneFile: 'Please upload at least one file', }, - llmTools: { - bad_calculator: { - name: 'Calculator', - description: - 'A tool to calculate the sum of two numbers (will give wrong answer)', - params: { - a: 'The first number', - b: 'The second number', - }, - }, - }, - modal: { - okText: 'Confirm', - cancelText: 'Cancel', - }, mcp: { export: 'Export', import: 'Import', @@ -991,6 +1327,11 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s cancelText: 'Cancel', chooseDataset: 'Please select a dataset first', }, + layout: { + file: 'file', + knowledge: 'knowledge', + chat: 'chat', + }, language: { english: 'English', chinese: 'Chinese', @@ -1133,7 +1474,7 @@ Important structured information may include: names, dates, locations, events, k }, }, cancel: 'Cancel', - swicthPromptMessage: + switchPromptMessage: 'The prompt word will change. Please confirm whether to abandon the existing prompt word?', tokenizerSearchMethodOptions: { full_text: 'Full-text', @@ -1157,5 +1498,83 @@ Important structured information may include: names, dates, locations, events, k downloading: 'Downloading', processing: 'Processing', }, + chunkPage: { + missingParams: 'Missing required parameters: Knowledge Base ID or Document ID', + knowledgeBase: 'Knowledge Base', + knowledgeBaseDetail: 'Knowledge Base Detail', + documentDetail: 'Document Detail', + documentChunkResult: 'Document Chunk Parsing Result', + viewDocument: 'View Document', + allChunkData: 'all chunk data', + totalChunkCount: 'Total Chunk Count', + viewFile: 'View File', + searchChunkPlaceholder: 'Search chunk content...', + chunkList: 'Chunk List', + chunkContent: 'Chunk Content', + chunkIndex: 'Chunk Index', + chunkScore: 'Chunk Score', + chunkKeywords: 'Chunk Keywords', + chunkQuestions: 'Chunk Questions', + noChunksFound: 'No chunk data found', + loadingChunks: 'Loading chunk data...', + chunkDetails: 'Chunk Details', + documentPreview: 'Document Preview', + previewNotAvailable: 'Preview not available', + fileNotFound: 'File not found', + unsupportedFileType: 'Unsupported file type', + loadingPreview: 'Loading preview...', + downloadFile: 'Download File', + openInNewTab: 'Open in new tab', + loadingChunkData: 'Loading chunk data...', + noChunkData: 'No chunk data', + noChunkDataDescription: 'No chunk data description', + selectAll: 'Select All', + selected: 'Selected', + items: 'items', + enable: 'Enable', + disable: 'Disable', + clearSelection: 'Clear Selection', + page: 'Page', + pageOf: 'of', + pages: 'pages', + total: 'Total', + chunks: 'chunks', + containsImage: 'Contains Image', + enabled: 'Enabled', + disabled: 'Disabled', + contentPreview: 'Content Preview', + noContent: 'No content', + relatedImage: 'Related Image', + chunkRelatedImage: 'Chunk Related Image', + keywordInfo: 'Keyword Info', + important: 'Important', + question: 'Question', + tag: 'Tag', + deleting: 'Deleting', + confirmDeleteChunks: 'Confirm delete selected chunks', + fetchDataFailed: 'Failed to fetch data', + fileFormatNotSupported: 'File format not supported', + getDocumentFileFailed: 'Failed to get document file', + fileTypeNotSupportedPreview: 'File type not supported for preview', + filePreview: 'File Preview', + loadingFile: 'Loading file...', + }, + dialog: { + confirm: 'Confirm', + cancel: 'Cancel', + delete: 'Delete', + save: 'Save', + close: 'Close', + warning: 'Warning', + error: 'Error', + success: 'Success', + info: 'Info', + confirmDelete: 'Confirm Delete', + confirmDeleteMessage: 'This operation cannot be undone. Are you sure you want to delete?', + operationSuccess: 'Operation successful', + operationFailed: 'Operation failed', + pleaseConfirm: 'Please confirm', + areYouSure: 'Are you sure?', + }, }, }; diff --git a/src/locales/zh.ts b/src/locales/zh.ts index 733fd62..4e6e0ec 100644 --- a/src/locales/zh.ts +++ b/src/locales/zh.ts @@ -1,6 +1,9 @@ export default { translation: { + common: { + back: '返回', + operationFailed: '操作失败', noResults: '无结果。', selectPlaceholder: '请选择', selectAll: '全选', @@ -26,9 +29,10 @@ export default { languagePlaceholder: '请选择语言', copy: '复制', copied: '复制成功', + loading: '加载中...', + close: '关闭', comingSoon: '即将推出', download: '下载', - close: '关闭', preview: '预览', move: '移动', warn: '提醒', @@ -49,7 +53,183 @@ export default { promptPlaceholder: '请输入或使用 / 快速插入变量。', update: '更新', configure: '配置', + all: '全部', + refresh: '刷新', + submitting: '提交中...', + description: '描述', + confirm: '确认', + enabled: '已启用', + clearFilter: '清空筛选', + confirmFilter: '确认筛选', + private: '私有', + moreActions: '更多操作', + disable: '禁用', + enable: '启用', + team: '团队', + public: '公开', + unknown: '未知', + noDescription: '无描述', + viewAll: '查看全部', + viewDetails: '查看详情', + mcp: { + namePlaceholder: '我的 MCP 服务器', + nameRequired: + '长度必须为 1-64 个字符,只能包含字母、数字、连字符和下划线。', + urlPlaceholder: 'https://api.example.com/v1/mcp', + tokenPlaceholder: '例如 eyJhbGciOiJIUzI1Ni...', + }, }, + + form: { + fieldRequired: '{{field}} 是必填项', + minValueExceeded: '{{field}} 不能小于 {{min}}', + maxValueExceeded: '{{field}} 不能大于 {{max}}', + configurationError: '表单配置错误', + chipListPlaceholder: '输入后按回车添加', + maxChipsExceeded: '{{field}}最多只能有{{max}}个标签', + noChips: '暂无标签', + formConfigError: '表单配置错误', + }, + + knowledge: { + basicInfo: '基础信息', + uploadAvatar: '上传头像', + nameRequired: '知识库名称不能为空', + knowledgeBaseName: '知识库名称', + descriptionPlaceholder: '请输入知识库描述...', + permissionSettings: '权限设置', + onlyMe: '仅自己', + teamMembers: '团队成员', + retrievalTest: '检索测试', + configSettings: '配置设置', + knowledgeBaseActions: '知识库操作', + filter: '筛选', + runStatus: { + unstart: '未开始', + running: '运行中', + cancel: '已取消', + done: '已完成', + fail: '失败', + unknown: '未知', + parsing: '解析中' + }, + runStatusFilter: '运行状态', + fileName: '文件名', + actions: '操作', + fileType: '文件类型', + fileTypeFilter: '文件类型', + searchFiles: '搜索文件', + uploadFile: '上传文件', + fileCount: '文件数量', + chunkCount: '块数量', + tokenCount: '令牌数量', + size: '大小', + createTime: '创建时间', + updateTime: '更新时间', + language: '语言', + permission: '权限', + embeddingModel: '嵌入模型', + parser: '解析器', + type: '类型', + status: '状态', + parseStatus: '解析状态', + uploadTime: '上传时间', + documentCount: '文档数量', + lastUpdate: '最后更新', + creator: '创建者', + testPrompt: '测试提示', + testResultOverview: '测试结果概览', + matchedChunks: '匹配的块', + relatedDocuments: '相关文档', + returnedChunks: '返回的块', + documentFilter: '文档筛选', + selectDocuments: '选择文档', + noContent: '无内容', + keywords: '关键词', + relatedDocumentStats: '相关文档统计', + selectParserMethod: '选择解析器方法', + unknown: '未知', + description: '描述', + pageRank: '页面排名', + edge: '边', + missingIds: '缺失的ID', + nodeNotExists: '节点不存在', + noGraphData: '无图数据可用', + legend: '图例', + graphStats: '图统计', + reparse: '重新解析', + cancelRun: '取消运行', + renameFile: '重命名文件', + viewDetails: '查看详情', + viewFileDetails: '查看文件详情', + matchedChunksCount: '匹配的块数量', + matchedChunksTitle: '匹配的块标题', + totalMatchedChunks: '总匹配块数', + similarity: '相似度', + vectorSimilarity: '向量相似度', + termSimilarity: '词项相似度', + nodeCount: '节点数量', + edgeCount: '边数量', + noMatchingKnowledgeBases: '没有匹配的知识库', + noKnowledgeBases: '没有知识库', + tryAdjustingFilters: '尝试调整筛选条件', + createFirstKnowledgeBase: '创建您的第一个知识库', + createKnowledgeBase: '创建知识库', + noDescription: '无描述', + config: { + basicConfig: '基础配置', + pageRankAndAutoExtract: '页面排名和自动提取', + raptorStrategy: 'RAPTOR策略', + knowledgeGraph: '知识图谱', + chunkMethod: '切片方法', + selectChunkMethod: '选择切片方法', + pageRank: '页面排名', + enterPageRank: '输入页面排名', + autoKeywords: '自动关键词提取', + enterKeywordCount: '输入关键词数量', + autoQuestions: '自动问题提取', + enterQuestionCount: '输入问题数量', + pdfParser: 'PDF解析器', + plainText: '纯文本', + experimental: '实验性', + delimiter: '分隔符', + enterDelimiter: '请输入分隔符', + embeddingModel: '嵌入模型', + pageRankConfigTodo: 'PageRank配置 - 待实现', + entityTypeConfigTodo: '实体类型配置 - 待实现', + maxTokenConfigTodo: '最大Token数量配置 (最大: 16384) - 待实现', + delimiterConfigTodo: '分隔符配置 - 待实现', + chunkTokenSize: '建议文本块大小', + htmlForExcel: '表格转HTML', + tags: '标签集', + useRaptorStrategy: '使用召回增强RAPTOR策略', + prompt: '提示词', + raptorPromptDefault: '请总结以下段落。小心数字,不要编造。段落如下:\n{cluster_content}\n以上就是你需要总结的内容。', + maxTokens: '最大token数', + threshold: '阈值', + maxClusterCount: '最大聚类数', + randomSeed: '随机种子', + extractKnowledgeGraph: '提取知识图谱', + entityTypes: '*实体类型', + method: '方法', + entityNormalization: '实体归一化', + communityReportGeneration: '社区报告生成', + parser: { + general: '通用解析器', + qa: 'Q&A解析器', + resume: 'Resume解析器', + manual: 'Manual解析器', + table: 'Table解析器', + paper: 'Paper解析器', + book: 'Book解析器', + laws: 'Laws解析器', + presentation: 'Presentation解析器', + one: 'One解析器', + tag: 'Tag解析器', + }, + }, + }, + login: { login: '登录', signUp: '注册', @@ -83,17 +263,150 @@ export default { flow: '智能体', search: '搜索', welcome: '欢迎来到', - dataset: '知识库', + }, + knowledgeHooks: { + fetchKnowledgeListFailed: '获取知识库列表失败', + fetchKnowledgeDetailFailed: '获取知识库详情失败', + fetchKnowledgeGraphFailed: '获取知识库图失败', + createKnowledgeFailed: '创建知识库失败', + updateKnowledgeBasicInfoFailed: '更新知识库基础信息失败', + updateKnowledgeModelConfigFailed: '更新知识库模型配置失败', + deleteKnowledgeFailed: '删除知识库失败', + batchDeleteKnowledgeFailed: '批量删除知识库失败', + batchDeleteFailedKnowledgeBases: '删除失败的知识库', + }, + knowledgeTesting: { + retrievalTestComplete: '检索测试完成', + retrievalTestFailed: '检索测试失败', + paginationRequestFailed: '分页请求失败', + knowledgeBase: '知识库', + knowledgeBaseDetail: '知识库详情', + testing: '测试', + knowledgeBaseTesting: '知识库测试', + testConfiguration: '测试配置', + testQuestion: '测试问题', + pleaseEnterTestQuestion: '请输入测试问题', + testQuestionPlaceholder: '请输入您想要测试的问题...', + similarityThreshold: '相似度阈值', + vectorSimilarityWeight: '向量相似度权重', + rerankModel: '重排序模型 (可选)', + noRerank: '不使用重排序', + pleaseEnterResultCount: '请输入返回结果数量', + minValue1: '最小值为1', + maxValue2048: '最大值为2048', + useWithRerankModel: '与Rerank模型配合使用', + crossLanguageSearch: '跨语言搜索', + useKnowledgeGraph: '使用知识图谱', + startTest: '开始测试', + languages: { + english: 'English', + chinese: 'Chinese', + japanese: 'Japanese', + korean: 'Korean', + french: 'French', + german: 'German', + spanish: 'Spanish', + italian: 'Italian', + portuguese: 'Portuguese', + russian: 'Russian', + arabic: 'Arabic', + hindi: 'Hindi', + thai: 'Thai', + vietnamese: 'Vietnamese', + indonesian: 'Indonesian', + malay: 'Malay', + filipino: 'Filipino', + turkish: 'Turkish', + polish: 'Polish', + dutch: 'Dutch', + swedish: 'Swedish', + danish: 'Danish', + norwegian: 'Norwegian', + finnish: 'Finnish', + hebrew: 'Hebrew', + czech: 'Czech', + slovak: 'Slovak', + hungarian: 'Hungarian', + romanian: 'Romanian', + bulgarian: 'Bulgarian', + croatian: 'Croatian', + serbian: 'Serbian', + slovenian: 'Slovenian', + estonian: 'Estonian', + latvian: 'Latvian', + lithuanian: 'Lithuanian', + maltese: 'Maltese', + irish: 'Irish', + welsh: 'Welsh', + basque: 'Basque', + catalan: 'Catalan', + galician: 'Galician', + icelandic: 'Icelandic', + macedonian: 'Macedonian', + albanian: 'Albanian', + belarusian: 'Belarusian', + ukrainian: 'Ukrainian', + kazakh: 'Kazakh', + kyrgyz: 'Kyrgyz', + uzbek: 'Uzbek', + tajik: 'Tajik', + mongolian: 'Mongolian', + georgian: 'Georgian', + armenian: 'Armenian', + azerbaijani: 'Azerbaijani', + persian: 'Persian', + urdu: 'Urdu', + bengali: 'Bengali', + tamil: 'Tamil', + telugu: 'Telugu', + malayalam: 'Malayalam', + kannada: 'Kannada', + gujarati: 'Gujarati', + punjabi: 'Punjabi', + odia: 'Odia', + assamese: 'Assamese', + nepali: 'Nepali', + sinhala: 'Sinhala', + burmese: 'Burmese', + khmer: 'Khmer', + lao: 'Lao', + swahili: 'Swahili', + amharic: 'Amharic', + yoruba: 'Yoruba', + igbo: 'Igbo', + hausa: 'Hausa', + zulu: 'Zulu', + xhosa: 'Xhosa', + afrikaans: 'Afrikaans', + } + }, + knowledgeSettings: { + submitData: '提交数据:', + basicInfoUpdateSuccess: '基础信息更新成功', + parseConfigUpdateSuccess: '解析配置更新成功', + updateFailed: '{{type}}更新失败', + basicInfo: '基础信息', + parseConfig: '解析配置', + knowledgeBase: '知识库', + knowledgeBaseDetail: '知识库详情', + settings: '设置', + knowledgeBaseSettings: '知识库设置', + settingsTabs: '设置选项卡', + backToKnowledgeDetail: '返回知识库详情', }, knowledgeList: { - welcome: '欢迎回来', - description: '今天我们要使用哪个知识库?', + welcome: '欢迎使用知识库', + description: '创建和管理您的知识库', createKnowledgeBase: '创建知识库', name: '名称', - namePlaceholder: '请输入名称', + namePlaceholder: '请输入知识库名称', doc: '文档', searchKnowledgePlaceholder: '搜索', noMoreData: '没有更多数据了', + confirmDeleteKnowledge: '是否确认删除知识库', + teamFilter: '团队筛选', + loadError: '加载知识库列表失败', + paginationInfo: '共 {{total}} 个知识库,第 {{current}} 页,共 {{totalPages}} 页', }, knowledgeDetails: { fileSize: '文件大小', @@ -141,6 +454,36 @@ export default { namePlaceholder: '请输入名称', doc: '文档', datasetDescription: '解析成功后才能问答哦。', + // 新增的字段 + deleteFileFailed: '删除文件失败', + uploadFiles: '上传文件:', + uploadFileFailed: '上传文件失败', + reparseFailed: '重新解析失败', + renameFailed: '重命名失败', + changeStatusFailed: '更改状态失败', + viewDetails: '查看详情', + viewProcessDetails: '查看解析详情', + knowledgeBase: '知识库', + knowledgeBaseDetail: '知识库详情', + documents: 'Documents', + graph: 'Graph', + deleteFiles: '删除文件:', + newSelectionModel: '新的选择模型:', + uploadFilesToKnowledge: '上传文件到知识库', + confirmDelete: '确认删除', + confirmDeleteMessage: '确定要删除选中的 {{count}} 个文件吗?此操作不可撤销。', + documentProcessDetails: '文档处理详情', + basicInfo: '基本信息', + parserId: '解析器ID', + notSpecified: '未指定', + processStatus: '处理状态', + startTime: '开始时间', + notStarted: '未开始', + processingTime: '处理时长', + notCompleted: '未完成', + progress: '进度', + processDetails: '处理详情', + seconds: '秒', addFile: '新增文件', searchFiles: '搜索文件', localFiles: '本地文件', @@ -174,6 +517,10 @@ export default { testText: '测试文本', testTextPlaceholder: '请输入您的问题!', testingLabel: '测试', + generateKnowledgeGraph: + '这将从此数据集中的所有文档中提取实体和关系。该过程可能需要一段时间才能完成。', + generateRaptor: + '这将从此数据集中的所有文档中提取实体和关系。该过程可能需要一段时间才能完成。', similarity: '混合相似度', termSimilarity: '关键词相似度', vectorSimilarity: '向量相似度', @@ -256,6 +603,20 @@ export default { theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除', }, knowledgeConfiguration: { + basicInfo: '基础信息', + configSettings: '配置设置', + createSuccess: '知识库创建成功,请配置解析设置', + configComplete: '知识库配置完成', + createFailed: '创建知识库失败', + configFailed: '配置知识库失败', + skipConfigSuccess: '知识库创建完成,您可以稍后在设置页面配置解析参数', + createSuccessConfig: '知识库已创建成功,现在可以配置解析设置', + configLaterTip: '您可以现在配置这些设置,也可以稍后在知识库详情页面中修改', + skipConfig: '跳过配置', + creating: '创建中...', + configuring: '配置中...', + createAndNext: '创建并下一步', + completeCreate: '完成创建', deleteGenerateModalContent: `

删除生成的 {{type}} 结果 将从此数据集中移除所有派生实体和关系。 @@ -290,7 +651,7 @@ export default { description: '描述', language: '文档语言', languageMessage: '请输入语言', - languagePlaceholder: '请输入语言', + languagePlaceholder: '请选择语言', permissions: '权限', embeddingModel: '嵌入模型', chunkTokenNumber: '建议文本块大小', @@ -819,6 +1180,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 logout: '登出成功', logged: '登录成功', pleaseSelectChunk: '请选择解析块', + registerDisabled: '用户注册已禁用', modified: '更新成功', created: '创建成功', deleted: '删除成功', @@ -872,28 +1234,11 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 destinationFolder: '目标文件夹', pleaseUploadAtLeastOneFile: '请上传至少一个文件', }, - footer: { - profile: 'All rights reserved @ React', - }, layout: { file: 'file', knowledge: 'knowledge', chat: 'chat', }, - llmTools: { - bad_calculator: { - name: '计算器', - description: '用于计算两个数的和的工具(会给出错误答案)', - params: { - a: '第一个数', - b: '第二个数', - }, - }, - }, - modal: { - okText: '确认', - cancelText: '取消', - }, mcp: { export: '导出', import: '导入', @@ -951,7 +1296,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 descriptionValue: '你是一位智能助手。', okText: '保存', cancelText: '返回', - chooseDataset: '请先选择知识库', + chooseDataset: '请先选择一个数据集', }, language: { english: '英语', @@ -1023,7 +1368,6 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 regularExpressions: '正则表达式', overlappedPercent: '重叠百分比', searchMethod: '搜索方法', - filenameEmbdWeight: '文件名嵌入权重', begin: '文件', parserMethod: '解析方法', systemPrompt: '系统提示词', @@ -1087,6 +1431,29 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 cancel: '取消', filenameEmbeddingWeight: '文件名嵌入权重', switchPromptMessage: '提示词将发生变化,请确认是否放弃已有提示词?', + fileFormatOptions: { + pdf: 'PDF', + spreadsheet: '电子表格', + image: '图片', + email: '邮件', + 'text&markdown': '文本和标记', + word: 'Word', + slides: 'PPT', + audio: '音频', + }, + tokenizerSearchMethodOptions: { + full_text: '全文', + embedding: '嵌入', + }, + tokenizerFieldsOptions: { + text: '处理后的文本', + keywords: '关键词', + questions: '问题', + summary: '增强上下文', + }, + imageParseMethodOptions: { + ocr: 'OCR', + }, }, datasetOverview: { downloadTip: '正在从数据源下载文件。', @@ -1095,5 +1462,83 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 downloading: '正在下载', processing: '正在处理', }, + chunkPage: { + loadingChunkData: '正在加载块数据...', + noChunkData: '无块数据', + noChunkDataDescription: '没有找到块数据', + selectAll: '全选', + selected: '已选择', + items: '项', + enable: '启用', + disable: '禁用', + clearSelection: '清除选择', + chunkList: '块列表', + page: '页', + pageOf: '页,共', + pages: '页', + total: '总计', + chunks: '块', + containsImage: '包含图片', + enabled: '已启用', + disabled: '已禁用', + contentPreview: '内容预览', + noContent: '无内容', + relatedImage: '相关图片', + chunkRelatedImage: '块相关图片', + keywordInfo: '关键词信息', + important: '重要', + question: '问题', + tag: '标签', + deleting: '删除中', + confirmDeleteChunks: '确认删除选中的块', + fetchDataFailed: '获取数据失败', + fileFormatNotSupported: '不支持的文件格式', + getDocumentFileFailed: '获取文档文件失败', + fileTypeNotSupportedPreview: '不支持预览的文件类型', + filePreview: '文件预览', + loadingFile: '正在加载文件', + missingParams: '缺少必要的参数:知识库ID或文档ID', + knowledgeBase: '知识库', + knowledgeBaseDetail: '知识库详情', + documentDetail: '文档详情', + documentChunkResult: '文档Chunk解析结果', + viewDocument: '查看文档', + allChunkData: '的所有chunk数据', + totalChunkCount: '总Chunk数量', + viewFile: '查看文件', + searchChunkPlaceholder: '搜索chunk内容...', + chunkContent: 'Chunk内容', + chunkIndex: 'Chunk索引', + chunkScore: 'Chunk评分', + chunkKeywords: 'Chunk关键词', + chunkQuestions: 'Chunk问题', + noChunksFound: '未找到chunk数据', + loadingChunks: '正在加载chunk数据...', + chunkDetails: 'Chunk详情', + documentPreview: '文档预览', + previewNotAvailable: '预览不可用', + fileNotFound: '文件未找到', + unsupportedFileType: '不支持的文件类型', + loadingPreview: '正在加载预览...', + downloadFile: '下载文件', + openInNewTab: '在新标签页中打开', + }, + dialog: { + confirm: '确认', + cancel: '取消', + delete: '删除', + save: '保存', + close: '关闭', + warning: '警告', + error: '错误', + success: '成功', + info: '信息', + confirmDelete: '确认删除', + confirmDeleteMessage: '此操作不可撤销,确定要删除吗?', + operationSuccess: '操作成功', + operationFailed: '操作失败', + pleaseConfirm: '请确认', + areYouSure: '您确定吗?', + }, }, }; diff --git a/src/pages/chunk/components/ChunkListResult.tsx b/src/pages/chunk/components/ChunkListResult.tsx index 6416d70..1109737 100644 --- a/src/pages/chunk/components/ChunkListResult.tsx +++ b/src/pages/chunk/components/ChunkListResult.tsx @@ -36,6 +36,7 @@ import { SelectAll as SelectAllIcon, Clear as ClearIcon, } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; import type { IChunk } from '@/interfaces/database/knowledge'; import knowledgeService from '@/services/knowledge_service'; @@ -54,6 +55,7 @@ interface ChunkListResultProps { function ChunkListResult(props: ChunkListResultProps) { const { doc_id, chunks, total, loading, error, page, pageSize, onPageChange, onRefresh, docName } = props; + const { t } = useTranslation(); // 选择状态 const [selectedChunks, setSelectedChunks] = useState([]); @@ -140,7 +142,7 @@ function ChunkListResult(props: ChunkListResultProps) { - 正在加载chunk数据... + {t('chunkPage.loadingChunkData')} ); @@ -160,10 +162,10 @@ function ChunkListResult(props: ChunkListResultProps) { return ( - 暂无chunk数据 + {t('chunkPage.noChunkData')} - 该文档还没有生成chunk数据,请检查文档是否已完成解析 + {t('chunkPage.noChunkDataDescription')} ); @@ -189,7 +191,7 @@ function ChunkListResult(props: ChunkListResultProps) { onChange={(e) => handleSelectAll(e.target.checked)} /> } - label={`全选 (已选择 ${selectedChunks.length} 个)`} + label={`${t('chunkPage.selectAll')} (${t('chunkPage.selected')} ${selectedChunks.length} ${t('chunkPage.items')})`} /> @@ -205,7 +207,7 @@ function ChunkListResult(props: ChunkListResultProps) { variant="outlined" size="small" > - 启用 ({selectedDisabledCount}) + {t('chunkPage.enable')} ({selectedDisabledCount}) )} @@ -218,7 +220,7 @@ function ChunkListResult(props: ChunkListResultProps) { variant="outlined" size="small" > - 禁用 ({selectedEnabledCount}) + {t('chunkPage.disable')} ({selectedEnabledCount}) )} @@ -230,7 +232,7 @@ function ChunkListResult(props: ChunkListResultProps) { variant="outlined" size="small" > - 删除 ({selectedChunks.length}) + {t('common.delete')} ({selectedChunks.length}) )} @@ -250,10 +252,10 @@ function ChunkListResult(props: ChunkListResultProps) { - Chunk列表 (第 {page} 页,共 {totalPages} 页) + {t('chunkPage.chunkList')} ({t('chunkPage.page')} {page} {t('chunkPage.pageOf')} {totalPages} {t('chunkPage.pages')}) - 共 {total} 个chunk + {t('chunkPage.total')} {total} {t('chunkPage.chunks')} @@ -307,7 +309,7 @@ function ChunkListResult(props: ChunkListResultProps) { {chunk.image_id && ( - + @@ -315,7 +317,7 @@ function ChunkListResult(props: ChunkListResultProps) { )} : } - label={chunk.available_int === 1 ? '已启用' : '未启用'} + label={chunk.available_int === 1 ? t('chunkPage.enabled') : t('chunkPage.disabled')} size="small" color={chunk.available_int === 1 ? 'success' : 'default'} variant={chunk.available_int === 1 ? 'filled' : 'outlined'} @@ -326,7 +328,7 @@ function ChunkListResult(props: ChunkListResultProps) { {/* 内容区域 */} - 内容预览 + {t('chunkPage.contentPreview')} - {chunk.content_with_weight || '无内容'} + {chunk.content_with_weight || t('chunkPage.noContent')} @@ -356,7 +358,7 @@ function ChunkListResult(props: ChunkListResultProps) { {chunk.image_id && ( - 相关图片 + {t('chunkPage.relatedImage')} 0 || (chunk.question_kwd ?? []).length > 0 || (chunk.tag_kwd ?? []).length > 0) && ( - 关键词信息 + {t('chunkPage.keywordInfo')} {chunk.important_kwd && chunk.important_kwd.length > 0 && ( - 重要: + {t('chunkPage.important')}: {chunk.important_kwd.map((keyword, kwdIndex) => ( 0 && ( - 问题: + {t('chunkPage.question')}: {chunk.question_kwd.map((keyword, kwdIndex) => ( 0 && ( - 标签: + {t('chunkPage.tag')}: {chunk.tag_kwd.map((keyword, kwdIndex) => ( setDeleteDialogOpen(false)} > - 确认删除 + {t('dialog.confirmDelete')} - 确定要删除选中的 {selectedChunks.length} 个chunk吗?此操作不可撤销。 + {t('chunkPage.confirmDeleteChunks', { count: selectedChunks.length })} diff --git a/src/pages/chunk/document-preview.tsx b/src/pages/chunk/document-preview.tsx index 603d7a0..4e1a39e 100644 --- a/src/pages/chunk/document-preview.tsx +++ b/src/pages/chunk/document-preview.tsx @@ -16,6 +16,7 @@ import { Download as DownloadIcon, Visibility as VisibilityIcon, } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; import knowledgeService from '@/services/knowledge_service'; import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge'; import KnowledgeBreadcrumbs from '@/pages/knowledge/components/KnowledgeBreadcrumbs'; @@ -25,6 +26,7 @@ interface DocumentPreviewProps {} function DocumentPreview(props: DocumentPreviewProps) { const { kb_id, doc_id } = useParams<{ kb_id: string; doc_id: string }>(); const navigate = useNavigate(); + const { t } = useTranslation(); const [kb, setKb] = useState(null); const [documentObj, setDocumentObj] = useState(null); @@ -41,7 +43,7 @@ function DocumentPreview(props: DocumentPreviewProps) { useEffect(() => { const fetchData = async () => { if (!kb_id || !doc_id) { - setError('缺少必要的参数'); + setError(t('chunkPage.missingParams')); setLoading(false); return; } @@ -67,7 +69,7 @@ function DocumentPreview(props: DocumentPreviewProps) { loadDocumentFile(); } catch (err) { console.error('获取数据失败:', err); - setError('获取数据失败,请稍后重试'); + setError(t('chunkPage.fetchDataFailed')); } finally { setLoading(false); } @@ -103,13 +105,13 @@ function DocumentPreview(props: DocumentPreviewProps) { const url = URL.createObjectURL(fileResponse.data); setFileUrl(url); } else { - setError('文件格式不支持预览'); + setError(t('chunkPage.fileFormatNotSupported')); } } catch (err: any) { console.log('err', err); if (err.name !== 'AbortError' && err.name !== 'CanceledError') { console.error('获取文档文件失败:', err); - setError('获取文档文件失败,请稍后重试'); + setError(t('chunkPage.getDocumentFileFailed')); } } finally { setFileLoading(false); @@ -168,7 +170,7 @@ function DocumentPreview(props: DocumentPreviewProps) { - 此文件类型不支持在线预览,请下载后查看。 + {t('chunkPage.fileTypeNotSupportedPreview')} ); } @@ -230,7 +232,7 @@ function DocumentPreview(props: DocumentPreviewProps) { onClick={handleGoBack} sx={{ mt: 2 }} > - 返回 + {t('common.back')} ); @@ -242,21 +244,21 @@ function DocumentPreview(props: DocumentPreviewProps) { @@ -265,7 +267,7 @@ function DocumentPreview(props: DocumentPreviewProps) { - 文件预览 + {t('chunkPage.filePreview')} {documentObj && ( @@ -280,7 +282,7 @@ function DocumentPreview(props: DocumentPreviewProps) { onClick={handleGoBack} variant="outlined" > - 返回 + {t('common.back')} {fileUrl && ( @@ -289,7 +291,7 @@ function DocumentPreview(props: DocumentPreviewProps) { onClick={handleDownload} variant="contained" > - 下载文件 + {t('chunkPage.downloadFile')} )} @@ -306,7 +308,7 @@ function DocumentPreview(props: DocumentPreviewProps) { {fileLoading && ( - 正在加载文件... + {t('chunkPage.loadingFile')} )} diff --git a/src/pages/chunk/parsed-result.tsx b/src/pages/chunk/parsed-result.tsx index 23e38ca..6fa8fca 100644 --- a/src/pages/chunk/parsed-result.tsx +++ b/src/pages/chunk/parsed-result.tsx @@ -12,6 +12,7 @@ import { CardContent } from "@mui/material"; import { Search as SearchIcon, Visibility as VisibilityIcon } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; import { useChunkList } from '@/hooks/chunk-hooks'; import ChunkListResult from './components/ChunkListResult'; import knowledgeService from '@/services/knowledge_service'; @@ -19,6 +20,7 @@ import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge import KnowledgeBreadcrumbs from '@/pages/knowledge/components/KnowledgeBreadcrumbs'; function ChunkParsedResult() { + const { t } = useTranslation(); const [searchParams] = useSearchParams(); const navigate = useNavigate(); const kb_id = searchParams.get('kb_id'); @@ -93,7 +95,7 @@ function ChunkParsedResult() { return ( - 缺少必要的参数:知识库ID或文档ID + {t('chunkPage.missingParams')} ); @@ -105,17 +107,17 @@ function ChunkParsedResult() { @@ -124,10 +126,10 @@ function ChunkParsedResult() { - 文档Chunk解析结果 + {t('chunkPage.documentChunkResult')} - 查看文档 "{document?.name}" 的所有chunk数据 + {t('chunkPage.viewDocument')} "{document?.name}" {t('chunkPage.allChunkData')} @@ -136,7 +138,7 @@ function ChunkParsedResult() { {total} - 总Chunk数量 + {t('chunkPage.totalChunkCount')} @@ -146,7 +148,7 @@ function ChunkParsedResult() { onClick={handleViewFile} sx={{ ml: 2 }} > - 查看文件 + {t('chunkPage.viewFile')} @@ -155,7 +157,7 @@ function ChunkParsedResult() { handleSearch(e.target.value)} InputProps={{ diff --git a/src/pages/knowledge/components/ChunkMethodForm.tsx b/src/pages/knowledge/components/ChunkMethodForm.tsx index 10d524f..18898e1 100644 --- a/src/pages/knowledge/components/ChunkMethodForm.tsx +++ b/src/pages/knowledge/components/ChunkMethodForm.tsx @@ -1,5 +1,6 @@ import React, { useMemo } from 'react'; import { useFormContext, useWatch, type UseFormReturn } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; import { Box, Typography, @@ -47,10 +48,11 @@ const ConfigurationComponentMap = { // 空组件 function EmptyComponent() { + const { t } = useTranslation(); return ( - 请选择一个解析方法来配置相关参数 + {t('knowledge.selectParserMethod')} ); @@ -74,9 +76,10 @@ function ChunkMethodForm({ onSubmit, isSubmitting, onCancel, - submitButtonText = '保存', - cancelButtonText = '取消', + submitButtonText, + cancelButtonText, }: ChunkMethodFormProps = {}) { + const { t } = useTranslation(); // 优先使用props传递的form,否则使用FormProvider的context let contextForm; try { @@ -92,7 +95,7 @@ function ChunkMethodForm({ return ( - 表单配置错误:请确保组件在FormProvider中使用或传递form参数 + {t('form.formConfigError')} ); @@ -127,7 +130,7 @@ function ChunkMethodForm({ onClick={onCancel} disabled={isSubmitting} > - {cancelButtonText} + {cancelButtonText || t('common.cancel')} )} )} diff --git a/src/pages/knowledge/components/DocumentListComponent.tsx b/src/pages/knowledge/components/DocumentListComponent.tsx index 3c7a8dc..6b677c3 100644 --- a/src/pages/knowledge/components/DocumentListComponent.tsx +++ b/src/pages/knowledge/components/DocumentListComponent.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { t as translate } from 'i18next'; import { Box, Typography, @@ -91,13 +92,13 @@ interface DocumentListComponentProps { const getRunStatusLabel = (status: string) => { const statusLabels = { - [RUNNING_STATUS_KEYS.UNSTART]: '未开始', - [RUNNING_STATUS_KEYS.RUNNING]: '运行中', - [RUNNING_STATUS_KEYS.CANCEL]: '已取消', - [RUNNING_STATUS_KEYS.DONE]: '完成', - [RUNNING_STATUS_KEYS.FAIL]: '失败', + [RUNNING_STATUS_KEYS.UNSTART]: translate('knowledge.runStatus.unstart'), + [RUNNING_STATUS_KEYS.RUNNING]: translate('knowledge.runStatus.running'), + [RUNNING_STATUS_KEYS.CANCEL]: translate('knowledge.runStatus.cancel'), + [RUNNING_STATUS_KEYS.DONE]: translate('knowledge.runStatus.done'), + [RUNNING_STATUS_KEYS.FAIL]: translate('knowledge.runStatus.fail'), }; - return statusLabels[status as keyof typeof statusLabels] || '未知'; + return statusLabels[status as keyof typeof statusLabels] || translate('knowledge.runStatus.unknown'); }; const getFileIcon = (type: string, suffix?: string) => { @@ -138,21 +139,21 @@ const formatFileSize = (bytes: number): string => { }; const getStatusChip = (status: string) => { - return ; }; const getRunStatusChip = (run: RunningStatus, progress: number) => { - const statusConfig = { - [RUNNING_STATUS_KEYS.UNSTART]: { label: '未开始', color: 'default' as const }, - [RUNNING_STATUS_KEYS.RUNNING]: { label: `解析中`, color: 'info' as const }, - [RUNNING_STATUS_KEYS.CANCEL]: { label: '已取消', color: 'warning' as const }, - [RUNNING_STATUS_KEYS.DONE]: { label: '完成', color: 'success' as const }, - [RUNNING_STATUS_KEYS.FAIL]: { label: '失败', color: 'error' as const }, + const statusConfig = { + [RUNNING_STATUS_KEYS.UNSTART]: { label: translate('knowledge.runStatus.unstart'), color: 'default' as const }, + [RUNNING_STATUS_KEYS.RUNNING]: { label: translate('knowledge.runStatus.parsing'), color: 'info' as const }, + [RUNNING_STATUS_KEYS.CANCEL]: { label: translate('knowledge.runStatus.cancel'), color: 'warning' as const }, + [RUNNING_STATUS_KEYS.DONE]: { label: translate('knowledge.runStatus.done'), color: 'success' as const }, + [RUNNING_STATUS_KEYS.FAIL]: { label: translate('knowledge.runStatus.fail'), color: 'error' as const }, }; - const config = statusConfig[run] || { label: '未知', color: 'default' as const }; + const config = statusConfig[run] || { label: translate('knowledge.runStatus.unknown'), color: 'default' as const }; if (run === RUNNING_STATUS_KEYS.RUNNING) { return ( @@ -209,7 +210,7 @@ const DocumentListComponent: React.FC = ({ const [renameDialogOpen, setRenameDialogOpen] = useState(false); const [newFileName, setNewFileName] = useState(''); - const { i18n } = useTranslation(); + const { i18n, t } = useTranslation(); // 根据当前语言获取DataGrid的localeText const getDataGridLocale = () => { @@ -328,7 +329,7 @@ const DocumentListComponent: React.FC = ({ const columns: GridColDef[] = [ { field: 'name', - headerName: '文件名', + headerName: t('knowledge.fileName'), flex: 2, minWidth: 200, cellClassName: 'grid-center-cell', @@ -349,7 +350,7 @@ const DocumentListComponent: React.FC = ({ } }} > - + {getFileIcon(params.row.type, params.row.suffix)} {params.value} @@ -360,7 +361,7 @@ const DocumentListComponent: React.FC = ({ }, { field: 'type', - headerName: '类型', + headerName: t('knowledge.type'), flex: 0.5, minWidth: 80, renderCell: (params) => ( @@ -369,42 +370,42 @@ const DocumentListComponent: React.FC = ({ }, { field: 'size', - headerName: '大小', + headerName: t('knowledge.size'), flex: 0.5, minWidth: 80, renderCell: (params) => formatFileSize(params.value), }, { field: 'chunk_num', - headerName: '分块数', + headerName: t('knowledge.chunkCount'), flex: 0.5, minWidth: 80, type: 'number', }, { field: 'status', - headerName: '状态', + headerName: t('knowledge.status'), flex: 0.8, minWidth: 100, renderCell: (params) => getStatusChip(params.value), }, { field: 'run', - headerName: '解析状态', + headerName: t('knowledge.parseStatus'), flex: 0.8, minWidth: 100, renderCell: (params) => getRunStatusChip(params.value, params.row.progress), }, { field: 'create_time', - headerName: '上传时间', + headerName: t('knowledge.uploadTime'), flex: 1, minWidth: 140, valueFormatter: (value) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'), }, { field: 'actions', - headerName: '操作', + headerName: t('knowledge.actions'), flex: 1.2, minWidth: 200, sortable: false, @@ -453,7 +454,7 @@ const DocumentListComponent: React.FC = ({ > - + handleMenuClick(e, params.row)} @@ -471,16 +472,16 @@ const DocumentListComponent: React.FC = ({ {/* 筛选器 */} {documentFilter && ( - 筛选器 + {t('knowledge.filter')} {/* 运行状态筛选 */} - 运行状态 + {t('knowledge.runStatusFilter')} } + input={} renderValue={(selected) => ( {selected.map((value) => ( @@ -529,7 +530,7 @@ const DocumentListComponent: React.FC = ({ }); handleFilterSubmit(); }}> - 清除筛选 + {t('common.clearFilter')} {/* submit filter */} @@ -548,7 +549,7 @@ const DocumentListComponent: React.FC = ({ onSearchChange(e.target.value)} InputProps={{ @@ -568,7 +569,7 @@ const DocumentListComponent: React.FC = ({ startIcon={} onClick={onUpload} > - 上传文件 + {t('knowledge.uploadFile')} {rowSelectionModel.ids.size > 0 && ( @@ -578,7 +579,7 @@ const DocumentListComponent: React.FC = ({ startIcon={} onClick={() => onReparse(Array.from(rowSelectionModel.ids).map((id) => id.toString()))} > - 重新解析 ({rowSelectionModel.ids.size}) + {t('knowledge.reparse')} ({rowSelectionModel.ids.size}) )} @@ -637,47 +638,47 @@ const DocumentListComponent: React.FC = ({ > handleViewDetails(selectedFile)}> - 查看详情 + {t('knowledge.viewDetails')} - 重命名 + {t('common.rename')} - 重新解析 + {t('knowledge.reparse')} {selectedFile?.run === RUNNING_STATUS_KEYS.RUNNING && ( - 取消运行 + {t('knowledge.cancelRun')} )} {selectedFile?.status === '1' ? ( handleChangeStatus('0')}> - 禁用 + {t('common.disable')} ) : ( handleChangeStatus('1')}> - 启用 + {t('common.enable')} )} - 删除 + {t('common.delete')} {/* 重命名对话框 */}

setRenameDialogOpen(false)}> - 重命名文件 + {t('knowledge.renameFile')} = ({ /> - - + + diff --git a/src/pages/knowledge/components/FloatingActionButtons.tsx b/src/pages/knowledge/components/FloatingActionButtons.tsx index 73def20..96134e5 100644 --- a/src/pages/knowledge/components/FloatingActionButtons.tsx +++ b/src/pages/knowledge/components/FloatingActionButtons.tsx @@ -6,6 +6,7 @@ import { Settings as ConfigIcon, } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; interface FloatingActionButtonsProps { onTestClick: () => void; @@ -16,22 +17,24 @@ const FloatingActionButtons: React.FC = ({ onTestClick, onConfigClick, }) => { + const { t } = useTranslation(); + const actions = [ { icon: , - name: '检索测试', + name: t('knowledge.retrievalTest'), onClick: onTestClick, }, { icon: , - name: '配置设置', + name: t('knowledge.configSettings'), onClick: onConfigClick, }, ]; return ( - 表单配置错误:请确保组件在FormProvider中使用或传递form参数 + {t('form.configurationError')} ); @@ -85,7 +90,7 @@ function GeneralForm({ return ( - 基础信息 + {t('knowledge.basicInfo')} @@ -106,7 +111,7 @@ function GeneralForm({ startIcon={} onClick={handleAvatarClick} > - 上传头像 + {t('knowledge.uploadAvatar')} {avatar && ( ( ( )} /> @@ -173,10 +178,10 @@ function GeneralForm({ control={control} render={({ field }) => ( - 权限设置 - + {t('knowledge.onlyMe')} + {t('knowledge.teamMembers')} )} @@ -195,7 +200,7 @@ function GeneralForm({ onClick={onCancel} disabled={isSubmitting} > - {cancelButtonText} + {defaultCancelButtonText} )} )} diff --git a/src/pages/knowledge/components/KnowledgeGraphView.tsx b/src/pages/knowledge/components/KnowledgeGraphView.tsx index 46ff0cf..e961c88 100644 --- a/src/pages/knowledge/components/KnowledgeGraphView.tsx +++ b/src/pages/knowledge/components/KnowledgeGraphView.tsx @@ -1,4 +1,5 @@ import React, { useMemo, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import { ReactFlow, type Node, @@ -39,6 +40,7 @@ const getNodeColor = (entityType?: string): string => { // 自定义节点组件 const CustomNode = ({ data }: { data: any }) => { + const { t } = useTranslation(); const nodeColor = getNodeColor(data.entity_type); const nodeSize = Math.max(80, Math.min(140, (data.pagerank || 0.1) * 500)); @@ -55,19 +57,19 @@ const CustomNode = ({ data }: { data: any }) => { const tooltipContent = ( - {data.label || data.name || 'Unknown'} + {data.label || data.name || t('knowledge.unknown')} - 类型: {data.entity_type || 'Unknown'} + {t('knowledge.type')}: {data.entity_type || t('knowledge.unknown')} {data.description && ( - 描述: {data.description} + {t('knowledge.description')}: {data.description} )} {data.pagerank && ( - PageRank: {data.pagerank.toFixed(4)} + {t('knowledge.pageRank')}: {data.pagerank.toFixed(4)} )} @@ -127,24 +129,24 @@ const CustomNode = ({ data }: { data: any }) => { }} > - {data.label || data.name || 'Unknown'} - + variant="caption" + sx={{ + color: '#fff', + fontWeight: 'bold', + fontSize: Math.max(10, nodeSize * 0.08), + textAlign: 'center', + lineHeight: 1.1, + wordBreak: 'break-word', + textShadow: '1px 1px 2px rgba(0,0,0,0.7)', + maxWidth: '90%', + overflow: 'hidden', + display: '-webkit-box', + WebkitLineClamp: 2, + WebkitBoxOrient: 'vertical', + }} + > + {data.label || data.name || t('knowledge.unknown')} + @@ -156,6 +158,8 @@ const nodeTypes = { }; const KnowledgeGraphView: React.FC = ({ knowledgeGraph }) => { + const { t } = useTranslation(); + // 转换数据格式为 React Flow 所需的格式 const { nodes: initialNodes, edges: initialEdges } = useMemo(() => { const graphData = knowledgeGraph?.graph || {}; @@ -166,9 +170,6 @@ const KnowledgeGraphView: React.FC = ({ knowledgeGraph }) = // 转换节点数据 // @ts-ignore const nodes: Node[] = graphData.nodes.map((node, index) => { - console.log(`节点 ${index}:`, node); - console.log(`节点ID: ${node.id}`); - const encodeId = encodeURIComponent(String(node.id)); const n: Node = { @@ -192,11 +193,11 @@ const KnowledgeGraphView: React.FC = ({ knowledgeGraph }) = // 转换边数据 // @ts-ignore const edges: Edge[] = graphData.edges.map((edge, index) => { - console.log(`边 ${index}:`, edge); + console.log(`${t('knowledge.edge')} ${index}:`, edge); console.log(`src_id: ${edge.src_id}, tgt_id: ${edge.tgt_id}`); // 检查source和target是否存在 if (!edge.src_id || !edge.tgt_id) { - console.warn(`边 ${index} 缺少src_id或tgt_id:`, edge); + console.warn(`${t('knowledge.edge')} ${index} ${t('knowledge.missingIds')}:`, edge); return null; } @@ -205,7 +206,7 @@ const KnowledgeGraphView: React.FC = ({ knowledgeGraph }) = const targetExists = nodes.some(node => node.id === encodeURIComponent(edge.tgt_id)); if (!sourceExists || !targetExists) { - console.warn(`边 ${index} 的节点不存在: source=${sourceExists}, target=${targetExists}`, edge); + console.warn(`${t('knowledge.edge')} ${index} ${t('knowledge.nodeNotExists')}: source=${sourceExists}, target=${targetExists}`, edge); return null; } @@ -261,7 +262,7 @@ const KnowledgeGraphView: React.FC = ({ knowledgeGraph }) = if (!knowledgeGraph || initialNodes.length === 0) { return ( - 暂无知识图谱数据 + {t('knowledge.noGraphData')} ); } @@ -295,7 +296,7 @@ const KnowledgeGraphView: React.FC = ({ knowledgeGraph }) = boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}> - 图例 + {t('knowledge.legend')} {['PERSON', 'ORGANIZATION', 'CATEGORY', 'TECHNOLOGY'].map((type) => ( @@ -325,17 +326,17 @@ const KnowledgeGraphView: React.FC = ({ knowledgeGraph }) = boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}> - 图谱统计 + {t('knowledge.graphStats')} { }; const KnowledgeInfoCard: React.FC = ({ knowledgeBase }) => { + const { t } = useTranslation(); + return ( @@ -32,34 +35,34 @@ const KnowledgeInfoCard: React.FC = ({ knowledgeBase }) {knowledgeBase.name} - {knowledgeBase.description || '暂无描述'} + {knowledgeBase.description || t('knowledge.noDescription')} - - - - + + + + - 创建时间: {dayjs(knowledgeBase.create_time).format('YYYY-MM-DD HH:mm:ss')} + {t('knowledge.createTime')}: {dayjs(knowledgeBase.create_time).format('YYYY-MM-DD HH:mm:ss')} - 更新时间: {dayjs(knowledgeBase.update_time).format('YYYY-MM-DD HH:mm:ss')} + {t('knowledge.updateTime')}: {dayjs(knowledgeBase.update_time).format('YYYY-MM-DD HH:mm:ss')} - 语言: {knowledgeBase.language || 'English'} + {t('knowledge.language')}: {knowledgeBase.language || 'English'} - 权限: {knowledgeBase.permission} + {t('knowledge.permission')}: {knowledgeBase.permission} - 嵌入模型: {knowledgeBase.embd_id} + {t('knowledge.embeddingModel')}: {knowledgeBase.embd_id} - 解析器: {knowledgeBase.parser_id} + {t('knowledge.parser')}: {knowledgeBase.parser_id} diff --git a/src/pages/knowledge/components/TestChunkResult.tsx b/src/pages/knowledge/components/TestChunkResult.tsx index f4580b8..b46aad6 100644 --- a/src/pages/knowledge/components/TestChunkResult.tsx +++ b/src/pages/knowledge/components/TestChunkResult.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { Box, Paper, @@ -31,11 +32,13 @@ interface TestChunkResultProps { function TestChunkResult(props: TestChunkResultProps) { const { result, loading, page, pageSize, onDocumentFilter, selectedDocIds } = props; + const { t } = useTranslation(); + if (!result) { return ( - 请输入问题并点击"开始测试"来查看检索结果 + {t('knowledge.testPrompt')} ); @@ -55,7 +58,7 @@ function TestChunkResult(props: TestChunkResultProps) { {/* 测试结果概览 */} - 测试结果概览 + {t('knowledge.testResultOverview')} @@ -65,7 +68,7 @@ function TestChunkResult(props: TestChunkResultProps) { {result.total} - 匹配的文档块 + {t('knowledge.matchedChunks')} @@ -77,7 +80,7 @@ function TestChunkResult(props: TestChunkResultProps) { {result.doc_aggs?.length || 0} - 相关文档 + {t('knowledge.relatedDocuments')} @@ -89,7 +92,7 @@ function TestChunkResult(props: TestChunkResultProps) { {result.chunks?.length || 0} - 返回的块数 + {t('knowledge.returnedChunks')} @@ -102,15 +105,15 @@ function TestChunkResult(props: TestChunkResultProps) { - 文档过滤 + {t('knowledge.documentFilter')} - 选择要显示的文档 + {t('knowledge.selectDocuments')} {parserOptions.map((option) => ( @@ -91,10 +98,11 @@ export function ChunkMethodItem() { // 分块token数量配置 export function ChunkTokenNumberItem() { + const { t } = useTranslation(); return ( ); } // 自动关键词数量配置 export function AutoKeywordsItem() { + const { t } = useTranslation(); return ( ); } // 自动问题数量配置 export function AutoQuestionsItem() { + const { t } = useTranslation(); return ( ); } // 表格转HTML开关 export function HtmlForExcelItem() { + const { t } = useTranslation(); return ( ); @@ -156,15 +168,16 @@ export function HtmlForExcelItem() { // 标签集选择 export function TagsItem() { + const { t } = useTranslation(); const tagsOptions: SelectOption[] = [ - { value: '', label: '请选择' }, + { value: '', label: t('common.pleaseSelect') }, // 这里可以根据实际需求添加标签选项 ]; return ( ); @@ -185,11 +199,12 @@ export function UseRaptorItem() { // RAPTOR提示词配置 export function RaptorPromptItem() { + const { t } = useTranslation(); return ( ); @@ -197,10 +212,11 @@ export function RaptorPromptItem() { // RAPTOR最大token数配置 export function RaptorMaxTokenItem() { + const { t } = useTranslation(); return ( @@ -254,10 +273,11 @@ export function RaptorRandomSeedItem() { // 知识图谱开关 export function UseGraphragItem() { + const { t } = useTranslation(); return ( ); @@ -265,10 +285,11 @@ export function UseGraphragItem() { // 实体类型配置 export function EntityTypesItem() { + const { t } = useTranslation(); return ( ); @@ -309,10 +332,11 @@ export function EntityNormalizeItem() { // 社区报告生成开关 export function CommunityReportItem() { + const { t } = useTranslation(); return ( ); @@ -320,12 +344,13 @@ export function CommunityReportItem() { export function EmbeddingModelItem() { const { control } = useFormContext(); + const { t } = useTranslation(); const { options } = useEmbeddingModelOptions(); return ( - 嵌入模型 + {t('knowledge.config.embeddingModel')} { // 基础选项 const basicOptions = [ { value: 'DeepDOC', label: 'DeepDOC' }, - { value: 'Plain Text', label: '纯文本' } + { value: 'Plain Text', label: t('knowledge.config.plainText') } ]; // 获取图像转文本模型选项 @@ -372,12 +398,12 @@ export function LayoutRecognizeItem() { const image2TextSelectOptions = image2TextOptions.flatMap(group => group.options.map(option => ({ value: option.value, - label: `${option.label} (实验性)` + label: `${option.label} (${t('knowledge.config.experimental')})` })) ); return [...basicOptions, ...image2TextSelectOptions]; - }, [getOptionsByModelType]); + }, [getOptionsByModelType, t]); return ( @@ -405,11 +431,12 @@ export function LayoutRecognizeItem() { // 文本分段标识符配置 export function DelimiterItem() { + const { t } = useTranslation(); return ( ); diff --git a/src/pages/knowledge/configuration/knowledge-graph.tsx b/src/pages/knowledge/configuration/knowledge-graph.tsx index f091f2f..caced89 100644 --- a/src/pages/knowledge/configuration/knowledge-graph.tsx +++ b/src/pages/knowledge/configuration/knowledge-graph.tsx @@ -1,8 +1,11 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { ChunkMethodItem, EmbeddingModelItem } from './common-items'; import { Box, Typography } from '@mui/material'; export function KnowledgeGraphConfiguration() { + const { t } = useTranslation(); + return ( <> @@ -10,25 +13,25 @@ export function KnowledgeGraphConfiguration() { - PageRank配置 - 待实现 + {t('knowledge.config.pageRankConfigTodo')} - 实体类型配置 - 待实现 + {t('knowledge.config.entityTypeConfigTodo')} - 最大Token数量配置 (最大: 16384) - 待实现 + {t('knowledge.config.maxTokenConfigTodo')} - 分隔符配置 - 待实现 + {t('knowledge.config.delimiterConfigTodo')} diff --git a/src/pages/knowledge/configuration/naive.tsx b/src/pages/knowledge/configuration/naive.tsx index c8c5b3f..055c6a8 100644 --- a/src/pages/knowledge/configuration/naive.tsx +++ b/src/pages/knowledge/configuration/naive.tsx @@ -8,6 +8,7 @@ import { } from '@mui/material'; import { ExpandMore as ExpandMoreIcon } from '@mui/icons-material'; import { useFormContext } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; import { ConfigurationFormContainer, MainContainer } from './configuration-form-container'; import { ChunkMethodItem, @@ -35,6 +36,7 @@ import { export function NaiveConfiguration() { const { formState: { errors } } = useFormContext(); + const { t } = useTranslation(); return ( @@ -42,7 +44,7 @@ export function NaiveConfiguration() { {/* 第一部分:基础配置 */} }> - 基础配置 + {t('knowledge.config.basicConfig')} @@ -67,7 +69,7 @@ export function NaiveConfiguration() { {/* 第二部分:页面排名和自动提取 */} }> - 页面排名和自动提取 + {t('knowledge.config.pageRankAndAutoExtract')} @@ -92,7 +94,7 @@ export function NaiveConfiguration() { {/* 第三部分:RAPTOR策略 */} }> - RAPTOR策略 + {t('knowledge.config.raptorStrategy')} @@ -120,7 +122,7 @@ export function NaiveConfiguration() { {/* 第四部分:知识图谱 */} }> - 知识图谱 + {t('knowledge.config.knowledgeGraph')} diff --git a/src/pages/knowledge/configuration/tag.tsx b/src/pages/knowledge/configuration/tag.tsx index 278be0a..b0331ea 100644 --- a/src/pages/knowledge/configuration/tag.tsx +++ b/src/pages/knowledge/configuration/tag.tsx @@ -1,9 +1,12 @@ import React from 'react'; +import { useTranslation } from 'react-i18next'; import { ConfigurationFormContainer } from './configuration-form-container'; import { ChunkMethodItem, EmbeddingModelItem } from './common-items'; import { Box, Typography } from '@mui/material'; export function TagConfiguration() { + const { t } = useTranslation(); + return ( @@ -11,7 +14,7 @@ export function TagConfiguration() { - PageRank配置 - 待实现 + {t('knowledge.config.pageRankConfigTodo')} diff --git a/src/pages/knowledge/create.tsx b/src/pages/knowledge/create.tsx index 3b45f49..676e687 100644 --- a/src/pages/knowledge/create.tsx +++ b/src/pages/knowledge/create.tsx @@ -12,12 +12,14 @@ import { Stepper, Step, StepLabel, + CircularProgress, } from '@mui/material'; import { ArrowBack as ArrowBackIcon, CheckCircle as CheckCircleIcon, SkipNext as SkipNextIcon, } from '@mui/icons-material'; +import { useTranslation } from 'react-i18next'; import { useKnowledgeOperations, useKnowledgeDetail } from '@/hooks/knowledge-hooks'; import GeneralForm from './components/GeneralForm'; import ChunkMethodForm from './components/ChunkMethodForm'; @@ -32,13 +34,14 @@ interface BaseFormData { avatar?: string; } -const steps = ['基础信息', '配置设置']; - function KnowledgeBaseCreate() { const navigate = useNavigate(); + const { t } = useTranslation(); const [activeStep, setActiveStep] = useState(0); const [createdKbId, setCreatedKbId] = useState(null); + const steps = [t('knowledgeConfiguration.basicInfo'), t('knowledgeConfiguration.configSettings')]; + // 使用知识库操作 hooks const { loading: isSubmitting, @@ -89,8 +92,6 @@ function KnowledgeBaseCreate() { // 处理表单提交 const handleSubmit = async ({ data }: { data: any }) => { clearError(); - console.log('提交数据:', data); - try { if (activeStep === 0) { // 第一步:创建知识库基础信息 @@ -110,7 +111,7 @@ function KnowledgeBaseCreate() { }, 500); setActiveStep(1); - showMessage.success('知识库创建成功,请配置解析设置'); + showMessage.success(t('knowledgeConfiguration.createSuccess')); } else { // 第二步:配置知识库解析设置 if (!createdKbId) return; @@ -130,23 +131,23 @@ function KnowledgeBaseCreate() { }; await updateKnowledgeModelConfig(configData); - showMessage.success('知识库配置完成'); + showMessage.success(t('knowledgeConfiguration.configComplete')); navigate('/knowledge'); } } catch (err) { console.error('操作失败:', err); - showMessage.error(activeStep === 0 ? '创建知识库失败' : '配置知识库失败'); + showMessage.error(activeStep === 0 ? t('knowledgeConfiguration.createFailed') : t('knowledgeConfiguration.configFailed')); } }; // 跳过配置,直接完成创建 const handleSkipConfig = async () => { try { - showMessage.success('知识库创建完成,您可以稍后在设置页面配置解析参数'); + showMessage.success(t('knowledgeConfiguration.skipConfigSuccess')); navigate('/knowledge'); } catch (err) { console.error('跳过配置失败:', err); - showMessage.error('操作失败'); + showMessage.error(t('common.operationFailed')); } }; @@ -159,10 +160,10 @@ function KnowledgeBaseCreate() { onClick={() => navigate('/knowledge')} sx={{ mr: 2 }} > - 返回 + {t('common.back')} - 创建知识库 + {t('knowledgeList.createKnowledgeBase')} @@ -198,11 +199,11 @@ function KnowledgeBaseCreate() { - 知识库已创建成功,现在可以配置解析设置 + {t('knowledgeConfiguration.createSuccessConfig')} - 您可以现在配置这些设置,也可以稍后在知识库详情页面中修改 + {t('knowledgeConfiguration.configLaterTip')} @@ -219,7 +220,7 @@ function KnowledgeBaseCreate() { variant="outlined" onClick={() => navigate('/knowledge')} > - 取消 + {t('common.cancel')} )} @@ -231,7 +232,7 @@ function KnowledgeBaseCreate() { disabled={isSubmitting} startIcon={} > - 跳过配置 + {t('knowledgeConfiguration.skipConfig')} )} @@ -239,19 +240,12 @@ function KnowledgeBaseCreate() { type="submit" variant="contained" disabled={isSubmitting} - startIcon={ - activeStep === steps.length - 1 ? ( - - ) : ( - - ) - } + startIcon={isSubmitting ? : null} > - {isSubmitting - ? '处理中...' - : activeStep === steps.length - 1 - ? '完成创建' - : '下一步'} + {isSubmitting + ? (activeStep === 0 ? t('knowledgeConfiguration.creating') : t('knowledgeConfiguration.configuring')) + : (activeStep === 0 ? t('knowledgeConfiguration.createAndNext') : t('knowledgeConfiguration.completeCreate')) + } diff --git a/src/pages/knowledge/detail.tsx b/src/pages/knowledge/detail.tsx index 17f1d86..7d989be 100644 --- a/src/pages/knowledge/detail.tsx +++ b/src/pages/knowledge/detail.tsx @@ -1,5 +1,6 @@ import React, { useState, useEffect, useRef } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { Box, Typography, @@ -38,6 +39,7 @@ import logger from '@/utils/logger'; function KnowledgeBaseDetail() { const { id } = useParams<{ id: string }>(); const navigate = useNavigate(); + const { t } = useTranslation(); // 状态管理 const [error, setError] = useState(null); @@ -114,20 +116,20 @@ function KnowledgeBaseDetail() { refreshFiles(); fetchKnowledgeDetail(); } catch (err: any) { - setError(err.response?.data?.message || err.message || '删除文件失败'); + setError(err.response?.data?.message || err.message || t('knowledgeDetails.deleteFileFailed')); } }; // 上传文件处理 const handleUploadFiles = async (uploadFiles: File[]) => { - console.log('上传文件:', uploadFiles); + console.log(t('knowledgeDetails.uploadFiles'), uploadFiles); const kb_id = knowledgeBase?.id || ''; try { await uploadDocuments(kb_id, uploadFiles); refreshFiles(); fetchKnowledgeDetail(); } catch (err: any) { - throw new Error(err.response?.data?.message || err.message || '上传文件失败'); + throw new Error(err.response?.data?.message || err.message || t('knowledgeDetails.uploadFileFailed')); } }; @@ -138,7 +140,7 @@ function KnowledgeBaseDetail() { refreshFiles(); // 刷新列表 startPolling(); // 开始轮询 } catch (err: any) { - setError(err.response?.data?.message || err.message || '重新解析失败'); + setError(err.response?.data?.message || err.message || t('knowledgeDetails.reparseFailed')); } }; @@ -148,7 +150,7 @@ function KnowledgeBaseDetail() { await renameDocument(fileId, newName); refreshFiles(); } catch (err: any) { - setError(err.response?.data?.message || err.message || '重命名失败'); + setError(err.response?.data?.message || err.message || t('knowledgeDetails.renameFailed')); } }; @@ -158,7 +160,7 @@ function KnowledgeBaseDetail() { await changeDocumentStatus(fileIds, status); refreshFiles(); } catch (err: any) { - setError(err.response?.data?.message || err.message || '更改状态失败'); + setError(err.response?.data?.message || err.message || t('knowledgeDetails.changeStatusFailed')); } }; @@ -168,20 +170,20 @@ function KnowledgeBaseDetail() { await cancelRunDocuments(fileIds); refreshFiles(); } catch (err: any) { - setError(err.response?.data?.message || err.message || '取消运行失败'); + setError(err.response?.data?.message || err.message || t('knowledgeDetails.cancelRunFailed')); } }; // 查看详情 const handleViewDetails = (file: IKnowledgeFile) => { - console.log("查看详情:", file); + console.log(t('knowledgeDetails.viewDetails'), file); navigate(`/chunk/parsed-result?kb_id=${id}&doc_id=${file.id}`); }; // 查看解析详情 const handleViewProcessDetails = (file: IKnowledgeFile) => { - console.log("查看解析详情:", file); + console.log(t('knowledgeDetails.viewProcessDetails'), file); setSelectedFileDetails(file); setProcessDetailsDialogOpen(true); }; @@ -262,7 +264,7 @@ function KnowledgeBaseDetail() { return ( - 加载中... + {t('common.loading')} ); } @@ -281,11 +283,11 @@ function KnowledgeBaseDetail() { setCurrentTab(newValue)} sx={{ borderBottom: 1, borderColor: 'divider' }} > - - + + {/* Document List 标签页内容 */} @@ -365,7 +367,7 @@ function KnowledgeBaseDetail() { onSearchChange={setSearchKeyword} onReparse={(fileIds) => handleReparse(fileIds)} onDelete={(fileIds) => { - console.log('删除文件:', fileIds); + console.log(t('knowledgeDetails.deleteFiles'), fileIds); setRowSelectionModel({ type: 'include', ids: new Set(fileIds) @@ -379,7 +381,7 @@ function KnowledgeBaseDetail() { }} rowSelectionModel={rowSelectionModel} onRowSelectionModelChange={(newModel) => { - console.log('新的选择模型:', newModel); + console.log(t('knowledgeDetails.newSelectionModel'), newModel); setRowSelectionModel(newModel); }} total={total} @@ -410,7 +412,7 @@ function KnowledgeBaseDetail() { open={uploadDialogOpen} onClose={() => setUploadDialogOpen(false)} onUpload={handleUploadFiles} - title="上传文件到知识库" + title={t('knowledgeDetails.uploadFilesToKnowledge')} acceptedFileTypes={['.pdf', '.docx', '.txt', '.md', '.png', '.jpg', '.jpeg', '.mp4', '.wav']} maxFileSize={100} maxFiles={10} @@ -418,15 +420,15 @@ function KnowledgeBaseDetail() { {/* 删除确认对话框 */} setDeleteDialogOpen(false)}> - 确认删除 + {t('knowledgeDetails.confirmDelete')} - 确定要删除选中的 {rowSelectionModel.ids.size} 个文件吗?此操作不可撤销。 + {t('knowledgeDetails.confirmDeleteMessage', { count: rowSelectionModel.ids.size })} - - + + @@ -437,7 +439,7 @@ function KnowledgeBaseDetail() { maxWidth="md" fullWidth > - 文档处理详情 + {t('knowledgeDetails.documentProcessDetails')} {selectedFileDetails && ( @@ -445,12 +447,12 @@ function KnowledgeBaseDetail() { - 基本信息 + {t('knowledgeDetails.basicInfo')} - 文件名 + {t('knowledgeDetails.fileName')} {selectedFileDetails.name} @@ -459,10 +461,10 @@ function KnowledgeBaseDetail() { - 解析器ID + {t('knowledgeDetails.parserId')} - {selectedFileDetails.parser_id || '未指定'} + {selectedFileDetails.parser_id || t('knowledgeDetails.notSpecified')} @@ -473,30 +475,30 @@ function KnowledgeBaseDetail() { - 处理状态 + {t('knowledgeDetails.processStatus')} - 开始时间 + {t('knowledgeDetails.startTime')} - {selectedFileDetails.process_begin_at || '未开始'} + {selectedFileDetails.process_begin_at || t('knowledgeDetails.notStarted')} - 处理时长 + {t('knowledgeDetails.processingTime')} - {selectedFileDetails.process_duration ? `${selectedFileDetails.process_duration}秒` : '未完成'} + {selectedFileDetails.process_duration ? `${selectedFileDetails.process_duration}${t('knowledgeDetails.seconds')}` : t('knowledgeDetails.notCompleted')} - 进度 + {t('knowledgeDetails.progress')} - 处理详情 + {t('knowledgeDetails.processDetails')} + {t('common.close')} + diff --git a/src/pages/knowledge/list.tsx b/src/pages/knowledge/list.tsx index 2de37ab..9b17cc0 100644 --- a/src/pages/knowledge/list.tsx +++ b/src/pages/knowledge/list.tsx @@ -25,8 +25,12 @@ import KnowledgeGridView from '@/components/knowledge/KnowledgeGridView'; import type { IKnowledge } from '@/interfaces/database/knowledge'; import { useDialog } from '@/hooks/useDialog'; import logger from '@/utils/logger'; +import { useTranslation } from 'react-i18next'; const KnowledgeBaseList: React.FC = () => { + + const {t} = useTranslation(); + const navigate = useNavigate(); const { deleteKnowledge } = useKnowledgeOperations(); @@ -95,8 +99,8 @@ const KnowledgeBaseList: React.FC = () => { const handleDeleteKnowledge = useCallback(async (kb: IKnowledge) => { // 需要确认删除 dialog.confirm({ - title: '确认删除', - content: `是否确认删除知识库 ${kb.name}?`, + title: t('common.deleteModalTitle'), + content: `${t('knowledgeList.confirmDeleteKnowledge')} ${kb.name}?`, onConfirm: async () => { try { await deleteKnowledge(kb.id); @@ -128,7 +132,7 @@ const KnowledgeBaseList: React.FC = () => { // 构建团队筛选选项 const teamFilterOptions = useMemo(() => { const options = [ - { value: 'all', label: '全部' }, + { value: 'all', label: t('common.all') }, ]; // 添加租户选项 @@ -149,7 +153,7 @@ const KnowledgeBaseList: React.FC = () => { {/* 页面标题 */} - 知识库管理 + {t('header.knowledgeBase')} {/* 搜索和筛选区域 */} handleSearch(e.target.value)} sx={{ flex: 1, maxWidth: 400 }} @@ -178,10 +182,10 @@ const KnowledgeBaseList: React.FC = () => { /> - 团队筛选 + {t('knowledgeList.teamFilter')} - 不使用重排序 + {t('knowledgeTesting.noRerank')} {rerankOptions.map((group) => [ {group.label}, @@ -345,9 +340,9 @@ function KnowledgeBaseTesting() { {watch('rerank_id') && ( )} - 跨语言搜索 + {t('knowledgeTesting.crossLanguageSearch')} } + label={t('knowledgeTesting.crossLanguageSearch')} + input={} renderValue={(selected) => ( {selected.map((value) => { @@ -399,7 +394,7 @@ function KnowledgeBaseTesting() { checked={watch('use_kg')} /> } - label="使用知识图谱" + label={t('knowledgeTesting.useKnowledgeGraph')} /> diff --git a/src/pages/knowledge/参考setting项目架构分析.md b/src/pages/knowledge/参考setting项目架构分析.md deleted file mode 100644 index 569e3ee..0000000 --- a/src/pages/knowledge/参考setting项目架构分析.md +++ /dev/null @@ -1,265 +0,0 @@ -# 参考Setting项目架构分析 - -## 项目概述 - -参考项目位于 `rag_web_core/src/pages/dataset/setting`,采用了基于 `react-hook-form` 和 `zod` 的现代表单管理架构,实现了高度模块化和可扩展的配置页面。 - -## 文件结构 - -``` -setting/ -├── index.tsx # 主页面入口 -├── form-schema.ts # 表单数据结构定义 -├── general-form.tsx # 通用表单组件 -├── chunk-method-form.tsx # 动态解析方法表单 -├── configuration-form-container.tsx # 表单容器组件 -├── hooks.ts # 数据获取和状态管理 -├── saving-button.tsx # 保存按钮组件 -├── permission-form-field.tsx # 权限表单字段 -├── tag-item.tsx # 标签项组件 -├── configuration/ # 配置组件目录 -│ ├── common-item.tsx # 通用配置项 -│ ├── naive.tsx # 通用解析配置 -│ ├── qa.tsx # Q&A解析配置 -│ ├── paper.tsx # 论文解析配置 -│ ├── book.tsx # 书籍解析配置 -│ ├── table.tsx # 表格解析配置 -│ ├── audio.tsx # 音频解析配置 -│ ├── email.tsx # 邮件解析配置 -│ ├── laws.tsx # 法律解析配置 -│ ├── manual.tsx # 手册解析配置 -│ ├── one.tsx # One解析配置 -│ ├── picture.tsx # 图片解析配置 -│ ├── presentation.tsx # 演示文稿解析配置 -│ ├── resume.tsx # 简历解析配置 -│ ├── knowledge-graph.tsx # 知识图谱配置 -│ └── tag.tsx # 标签配置 -└── tag-table/ # 标签表格相关组件 - └── ... -``` - -## 核心架构模式 - -### 1. 主从架构模式 (Master-Slave) -- **主控制器**: `index.tsx` 作为主页面,管理整体状态和表单实例 -- **从组件**: `general-form.tsx` 和 `chunk-method-form.tsx` 作为子表单组件 - -### 2. 策略模式 (Strategy Pattern) -- **配置映射**: `ConfigurationComponentMap` 根据 `parser_id` 动态选择配置组件 -- **组件策略**: 每个解析类型对应一个独立的配置组件 - -### 3. 模块化设计 -- **功能分离**: 通用配置与特定解析配置分离 -- **组件复用**: 通过 `configuration-form-container.tsx` 提供统一的布局容器 - -## 关键技术实现 - -### 1. 表单管理系统 - -#### 数据结构 (form-schema.ts) -```typescript -export const formSchema = z.object({ - name: z.string().min(1), - description: z.string().min(2), - avatar: z.any().nullish(), - permission: z.string().optional(), - parser_id: z.string(), - embd_id: z.string(), - parser_config: z.object({ - layout_recognize: z.string(), - chunk_token_num: z.number(), - delimiter: z.string(), - auto_keywords: z.number().optional(), - auto_questions: z.number().optional(), - html4excel: z.boolean(), - raptor: z.object({...}), - graphrag: z.object({...}), - }).optional(), - pagerank: z.number(), -}); -``` - -#### 表单初始化 -```typescript -const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - name: '', - parser_id: DocumentParserType.Naive, - permission: PermissionRole.Me, - parser_config: { - layout_recognize: DocumentType.DeepDOC, - chunk_token_num: 512, - delimiter: `\n`, - // ... 其他默认值 - }, - }, -}); -``` - -### 2. 动态表单系统 - -#### 配置组件映射 -```typescript -const ConfigurationComponentMap = { - [DocumentParserType.Naive]: NaiveConfiguration, - [DocumentParserType.Qa]: QAConfiguration, - [DocumentParserType.Resume]: ResumeConfiguration, - // ... 其他映射 -}; -``` - -#### 动态组件渲染 -```typescript -const ConfigurationComponent = useMemo(() => { - return finalParserId - ? ConfigurationComponentMap[finalParserId] - : EmptyComponent; -}, [finalParserId]); -``` - -### 3. 状态管理 - -#### 表单上下文共享 -- 使用 `useFormContext()` 在子组件中访问表单实例 -- 通过 `useWatch()` 监听特定字段变化 - -#### 数据获取钩子 -```typescript -// hooks.ts -export function useFetchKnowledgeConfigurationOnMount(form) { - // 获取知识库配置并初始化表单 -} -``` - -### 4. 页面布局结构 - -#### 主页面布局 (index.tsx) -```typescript -
- -
-
- - - - 通用 - 解析方法 - - - - - - - - -
- - -
-
-``` - -#### 表单容器组件 -```typescript -// configuration-form-container.tsx -export function ConfigurationFormContainer({ children, className }) { - return ( - - {children} - - ); -} - -export function MainContainer({ children }) { - return
{children}
; -} -``` - -## 组件设计模式 - -### 1. 通用表单组件 (general-form.tsx) -- 使用 `FormField` 组件统一表单字段样式 -- 采用 1/4 和 3/4 的标签与输入框布局比例 -- 集成头像上传、权限选择等通用功能 - -### 2. 配置组件模式 -每个配置组件遵循统一的结构: -```typescript -export function NaiveConfiguration() { - return ( - - - - - - - - - - - ); -} -``` - -### 3. 表单字段组件 -- 统一的 `FormField` 包装器 -- 一致的错误处理和验证显示 -- 响应式布局设计 - -## 技术特色 - -### 1. 类型安全 -- 使用 TypeScript 和 Zod 确保类型安全 -- 表单数据结构与后端API对齐 - -### 2. 性能优化 -- 使用 `useMemo` 优化动态组件渲染 -- `useWatch` 精确监听字段变化,避免不必要的重渲染 - -### 3. 可扩展性 -- 新增解析类型只需添加配置组件和映射关系 -- 模块化设计便于功能扩展 - -### 4. 用户体验 -- Tab切换提供清晰的功能分区 -- 右侧学习面板提供上下文帮助 -- 统一的保存和重置操作 - -## 与用户项目的对比 - -### 用户当前架构 -- 使用 Material-UI 组件库 -- 基于 `react-hook-form` 但结构相对简单 -- 单一的 Accordion 布局 -- 配置逻辑集中在一个组件中 - -### 参考项目优势 -- 更清晰的模块化分离 -- 动态配置组件系统 -- 更好的代码组织和可维护性 -- 统一的设计语言和布局模式 - -## 适配建议 - -### 1. 保持现有样式 -- 继续使用 Material-UI 组件 -- 保持 Accordion 布局风格 -- 适配现有的主题和设计规范 - -### 2. 采用核心架构 -- 引入动态配置组件映射机制 -- 分离通用配置和特定解析配置 -- 使用 `useFormContext` 实现组件间状态共享 - -### 3. 数据结构对齐 -- 参考 `form-schema.ts` 调整数据结构 -- 使用 Zod 进行数据验证 -- 统一默认值设置 - -### 4. 状态管理优化 -- 使用 `useWatch` 监听字段变化 -- 实现数据获取钩子 -- 优化表单重置和提交逻辑 - -这种架构设计为大型表单应用提供了良好的可维护性和扩展性基础,值得在用户项目中借鉴和应用。 \ No newline at end of file