feat(i18n): add internationalization support across multiple components
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
|||||||
InsertDriveFile as FileIcon,
|
InsertDriveFile as FileIcon,
|
||||||
Delete as DeleteIcon,
|
Delete as DeleteIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface FileUploadDialogProps {
|
interface FileUploadDialogProps {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
@@ -46,24 +47,27 @@ const FileUploadDialog: React.FC<FileUploadDialogProps> = ({
|
|||||||
acceptedFileTypes = ['.pdf', '.docx', '.txt', '.md', '.png', '.jpg', '.jpeg', '.mp4', '.wav'],
|
acceptedFileTypes = ['.pdf', '.docx', '.txt', '.md', '.png', '.jpg', '.jpeg', '.mp4', '.wav'],
|
||||||
maxFileSize = 100, // 100MB
|
maxFileSize = 100, // 100MB
|
||||||
maxFiles = 10,
|
maxFiles = 10,
|
||||||
title = '上传文件',
|
title,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [files, setFiles] = useState<UploadFile[]>([]);
|
const [files, setFiles] = useState<UploadFile[]>([]);
|
||||||
const [isDragOver, setIsDragOver] = useState(false);
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
|
const dialogTitle = title || t('fileUpload.uploadFiles');
|
||||||
|
|
||||||
const validateFile = (file: File): string | null => {
|
const validateFile = (file: File): string | null => {
|
||||||
// 检查文件大小
|
// 检查文件大小
|
||||||
if (file.size > maxFileSize * 1024 * 1024) {
|
if (file.size > maxFileSize * 1024 * 1024) {
|
||||||
return `文件大小不能超过 ${maxFileSize}MB`;
|
return t('fileUpload.fileSizeExceeded', { maxSize: maxFileSize });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查文件类型
|
// 检查文件类型
|
||||||
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
|
const fileExtension = '.' + file.name.split('.').pop()?.toLowerCase();
|
||||||
if (!acceptedFileTypes.includes(fileExtension)) {
|
if (!acceptedFileTypes.includes(fileExtension)) {
|
||||||
return `不支持的文件类型: ${fileExtension}`;
|
return t('fileUpload.unsupportedFileType', { extension: fileExtension });
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@@ -76,7 +80,7 @@ const FileUploadDialog: React.FC<FileUploadDialogProps> = ({
|
|||||||
|
|
||||||
// 检查文件数量限制
|
// 检查文件数量限制
|
||||||
if (files.length + fileArray.length > maxFiles) {
|
if (files.length + fileArray.length > maxFiles) {
|
||||||
setError(`最多只能上传 ${maxFiles} 个文件`);
|
setError(t('fileUpload.maxFilesExceeded', { maxFiles }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +95,7 @@ const FileUploadDialog: React.FC<FileUploadDialogProps> = ({
|
|||||||
// 检查是否已存在同名文件
|
// 检查是否已存在同名文件
|
||||||
const isDuplicate = files.some(f => f.file.name === file.name);
|
const isDuplicate = files.some(f => f.file.name === file.name);
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
setError(`文件 "${file.name}" 已存在`);
|
setError(t('fileUpload.duplicateFile', { fileName: file.name }));
|
||||||
hasError = true;
|
hasError = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -162,7 +166,7 @@ const FileUploadDialog: React.FC<FileUploadDialogProps> = ({
|
|||||||
setFiles([]);
|
setFiles([]);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setError(err instanceof Error ? err.message : '上传失败');
|
setError(err instanceof Error ? err.message : t('fileUpload.uploadFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setUploading(false);
|
setUploading(false);
|
||||||
}
|
}
|
||||||
@@ -186,7 +190,7 @@ const FileUploadDialog: React.FC<FileUploadDialogProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
|
<Dialog open={open} onClose={handleClose} maxWidth="md" fullWidth>
|
||||||
<DialogTitle>{title}</DialogTitle>
|
<DialogTitle>{dialogTitle}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
{error && (
|
{error && (
|
||||||
<Alert severity="error" sx={{ mb: 2 }}>
|
<Alert severity="error" sx={{ mb: 2 }}>
|
||||||
@@ -217,13 +221,13 @@ const FileUploadDialog: React.FC<FileUploadDialogProps> = ({
|
|||||||
>
|
>
|
||||||
<CloudUploadIcon sx={{ fontSize: 48, color: 'text.secondary', mb: 2 }} />
|
<CloudUploadIcon sx={{ fontSize: 48, color: 'text.secondary', mb: 2 }} />
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
{isDragOver ? '释放文件到此处' : '拖拽文件到此处或点击上传'}
|
{isDragOver ? t('fileUpload.dropFilesHere') : t('fileUpload.dragOrClickToUpload')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
支持格式: {acceptedFileTypes.join(', ')}
|
{t('fileUpload.supportedFormats')}: {acceptedFileTypes.join(', ')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
最大文件大小: {maxFileSize}MB,最多 {maxFiles} 个文件
|
{t('fileUpload.fileLimits', { maxSize: maxFileSize, maxFiles })}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -240,7 +244,7 @@ const FileUploadDialog: React.FC<FileUploadDialogProps> = ({
|
|||||||
{files.length > 0 && (
|
{files.length > 0 && (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle2" gutterBottom>
|
<Typography variant="subtitle2" gutterBottom>
|
||||||
已选择的文件 ({files.length}/{maxFiles})
|
{t('fileUpload.selectedFiles', { current: files.length, max: maxFiles })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<List dense>
|
<List dense>
|
||||||
{files.map((uploadFile) => (
|
{files.map((uploadFile) => (
|
||||||
@@ -270,7 +274,7 @@ const FileUploadDialog: React.FC<FileUploadDialogProps> = ({
|
|||||||
{uploading && (
|
{uploading && (
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
<Typography variant="body2" gutterBottom>
|
<Typography variant="body2" gutterBottom>
|
||||||
正在上传...
|
{t('fileUpload.uploading')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<LinearProgress />
|
<LinearProgress />
|
||||||
</Box>
|
</Box>
|
||||||
@@ -278,14 +282,14 @@ const FileUploadDialog: React.FC<FileUploadDialogProps> = ({
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={handleClose} disabled={uploading}>
|
<Button onClick={handleClose} disabled={uploading}>
|
||||||
取消
|
{t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleUpload}
|
onClick={handleUpload}
|
||||||
disabled={files.length === 0 || uploading}
|
disabled={files.length === 0 || uploading}
|
||||||
>
|
>
|
||||||
{uploading ? '上传中...' : `上传 (${files.length})`}
|
{uploading ? t('fileUpload.uploading') : t('fileUpload.uploadFiles', { count: files.length })}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Add as AddIcon } from '@mui/icons-material';
|
import { Add as AddIcon } from '@mui/icons-material';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export interface ChipListFormFieldProps {
|
export interface ChipListFormFieldProps {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -35,13 +36,16 @@ export const ChipListFormField: React.FC<ChipListFormFieldProps> = ({
|
|||||||
allowAdd = true,
|
allowAdd = true,
|
||||||
allowDelete = true,
|
allowDelete = true,
|
||||||
maxChips,
|
maxChips,
|
||||||
placeholder = '输入后按回车添加',
|
placeholder,
|
||||||
chipColor = 'default',
|
chipColor = 'default',
|
||||||
chipVariant = 'outlined',
|
chipVariant = 'outlined',
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { control, setValue, getValues } = useFormContext();
|
const { control, setValue, getValues } = useFormContext();
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
|
|
||||||
|
const defaultPlaceholder = placeholder || t('form.chipListPlaceholder');
|
||||||
|
|
||||||
const handleAddChip = (field: any) => {
|
const handleAddChip = (field: any) => {
|
||||||
if (inputValue.trim() && (!maxChips || field.value.length < maxChips)) {
|
if (inputValue.trim() && (!maxChips || field.value.length < maxChips)) {
|
||||||
const newChips = [...field.value, inputValue.trim()];
|
const newChips = [...field.value, inputValue.trim()];
|
||||||
@@ -75,10 +79,10 @@ export const ChipListFormField: React.FC<ChipListFormFieldProps> = ({
|
|||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
rules={{
|
rules={{
|
||||||
required: required ? `${label}是必填项` : false,
|
required: required ? t('form.fieldRequired', { field: label }) : false,
|
||||||
validate: (value) => {
|
validate: (value) => {
|
||||||
if (maxChips && value.length > maxChips) {
|
if (maxChips && value.length > maxChips) {
|
||||||
return `${label}最多只能有${maxChips}个标签`;
|
return t('form.maxChipsExceeded', { field: label, max: maxChips });
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@@ -99,7 +103,7 @@ export const ChipListFormField: React.FC<ChipListFormFieldProps> = ({
|
|||||||
))}
|
))}
|
||||||
{field.value.length === 0 && (
|
{field.value.length === 0 && (
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ py: 1 }}>
|
||||||
暂无标签
|
{t('form.noChips')}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -111,7 +115,7 @@ export const ChipListFormField: React.FC<ChipListFormFieldProps> = ({
|
|||||||
value={inputValue}
|
value={inputValue}
|
||||||
onChange={(e) => setInputValue(e.target.value)}
|
onChange={(e) => setInputValue(e.target.value)}
|
||||||
onKeyPress={(e) => handleKeyPress(e, field)}
|
onKeyPress={(e) => handleKeyPress(e, field)}
|
||||||
placeholder={placeholder}
|
placeholder={defaultPlaceholder}
|
||||||
size="small"
|
size="small"
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
TextField,
|
TextField,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export interface MultilineTextFormFieldProps {
|
export interface MultilineTextFormFieldProps {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -38,6 +39,7 @@ export const MultilineTextFormField: React.FC<MultilineTextFormFieldProps> = ({
|
|||||||
maxLength,
|
maxLength,
|
||||||
}) => {
|
}) => {
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -50,10 +52,10 @@ export const MultilineTextFormField: React.FC<MultilineTextFormFieldProps> = ({
|
|||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
rules={{
|
rules={{
|
||||||
required: required ? `${label}是必填项` : false,
|
required: required ? t('form.fieldRequired', { field: label }) : false,
|
||||||
maxLength: maxLength ? {
|
maxLength: maxLength ? {
|
||||||
value: maxLength,
|
value: maxLength,
|
||||||
message: `${label}不能超过${maxLength}个字符`
|
message: t('form.maxLengthExceeded', { field: label, maxLength })
|
||||||
} : undefined,
|
} : undefined,
|
||||||
}}
|
}}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Add as AddIcon } from '@mui/icons-material';
|
import { Add as AddIcon } from '@mui/icons-material';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export interface NumberInputFormFieldProps {
|
export interface NumberInputFormFieldProps {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -42,6 +43,7 @@ export const NumberInputFormField: React.FC<NumberInputFormFieldProps> = ({
|
|||||||
onRandomClick,
|
onRandomClick,
|
||||||
}) => {
|
}) => {
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleRandomClick = () => {
|
const handleRandomClick = () => {
|
||||||
if (onRandomClick) {
|
if (onRandomClick) {
|
||||||
@@ -64,14 +66,14 @@ export const NumberInputFormField: React.FC<NumberInputFormFieldProps> = ({
|
|||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
rules={{
|
rules={{
|
||||||
required: required ? `${label}是必填项` : false,
|
required: required ? t('form.fieldRequired', { field: label }) : false,
|
||||||
min: min !== undefined ? {
|
min: min !== undefined ? {
|
||||||
value: min,
|
value: min,
|
||||||
message: `${label}不能小于${min}`
|
message: t('form.minValueExceeded', { field: label, min })
|
||||||
} : undefined,
|
} : undefined,
|
||||||
max: max !== undefined ? {
|
max: max !== undefined ? {
|
||||||
value: max,
|
value: max,
|
||||||
message: `${label}不能大于${max}`
|
message: t('form.maxValueExceeded', { field: label, max })
|
||||||
} : undefined,
|
} : undefined,
|
||||||
}}
|
}}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
InputLabel,
|
InputLabel,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export interface SelectOption {
|
export interface SelectOption {
|
||||||
value: string | number;
|
value: string | number;
|
||||||
@@ -42,10 +43,13 @@ export const SelectFormField: React.FC<SelectFormFieldProps> = ({
|
|||||||
fullWidth = true,
|
fullWidth = true,
|
||||||
size = 'medium',
|
size = 'medium',
|
||||||
displayEmpty = true,
|
displayEmpty = true,
|
||||||
emptyLabel = '请选择',
|
emptyLabel,
|
||||||
multiple = false,
|
multiple = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const defaultEmptyLabel = emptyLabel || t('common.pleaseSelect');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -58,7 +62,7 @@ export const SelectFormField: React.FC<SelectFormFieldProps> = ({
|
|||||||
control={control}
|
control={control}
|
||||||
defaultValue={multiple ? [] : defaultValue}
|
defaultValue={multiple ? [] : defaultValue}
|
||||||
rules={{
|
rules={{
|
||||||
required: required ? `${label}是必填项` : false,
|
required: required ? t('form.fieldRequired', { field: label }) : false,
|
||||||
}}
|
}}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<FormControl fullWidth={fullWidth} error={!!error} size={size}>
|
<FormControl fullWidth={fullWidth} error={!!error} size={size}>
|
||||||
@@ -70,7 +74,7 @@ export const SelectFormField: React.FC<SelectFormFieldProps> = ({
|
|||||||
>
|
>
|
||||||
{displayEmpty && !multiple && (
|
{displayEmpty && !multiple && (
|
||||||
<MenuItem value="">
|
<MenuItem value="">
|
||||||
{emptyLabel}
|
{defaultEmptyLabel}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
FormHelperText,
|
FormHelperText,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Controller, useFormContext } from 'react-hook-form';
|
import { Controller, useFormContext } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export interface SwitchFormFieldProps {
|
export interface SwitchFormFieldProps {
|
||||||
name: string;
|
name: string;
|
||||||
@@ -30,6 +31,7 @@ export const SwitchFormField: React.FC<SwitchFormFieldProps> = ({
|
|||||||
color = 'primary',
|
color = 'primary',
|
||||||
}) => {
|
}) => {
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
@@ -38,7 +40,7 @@ export const SwitchFormField: React.FC<SwitchFormFieldProps> = ({
|
|||||||
control={control}
|
control={control}
|
||||||
defaultValue={defaultValue}
|
defaultValue={defaultValue}
|
||||||
rules={{
|
rules={{
|
||||||
required: required ? `${label}是必填项` : false,
|
required: required ? t('form.fieldRequired', { field: label }) : false,
|
||||||
}}
|
}}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -10,10 +10,10 @@ import ExtensionOutlinedIcon from '@mui/icons-material/ExtensionOutlined';
|
|||||||
const navItems = [
|
const navItems = [
|
||||||
// { text: 'Overview', path: '/', icon: DashboardOutlinedIcon },
|
// { text: 'Overview', path: '/', icon: DashboardOutlinedIcon },
|
||||||
{ text: 'Knowledge Bases', path: '/', icon: LibraryBooksOutlinedIcon },
|
{ text: 'Knowledge Bases', path: '/', icon: LibraryBooksOutlinedIcon },
|
||||||
{ text: 'RAG Pipeline', path: '/pipeline-config', icon: AccountTreeOutlinedIcon },
|
// { text: 'RAG Pipeline', path: '/pipeline-config', icon: AccountTreeOutlinedIcon },
|
||||||
{ text: 'Operations', path: '/dashboard', icon: SettingsOutlinedIcon },
|
// { text: 'Operations', path: '/dashboard', icon: SettingsOutlinedIcon },
|
||||||
{ text: 'Models & Resources', path: '/models-resources', icon: StorageOutlinedIcon },
|
// { text: 'Models & Resources', path: '/models-resources', icon: StorageOutlinedIcon },
|
||||||
{ text: 'MCP', path: '/mcp', icon: ExtensionOutlinedIcon },
|
// { text: 'MCP', path: '/mcp', icon: ExtensionOutlinedIcon },
|
||||||
];
|
];
|
||||||
|
|
||||||
const Sidebar = () => {
|
const Sidebar = () => {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
Error as ErrorIcon,
|
Error as ErrorIcon,
|
||||||
Help as ConfirmIcon,
|
Help as ConfirmIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { type IDialogInstance } from '../../interfaces/common';
|
import { type IDialogInstance } from '../../interfaces/common';
|
||||||
|
|
||||||
interface DialogComponentProps {
|
interface DialogComponentProps {
|
||||||
@@ -26,6 +27,7 @@ interface DialogComponentProps {
|
|||||||
|
|
||||||
const DialogComponent: React.FC<DialogComponentProps> = ({ dialog, onClose }) => {
|
const DialogComponent: React.FC<DialogComponentProps> = ({ dialog, onClose }) => {
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
const { t } = useTranslation();
|
||||||
const { config } = dialog;
|
const { config } = dialog;
|
||||||
|
|
||||||
// 获取对话框图标
|
// 获取对话框图标
|
||||||
@@ -118,7 +120,7 @@ const DialogComponent: React.FC<DialogComponentProps> = ({ dialog, onClose }) =>
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', flex: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', flex: 1 }}>
|
||||||
{getDialogIcon()}
|
{getDialogIcon()}
|
||||||
<Typography variant="h6" component="span">
|
<Typography variant="h6" component="span">
|
||||||
{config.title || '提示'}
|
{config.title || t('dialog.defaultTitle')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -145,7 +147,7 @@ const DialogComponent: React.FC<DialogComponentProps> = ({ dialog, onClose }) =>
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
{config.cancelText || '取消'}
|
{config.cancelText || t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
@@ -155,7 +157,7 @@ const DialogComponent: React.FC<DialogComponentProps> = ({ dialog, onClose }) =>
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
sx={{ ml: 1 }}
|
sx={{ ml: 1 }}
|
||||||
>
|
>
|
||||||
{loading ? '处理中...' : (config.confirmText || '确定')}
|
{loading ? t('dialog.processing') : (config.confirmText || t('common.confirm'))}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { createContext, useContext, useState, useCallback, type PropsWithChildren } from 'react';
|
import React, { createContext, useContext, useState, useCallback, type PropsWithChildren } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { IDialogConfig, IDialogInstance, IDialogContextValue } from '../../interfaces/common';
|
import type { IDialogConfig, IDialogInstance, IDialogContextValue } from '../../interfaces/common';
|
||||||
import DialogComponent from './DialogComponent';
|
import DialogComponent from './DialogComponent';
|
||||||
|
|
||||||
@@ -10,6 +11,7 @@ const generateId = () => `dialog_${Date.now()}_${Math.random().toString(36).subs
|
|||||||
|
|
||||||
export const DialogProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
export const DialogProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
||||||
const [dialogs, setDialogs] = useState<IDialogInstance[]>([]);
|
const [dialogs, setDialogs] = useState<IDialogInstance[]>([]);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// 打开对话框的通用方法
|
// 打开对话框的通用方法
|
||||||
const openDialog = useCallback((config: IDialogConfig): Promise<boolean> => {
|
const openDialog = useCallback((config: IDialogConfig): Promise<boolean> => {
|
||||||
@@ -43,10 +45,10 @@ export const DialogProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|||||||
...config,
|
...config,
|
||||||
type: 'confirm',
|
type: 'confirm',
|
||||||
showCancel: true,
|
showCancel: true,
|
||||||
confirmText: config.confirmText || '确定',
|
confirmText: config.confirmText || t('common.confirm'),
|
||||||
cancelText: config.cancelText || '取消',
|
cancelText: config.cancelText || t('common.cancel'),
|
||||||
});
|
});
|
||||||
}, [openDialog]);
|
}, [openDialog, t]);
|
||||||
|
|
||||||
// 信息对话框
|
// 信息对话框
|
||||||
const info = useCallback((config: Omit<IDialogConfig, 'type'>): Promise<boolean> => {
|
const info = useCallback((config: Omit<IDialogConfig, 'type'>): Promise<boolean> => {
|
||||||
@@ -54,9 +56,9 @@ export const DialogProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|||||||
...config,
|
...config,
|
||||||
type: 'info',
|
type: 'info',
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: config.confirmText || '确定',
|
confirmText: config.confirmText || t('common.confirm'),
|
||||||
});
|
});
|
||||||
}, [openDialog]);
|
}, [openDialog, t]);
|
||||||
|
|
||||||
// 成功对话框
|
// 成功对话框
|
||||||
const success = useCallback((config: Omit<IDialogConfig, 'type'>): Promise<boolean> => {
|
const success = useCallback((config: Omit<IDialogConfig, 'type'>): Promise<boolean> => {
|
||||||
@@ -64,9 +66,9 @@ export const DialogProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|||||||
...config,
|
...config,
|
||||||
type: 'success',
|
type: 'success',
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: config.confirmText || '确定',
|
confirmText: config.confirmText || t('common.confirm'),
|
||||||
});
|
});
|
||||||
}, [openDialog]);
|
}, [openDialog, t]);
|
||||||
|
|
||||||
// 警告对话框
|
// 警告对话框
|
||||||
const warning = useCallback((config: Omit<IDialogConfig, 'type'>): Promise<boolean> => {
|
const warning = useCallback((config: Omit<IDialogConfig, 'type'>): Promise<boolean> => {
|
||||||
@@ -74,9 +76,9 @@ export const DialogProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|||||||
...config,
|
...config,
|
||||||
type: 'warning',
|
type: 'warning',
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: config.confirmText || '确定',
|
confirmText: config.confirmText || t('common.confirm'),
|
||||||
});
|
});
|
||||||
}, [openDialog]);
|
}, [openDialog, t]);
|
||||||
|
|
||||||
// 错误对话框
|
// 错误对话框
|
||||||
const error = useCallback((config: Omit<IDialogConfig, 'type'>): Promise<boolean> => {
|
const error = useCallback((config: Omit<IDialogConfig, 'type'>): Promise<boolean> => {
|
||||||
@@ -84,9 +86,9 @@ export const DialogProvider: React.FC<PropsWithChildren> = ({ children }) => {
|
|||||||
...config,
|
...config,
|
||||||
type: 'error',
|
type: 'error',
|
||||||
showCancel: false,
|
showCancel: false,
|
||||||
confirmText: config.confirmText || '确定',
|
confirmText: config.confirmText || t('common.confirm'),
|
||||||
});
|
});
|
||||||
}, [openDialog]);
|
}, [openDialog, t]);
|
||||||
|
|
||||||
const contextValue: IDialogContextValue = {
|
const contextValue: IDialogContextValue = {
|
||||||
dialogs,
|
dialogs,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
ArrowForward as ArrowForwardIcon,
|
ArrowForward as ArrowForwardIcon,
|
||||||
Add as AddIcon,
|
Add as AddIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { IKnowledge } from '@/interfaces/database/knowledge';
|
import type { IKnowledge } from '@/interfaces/database/knowledge';
|
||||||
|
|
||||||
interface KnowledgeCardProps {
|
interface KnowledgeCardProps {
|
||||||
@@ -27,14 +28,16 @@ interface KnowledgeCardProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const KnowledgeCard: React.FC<KnowledgeCardProps> = ({ knowledge, onMenuClick, onViewKnowledge }) => {
|
const KnowledgeCard: React.FC<KnowledgeCardProps> = ({ knowledge, onMenuClick, onViewKnowledge }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const getStatusInfo = (permission: string) => {
|
const getStatusInfo = (permission: string) => {
|
||||||
switch (permission) {
|
switch (permission) {
|
||||||
case 'me':
|
case 'me':
|
||||||
return { label: '私有', color: '#E3F2FD', textColor: '#1976D2' };
|
return { label: t('common.private'), color: '#E3F2FD', textColor: '#1976D2' };
|
||||||
case 'team':
|
case 'team':
|
||||||
return { label: '团队', color: '#E8F5E8', textColor: '#388E3C' };
|
return { label: t('common.team'), color: '#E8F5E8', textColor: '#388E3C' };
|
||||||
default:
|
default:
|
||||||
return { label: '公开', color: '#FFF3E0', textColor: '#F57C00' };
|
return { label: t('common.public'), color: '#FFF3E0', textColor: '#F57C00' };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -42,7 +45,7 @@ const KnowledgeCard: React.FC<KnowledgeCardProps> = ({ knowledge, onMenuClick, o
|
|||||||
|
|
||||||
// 格式化更新时间
|
// 格式化更新时间
|
||||||
const formatUpdateTime = (timestamp: number) => {
|
const formatUpdateTime = (timestamp: number) => {
|
||||||
if (!timestamp) return '未知';
|
if (!timestamp) return t('common.unknown');
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
return date.toLocaleDateString('zh-CN', {
|
return date.toLocaleDateString('zh-CN', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
@@ -123,7 +126,7 @@ const KnowledgeCard: React.FC<KnowledgeCardProps> = ({ knowledge, onMenuClick, o
|
|||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{knowledge.description || '暂无描述'}
|
{knowledge.description || t('common.noDescription')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
@@ -144,7 +147,7 @@ const KnowledgeCard: React.FC<KnowledgeCardProps> = ({ knowledge, onMenuClick, o
|
|||||||
{knowledge.doc_num || 0}
|
{knowledge.doc_num || 0}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
文档数量
|
{t('knowledge.documentCount')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ textAlign: 'center' }}>
|
<Box sx={{ textAlign: 'center' }}>
|
||||||
@@ -155,7 +158,7 @@ const KnowledgeCard: React.FC<KnowledgeCardProps> = ({ knowledge, onMenuClick, o
|
|||||||
{knowledge.chunk_num || 0}
|
{knowledge.chunk_num || 0}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
分块数量
|
{t('knowledge.chunkCount')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Box sx={{ textAlign: 'center' }}>
|
<Box sx={{ textAlign: 'center' }}>
|
||||||
@@ -166,27 +169,27 @@ const KnowledgeCard: React.FC<KnowledgeCardProps> = ({ knowledge, onMenuClick, o
|
|||||||
{formatTokenNum(knowledge.token_num || 0)}
|
{formatTokenNum(knowledge.token_num || 0)}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography variant="caption" color="text.secondary">
|
||||||
Token数量
|
{t('knowledge.tokenCount')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 显示更新时间 */}
|
{/* 显示更新时间 */}
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ mt: 1, display: 'block' }}>
|
||||||
最后更新: {formatUpdateTime(knowledge.update_time)}
|
{t('knowledge.lastUpdate')}: {formatUpdateTime(knowledge.update_time)}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
{/* 显示创建者 */}
|
{/* 显示创建者 */}
|
||||||
{knowledge.nickname && (
|
{knowledge.nickname && (
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>
|
||||||
创建者: {knowledge.nickname}
|
{t('knowledge.creator')}: {knowledge.nickname}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 显示语言 */}
|
{/* 显示语言 */}
|
||||||
{knowledge.language && (
|
{knowledge.language && (
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ display: 'block' }}>
|
||||||
语言: {knowledge.language}
|
{t('common.language')}: {knowledge.language}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import {
|
|||||||
ArrowForward as ArrowForwardIcon,
|
ArrowForward as ArrowForwardIcon,
|
||||||
Add as AddIcon,
|
Add as AddIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { IKnowledge } from '@/interfaces/database/knowledge';
|
import type { IKnowledge } from '@/interfaces/database/knowledge';
|
||||||
import KnowledgeCard from './KnowledgeCard';
|
import KnowledgeCard from './KnowledgeCard';
|
||||||
|
|
||||||
@@ -50,6 +51,7 @@ const KnowledgeGridView: React.FC<KnowledgeGridViewProps> = ({
|
|||||||
teamFilter = 'all',
|
teamFilter = 'all',
|
||||||
onCreateKnowledge,
|
onCreateKnowledge,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||||
const [selectedKB, setSelectedKB] = React.useState<IKnowledge | null>(null);
|
const [selectedKB, setSelectedKB] = React.useState<IKnowledge | null>(null);
|
||||||
|
|
||||||
@@ -90,7 +92,7 @@ const KnowledgeGridView: React.FC<KnowledgeGridViewProps> = ({
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||||
<Typography>加载中...</Typography>
|
<Typography>{t('common.loading')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -100,12 +102,12 @@ const KnowledgeGridView: React.FC<KnowledgeGridViewProps> = ({
|
|||||||
<Box sx={{ textAlign: 'center', py: 8 }}>
|
<Box sx={{ textAlign: 'center', py: 8 }}>
|
||||||
<FolderIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
<FolderIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
|
||||||
<Typography variant="h6" color="text.secondary" gutterBottom>
|
<Typography variant="h6" color="text.secondary" gutterBottom>
|
||||||
{searchTerm || teamFilter !== 'all' ? '没有找到匹配的知识库' : '暂无知识库'}
|
{searchTerm || teamFilter !== 'all' ? t('knowledge.noMatchingKnowledgeBases') : t('knowledge.noKnowledgeBases')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
{searchTerm || teamFilter !== 'all'
|
{searchTerm || teamFilter !== 'all'
|
||||||
? '尝试调整搜索条件或筛选器'
|
? t('knowledge.tryAdjustingFilters')
|
||||||
: '创建您的第一个知识库开始使用'
|
: t('knowledge.createFirstKnowledgeBase')
|
||||||
}
|
}
|
||||||
</Typography>
|
</Typography>
|
||||||
{(!searchTerm && teamFilter === 'all' && onCreateKnowledge) && (
|
{(!searchTerm && teamFilter === 'all' && onCreateKnowledge) && (
|
||||||
@@ -114,7 +116,7 @@ const KnowledgeGridView: React.FC<KnowledgeGridViewProps> = ({
|
|||||||
startIcon={<AddIcon />}
|
startIcon={<AddIcon />}
|
||||||
onClick={onCreateKnowledge}
|
onClick={onCreateKnowledge}
|
||||||
>
|
>
|
||||||
新建知识库
|
{t('knowledge.createKnowledgeBase')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -139,7 +141,7 @@ const KnowledgeGridView: React.FC<KnowledgeGridViewProps> = ({
|
|||||||
onClick={onSeeAll}
|
onClick={onSeeAll}
|
||||||
sx={{ borderRadius: 2 }}
|
sx={{ borderRadius: 2 }}
|
||||||
>
|
>
|
||||||
查看全部 ({knowledgeBases.length})
|
{t('common.viewAll')} ({knowledgeBases.length})
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -149,9 +151,9 @@ const KnowledgeGridView: React.FC<KnowledgeGridViewProps> = ({
|
|||||||
open={Boolean(anchorEl)}
|
open={Boolean(anchorEl)}
|
||||||
onClose={handleMenuClose}
|
onClose={handleMenuClose}
|
||||||
>
|
>
|
||||||
<MenuItem onClick={handleView}>查看详情</MenuItem>
|
<MenuItem onClick={handleView}>{t('common.viewDetails')}</MenuItem>
|
||||||
<MenuItem onClick={handleDelete} sx={{ color: 'error.main' }}>
|
<MenuItem onClick={handleDelete} sx={{ color: 'error.main' }}>
|
||||||
删除
|
{t('common.delete')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import knowledgeService from '@/services/knowledge_service';
|
import knowledgeService from '@/services/knowledge_service';
|
||||||
import type { IKnowledge, IKnowledgeGraph, IKnowledgeResult } from '@/interfaces/database/knowledge';
|
import type { IKnowledge, IKnowledgeGraph, IKnowledgeResult } from '@/interfaces/database/knowledge';
|
||||||
import type { IFetchKnowledgeListRequestBody, IFetchKnowledgeListRequestParams } from '@/interfaces/request/knowledge';
|
import type { IFetchKnowledgeListRequestBody, IFetchKnowledgeListRequestParams } from '@/interfaces/request/knowledge';
|
||||||
@@ -80,6 +81,7 @@ export interface UseKnowledgeListReturn extends UseKnowledgeListState {
|
|||||||
export const useKnowledgeList = (
|
export const useKnowledgeList = (
|
||||||
initialParams?: IFetchKnowledgeListRequestParams & IFetchKnowledgeListRequestBody
|
initialParams?: IFetchKnowledgeListRequestParams & IFetchKnowledgeListRequestBody
|
||||||
): UseKnowledgeListReturn => {
|
): UseKnowledgeListReturn => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [knowledgeBases, setKnowledgeBases] = useState<IKnowledge[]>([]);
|
const [knowledgeBases, setKnowledgeBases] = useState<IKnowledge[]>([]);
|
||||||
const [total, setTotal] = useState(0);
|
const [total, setTotal] = useState(0);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -137,10 +139,10 @@ export const useKnowledgeList = (
|
|||||||
setKnowledgeBases(data.kbs || []);
|
setKnowledgeBases(data.kbs || []);
|
||||||
setTotal(data.total || 0);
|
setTotal(data.total || 0);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.data.message || '获取知识库列表失败');
|
throw new Error(response.data.message || t('knowledgeHooks.fetchKnowledgeListFailed'));
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage = err.response?.data?.message || err.message || '获取知识库列表失败';
|
const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.fetchKnowledgeListFailed');
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
console.error('Failed to fetch knowledge bases:', err);
|
console.error('Failed to fetch knowledge bases:', err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -210,6 +212,7 @@ export const useKnowledgeList = (
|
|||||||
* 知识库详情Hook
|
* 知识库详情Hook
|
||||||
*/
|
*/
|
||||||
export const useKnowledgeDetail = (kbId: string) => {
|
export const useKnowledgeDetail = (kbId: string) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [knowledge, setKnowledge] = useState<IKnowledge | null>(null);
|
const [knowledge, setKnowledge] = useState<IKnowledge | null>(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -232,10 +235,10 @@ export const useKnowledgeDetail = (kbId: string) => {
|
|||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
setKnowledge(response.data.data);
|
setKnowledge(response.data.data);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.data.message || '获取知识库详情失败');
|
throw new Error(response.data.message || t('knowledgeHooks.fetchKnowledgeDetailFailed'));
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage = err.response?.data?.message || err.message || '获取知识库详情失败';
|
const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.fetchKnowledgeDetailFailed');
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
console.error('Failed to fetch knowledge detail:', err);
|
console.error('Failed to fetch knowledge detail:', err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -255,10 +258,10 @@ export const useKnowledgeDetail = (kbId: string) => {
|
|||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
setKnowledgeGraph(response.data.data);
|
setKnowledgeGraph(response.data.data);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.data.message || '获取知识库图失败');
|
throw new Error(response.data.message || t('knowledgeHooks.fetchKnowledgeGraphFailed'));
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage = err.response?.data?.message || err.message || '获取知识库图失败';
|
const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.fetchKnowledgeGraphFailed');
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
console.error('Failed to fetch knowledge graph:', err);
|
console.error('Failed to fetch knowledge graph:', err);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -286,6 +289,7 @@ export const useKnowledgeDetail = (kbId: string) => {
|
|||||||
* 提供创建、更新、删除知识库的功能
|
* 提供创建、更新、删除知识库的功能
|
||||||
*/
|
*/
|
||||||
export const useKnowledgeOperations = () => {
|
export const useKnowledgeOperations = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -302,10 +306,10 @@ export const useKnowledgeOperations = () => {
|
|||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.data.message || '创建知识库失败');
|
throw new Error(response.data.message || t('knowledgeHooks.createKnowledgeFailed'));
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage = err.response?.data?.message || err.message || '创建知识库失败';
|
const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.createKnowledgeFailed');
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
console.error('Failed to create knowledge:', err);
|
console.error('Failed to create knowledge:', err);
|
||||||
throw err;
|
throw err;
|
||||||
@@ -330,10 +334,10 @@ export const useKnowledgeOperations = () => {
|
|||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.data.message || '更新知识库基础信息失败');
|
throw new Error(response.data.message || t('knowledgeHooks.updateKnowledgeBasicInfoFailed'));
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage = err.response?.data?.message || err.message || '更新知识库基础信息失败';
|
const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.updateKnowledgeBasicInfoFailed');
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
console.error('Failed to update knowledge basic info:', err);
|
console.error('Failed to update knowledge basic info:', err);
|
||||||
throw err;
|
throw err;
|
||||||
@@ -362,10 +366,10 @@ export const useKnowledgeOperations = () => {
|
|||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.data.message || '更新知识库模型配置失败');
|
throw new Error(response.data.message || t('knowledgeHooks.updateKnowledgeModelConfigFailed'));
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage = err.response?.data?.message || err.message || '更新知识库模型配置失败';
|
const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.updateKnowledgeModelConfigFailed');
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
console.error('Failed to update knowledge model config:', err);
|
console.error('Failed to update knowledge model config:', err);
|
||||||
throw err;
|
throw err;
|
||||||
@@ -387,10 +391,10 @@ export const useKnowledgeOperations = () => {
|
|||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.data.message || '删除知识库失败');
|
throw new Error(response.data.message || t('knowledgeHooks.deleteKnowledgeFailed'));
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage = err.response?.data?.message || err.message || '删除知识库失败';
|
const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.deleteKnowledgeFailed');
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
console.error('Failed to delete knowledge:', err);
|
console.error('Failed to delete knowledge:', err);
|
||||||
throw err;
|
throw err;
|
||||||
@@ -422,6 +426,7 @@ export const useKnowledgeOperations = () => {
|
|||||||
* 提供批量删除等功能
|
* 提供批量删除等功能
|
||||||
*/
|
*/
|
||||||
export const useKnowledgeBatchOperations = () => {
|
export const useKnowledgeBatchOperations = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
@@ -443,12 +448,12 @@ export const useKnowledgeBatchOperations = () => {
|
|||||||
.map(({ index }) => kbIds[index]);
|
.map(({ index }) => kbIds[index]);
|
||||||
|
|
||||||
if (failures.length > 0) {
|
if (failures.length > 0) {
|
||||||
throw new Error(`删除失败的知识库: ${failures.join(', ')}`);
|
throw new Error(`${t('knowledgeHooks.batchDeleteFailedKnowledgeBases')}: ${failures.join(', ')}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
const errorMessage = err.response?.data?.message || err.message || '批量删除知识库失败';
|
const errorMessage = err.response?.data?.message || err.message || t('knowledgeHooks.batchDeleteKnowledgeFailed');
|
||||||
setError(errorMessage);
|
setError(errorMessage);
|
||||||
console.error('Failed to batch delete knowledge:', err);
|
console.error('Failed to batch delete knowledge:', err);
|
||||||
throw err;
|
throw err;
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
|
|
||||||
common: {
|
common: {
|
||||||
|
back: 'Back',
|
||||||
|
operationFailed: 'Operation failed',
|
||||||
noResults: 'No results.',
|
noResults: 'No results.',
|
||||||
selectPlaceholder: 'select value',
|
selectPlaceholder: 'select value',
|
||||||
selectAll: 'Select All',
|
selectAll: 'Select All',
|
||||||
@@ -26,12 +29,13 @@ export default {
|
|||||||
traditionalChinese: 'Traditional Chinese',
|
traditionalChinese: 'Traditional Chinese',
|
||||||
language: 'Language',
|
language: 'Language',
|
||||||
languageMessage: 'Please input your language!',
|
languageMessage: 'Please input your language!',
|
||||||
|
loading: 'Loading...',
|
||||||
|
close: 'Close',
|
||||||
languagePlaceholder: 'select your language',
|
languagePlaceholder: 'select your language',
|
||||||
copy: 'Copy',
|
copy: 'Copy',
|
||||||
copied: 'Copied',
|
copied: 'Copied',
|
||||||
comingSoon: 'Coming soon',
|
comingSoon: 'Coming soon',
|
||||||
download: 'Download',
|
download: 'Download',
|
||||||
close: 'Close',
|
|
||||||
preview: 'Preview',
|
preview: 'Preview',
|
||||||
move: 'Move',
|
move: 'Move',
|
||||||
warn: 'Warn',
|
warn: 'Warn',
|
||||||
@@ -50,6 +54,24 @@ export default {
|
|||||||
noDataFound: 'No data found.',
|
noDataFound: 'No data found.',
|
||||||
noData: 'No data',
|
noData: 'No data',
|
||||||
promptPlaceholder: `Please input or use / to quickly insert variables.`,
|
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: {
|
mcp: {
|
||||||
namePlaceholder: 'My MCP Server',
|
namePlaceholder: 'My MCP Server',
|
||||||
nameRequired:
|
nameRequired:
|
||||||
@@ -58,6 +80,172 @@ export default {
|
|||||||
tokenPlaceholder: 'e.g. eyJhbGciOiJIUzI1Ni...',
|
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: {
|
||||||
login: 'Sign in',
|
login: 'Sign in',
|
||||||
signUp: 'Sign up',
|
signUp: 'Sign up',
|
||||||
@@ -80,7 +268,7 @@ export default {
|
|||||||
review: 'from 500+ reviews',
|
review: 'from 500+ reviews',
|
||||||
},
|
},
|
||||||
header: {
|
header: {
|
||||||
knowledgeBase: 'Dataset',
|
knowledgeBase: 'Knowledge Bases',
|
||||||
chat: 'Chat',
|
chat: 'Chat',
|
||||||
register: 'Register',
|
register: 'Register',
|
||||||
signin: 'Sign in',
|
signin: 'Sign in',
|
||||||
@@ -91,17 +279,136 @@ export default {
|
|||||||
flow: 'Agent',
|
flow: 'Agent',
|
||||||
search: 'Search',
|
search: 'Search',
|
||||||
welcome: 'Welcome to',
|
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: {
|
knowledgeList: {
|
||||||
welcome: 'Welcome back',
|
welcome: 'Welcome back',
|
||||||
description: 'Which knowledge bases will you use today?',
|
description: 'Which knowledge bases will you use today?',
|
||||||
createKnowledgeBase: 'Create Dataset',
|
createKnowledgeBase: 'Create Knowledge Base',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
namePlaceholder: 'Please input name!',
|
namePlaceholder: 'Please input name!',
|
||||||
doc: 'Docs',
|
doc: 'Docs',
|
||||||
searchKnowledgePlaceholder: 'Search',
|
searchKnowledgePlaceholder: 'Search',
|
||||||
noMoreData: `That's all. Nothing more.`,
|
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: {
|
knowledgeDetails: {
|
||||||
fileSize: 'File Size',
|
fileSize: 'File Size',
|
||||||
@@ -151,6 +458,36 @@ export default {
|
|||||||
knowledgeGraph: 'Knowledge Graph',
|
knowledgeGraph: 'Knowledge Graph',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
namePlaceholder: 'Please input 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',
|
doc: 'Docs',
|
||||||
datasetDescription:
|
datasetDescription:
|
||||||
'Please wait for your files to finish parsing before starting an AI-powered chat.',
|
'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.',
|
reRankModelWaring: 'Re-rank model is very time consuming.',
|
||||||
},
|
},
|
||||||
knowledgeConfiguration: {
|
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: `
|
deleteGenerateModalContent: `
|
||||||
<p>Deleting the generated <strong class='text-text-primary'>{{type}}</strong> results
|
<p>Deleting the generated <strong class='text-text-primary'>{{type}}</strong> results
|
||||||
will remove all derived entities and relationships from this dataset.
|
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',
|
destinationFolder: 'Destination folder',
|
||||||
pleaseUploadAtLeastOneFile: 'Please upload at least one file',
|
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: {
|
mcp: {
|
||||||
export: 'Export',
|
export: 'Export',
|
||||||
import: 'Import',
|
import: 'Import',
|
||||||
@@ -991,6 +1327,11 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
|||||||
cancelText: 'Cancel',
|
cancelText: 'Cancel',
|
||||||
chooseDataset: 'Please select a dataset first',
|
chooseDataset: 'Please select a dataset first',
|
||||||
},
|
},
|
||||||
|
layout: {
|
||||||
|
file: 'file',
|
||||||
|
knowledge: 'knowledge',
|
||||||
|
chat: 'chat',
|
||||||
|
},
|
||||||
language: {
|
language: {
|
||||||
english: 'English',
|
english: 'English',
|
||||||
chinese: 'Chinese',
|
chinese: 'Chinese',
|
||||||
@@ -1133,7 +1474,7 @@ Important structured information may include: names, dates, locations, events, k
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
cancel: 'Cancel',
|
cancel: 'Cancel',
|
||||||
swicthPromptMessage:
|
switchPromptMessage:
|
||||||
'The prompt word will change. Please confirm whether to abandon the existing prompt word?',
|
'The prompt word will change. Please confirm whether to abandon the existing prompt word?',
|
||||||
tokenizerSearchMethodOptions: {
|
tokenizerSearchMethodOptions: {
|
||||||
full_text: 'Full-text',
|
full_text: 'Full-text',
|
||||||
@@ -1157,5 +1498,83 @@ Important structured information may include: names, dates, locations, events, k
|
|||||||
downloading: 'Downloading',
|
downloading: 'Downloading',
|
||||||
processing: 'Processing',
|
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?',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
|
|
||||||
common: {
|
common: {
|
||||||
|
back: '返回',
|
||||||
|
operationFailed: '操作失败',
|
||||||
noResults: '无结果。',
|
noResults: '无结果。',
|
||||||
selectPlaceholder: '请选择',
|
selectPlaceholder: '请选择',
|
||||||
selectAll: '全选',
|
selectAll: '全选',
|
||||||
@@ -26,9 +29,10 @@ export default {
|
|||||||
languagePlaceholder: '请选择语言',
|
languagePlaceholder: '请选择语言',
|
||||||
copy: '复制',
|
copy: '复制',
|
||||||
copied: '复制成功',
|
copied: '复制成功',
|
||||||
|
loading: '加载中...',
|
||||||
|
close: '关闭',
|
||||||
comingSoon: '即将推出',
|
comingSoon: '即将推出',
|
||||||
download: '下载',
|
download: '下载',
|
||||||
close: '关闭',
|
|
||||||
preview: '预览',
|
preview: '预览',
|
||||||
move: '移动',
|
move: '移动',
|
||||||
warn: '提醒',
|
warn: '提醒',
|
||||||
@@ -49,7 +53,183 @@ export default {
|
|||||||
promptPlaceholder: '请输入或使用 / 快速插入变量。',
|
promptPlaceholder: '请输入或使用 / 快速插入变量。',
|
||||||
update: '更新',
|
update: '更新',
|
||||||
configure: '配置',
|
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: {
|
||||||
login: '登录',
|
login: '登录',
|
||||||
signUp: '注册',
|
signUp: '注册',
|
||||||
@@ -83,17 +263,150 @@ export default {
|
|||||||
flow: '智能体',
|
flow: '智能体',
|
||||||
search: '搜索',
|
search: '搜索',
|
||||||
welcome: '欢迎来到',
|
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: {
|
knowledgeList: {
|
||||||
welcome: '欢迎回来',
|
welcome: '欢迎使用知识库',
|
||||||
description: '今天我们要使用哪个知识库?',
|
description: '创建和管理您的知识库',
|
||||||
createKnowledgeBase: '创建知识库',
|
createKnowledgeBase: '创建知识库',
|
||||||
name: '名称',
|
name: '名称',
|
||||||
namePlaceholder: '请输入名称',
|
namePlaceholder: '请输入知识库名称',
|
||||||
doc: '文档',
|
doc: '文档',
|
||||||
searchKnowledgePlaceholder: '搜索',
|
searchKnowledgePlaceholder: '搜索',
|
||||||
noMoreData: '没有更多数据了',
|
noMoreData: '没有更多数据了',
|
||||||
|
confirmDeleteKnowledge: '是否确认删除知识库',
|
||||||
|
teamFilter: '团队筛选',
|
||||||
|
loadError: '加载知识库列表失败',
|
||||||
|
paginationInfo: '共 {{total}} 个知识库,第 {{current}} 页,共 {{totalPages}} 页',
|
||||||
},
|
},
|
||||||
knowledgeDetails: {
|
knowledgeDetails: {
|
||||||
fileSize: '文件大小',
|
fileSize: '文件大小',
|
||||||
@@ -141,6 +454,36 @@ export default {
|
|||||||
namePlaceholder: '请输入名称',
|
namePlaceholder: '请输入名称',
|
||||||
doc: '文档',
|
doc: '文档',
|
||||||
datasetDescription: '解析成功后才能问答哦。',
|
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: '新增文件',
|
addFile: '新增文件',
|
||||||
searchFiles: '搜索文件',
|
searchFiles: '搜索文件',
|
||||||
localFiles: '本地文件',
|
localFiles: '本地文件',
|
||||||
@@ -174,6 +517,10 @@ export default {
|
|||||||
testText: '测试文本',
|
testText: '测试文本',
|
||||||
testTextPlaceholder: '请输入您的问题!',
|
testTextPlaceholder: '请输入您的问题!',
|
||||||
testingLabel: '测试',
|
testingLabel: '测试',
|
||||||
|
generateKnowledgeGraph:
|
||||||
|
'这将从此数据集中的所有文档中提取实体和关系。该过程可能需要一段时间才能完成。',
|
||||||
|
generateRaptor:
|
||||||
|
'这将从此数据集中的所有文档中提取实体和关系。该过程可能需要一段时间才能完成。',
|
||||||
similarity: '混合相似度',
|
similarity: '混合相似度',
|
||||||
termSimilarity: '关键词相似度',
|
termSimilarity: '关键词相似度',
|
||||||
vectorSimilarity: '向量相似度',
|
vectorSimilarity: '向量相似度',
|
||||||
@@ -256,6 +603,20 @@ export default {
|
|||||||
theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除',
|
theDocumentBeingParsedCannotBeDeleted: '正在解析的文档不能被删除',
|
||||||
},
|
},
|
||||||
knowledgeConfiguration: {
|
knowledgeConfiguration: {
|
||||||
|
basicInfo: '基础信息',
|
||||||
|
configSettings: '配置设置',
|
||||||
|
createSuccess: '知识库创建成功,请配置解析设置',
|
||||||
|
configComplete: '知识库配置完成',
|
||||||
|
createFailed: '创建知识库失败',
|
||||||
|
configFailed: '配置知识库失败',
|
||||||
|
skipConfigSuccess: '知识库创建完成,您可以稍后在设置页面配置解析参数',
|
||||||
|
createSuccessConfig: '知识库已创建成功,现在可以配置解析设置',
|
||||||
|
configLaterTip: '您可以现在配置这些设置,也可以稍后在知识库详情页面中修改',
|
||||||
|
skipConfig: '跳过配置',
|
||||||
|
creating: '创建中...',
|
||||||
|
configuring: '配置中...',
|
||||||
|
createAndNext: '创建并下一步',
|
||||||
|
completeCreate: '完成创建',
|
||||||
deleteGenerateModalContent: `
|
deleteGenerateModalContent: `
|
||||||
<p>删除生成的 <strong class='text-text-primary'>{{type}}</strong> 结果
|
<p>删除生成的 <strong class='text-text-primary'>{{type}}</strong> 结果
|
||||||
将从此数据集中移除所有派生实体和关系。
|
将从此数据集中移除所有派生实体和关系。
|
||||||
@@ -290,7 +651,7 @@ export default {
|
|||||||
description: '描述',
|
description: '描述',
|
||||||
language: '文档语言',
|
language: '文档语言',
|
||||||
languageMessage: '请输入语言',
|
languageMessage: '请输入语言',
|
||||||
languagePlaceholder: '请输入语言',
|
languagePlaceholder: '请选择语言',
|
||||||
permissions: '权限',
|
permissions: '权限',
|
||||||
embeddingModel: '嵌入模型',
|
embeddingModel: '嵌入模型',
|
||||||
chunkTokenNumber: '建议文本块大小',
|
chunkTokenNumber: '建议文本块大小',
|
||||||
@@ -819,6 +1180,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
logout: '登出成功',
|
logout: '登出成功',
|
||||||
logged: '登录成功',
|
logged: '登录成功',
|
||||||
pleaseSelectChunk: '请选择解析块',
|
pleaseSelectChunk: '请选择解析块',
|
||||||
|
registerDisabled: '用户注册已禁用',
|
||||||
modified: '更新成功',
|
modified: '更新成功',
|
||||||
created: '创建成功',
|
created: '创建成功',
|
||||||
deleted: '删除成功',
|
deleted: '删除成功',
|
||||||
@@ -872,28 +1234,11 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
destinationFolder: '目标文件夹',
|
destinationFolder: '目标文件夹',
|
||||||
pleaseUploadAtLeastOneFile: '请上传至少一个文件',
|
pleaseUploadAtLeastOneFile: '请上传至少一个文件',
|
||||||
},
|
},
|
||||||
footer: {
|
|
||||||
profile: 'All rights reserved @ React',
|
|
||||||
},
|
|
||||||
layout: {
|
layout: {
|
||||||
file: 'file',
|
file: 'file',
|
||||||
knowledge: 'knowledge',
|
knowledge: 'knowledge',
|
||||||
chat: 'chat',
|
chat: 'chat',
|
||||||
},
|
},
|
||||||
llmTools: {
|
|
||||||
bad_calculator: {
|
|
||||||
name: '计算器',
|
|
||||||
description: '用于计算两个数的和的工具(会给出错误答案)',
|
|
||||||
params: {
|
|
||||||
a: '第一个数',
|
|
||||||
b: '第二个数',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
modal: {
|
|
||||||
okText: '确认',
|
|
||||||
cancelText: '取消',
|
|
||||||
},
|
|
||||||
mcp: {
|
mcp: {
|
||||||
export: '导出',
|
export: '导出',
|
||||||
import: '导入',
|
import: '导入',
|
||||||
@@ -951,7 +1296,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
descriptionValue: '你是一位智能助手。',
|
descriptionValue: '你是一位智能助手。',
|
||||||
okText: '保存',
|
okText: '保存',
|
||||||
cancelText: '返回',
|
cancelText: '返回',
|
||||||
chooseDataset: '请先选择知识库',
|
chooseDataset: '请先选择一个数据集',
|
||||||
},
|
},
|
||||||
language: {
|
language: {
|
||||||
english: '英语',
|
english: '英语',
|
||||||
@@ -1023,7 +1368,6 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
regularExpressions: '正则表达式',
|
regularExpressions: '正则表达式',
|
||||||
overlappedPercent: '重叠百分比',
|
overlappedPercent: '重叠百分比',
|
||||||
searchMethod: '搜索方法',
|
searchMethod: '搜索方法',
|
||||||
filenameEmbdWeight: '文件名嵌入权重',
|
|
||||||
begin: '文件',
|
begin: '文件',
|
||||||
parserMethod: '解析方法',
|
parserMethod: '解析方法',
|
||||||
systemPrompt: '系统提示词',
|
systemPrompt: '系统提示词',
|
||||||
@@ -1087,6 +1431,29 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
cancel: '取消',
|
cancel: '取消',
|
||||||
filenameEmbeddingWeight: '文件名嵌入权重',
|
filenameEmbeddingWeight: '文件名嵌入权重',
|
||||||
switchPromptMessage: '提示词将发生变化,请确认是否放弃已有提示词?',
|
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: {
|
datasetOverview: {
|
||||||
downloadTip: '正在从数据源下载文件。',
|
downloadTip: '正在从数据源下载文件。',
|
||||||
@@ -1095,5 +1462,83 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
downloading: '正在下载',
|
downloading: '正在下载',
|
||||||
processing: '正在处理',
|
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: '您确定吗?',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import {
|
|||||||
SelectAll as SelectAllIcon,
|
SelectAll as SelectAllIcon,
|
||||||
Clear as ClearIcon,
|
Clear as ClearIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import type { IChunk } from '@/interfaces/database/knowledge';
|
import type { IChunk } from '@/interfaces/database/knowledge';
|
||||||
import knowledgeService from '@/services/knowledge_service';
|
import knowledgeService from '@/services/knowledge_service';
|
||||||
|
|
||||||
@@ -54,6 +55,7 @@ interface ChunkListResultProps {
|
|||||||
|
|
||||||
function ChunkListResult(props: ChunkListResultProps) {
|
function ChunkListResult(props: ChunkListResultProps) {
|
||||||
const { doc_id, chunks, total, loading, error, page, pageSize, onPageChange, onRefresh, docName } = props;
|
const { doc_id, chunks, total, loading, error, page, pageSize, onPageChange, onRefresh, docName } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// 选择状态
|
// 选择状态
|
||||||
const [selectedChunks, setSelectedChunks] = useState<string[]>([]);
|
const [selectedChunks, setSelectedChunks] = useState<string[]>([]);
|
||||||
@@ -140,7 +142,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
<Paper sx={{ p: 3, textAlign: 'center' }}>
|
<Paper sx={{ p: 3, textAlign: 'center' }}>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
|
||||||
正在加载chunk数据...
|
{t('chunkPage.loadingChunkData')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
@@ -160,10 +162,10 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
return (
|
return (
|
||||||
<Paper sx={{ p: 3, textAlign: 'center' }}>
|
<Paper sx={{ p: 3, textAlign: 'center' }}>
|
||||||
<Typography variant="h6" color="text.secondary">
|
<Typography variant="h6" color="text.secondary">
|
||||||
暂无chunk数据
|
{t('chunkPage.noChunkData')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||||
该文档还没有生成chunk数据,请检查文档是否已完成解析
|
{t('chunkPage.noChunkDataDescription')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
@@ -189,7 +191,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
onChange={(e) => handleSelectAll(e.target.checked)}
|
onChange={(e) => handleSelectAll(e.target.checked)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={`全选 (已选择 ${selectedChunks.length} 个)`}
|
label={`${t('chunkPage.selectAll')} (${t('chunkPage.selected')} ${selectedChunks.length} ${t('chunkPage.items')})`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ flexGrow: 1 }} />
|
<Box sx={{ flexGrow: 1 }} />
|
||||||
@@ -205,7 +207,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
启用 ({selectedDisabledCount})
|
{t('chunkPage.enable')} ({selectedDisabledCount})
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -218,7 +220,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
禁用 ({selectedEnabledCount})
|
{t('chunkPage.disable')} ({selectedEnabledCount})
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -230,7 +232,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
删除 ({selectedChunks.length})
|
{t('common.delete')} ({selectedChunks.length})
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -239,7 +241,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
size="small"
|
size="small"
|
||||||
>
|
>
|
||||||
清空选择
|
{t('chunkPage.clearSelection')}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
)}
|
)}
|
||||||
@@ -250,10 +252,10 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
<Paper sx={{ p: 3, mb: 3 }}>
|
<Paper sx={{ p: 3, mb: 3 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
Chunk列表 (第 {page} 页,共 {totalPages} 页)
|
{t('chunkPage.chunkList')} ({t('chunkPage.page')} {page} {t('chunkPage.pageOf')} {totalPages} {t('chunkPage.pages')})
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
共 {total} 个chunk
|
{t('chunkPage.total')} {total} {t('chunkPage.chunks')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -307,7 +309,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
|
|
||||||
<Stack direction="row" spacing={1} alignItems="center">
|
<Stack direction="row" spacing={1} alignItems="center">
|
||||||
{chunk.image_id && (
|
{chunk.image_id && (
|
||||||
<Tooltip title="包含图片">
|
<Tooltip title={t('chunkPage.containsImage')}>
|
||||||
<Avatar sx={{ bgcolor: 'info.main', width: 24, height: 24 }}>
|
<Avatar sx={{ bgcolor: 'info.main', width: 24, height: 24 }}>
|
||||||
<ImageIcon fontSize="small" />
|
<ImageIcon fontSize="small" />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
@@ -315,7 +317,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
)}
|
)}
|
||||||
<Chip
|
<Chip
|
||||||
icon={chunk.available_int === 1 ? <VisibilityIcon /> : <VisibilityOffIcon />}
|
icon={chunk.available_int === 1 ? <VisibilityIcon /> : <VisibilityOffIcon />}
|
||||||
label={chunk.available_int === 1 ? '已启用' : '未启用'}
|
label={chunk.available_int === 1 ? t('chunkPage.enabled') : t('chunkPage.disabled')}
|
||||||
size="small"
|
size="small"
|
||||||
color={chunk.available_int === 1 ? 'success' : 'default'}
|
color={chunk.available_int === 1 ? 'success' : 'default'}
|
||||||
variant={chunk.available_int === 1 ? 'filled' : 'outlined'}
|
variant={chunk.available_int === 1 ? 'filled' : 'outlined'}
|
||||||
@@ -326,7 +328,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
{/* 内容区域 */}
|
{/* 内容区域 */}
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2 }}>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
||||||
内容预览
|
{t('chunkPage.contentPreview')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Paper
|
<Paper
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
@@ -347,7 +349,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
color: 'text.primary',
|
color: 'text.primary',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{chunk.content_with_weight || '无内容'}
|
{chunk.content_with_weight || t('chunkPage.noContent')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
@@ -356,7 +358,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
{chunk.image_id && (
|
{chunk.image_id && (
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2 }}>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
||||||
相关图片
|
{t('chunkPage.relatedImage')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -370,7 +372,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
<Box
|
<Box
|
||||||
component="img"
|
component="img"
|
||||||
src={`${import.meta.env.VITE_API_BASE_URL}/v1/document/image/${chunk.image_id}`}
|
src={`${import.meta.env.VITE_API_BASE_URL}/v1/document/image/${chunk.image_id}`}
|
||||||
alt="Chunk相关图片"
|
alt={t('chunkPage.chunkRelatedImage')}
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '200px',
|
maxHeight: '200px',
|
||||||
@@ -390,14 +392,14 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
{((chunk.important_kwd ?? []).length > 0 || (chunk.question_kwd ?? []).length > 0 || (chunk.tag_kwd ?? []).length > 0) && (
|
{((chunk.important_kwd ?? []).length > 0 || (chunk.question_kwd ?? []).length > 0 || (chunk.tag_kwd ?? []).length > 0) && (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
||||||
关键词信息
|
{t('chunkPage.keywordInfo')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
{chunk.important_kwd && chunk.important_kwd.length > 0 && (
|
{chunk.important_kwd && chunk.important_kwd.length > 0 && (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ mr: 1 }}>
|
<Typography variant="caption" color="text.secondary" sx={{ mr: 1 }}>
|
||||||
重要:
|
{t('chunkPage.important')}:
|
||||||
</Typography>
|
</Typography>
|
||||||
{chunk.important_kwd.map((keyword, kwdIndex) => (
|
{chunk.important_kwd.map((keyword, kwdIndex) => (
|
||||||
<Chip
|
<Chip
|
||||||
@@ -415,7 +417,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
{chunk.question_kwd && chunk.question_kwd.length > 0 && (
|
{chunk.question_kwd && chunk.question_kwd.length > 0 && (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ mr: 1 }}>
|
<Typography variant="caption" color="text.secondary" sx={{ mr: 1 }}>
|
||||||
问题:
|
{t('chunkPage.question')}:
|
||||||
</Typography>
|
</Typography>
|
||||||
{chunk.question_kwd.map((keyword, kwdIndex) => (
|
{chunk.question_kwd.map((keyword, kwdIndex) => (
|
||||||
<Chip
|
<Chip
|
||||||
@@ -433,7 +435,7 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
{chunk.tag_kwd && chunk.tag_kwd.length > 0 && (
|
{chunk.tag_kwd && chunk.tag_kwd.length > 0 && (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ mr: 1 }}>
|
<Typography variant="caption" color="text.secondary" sx={{ mr: 1 }}>
|
||||||
标签:
|
{t('chunkPage.tag')}:
|
||||||
</Typography>
|
</Typography>
|
||||||
{chunk.tag_kwd.map((keyword, kwdIndex) => (
|
{chunk.tag_kwd.map((keyword, kwdIndex) => (
|
||||||
<Chip
|
<Chip
|
||||||
@@ -476,22 +478,22 @@ function ChunkListResult(props: ChunkListResultProps) {
|
|||||||
open={deleteDialogOpen}
|
open={deleteDialogOpen}
|
||||||
onClose={() => setDeleteDialogOpen(false)}
|
onClose={() => setDeleteDialogOpen(false)}
|
||||||
>
|
>
|
||||||
<DialogTitle>确认删除</DialogTitle>
|
<DialogTitle>{t('dialog.confirmDelete')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText>
|
<DialogContentText>
|
||||||
确定要删除选中的 {selectedChunks.length} 个chunk吗?此操作不可撤销。
|
{t('chunkPage.confirmDeleteChunks', { count: selectedChunks.length })}
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteDialogOpen(false)}>
|
<Button onClick={() => setDeleteDialogOpen(false)}>
|
||||||
取消
|
{t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
onClick={handleDeleteChunks}
|
onClick={handleDeleteChunks}
|
||||||
color="error"
|
color="error"
|
||||||
disabled={operationLoading}
|
disabled={operationLoading}
|
||||||
>
|
>
|
||||||
{operationLoading ? '删除中...' : '确认删除'}
|
{operationLoading ? t('chunkPage.deleting') : t('dialog.confirmDelete')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {
|
|||||||
Download as DownloadIcon,
|
Download as DownloadIcon,
|
||||||
Visibility as VisibilityIcon,
|
Visibility as VisibilityIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import knowledgeService from '@/services/knowledge_service';
|
import knowledgeService from '@/services/knowledge_service';
|
||||||
import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
import type { IKnowledge, IKnowledgeFile } from '@/interfaces/database/knowledge';
|
||||||
import KnowledgeBreadcrumbs from '@/pages/knowledge/components/KnowledgeBreadcrumbs';
|
import KnowledgeBreadcrumbs from '@/pages/knowledge/components/KnowledgeBreadcrumbs';
|
||||||
@@ -25,6 +26,7 @@ interface DocumentPreviewProps {}
|
|||||||
function DocumentPreview(props: DocumentPreviewProps) {
|
function DocumentPreview(props: DocumentPreviewProps) {
|
||||||
const { kb_id, doc_id } = useParams<{ kb_id: string; doc_id: string }>();
|
const { kb_id, doc_id } = useParams<{ kb_id: string; doc_id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [kb, setKb] = useState<IKnowledge | null>(null);
|
const [kb, setKb] = useState<IKnowledge | null>(null);
|
||||||
const [documentObj, setDocumentObj] = useState<IKnowledgeFile | null>(null);
|
const [documentObj, setDocumentObj] = useState<IKnowledgeFile | null>(null);
|
||||||
@@ -41,7 +43,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
if (!kb_id || !doc_id) {
|
if (!kb_id || !doc_id) {
|
||||||
setError('缺少必要的参数');
|
setError(t('chunkPage.missingParams'));
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -67,7 +69,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
loadDocumentFile();
|
loadDocumentFile();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取数据失败:', err);
|
console.error('获取数据失败:', err);
|
||||||
setError('获取数据失败,请稍后重试');
|
setError(t('chunkPage.fetchDataFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
@@ -103,13 +105,13 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
const url = URL.createObjectURL(fileResponse.data);
|
const url = URL.createObjectURL(fileResponse.data);
|
||||||
setFileUrl(url);
|
setFileUrl(url);
|
||||||
} else {
|
} else {
|
||||||
setError('文件格式不支持预览');
|
setError(t('chunkPage.fileFormatNotSupported'));
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log('err', err);
|
console.log('err', err);
|
||||||
if (err.name !== 'AbortError' && err.name !== 'CanceledError') {
|
if (err.name !== 'AbortError' && err.name !== 'CanceledError') {
|
||||||
console.error('获取文档文件失败:', err);
|
console.error('获取文档文件失败:', err);
|
||||||
setError('获取文档文件失败,请稍后重试');
|
setError(t('chunkPage.getDocumentFileFailed'));
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setFileLoading(false);
|
setFileLoading(false);
|
||||||
@@ -168,7 +170,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
<CardMedia
|
<CardMedia
|
||||||
component="img"
|
component="img"
|
||||||
image={fileUrl}
|
image={fileUrl}
|
||||||
alt={documentObj?.name || '文档预览'}
|
alt={documentObj?.name || t('chunkPage.documentPreview')}
|
||||||
sx={{
|
sx={{
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
maxHeight: '80vh',
|
maxHeight: '80vh',
|
||||||
@@ -207,7 +209,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Alert severity="info" sx={{ mt: 2 }}>
|
<Alert severity="info" sx={{ mt: 2 }}>
|
||||||
此文件类型不支持在线预览,请下载后查看。
|
{t('chunkPage.fileTypeNotSupportedPreview')}
|
||||||
</Alert>
|
</Alert>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -230,7 +232,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
onClick={handleGoBack}
|
onClick={handleGoBack}
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
>
|
>
|
||||||
返回
|
{t('common.back')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -242,21 +244,21 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
<KnowledgeBreadcrumbs
|
<KnowledgeBreadcrumbs
|
||||||
kbItems={[
|
kbItems={[
|
||||||
{
|
{
|
||||||
label: '知识库',
|
label: t('chunkPage.knowledgeBase'),
|
||||||
path: '/knowledge'
|
path: '/knowledge'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: kb?.name || '知识库详情',
|
label: kb?.name || t('chunkPage.knowledgeBaseDetail'),
|
||||||
path: `/knowledge/${kb_id}`
|
path: `/knowledge/${kb_id}`
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
extraItems={[
|
extraItems={[
|
||||||
{
|
{
|
||||||
label: documentObj?.name || '文档详情',
|
label: documentObj?.name || t('chunkPage.documentDetail'),
|
||||||
path: `/chunk/parsed-result?kb_id=${kb_id}&doc_id=${doc_id}`
|
path: `/chunk/parsed-result?kb_id=${kb_id}&doc_id=${doc_id}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '文件预览'
|
label: t('chunkPage.filePreview')
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -265,7 +267,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h4" gutterBottom>
|
||||||
文件预览
|
{t('chunkPage.filePreview')}
|
||||||
</Typography>
|
</Typography>
|
||||||
{documentObj && (
|
{documentObj && (
|
||||||
<Typography variant="subtitle1" color="text.secondary">
|
<Typography variant="subtitle1" color="text.secondary">
|
||||||
@@ -280,7 +282,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
onClick={handleGoBack}
|
onClick={handleGoBack}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
>
|
>
|
||||||
返回
|
{t('common.back')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{fileUrl && (
|
{fileUrl && (
|
||||||
@@ -289,7 +291,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
onClick={handleDownload}
|
onClick={handleDownload}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
>
|
>
|
||||||
下载文件
|
{t('chunkPage.downloadFile')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -306,7 +308,7 @@ function DocumentPreview(props: DocumentPreviewProps) {
|
|||||||
{fileLoading && (
|
{fileLoading && (
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '200px' }}>
|
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: '200px' }}>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
<Typography sx={{ ml: 2 }}>正在加载文件...</Typography>
|
<Typography sx={{ ml: 2 }}>{t('chunkPage.loadingFile')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
CardContent
|
CardContent
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import { Search as SearchIcon, Visibility as VisibilityIcon } from '@mui/icons-material';
|
import { Search as SearchIcon, Visibility as VisibilityIcon } from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useChunkList } from '@/hooks/chunk-hooks';
|
import { useChunkList } from '@/hooks/chunk-hooks';
|
||||||
import ChunkListResult from './components/ChunkListResult';
|
import ChunkListResult from './components/ChunkListResult';
|
||||||
import knowledgeService from '@/services/knowledge_service';
|
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';
|
import KnowledgeBreadcrumbs from '@/pages/knowledge/components/KnowledgeBreadcrumbs';
|
||||||
|
|
||||||
function ChunkParsedResult() {
|
function ChunkParsedResult() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const kb_id = searchParams.get('kb_id');
|
const kb_id = searchParams.get('kb_id');
|
||||||
@@ -93,7 +95,7 @@ function ChunkParsedResult() {
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ p: 3 }}>
|
<Box sx={{ p: 3 }}>
|
||||||
<Alert severity="error">
|
<Alert severity="error">
|
||||||
缺少必要的参数:知识库ID或文档ID
|
{t('chunkPage.missingParams')}
|
||||||
</Alert>
|
</Alert>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -105,17 +107,17 @@ function ChunkParsedResult() {
|
|||||||
<KnowledgeBreadcrumbs
|
<KnowledgeBreadcrumbs
|
||||||
kbItems={[
|
kbItems={[
|
||||||
{
|
{
|
||||||
label: '知识库',
|
label: t('chunkPage.knowledgeBase'),
|
||||||
path: '/knowledge'
|
path: '/knowledge'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: knowledgeBase?.name || '知识库详情',
|
label: knowledgeBase?.name || t('chunkPage.knowledgeBaseDetail'),
|
||||||
path: `/knowledge/${kb_id}`
|
path: `/knowledge/${kb_id}`
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
extraItems={[
|
extraItems={[
|
||||||
{
|
{
|
||||||
label: document?.name || '文档详情'
|
label: document?.name || t('chunkPage.documentDetail')
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
@@ -124,10 +126,10 @@ function ChunkParsedResult() {
|
|||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h4" gutterBottom>
|
||||||
文档Chunk解析结果
|
{t('chunkPage.documentChunkResult')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" color="text.secondary">
|
<Typography variant="body1" color="text.secondary">
|
||||||
查看文档 "{document?.name}" 的所有chunk数据
|
{t('chunkPage.viewDocument')} "{document?.name}" {t('chunkPage.allChunkData')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Card>
|
<Card>
|
||||||
@@ -136,7 +138,7 @@ function ChunkParsedResult() {
|
|||||||
{total}
|
{total}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
总Chunk数量
|
{t('chunkPage.totalChunkCount')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -146,7 +148,7 @@ function ChunkParsedResult() {
|
|||||||
onClick={handleViewFile}
|
onClick={handleViewFile}
|
||||||
sx={{ ml: 2 }}
|
sx={{ ml: 2 }}
|
||||||
>
|
>
|
||||||
查看文件
|
{t('chunkPage.viewFile')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -155,7 +157,7 @@ function ChunkParsedResult() {
|
|||||||
<Paper sx={{ p: 3, mb: 3 }}>
|
<Paper sx={{ p: 3, mb: 3 }}>
|
||||||
<TextField
|
<TextField
|
||||||
fullWidth
|
fullWidth
|
||||||
placeholder="搜索chunk内容..."
|
placeholder={t('chunkPage.searchChunkPlaceholder')}
|
||||||
value={searchKeyword}
|
value={searchKeyword}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useMemo } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import { useFormContext, useWatch, type UseFormReturn } from 'react-hook-form';
|
import { useFormContext, useWatch, type UseFormReturn } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -47,10 +48,11 @@ const ConfigurationComponentMap = {
|
|||||||
|
|
||||||
// 空组件
|
// 空组件
|
||||||
function EmptyComponent() {
|
function EmptyComponent() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<Box sx={{ textAlign: 'center', py: 4 }}>
|
<Box sx={{ textAlign: 'center', py: 4 }}>
|
||||||
<Typography color="text.secondary">
|
<Typography color="text.secondary">
|
||||||
请选择一个解析方法来配置相关参数
|
{t('knowledge.selectParserMethod')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -74,9 +76,10 @@ function ChunkMethodForm({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText = '保存',
|
submitButtonText,
|
||||||
cancelButtonText = '取消',
|
cancelButtonText,
|
||||||
}: ChunkMethodFormProps = {}) {
|
}: ChunkMethodFormProps = {}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
// 优先使用props传递的form,否则使用FormProvider的context
|
// 优先使用props传递的form,否则使用FormProvider的context
|
||||||
let contextForm;
|
let contextForm;
|
||||||
try {
|
try {
|
||||||
@@ -92,7 +95,7 @@ function ChunkMethodForm({
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2, textAlign: 'center' }}>
|
<Box sx={{ p: 2, textAlign: 'center' }}>
|
||||||
<Typography color="error">
|
<Typography color="error">
|
||||||
表单配置错误:请确保组件在FormProvider中使用或传递form参数
|
{t('form.formConfigError')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -127,7 +130,7 @@ function ChunkMethodForm({
|
|||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
{cancelButtonText}
|
{cancelButtonText || t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
@@ -135,7 +138,7 @@ function ChunkMethodForm({
|
|||||||
onClick={form ? form.handleSubmit(onSubmit) : undefined}
|
onClick={form ? form.handleSubmit(onSubmit) : undefined}
|
||||||
disabled={isSubmitting || !form}
|
disabled={isSubmitting || !form}
|
||||||
>
|
>
|
||||||
{isSubmitting ? '提交中...' : submitButtonText}
|
{isSubmitting ? t('common.submitting') : (submitButtonText || t('common.save'))}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
import React, { useState, useEffect, useRef, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { t as translate } from 'i18next';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -91,13 +92,13 @@ interface DocumentListComponentProps {
|
|||||||
|
|
||||||
const getRunStatusLabel = (status: string) => {
|
const getRunStatusLabel = (status: string) => {
|
||||||
const statusLabels = {
|
const statusLabels = {
|
||||||
[RUNNING_STATUS_KEYS.UNSTART]: '未开始',
|
[RUNNING_STATUS_KEYS.UNSTART]: translate('knowledge.runStatus.unstart'),
|
||||||
[RUNNING_STATUS_KEYS.RUNNING]: '运行中',
|
[RUNNING_STATUS_KEYS.RUNNING]: translate('knowledge.runStatus.running'),
|
||||||
[RUNNING_STATUS_KEYS.CANCEL]: '已取消',
|
[RUNNING_STATUS_KEYS.CANCEL]: translate('knowledge.runStatus.cancel'),
|
||||||
[RUNNING_STATUS_KEYS.DONE]: '完成',
|
[RUNNING_STATUS_KEYS.DONE]: translate('knowledge.runStatus.done'),
|
||||||
[RUNNING_STATUS_KEYS.FAIL]: '失败',
|
[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) => {
|
const getFileIcon = (type: string, suffix?: string) => {
|
||||||
@@ -138,21 +139,21 @@ const formatFileSize = (bytes: number): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getStatusChip = (status: string) => {
|
const getStatusChip = (status: string) => {
|
||||||
return <Chip label={status === '1' ? '启用' : '禁用'}
|
return <Chip label={status === '1' ? translate('common.enabled') : translate('common.disabled')}
|
||||||
color={status === '1' ? 'success' : 'error'}
|
color={status === '1' ? 'success' : 'error'}
|
||||||
size="small" />;
|
size="small" />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRunStatusChip = (run: RunningStatus, progress: number) => {
|
const getRunStatusChip = (run: RunningStatus, progress: number) => {
|
||||||
const statusConfig = {
|
const statusConfig = {
|
||||||
[RUNNING_STATUS_KEYS.UNSTART]: { label: '未开始', color: 'default' as const },
|
[RUNNING_STATUS_KEYS.UNSTART]: { label: translate('knowledge.runStatus.unstart'), color: 'default' as const },
|
||||||
[RUNNING_STATUS_KEYS.RUNNING]: { label: `解析中`, color: 'info' as const },
|
[RUNNING_STATUS_KEYS.RUNNING]: { label: translate('knowledge.runStatus.parsing'), color: 'info' as const },
|
||||||
[RUNNING_STATUS_KEYS.CANCEL]: { label: '已取消', color: 'warning' as const },
|
[RUNNING_STATUS_KEYS.CANCEL]: { label: translate('knowledge.runStatus.cancel'), color: 'warning' as const },
|
||||||
[RUNNING_STATUS_KEYS.DONE]: { label: '完成', color: 'success' as const },
|
[RUNNING_STATUS_KEYS.DONE]: { label: translate('knowledge.runStatus.done'), color: 'success' as const },
|
||||||
[RUNNING_STATUS_KEYS.FAIL]: { label: '失败', color: 'error' 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) {
|
if (run === RUNNING_STATUS_KEYS.RUNNING) {
|
||||||
return (
|
return (
|
||||||
@@ -209,7 +210,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
|
const [renameDialogOpen, setRenameDialogOpen] = useState(false);
|
||||||
const [newFileName, setNewFileName] = useState('');
|
const [newFileName, setNewFileName] = useState('');
|
||||||
|
|
||||||
const { i18n } = useTranslation();
|
const { i18n, t } = useTranslation();
|
||||||
|
|
||||||
// 根据当前语言获取DataGrid的localeText
|
// 根据当前语言获取DataGrid的localeText
|
||||||
const getDataGridLocale = () => {
|
const getDataGridLocale = () => {
|
||||||
@@ -328,7 +329,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
const columns: GridColDef[] = [
|
const columns: GridColDef[] = [
|
||||||
{
|
{
|
||||||
field: 'name',
|
field: 'name',
|
||||||
headerName: '文件名',
|
headerName: t('knowledge.fileName'),
|
||||||
flex: 2,
|
flex: 2,
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
cellClassName: 'grid-center-cell',
|
cellClassName: 'grid-center-cell',
|
||||||
@@ -349,7 +350,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Tooltip title={`查看 ${params.value} 的详情`}>
|
<Tooltip title={t('knowledge.viewFileDetails', { fileName: params.value })}>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
{getFileIcon(params.row.type, params.row.suffix)}
|
{getFileIcon(params.row.type, params.row.suffix)}
|
||||||
<Typography variant="body2" noWrap>{params.value}</Typography>
|
<Typography variant="body2" noWrap>{params.value}</Typography>
|
||||||
@@ -360,7 +361,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'type',
|
field: 'type',
|
||||||
headerName: '类型',
|
headerName: t('knowledge.type'),
|
||||||
flex: 0.5,
|
flex: 0.5,
|
||||||
minWidth: 80,
|
minWidth: 80,
|
||||||
renderCell: (params) => (
|
renderCell: (params) => (
|
||||||
@@ -369,42 +370,42 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'size',
|
field: 'size',
|
||||||
headerName: '大小',
|
headerName: t('knowledge.size'),
|
||||||
flex: 0.5,
|
flex: 0.5,
|
||||||
minWidth: 80,
|
minWidth: 80,
|
||||||
renderCell: (params) => formatFileSize(params.value),
|
renderCell: (params) => formatFileSize(params.value),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'chunk_num',
|
field: 'chunk_num',
|
||||||
headerName: '分块数',
|
headerName: t('knowledge.chunkCount'),
|
||||||
flex: 0.5,
|
flex: 0.5,
|
||||||
minWidth: 80,
|
minWidth: 80,
|
||||||
type: 'number',
|
type: 'number',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'status',
|
field: 'status',
|
||||||
headerName: '状态',
|
headerName: t('knowledge.status'),
|
||||||
flex: 0.8,
|
flex: 0.8,
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
renderCell: (params) => getStatusChip(params.value),
|
renderCell: (params) => getStatusChip(params.value),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'run',
|
field: 'run',
|
||||||
headerName: '解析状态',
|
headerName: t('knowledge.parseStatus'),
|
||||||
flex: 0.8,
|
flex: 0.8,
|
||||||
minWidth: 100,
|
minWidth: 100,
|
||||||
renderCell: (params) => getRunStatusChip(params.value, params.row.progress),
|
renderCell: (params) => getRunStatusChip(params.value, params.row.progress),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'create_time',
|
field: 'create_time',
|
||||||
headerName: '上传时间',
|
headerName: t('knowledge.uploadTime'),
|
||||||
flex: 1,
|
flex: 1,
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
valueFormatter: (value) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'),
|
valueFormatter: (value) => dayjs(value).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: 'actions',
|
field: 'actions',
|
||||||
headerName: '操作',
|
headerName: t('knowledge.actions'),
|
||||||
flex: 1.2,
|
flex: 1.2,
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
@@ -453,7 +454,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
>
|
>
|
||||||
<ProcessIcon fontSize="small" />
|
<ProcessIcon fontSize="small" />
|
||||||
</Box>
|
</Box>
|
||||||
<Tooltip title="更多操作">
|
<Tooltip title={t('common.moreActions')}>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="small"
|
size="small"
|
||||||
onClick={(e) => handleMenuClick(e, params.row)}
|
onClick={(e) => handleMenuClick(e, params.row)}
|
||||||
@@ -471,16 +472,16 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
{/* 筛选器 */}
|
{/* 筛选器 */}
|
||||||
{documentFilter && (
|
{documentFilter && (
|
||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Typography variant="h6" sx={{ mb: 2 }}>筛选器</Typography>
|
<Typography variant="h6" sx={{ mb: 2 }}>{t('knowledge.filter')}</Typography>
|
||||||
<Stack direction="row" spacing={2} alignItems="center">
|
<Stack direction="row" spacing={2} alignItems="center">
|
||||||
{/* 运行状态筛选 */}
|
{/* 运行状态筛选 */}
|
||||||
<FormControl sx={{ minWidth: 200 }}>
|
<FormControl sx={{ minWidth: 200 }}>
|
||||||
<InputLabel>运行状态</InputLabel>
|
<InputLabel>{t('knowledge.runStatusFilter')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={selectedRunStatus}
|
value={selectedRunStatus}
|
||||||
onChange={handleRunStatusChange}
|
onChange={handleRunStatusChange}
|
||||||
input={<OutlinedInput label="运行状态" />}
|
input={<OutlinedInput label={t('knowledge.runStatusFilter')} />}
|
||||||
renderValue={(selected) => (
|
renderValue={(selected) => (
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||||
{selected.map((value) => (
|
{selected.map((value) => (
|
||||||
@@ -499,12 +500,12 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
|
|
||||||
{/* 文件类型筛选 */}
|
{/* 文件类型筛选 */}
|
||||||
<FormControl sx={{ minWidth: 200 }}>
|
<FormControl sx={{ minWidth: 200 }}>
|
||||||
<InputLabel>文件类型</InputLabel>
|
<InputLabel>{t('knowledge.fileTypeFilter')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={selectedSuffix}
|
value={selectedSuffix}
|
||||||
onChange={handleSuffixChange}
|
onChange={handleSuffixChange}
|
||||||
input={<OutlinedInput label="文件类型" />}
|
input={<OutlinedInput label={t('knowledge.fileTypeFilter')} />}
|
||||||
renderValue={(selected) => (
|
renderValue={(selected) => (
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||||
{selected.map((value) => (
|
{selected.map((value) => (
|
||||||
@@ -529,7 +530,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
});
|
});
|
||||||
handleFilterSubmit();
|
handleFilterSubmit();
|
||||||
}}>
|
}}>
|
||||||
清除筛选
|
{t('common.clearFilter')}
|
||||||
</Button>
|
</Button>
|
||||||
{/* submit filter */}
|
{/* submit filter */}
|
||||||
<Button variant="contained" onClick={async () => {
|
<Button variant="contained" onClick={async () => {
|
||||||
@@ -538,7 +539,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
});
|
});
|
||||||
handleFilterSubmit();
|
handleFilterSubmit();
|
||||||
}}>
|
}}>
|
||||||
确认筛选
|
{t('common.confirmFilter')}
|
||||||
</Button>
|
</Button>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
@@ -548,7 +549,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
<Paper sx={{ p: 2, mb: 2 }}>
|
<Paper sx={{ p: 2, mb: 2 }}>
|
||||||
<Stack direction="row" spacing={2} alignItems="center" justifyContent="space-between">
|
<Stack direction="row" spacing={2} alignItems="center" justifyContent="space-between">
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="搜索文件..."
|
placeholder={t('knowledge.searchFiles')}
|
||||||
value={searchKeyword}
|
value={searchKeyword}
|
||||||
onChange={(e) => onSearchChange(e.target.value)}
|
onChange={(e) => onSearchChange(e.target.value)}
|
||||||
InputProps={{
|
InputProps={{
|
||||||
@@ -568,7 +569,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
startIcon={<UploadIcon />}
|
startIcon={<UploadIcon />}
|
||||||
onClick={onUpload}
|
onClick={onUpload}
|
||||||
>
|
>
|
||||||
上传文件
|
{t('knowledge.uploadFile')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{rowSelectionModel.ids.size > 0 && (
|
{rowSelectionModel.ids.size > 0 && (
|
||||||
@@ -578,7 +579,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
startIcon={<RefreshIcon />}
|
startIcon={<RefreshIcon />}
|
||||||
onClick={() => onReparse(Array.from(rowSelectionModel.ids).map((id) => id.toString()))}
|
onClick={() => onReparse(Array.from(rowSelectionModel.ids).map((id) => id.toString()))}
|
||||||
>
|
>
|
||||||
重新解析 ({rowSelectionModel.ids.size})
|
{t('knowledge.reparse')} ({rowSelectionModel.ids.size})
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -587,7 +588,7 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
startIcon={<DeleteIcon />}
|
startIcon={<DeleteIcon />}
|
||||||
onClick={() => onDelete(Array.from(rowSelectionModel.ids).map((id) => id.toString()))}
|
onClick={() => onDelete(Array.from(rowSelectionModel.ids).map((id) => id.toString()))}
|
||||||
>
|
>
|
||||||
删除 ({rowSelectionModel.ids.size})
|
{t('common.delete')} ({rowSelectionModel.ids.size})
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -637,47 +638,47 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
>
|
>
|
||||||
<MenuItem onClick={() => handleViewDetails(selectedFile)}>
|
<MenuItem onClick={() => handleViewDetails(selectedFile)}>
|
||||||
<ViewIcon sx={{ mr: 1 }} />
|
<ViewIcon sx={{ mr: 1 }} />
|
||||||
<Typography>查看详情</Typography>
|
<Typography>{t('knowledge.viewDetails')}</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={handleRename}>
|
<MenuItem onClick={handleRename}>
|
||||||
<EditIcon sx={{ mr: 1 }} />
|
<EditIcon sx={{ mr: 1 }} />
|
||||||
<Typography>重命名</Typography>
|
<Typography>{t('common.rename')}</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem onClick={handleReparse}>
|
<MenuItem onClick={handleReparse}>
|
||||||
<PlayIcon sx={{ mr: 1 }} />
|
<PlayIcon sx={{ mr: 1 }} />
|
||||||
<Typography>重新解析</Typography>
|
<Typography>{t('knowledge.reparse')}</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{selectedFile?.run === RUNNING_STATUS_KEYS.RUNNING && (
|
{selectedFile?.run === RUNNING_STATUS_KEYS.RUNNING && (
|
||||||
<MenuItem onClick={handleCancelRun}>
|
<MenuItem onClick={handleCancelRun}>
|
||||||
<StopIcon sx={{ mr: 1 }} />
|
<StopIcon sx={{ mr: 1 }} />
|
||||||
<Typography>取消运行</Typography>
|
<Typography>{t('knowledge.cancelRun')}</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
{selectedFile?.status === '1' ? (
|
{selectedFile?.status === '1' ? (
|
||||||
<MenuItem onClick={() => handleChangeStatus('0')}>
|
<MenuItem onClick={() => handleChangeStatus('0')}>
|
||||||
<DisableIcon sx={{ mr: 1 }} />
|
<DisableIcon sx={{ mr: 1 }} />
|
||||||
<Typography>禁用</Typography>
|
<Typography>{t('common.disable')}</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
) : (
|
) : (
|
||||||
<MenuItem onClick={() => handleChangeStatus('1')}>
|
<MenuItem onClick={() => handleChangeStatus('1')}>
|
||||||
<EnableIcon sx={{ mr: 1 }} />
|
<EnableIcon sx={{ mr: 1 }} />
|
||||||
<Typography>启用</Typography>
|
<Typography>{t('common.enable')}</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem onClick={handleDelete}>
|
<MenuItem onClick={handleDelete}>
|
||||||
<DeleteIcon sx={{ mr: 1 }} color="error" />
|
<DeleteIcon sx={{ mr: 1 }} color="error" />
|
||||||
<Typography color="error">删除</Typography>
|
<Typography color="error">{t('common.delete')}</Typography>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</Menu>
|
</Menu>
|
||||||
|
|
||||||
{/* 重命名对话框 */}
|
{/* 重命名对话框 */}
|
||||||
<Dialog open={renameDialogOpen} onClose={() => setRenameDialogOpen(false)}>
|
<Dialog open={renameDialogOpen} onClose={() => setRenameDialogOpen(false)}>
|
||||||
<DialogTitle>重命名文件</DialogTitle>
|
<DialogTitle>{t('knowledge.renameFile')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<TextField
|
<TextField
|
||||||
autoFocus
|
autoFocus
|
||||||
margin="dense"
|
margin="dense"
|
||||||
label="文件名"
|
label={t('knowledge.fileName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
value={newFileName}
|
value={newFileName}
|
||||||
@@ -685,8 +686,8 @@ const DocumentListComponent: React.FC<DocumentListComponentProps> = ({
|
|||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setRenameDialogOpen(false)}>取消</Button>
|
<Button onClick={() => setRenameDialogOpen(false)}>{t('common.cancel')}</Button>
|
||||||
<Button onClick={handleRenameConfirm} variant="contained">确认</Button>
|
<Button onClick={handleRenameConfirm} variant="contained">{t('common.confirm')}</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Settings as ConfigIcon,
|
Settings as ConfigIcon,
|
||||||
|
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface FloatingActionButtonsProps {
|
interface FloatingActionButtonsProps {
|
||||||
onTestClick: () => void;
|
onTestClick: () => void;
|
||||||
@@ -16,22 +17,24 @@ const FloatingActionButtons: React.FC<FloatingActionButtonsProps> = ({
|
|||||||
onTestClick,
|
onTestClick,
|
||||||
onConfigClick,
|
onConfigClick,
|
||||||
}) => {
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const actions = [
|
const actions = [
|
||||||
{
|
{
|
||||||
icon: <TestIcon />,
|
icon: <TestIcon />,
|
||||||
name: '检索测试',
|
name: t('knowledge.retrievalTest'),
|
||||||
onClick: onTestClick,
|
onClick: onTestClick,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <ConfigIcon />,
|
icon: <ConfigIcon />,
|
||||||
name: '配置设置',
|
name: t('knowledge.configSettings'),
|
||||||
onClick: onConfigClick,
|
onClick: onConfigClick,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SpeedDial
|
<SpeedDial
|
||||||
ariaLabel="知识库操作"
|
ariaLabel={t('knowledge.knowledgeBaseActions')}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
bottom: 128,
|
bottom: 128,
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
PhotoCamera as PhotoCameraIcon,
|
PhotoCamera as PhotoCameraIcon,
|
||||||
Delete as DeleteIcon,
|
Delete as DeleteIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface GeneralFormProps {
|
interface GeneralFormProps {
|
||||||
form?: UseFormReturn;
|
form?: UseFormReturn;
|
||||||
@@ -32,9 +33,13 @@ function GeneralForm({
|
|||||||
onSubmit,
|
onSubmit,
|
||||||
isSubmitting,
|
isSubmitting,
|
||||||
onCancel,
|
onCancel,
|
||||||
submitButtonText = '保存',
|
submitButtonText,
|
||||||
cancelButtonText = '取消',
|
cancelButtonText,
|
||||||
}: GeneralFormProps = {}) {
|
}: GeneralFormProps = {}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const defaultSubmitButtonText = submitButtonText || t('common.save');
|
||||||
|
const defaultCancelButtonText = cancelButtonText || t('common.cancel');
|
||||||
// 优先使用props传递的form,否则使用FormProvider的context
|
// 优先使用props传递的form,否则使用FormProvider的context
|
||||||
let contextForm: UseFormReturn | null = null;
|
let contextForm: UseFormReturn | null = null;
|
||||||
try {
|
try {
|
||||||
@@ -50,7 +55,7 @@ function GeneralForm({
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2, textAlign: 'center' }}>
|
<Box sx={{ p: 2, textAlign: 'center' }}>
|
||||||
<Typography color="error">
|
<Typography color="error">
|
||||||
表单配置错误:请确保组件在FormProvider中使用或传递form参数
|
{t('form.configurationError')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
@@ -85,7 +90,7 @@ function GeneralForm({
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ p: 3 }}>
|
<Box sx={{ p: 3 }}>
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
基础信息
|
{t('knowledge.basicInfo')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
@@ -106,7 +111,7 @@ function GeneralForm({
|
|||||||
startIcon={<PhotoCameraIcon />}
|
startIcon={<PhotoCameraIcon />}
|
||||||
onClick={handleAvatarClick}
|
onClick={handleAvatarClick}
|
||||||
>
|
>
|
||||||
上传头像
|
{t('knowledge.uploadAvatar')}
|
||||||
</Button>
|
</Button>
|
||||||
{avatar && (
|
{avatar && (
|
||||||
<IconButton
|
<IconButton
|
||||||
@@ -136,11 +141,11 @@ function GeneralForm({
|
|||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={control}
|
control={control}
|
||||||
rules={{ required: '知识库名称不能为空' }}
|
rules={{ required: t('knowledge.nameRequired') }}
|
||||||
render={({ field, fieldState: { error } }) => (
|
render={({ field, fieldState: { error } }) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...field}
|
{...field}
|
||||||
label="知识库名称"
|
label={t('knowledge.knowledgeBaseName')}
|
||||||
fullWidth
|
fullWidth
|
||||||
required
|
required
|
||||||
error={!!error}
|
error={!!error}
|
||||||
@@ -157,11 +162,11 @@ function GeneralForm({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...field}
|
{...field}
|
||||||
label="描述"
|
label={t('common.description')}
|
||||||
fullWidth
|
fullWidth
|
||||||
multiline
|
multiline
|
||||||
rows={3}
|
rows={3}
|
||||||
placeholder="请输入知识库描述..."
|
placeholder={t('knowledge.descriptionPlaceholder')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -173,10 +178,10 @@ function GeneralForm({
|
|||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>权限设置</InputLabel>
|
<InputLabel>{t('knowledge.permissionSettings')}</InputLabel>
|
||||||
<Select {...field} label="权限设置">
|
<Select {...field} label={t('knowledge.permissionSettings')}>
|
||||||
<MenuItem value="me">仅自己</MenuItem>
|
<MenuItem value="me">{t('knowledge.onlyMe')}</MenuItem>
|
||||||
<MenuItem value="team">团队成员</MenuItem>
|
<MenuItem value="team">{t('knowledge.teamMembers')}</MenuItem>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@@ -195,7 +200,7 @@ function GeneralForm({
|
|||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
>
|
>
|
||||||
{cancelButtonText}
|
{defaultCancelButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
@@ -203,7 +208,7 @@ function GeneralForm({
|
|||||||
onClick={form ? form.handleSubmit(onSubmit) : undefined}
|
onClick={form ? form.handleSubmit(onSubmit) : undefined}
|
||||||
disabled={isSubmitting || !form}
|
disabled={isSubmitting || !form}
|
||||||
>
|
>
|
||||||
{isSubmitting ? '提交中...' : submitButtonText}
|
{isSubmitting ? t('common.submitting') : defaultSubmitButtonText}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React, { useMemo, useCallback } from 'react';
|
import React, { useMemo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
ReactFlow,
|
ReactFlow,
|
||||||
type Node,
|
type Node,
|
||||||
@@ -39,6 +40,7 @@ const getNodeColor = (entityType?: string): string => {
|
|||||||
|
|
||||||
// 自定义节点组件
|
// 自定义节点组件
|
||||||
const CustomNode = ({ data }: { data: any }) => {
|
const CustomNode = ({ data }: { data: any }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const nodeColor = getNodeColor(data.entity_type);
|
const nodeColor = getNodeColor(data.entity_type);
|
||||||
const nodeSize = Math.max(80, Math.min(140, (data.pagerank || 0.1) * 500));
|
const nodeSize = Math.max(80, Math.min(140, (data.pagerank || 0.1) * 500));
|
||||||
|
|
||||||
@@ -55,19 +57,19 @@ const CustomNode = ({ data }: { data: any }) => {
|
|||||||
const tooltipContent = (
|
const tooltipContent = (
|
||||||
<Box sx={{ p: 1, maxWidth: 300 }}>
|
<Box sx={{ p: 1, maxWidth: 300 }}>
|
||||||
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', mb: 1 }}>
|
<Typography variant="subtitle2" sx={{ fontWeight: 'bold', mb: 1 }}>
|
||||||
{data.label || data.name || 'Unknown'}
|
{data.label || data.name || t('knowledge.unknown')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ mb: 0.5 }}>
|
<Typography variant="body2" sx={{ mb: 0.5 }}>
|
||||||
<strong>类型:</strong> {data.entity_type || 'Unknown'}
|
<strong>{t('knowledge.type')}:</strong> {data.entity_type || t('knowledge.unknown')}
|
||||||
</Typography>
|
</Typography>
|
||||||
{data.description && (
|
{data.description && (
|
||||||
<Typography variant="body2">
|
<Typography variant="body2">
|
||||||
<strong>描述:</strong> {data.description}
|
<strong>{t('knowledge.description')}:</strong> {data.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
{data.pagerank && (
|
{data.pagerank && (
|
||||||
<Typography variant="caption" sx={{ display: 'block', mt: 1 }}>
|
<Typography variant="caption" sx={{ display: 'block', mt: 1 }}>
|
||||||
PageRank: {data.pagerank.toFixed(4)}
|
{t('knowledge.pageRank')}: {data.pagerank.toFixed(4)}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@@ -143,7 +145,7 @@ const CustomNode = ({ data }: { data: any }) => {
|
|||||||
WebkitBoxOrient: 'vertical',
|
WebkitBoxOrient: 'vertical',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{data.label || data.name || 'Unknown'}
|
{data.label || data.name || t('knowledge.unknown')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -156,6 +158,8 @@ const nodeTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const KnowledgeGraphView: React.FC<KnowledgeGraphProps> = ({ knowledgeGraph }) => {
|
const KnowledgeGraphView: React.FC<KnowledgeGraphProps> = ({ knowledgeGraph }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// 转换数据格式为 React Flow 所需的格式
|
// 转换数据格式为 React Flow 所需的格式
|
||||||
const { nodes: initialNodes, edges: initialEdges } = useMemo(() => {
|
const { nodes: initialNodes, edges: initialEdges } = useMemo(() => {
|
||||||
const graphData = knowledgeGraph?.graph || {};
|
const graphData = knowledgeGraph?.graph || {};
|
||||||
@@ -166,9 +170,6 @@ const KnowledgeGraphView: React.FC<KnowledgeGraphProps> = ({ knowledgeGraph }) =
|
|||||||
// 转换节点数据
|
// 转换节点数据
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const nodes: Node[] = graphData.nodes.map((node, index) => {
|
const nodes: Node[] = graphData.nodes.map((node, index) => {
|
||||||
console.log(`节点 ${index}:`, node);
|
|
||||||
console.log(`节点ID: ${node.id}`);
|
|
||||||
|
|
||||||
const encodeId = encodeURIComponent(String(node.id));
|
const encodeId = encodeURIComponent(String(node.id));
|
||||||
|
|
||||||
const n: Node = {
|
const n: Node = {
|
||||||
@@ -192,11 +193,11 @@ const KnowledgeGraphView: React.FC<KnowledgeGraphProps> = ({ knowledgeGraph }) =
|
|||||||
// 转换边数据
|
// 转换边数据
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const edges: Edge[] = graphData.edges.map((edge, index) => {
|
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}`);
|
console.log(`src_id: ${edge.src_id}, tgt_id: ${edge.tgt_id}`);
|
||||||
// 检查source和target是否存在
|
// 检查source和target是否存在
|
||||||
if (!edge.src_id || !edge.tgt_id) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,7 +206,7 @@ const KnowledgeGraphView: React.FC<KnowledgeGraphProps> = ({ knowledgeGraph }) =
|
|||||||
const targetExists = nodes.some(node => node.id === encodeURIComponent(edge.tgt_id));
|
const targetExists = nodes.some(node => node.id === encodeURIComponent(edge.tgt_id));
|
||||||
|
|
||||||
if (!sourceExists || !targetExists) {
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,7 +262,7 @@ const KnowledgeGraphView: React.FC<KnowledgeGraphProps> = ({ knowledgeGraph }) =
|
|||||||
if (!knowledgeGraph || initialNodes.length === 0) {
|
if (!knowledgeGraph || initialNodes.length === 0) {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', height: 400 }}>
|
<Box sx={{ p: 2, display: 'flex', justifyContent: 'center', alignItems: 'center', height: 400 }}>
|
||||||
<Alert severity="info">暂无知识图谱数据</Alert>
|
<Alert severity="info">{t('knowledge.noGraphData')}</Alert>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -295,7 +296,7 @@ const KnowledgeGraphView: React.FC<KnowledgeGraphProps> = ({ knowledgeGraph }) =
|
|||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
||||||
}}>
|
}}>
|
||||||
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
|
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
|
||||||
图例
|
{t('knowledge.legend')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
|
||||||
{['PERSON', 'ORGANIZATION', 'CATEGORY', 'TECHNOLOGY'].map((type) => (
|
{['PERSON', 'ORGANIZATION', 'CATEGORY', 'TECHNOLOGY'].map((type) => (
|
||||||
@@ -325,17 +326,17 @@ const KnowledgeGraphView: React.FC<KnowledgeGraphProps> = ({ knowledgeGraph }) =
|
|||||||
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
|
||||||
}}>
|
}}>
|
||||||
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
|
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 'bold' }}>
|
||||||
图谱统计
|
{t('knowledge.graphStats')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||||
<Chip
|
<Chip
|
||||||
label={`节点: ${nodes.length}`}
|
label={t('knowledge.nodeCount', { count: nodes.length })}
|
||||||
size="small"
|
size="small"
|
||||||
color="primary"
|
color="primary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
<Chip
|
<Chip
|
||||||
label={`边: ${edges.length}`}
|
label={t('knowledge.edgeCount', { count: edges.length })}
|
||||||
size="small"
|
size="small"
|
||||||
color="secondary"
|
color="secondary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardContent,
|
CardContent,
|
||||||
@@ -23,6 +24,8 @@ const formatFileSize = (bytes: number): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const KnowledgeInfoCard: React.FC<KnowledgeInfoCardProps> = ({ knowledgeBase }) => {
|
const KnowledgeInfoCard: React.FC<KnowledgeInfoCardProps> = ({ knowledgeBase }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card sx={{ mb: 3 }}>
|
<Card sx={{ mb: 3 }}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
@@ -32,34 +35,34 @@ const KnowledgeInfoCard: React.FC<KnowledgeInfoCardProps> = ({ knowledgeBase })
|
|||||||
{knowledgeBase.name}
|
{knowledgeBase.name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
|
||||||
{knowledgeBase.description || '暂无描述'}
|
{knowledgeBase.description || t('knowledge.noDescription')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack direction="row" spacing={2} flexWrap="wrap">
|
<Stack direction="row" spacing={2} flexWrap="wrap">
|
||||||
<Chip label={`${knowledgeBase.doc_num || 0} 个文件`} variant="outlined" />
|
<Chip label={t('knowledge.fileCount', { count: knowledgeBase.doc_num || 0 })} variant="outlined" />
|
||||||
<Chip label={`${knowledgeBase.chunk_num || 0} 个分块`} variant="outlined" />
|
<Chip label={t('knowledge.chunkCount', { count: knowledgeBase.chunk_num || 0 })} variant="outlined" />
|
||||||
<Chip label={`${knowledgeBase.token_num || 0} 个令牌`} variant="outlined" />
|
<Chip label={t('knowledge.tokenCount', { count: knowledgeBase.token_num || 0 })} variant="outlined" />
|
||||||
<Chip label={`大小: ${formatFileSize(knowledgeBase.size || 0)}`} variant="outlined" />
|
<Chip label={t('knowledge.size', { size: formatFileSize(knowledgeBase.size || 0) })} variant="outlined" />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid size={{ xs: 12, md: 4 }}>
|
<Grid size={{ xs: 12, md: 4 }}>
|
||||||
<Stack spacing={1} alignItems="flex-end">
|
<Stack spacing={1} alignItems="flex-end">
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
创建时间: {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')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
更新时间: {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')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
语言: {knowledgeBase.language || 'English'}
|
{t('knowledge.language')}: {knowledgeBase.language || 'English'}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
权限: {knowledgeBase.permission}
|
{t('knowledge.permission')}: {knowledgeBase.permission}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
嵌入模型: {knowledgeBase.embd_id}
|
{t('knowledge.embeddingModel')}: {knowledgeBase.embd_id}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
解析器: {knowledgeBase.parser_id}
|
{t('knowledge.parser')}: {knowledgeBase.parser_id}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Paper,
|
Paper,
|
||||||
@@ -31,11 +32,13 @@ interface TestChunkResultProps {
|
|||||||
|
|
||||||
function TestChunkResult(props: TestChunkResultProps) {
|
function TestChunkResult(props: TestChunkResultProps) {
|
||||||
const { result, loading, page, pageSize, onDocumentFilter, selectedDocIds } = props;
|
const { result, loading, page, pageSize, onDocumentFilter, selectedDocIds } = props;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return (
|
return (
|
||||||
<Paper sx={{ p: 3, textAlign: 'center' }}>
|
<Paper sx={{ p: 3, textAlign: 'center' }}>
|
||||||
<Typography variant="h6" color="text.secondary">
|
<Typography variant="h6" color="text.secondary">
|
||||||
请输入问题并点击"开始测试"来查看检索结果
|
{t('knowledge.testPrompt')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
@@ -55,7 +58,7 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
{/* 测试结果概览 */}
|
{/* 测试结果概览 */}
|
||||||
<Paper sx={{ p: 3, mb: 3 }}>
|
<Paper sx={{ p: 3, mb: 3 }}>
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
测试结果概览
|
{t('knowledge.testResultOverview')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
<Grid size={{ xs: 12, sm: 4 }}>
|
<Grid size={{ xs: 12, sm: 4 }}>
|
||||||
@@ -65,7 +68,7 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
{result.total}
|
{result.total}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
匹配的文档块
|
{t('knowledge.matchedChunks')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -77,7 +80,7 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
{result.doc_aggs?.length || 0}
|
{result.doc_aggs?.length || 0}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
相关文档
|
{t('knowledge.relatedDocuments')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -89,7 +92,7 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
{result.chunks?.length || 0}
|
{result.chunks?.length || 0}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
返回的块数
|
{t('knowledge.returnedChunks')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -102,15 +105,15 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
<Paper sx={{ p: 3, mb: 3 }}>
|
<Paper sx={{ p: 3, mb: 3 }}>
|
||||||
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Typography variant="h6" gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||||
<FilterListIcon />
|
<FilterListIcon />
|
||||||
文档过滤
|
{t('knowledge.documentFilter')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
<InputLabel>选择要显示的文档</InputLabel>
|
<InputLabel>{t('knowledge.selectDocuments')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={selectedDocIds}
|
value={selectedDocIds}
|
||||||
onChange={handleDocumentFilterChange}
|
onChange={handleDocumentFilterChange}
|
||||||
input={<OutlinedInput label="选择要显示的文档" />}
|
input={<OutlinedInput label={t('knowledge.selectDocuments')} />}
|
||||||
renderValue={(selected) => (
|
renderValue={(selected) => (
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||||
{selected.map((value) => {
|
{selected.map((value) => {
|
||||||
@@ -127,7 +130,7 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
<Checkbox checked={selectedDocIds.indexOf(doc.doc_id) > -1} />
|
<Checkbox checked={selectedDocIds.indexOf(doc.doc_id) > -1} />
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary={doc.doc_name}
|
primary={doc.doc_name}
|
||||||
secondary={`${doc.count} 个匹配块`}
|
secondary={t('knowledge.matchedChunksCount', { count: doc.count })}
|
||||||
/>
|
/>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
))}
|
))}
|
||||||
@@ -141,10 +144,10 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
<Paper sx={{ p: 3, mb: 3 }}>
|
<Paper sx={{ p: 3, mb: 3 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
匹配的文档块 (第 {page} 页,共 {totalPages} 页)
|
{t('knowledge.matchedChunksTitle', { page, totalPages })}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
共找到 {result.total} 个匹配块
|
{t('knowledge.totalMatchedChunks', { total: result.total })}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -159,20 +162,20 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
</Typography>
|
</Typography>
|
||||||
<Stack direction="row" spacing={1}>
|
<Stack direction="row" spacing={1}>
|
||||||
<Chip
|
<Chip
|
||||||
label={`相似度: ${(chunk.similarity * 100).toFixed(1)}%`}
|
label={t('knowledge.similarity', { value: (chunk.similarity * 100).toFixed(1) })}
|
||||||
size="small"
|
size="small"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
{chunk.vector_similarity !== undefined && (
|
{chunk.vector_similarity !== undefined && (
|
||||||
<Chip
|
<Chip
|
||||||
label={`向量: ${(chunk.vector_similarity * 100).toFixed(1)}%`}
|
label={t('knowledge.vectorSimilarity', { value: (chunk.vector_similarity * 100).toFixed(1) })}
|
||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{chunk.term_similarity !== undefined && (
|
{chunk.term_similarity !== undefined && (
|
||||||
<Chip
|
<Chip
|
||||||
label={`词项: ${(chunk.term_similarity * 100).toFixed(1)}%`}
|
label={t('knowledge.termSimilarity', { value: (chunk.term_similarity * 100).toFixed(1) })}
|
||||||
size="small"
|
size="small"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
/>
|
/>
|
||||||
@@ -191,14 +194,14 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
dangerouslySetInnerHTML={{
|
dangerouslySetInnerHTML={{
|
||||||
__html: chunk.content_with_weight || chunk.content_ltks || '无内容'
|
__html: chunk.content_with_weight || chunk.content_ltks || t('knowledge.noContent')
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{chunk.important_kwd && chunk.important_kwd.length > 0 && (
|
{chunk.important_kwd && chunk.important_kwd.length > 0 && (
|
||||||
<Box sx={{ mt: 2 }}>
|
<Box sx={{ mt: 2 }}>
|
||||||
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
||||||
关键词:
|
{t('knowledge.keywords')}:
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||||
{chunk.important_kwd.map((keyword, kwdIndex) => (
|
{chunk.important_kwd.map((keyword, kwdIndex) => (
|
||||||
@@ -225,7 +228,7 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
{result.doc_aggs && result.doc_aggs.length > 0 && (
|
{result.doc_aggs && result.doc_aggs.length > 0 && (
|
||||||
<Paper sx={{ p: 3 }}>
|
<Paper sx={{ p: 3 }}>
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
相关文档统计
|
{t('knowledge.relatedDocumentStats')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack spacing={1}>
|
<Stack spacing={1}>
|
||||||
{result.doc_aggs.map((doc: ITestingDocument, index: number) => (
|
{result.doc_aggs.map((doc: ITestingDocument, index: number) => (
|
||||||
@@ -234,7 +237,7 @@ function TestChunkResult(props: TestChunkResultProps) {
|
|||||||
{doc.doc_name}
|
{doc.doc_name}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Chip
|
<Chip
|
||||||
label={`${doc.count} 个匹配块`}
|
label={t('knowledge.matchedChunksCount', { count: doc.count })}
|
||||||
size="small"
|
size="small"
|
||||||
color="default"
|
color="default"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Shuffle as ShuffleIcon } from '@mui/icons-material';
|
import { Shuffle as ShuffleIcon } from '@mui/icons-material';
|
||||||
import { useFormContext, Controller } from 'react-hook-form';
|
import { useFormContext, Controller } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { t as translate } from 'i18next';
|
||||||
import { DOCUMENT_PARSER_TYPES, LLM_MODEL_TYPES, type LlmModelType } from '@/constants/knowledge';
|
import { DOCUMENT_PARSER_TYPES, LLM_MODEL_TYPES, type LlmModelType } from '@/constants/knowledge';
|
||||||
import { useSelectChunkMethodList } from '../hooks';
|
import { useSelectChunkMethodList } from '../hooks';
|
||||||
import { useEmbeddingModelOptions, useLlmOptionsByModelType } from '@/hooks/llm-hooks';
|
import { useEmbeddingModelOptions, useLlmOptionsByModelType } from '@/hooks/llm-hooks';
|
||||||
@@ -28,43 +30,48 @@ import {
|
|||||||
|
|
||||||
// 解析器选项配置
|
// 解析器选项配置
|
||||||
const PARSER_OPTIONS = [
|
const PARSER_OPTIONS = [
|
||||||
{ value: DOCUMENT_PARSER_TYPES.Naive, label: 'General', description: '通用解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.Naive, label: 'General', description: translate('knowledge.config.parser.general') },
|
||||||
{ value: DOCUMENT_PARSER_TYPES.Qa, label: 'Q&A', description: 'Q&A解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.Qa, label: 'Q&A', description: translate('knowledge.config.parser.qa') },
|
||||||
{ value: DOCUMENT_PARSER_TYPES.Resume, label: 'Resume', description: 'Resume解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.Resume, label: 'Resume', description: translate('knowledge.config.parser.resume') },
|
||||||
{ value: DOCUMENT_PARSER_TYPES.Manual, label: 'Manual', description: 'Manual解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.Manual, label: 'Manual', description: translate('knowledge.config.parser.manual') },
|
||||||
{ value: DOCUMENT_PARSER_TYPES.Table, label: 'Table', description: 'Table解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.Table, label: 'Table', description: translate('knowledge.config.parser.table') },
|
||||||
{ value: DOCUMENT_PARSER_TYPES.Paper, label: 'Paper', description: 'Paper解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.Paper, label: 'Paper', description: translate('knowledge.config.parser.paper') },
|
||||||
{ value: DOCUMENT_PARSER_TYPES.Book, label: 'Book', description: 'Book解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.Book, label: 'Book', description: translate('knowledge.config.parser.book') },
|
||||||
{ value: DOCUMENT_PARSER_TYPES.Laws, label: 'Laws', description: 'Laws解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.Laws, label: 'Laws', description: translate('knowledge.config.parser.laws') },
|
||||||
{ value: DOCUMENT_PARSER_TYPES.Presentation, label: 'Presentation', description: 'Presentation解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.Presentation, label: 'Presentation', description: translate('knowledge.config.parser.presentation') },
|
||||||
{ value: DOCUMENT_PARSER_TYPES.One, label: 'One', description: 'One解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.One, label: 'One', description: translate('knowledge.config.parser.one') },
|
||||||
{ value: DOCUMENT_PARSER_TYPES.Tag, label: 'Tag', description: 'Tag解析器' },
|
{ value: DOCUMENT_PARSER_TYPES.Tag, label: 'Tag', description: translate('knowledge.config.parser.tag') },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function ChunkMethodItem() {
|
export function ChunkMethodItem() {
|
||||||
const { control, formState: { errors } } = useFormContext();
|
const { control, formState: { errors } } = useFormContext();
|
||||||
const parserIds = useSelectChunkMethodList();
|
const parserIds = useSelectChunkMethodList();
|
||||||
|
|
||||||
const parserOptions = parserIds.map((x) => ({
|
const {t} = useTranslation();
|
||||||
|
|
||||||
|
const parserOptions = parserIds.map((x) => {
|
||||||
|
const parserOption = PARSER_OPTIONS.find((y) => y.value === x);
|
||||||
|
return {
|
||||||
value: x,
|
value: x,
|
||||||
label: PARSER_OPTIONS.find((y) => y.value === x)?.label || x,
|
label: parserOption?.label || x,
|
||||||
description: PARSER_OPTIONS.find((y) => y.value === x)?.description || x,
|
description: parserOption?.description || x,
|
||||||
}));
|
};
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
切片方法
|
{t('knowledge.config.chunkMethod')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Controller
|
<Controller
|
||||||
name="parser_id"
|
name="parser_id"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl fullWidth error={!!errors.parser_id}>
|
<FormControl fullWidth error={!!errors.parser_id}>
|
||||||
<InputLabel>选择切片方法</InputLabel>
|
<InputLabel>{t('knowledge.config.selectChunkMethod')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
{...field}
|
{...field}
|
||||||
label="选择切片方法"
|
label={t('knowledge.config.selectChunkMethod')}
|
||||||
>
|
>
|
||||||
{parserOptions.map((option) => (
|
{parserOptions.map((option) => (
|
||||||
<MenuItem key={option.value} value={option.value}>
|
<MenuItem key={option.value} value={option.value}>
|
||||||
@@ -91,10 +98,11 @@ export function ChunkMethodItem() {
|
|||||||
|
|
||||||
// 分块token数量配置
|
// 分块token数量配置
|
||||||
export function ChunkTokenNumberItem() {
|
export function ChunkTokenNumberItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SliderInputFormField
|
<SliderInputFormField
|
||||||
name="parser_config.chunk_token_num"
|
name="parser_config.chunk_token_num"
|
||||||
label="建议文本块大小"
|
label={t('knowledge.config.chunkTokenSize')}
|
||||||
min={64}
|
min={64}
|
||||||
max={2048}
|
max={2048}
|
||||||
step={64}
|
step={64}
|
||||||
@@ -106,49 +114,53 @@ export function ChunkTokenNumberItem() {
|
|||||||
|
|
||||||
// 页面排名配置
|
// 页面排名配置
|
||||||
export function PageRankItem() {
|
export function PageRankItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<NumberInputFormField
|
<NumberInputFormField
|
||||||
name="parser_config.page_rank"
|
name="parser_config.page_rank"
|
||||||
label="页面排名"
|
label={t('knowledge.config.pageRank')}
|
||||||
defaultValue={0}
|
defaultValue={0}
|
||||||
min={0}
|
min={0}
|
||||||
placeholder="输入页面排名"
|
placeholder={t('knowledge.config.enterPageRank')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自动关键词数量配置
|
// 自动关键词数量配置
|
||||||
export function AutoKeywordsItem() {
|
export function AutoKeywordsItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<NumberInputFormField
|
<NumberInputFormField
|
||||||
name="parser_config.auto_keywords"
|
name="parser_config.auto_keywords"
|
||||||
label="自动关键词"
|
label={t('knowledge.config.autoKeywords')}
|
||||||
defaultValue={0}
|
defaultValue={0}
|
||||||
min={0}
|
min={0}
|
||||||
placeholder="输入关键词数量"
|
placeholder={t('knowledge.config.enterKeywordCount')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 自动问题数量配置
|
// 自动问题数量配置
|
||||||
export function AutoQuestionsItem() {
|
export function AutoQuestionsItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<NumberInputFormField
|
<NumberInputFormField
|
||||||
name="parser_config.auto_questions"
|
name="parser_config.auto_questions"
|
||||||
label="自动问题"
|
label={t('knowledge.config.autoQuestions')}
|
||||||
defaultValue={0}
|
defaultValue={0}
|
||||||
min={0}
|
min={0}
|
||||||
placeholder="输入问题数量"
|
placeholder={t('knowledge.config.enterQuestionCount')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 表格转HTML开关
|
// 表格转HTML开关
|
||||||
export function HtmlForExcelItem() {
|
export function HtmlForExcelItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SwitchFormField
|
<SwitchFormField
|
||||||
name="parser_config.html4excel"
|
name="parser_config.html4excel"
|
||||||
label="表格转HTML"
|
label={t('knowledge.config.htmlForExcel')}
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -156,15 +168,16 @@ export function HtmlForExcelItem() {
|
|||||||
|
|
||||||
// 标签集选择
|
// 标签集选择
|
||||||
export function TagsItem() {
|
export function TagsItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const tagsOptions: SelectOption[] = [
|
const tagsOptions: SelectOption[] = [
|
||||||
{ value: '', label: '请选择' },
|
{ value: '', label: t('common.pleaseSelect') },
|
||||||
// 这里可以根据实际需求添加标签选项
|
// 这里可以根据实际需求添加标签选项
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SelectFormField
|
<SelectFormField
|
||||||
name="parser_config.tags"
|
name="parser_config.tags"
|
||||||
label="标签集"
|
label={t('knowledge.config.tags')}
|
||||||
options={tagsOptions}
|
options={tagsOptions}
|
||||||
defaultValue=""
|
defaultValue=""
|
||||||
displayEmpty
|
displayEmpty
|
||||||
@@ -174,10 +187,11 @@ export function TagsItem() {
|
|||||||
|
|
||||||
// RAPTOR策略开关
|
// RAPTOR策略开关
|
||||||
export function UseRaptorItem() {
|
export function UseRaptorItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SwitchFormField
|
<SwitchFormField
|
||||||
name="parser_config.raptor.use_raptor"
|
name="parser_config.raptor.use_raptor"
|
||||||
label="使用召回增强RAPTOR策略"
|
label={t('knowledge.config.useRaptorStrategy')}
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -185,11 +199,12 @@ export function UseRaptorItem() {
|
|||||||
|
|
||||||
// RAPTOR提示词配置
|
// RAPTOR提示词配置
|
||||||
export function RaptorPromptItem() {
|
export function RaptorPromptItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<MultilineTextFormField
|
<MultilineTextFormField
|
||||||
name="parser_config.raptor.prompt"
|
name="parser_config.raptor.prompt"
|
||||||
label="提示词"
|
label={t('knowledge.config.prompt')}
|
||||||
defaultValue="请总结以下段落。小心数字,不要编造。段落如下:\n{cluster_content}\n以上就是你需要总结的内容。"
|
defaultValue={t('knowledge.config.raptorPromptDefault')}
|
||||||
rows={4}
|
rows={4}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -197,10 +212,11 @@ export function RaptorPromptItem() {
|
|||||||
|
|
||||||
// RAPTOR最大token数配置
|
// RAPTOR最大token数配置
|
||||||
export function RaptorMaxTokenItem() {
|
export function RaptorMaxTokenItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SliderInputFormField
|
<SliderInputFormField
|
||||||
name="parser_config.raptor.max_token"
|
name="parser_config.raptor.max_token"
|
||||||
label="最大token数"
|
label={t('knowledge.config.maxTokens')}
|
||||||
min={64}
|
min={64}
|
||||||
max={512}
|
max={512}
|
||||||
step={32}
|
step={32}
|
||||||
@@ -212,10 +228,11 @@ export function RaptorMaxTokenItem() {
|
|||||||
|
|
||||||
// RAPTOR阈值配置
|
// RAPTOR阈值配置
|
||||||
export function RaptorThresholdItem() {
|
export function RaptorThresholdItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SliderInputFormField
|
<SliderInputFormField
|
||||||
name="parser_config.raptor.threshold"
|
name="parser_config.raptor.threshold"
|
||||||
label="阈值"
|
label={t('knowledge.config.threshold')}
|
||||||
min={0}
|
min={0}
|
||||||
max={1}
|
max={1}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
@@ -227,10 +244,11 @@ export function RaptorThresholdItem() {
|
|||||||
|
|
||||||
// RAPTOR最大聚类数配置
|
// RAPTOR最大聚类数配置
|
||||||
export function RaptorMaxClusterItem() {
|
export function RaptorMaxClusterItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SliderInputFormField
|
<SliderInputFormField
|
||||||
name="parser_config.raptor.max_cluster"
|
name="parser_config.raptor.max_cluster"
|
||||||
label="最大聚类数"
|
label={t('knowledge.config.maxClusterCount')}
|
||||||
min={16}
|
min={16}
|
||||||
max={128}
|
max={128}
|
||||||
step={16}
|
step={16}
|
||||||
@@ -242,10 +260,11 @@ export function RaptorMaxClusterItem() {
|
|||||||
|
|
||||||
// RAPTOR随机种子配置
|
// RAPTOR随机种子配置
|
||||||
export function RaptorRandomSeedItem() {
|
export function RaptorRandomSeedItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<NumberInputFormField
|
<NumberInputFormField
|
||||||
name="parser_config.raptor.random_seed"
|
name="parser_config.raptor.random_seed"
|
||||||
label="随机种子"
|
label={t('knowledge.config.randomSeed')}
|
||||||
defaultValue={0}
|
defaultValue={0}
|
||||||
showRandomButton
|
showRandomButton
|
||||||
/>
|
/>
|
||||||
@@ -254,10 +273,11 @@ export function RaptorRandomSeedItem() {
|
|||||||
|
|
||||||
// 知识图谱开关
|
// 知识图谱开关
|
||||||
export function UseGraphragItem() {
|
export function UseGraphragItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SwitchFormField
|
<SwitchFormField
|
||||||
name="parser_config.graphrag.use_graphrag"
|
name="parser_config.graphrag.use_graphrag"
|
||||||
label="提取知识图谱"
|
label={t('knowledge.config.extractKnowledgeGraph')}
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -265,10 +285,11 @@ export function UseGraphragItem() {
|
|||||||
|
|
||||||
// 实体类型配置
|
// 实体类型配置
|
||||||
export function EntityTypesItem() {
|
export function EntityTypesItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<ChipListFormField
|
<ChipListFormField
|
||||||
name="parser_config.graphrag.entity_types"
|
name="parser_config.graphrag.entity_types"
|
||||||
label="*实体类型"
|
label={t('knowledge.config.entityTypes')}
|
||||||
defaultValue={['organization', 'person', 'geo', 'event', 'category']}
|
defaultValue={['organization', 'person', 'geo', 'event', 'category']}
|
||||||
required
|
required
|
||||||
allowAdd
|
allowAdd
|
||||||
@@ -280,6 +301,7 @@ export function EntityTypesItem() {
|
|||||||
|
|
||||||
// GraphRAG方法选择
|
// GraphRAG方法选择
|
||||||
export function GraphragMethodItem() {
|
export function GraphragMethodItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const methodOptions: SelectOption[] = [
|
const methodOptions: SelectOption[] = [
|
||||||
{ value: 'Light', label: 'Light' },
|
{ value: 'Light', label: 'Light' },
|
||||||
{ value: 'General', label: 'General' },
|
{ value: 'General', label: 'General' },
|
||||||
@@ -288,7 +310,7 @@ export function GraphragMethodItem() {
|
|||||||
return (
|
return (
|
||||||
<SelectFormField
|
<SelectFormField
|
||||||
name="parser_config.graphrag.method"
|
name="parser_config.graphrag.method"
|
||||||
label="方法"
|
label={t('knowledge.config.method')}
|
||||||
options={methodOptions}
|
options={methodOptions}
|
||||||
defaultValue="Light"
|
defaultValue="Light"
|
||||||
displayEmpty={false}
|
displayEmpty={false}
|
||||||
@@ -298,10 +320,11 @@ export function GraphragMethodItem() {
|
|||||||
|
|
||||||
// 实体归一化开关
|
// 实体归一化开关
|
||||||
export function EntityNormalizeItem() {
|
export function EntityNormalizeItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SwitchFormField
|
<SwitchFormField
|
||||||
name="parser_config.graphrag.entity_normalize"
|
name="parser_config.graphrag.entity_normalize"
|
||||||
label="实体归一化"
|
label={t('knowledge.config.entityNormalization')}
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -309,10 +332,11 @@ export function EntityNormalizeItem() {
|
|||||||
|
|
||||||
// 社区报告生成开关
|
// 社区报告生成开关
|
||||||
export function CommunityReportItem() {
|
export function CommunityReportItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<SwitchFormField
|
<SwitchFormField
|
||||||
name="parser_config.graphrag.community_report"
|
name="parser_config.graphrag.community_report"
|
||||||
label="社区报告生成"
|
label={t('knowledge.config.communityReportGeneration')}
|
||||||
defaultValue={false}
|
defaultValue={false}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@@ -320,12 +344,13 @@ export function CommunityReportItem() {
|
|||||||
|
|
||||||
export function EmbeddingModelItem() {
|
export function EmbeddingModelItem() {
|
||||||
const { control } = useFormContext();
|
const { control } = useFormContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
const { options } = useEmbeddingModelOptions();
|
const { options } = useEmbeddingModelOptions();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1" sx={{ minWidth: 120 }}>
|
<Typography variant="subtitle1" sx={{ minWidth: 120 }}>
|
||||||
嵌入模型
|
{t('knowledge.config.embeddingModel')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ flex: 1 }}>
|
<Box sx={{ flex: 1 }}>
|
||||||
<Controller
|
<Controller
|
||||||
@@ -356,13 +381,14 @@ export function EmbeddingModelItem() {
|
|||||||
// PDF解析器配置
|
// PDF解析器配置
|
||||||
export function LayoutRecognizeItem() {
|
export function LayoutRecognizeItem() {
|
||||||
const { control, setValue, formState } = useFormContext();
|
const { control, setValue, formState } = useFormContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
const { getOptionsByModelType } = useLlmOptionsByModelType();
|
const { getOptionsByModelType } = useLlmOptionsByModelType();
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const options = useMemo(() => {
|
||||||
// 基础选项
|
// 基础选项
|
||||||
const basicOptions = [
|
const basicOptions = [
|
||||||
{ value: 'DeepDOC', label: 'DeepDOC' },
|
{ 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 =>
|
const image2TextSelectOptions = image2TextOptions.flatMap(group =>
|
||||||
group.options.map(option => ({
|
group.options.map(option => ({
|
||||||
value: option.value,
|
value: option.value,
|
||||||
label: `${option.label} (实验性)`
|
label: `${option.label} (${t('knowledge.config.experimental')})`
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
return [...basicOptions, ...image2TextSelectOptions];
|
return [...basicOptions, ...image2TextSelectOptions];
|
||||||
}, [getOptionsByModelType]);
|
}, [getOptionsByModelType, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Controller
|
<Controller
|
||||||
@@ -393,7 +419,7 @@ export function LayoutRecognizeItem() {
|
|||||||
return (
|
return (
|
||||||
<SelectFormField
|
<SelectFormField
|
||||||
name="parser_config.layout_recognize"
|
name="parser_config.layout_recognize"
|
||||||
label="PDF解析器"
|
label={t('knowledge.config.pdfParser')}
|
||||||
options={options}
|
options={options}
|
||||||
defaultValue="DeepDOC"
|
defaultValue="DeepDOC"
|
||||||
/>
|
/>
|
||||||
@@ -405,11 +431,12 @@ export function LayoutRecognizeItem() {
|
|||||||
|
|
||||||
// 文本分段标识符配置
|
// 文本分段标识符配置
|
||||||
export function DelimiterItem() {
|
export function DelimiterItem() {
|
||||||
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<TextFormField
|
<TextFormField
|
||||||
name="parser_config.delimiter"
|
name="parser_config.delimiter"
|
||||||
label="分隔符"
|
label={t('knowledge.config.delimiter')}
|
||||||
placeholder="请输入分隔符"
|
placeholder={t('knowledge.config.enterDelimiter')}
|
||||||
defaultValue="\n!?。;!?"
|
defaultValue="\n!?。;!?"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-items';
|
import { ChunkMethodItem, EmbeddingModelItem } from './common-items';
|
||||||
import { Box, Typography } from '@mui/material';
|
import { Box, Typography } from '@mui/material';
|
||||||
|
|
||||||
export function KnowledgeGraphConfiguration() {
|
export function KnowledgeGraphConfiguration() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChunkMethodItem />
|
<ChunkMethodItem />
|
||||||
@@ -10,25 +13,25 @@ export function KnowledgeGraphConfiguration() {
|
|||||||
|
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2 }}>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
PageRank配置 - 待实现
|
{t('knowledge.config.pageRankConfigTodo')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2 }}>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
实体类型配置 - 待实现
|
{t('knowledge.config.entityTypeConfigTodo')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2 }}>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
最大Token数量配置 (最大: 16384) - 待实现
|
{t('knowledge.config.maxTokenConfigTodo')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2 }}>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
分隔符配置 - 待实现
|
{t('knowledge.config.delimiterConfigTodo')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { ExpandMore as ExpandMoreIcon } from '@mui/icons-material';
|
import { ExpandMore as ExpandMoreIcon } from '@mui/icons-material';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
|
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
|
||||||
import {
|
import {
|
||||||
ChunkMethodItem,
|
ChunkMethodItem,
|
||||||
@@ -35,6 +36,7 @@ import {
|
|||||||
|
|
||||||
export function NaiveConfiguration() {
|
export function NaiveConfiguration() {
|
||||||
const { formState: { errors } } = useFormContext();
|
const { formState: { errors } } = useFormContext();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigurationFormContainer>
|
<ConfigurationFormContainer>
|
||||||
@@ -42,7 +44,7 @@ export function NaiveConfiguration() {
|
|||||||
{/* 第一部分:基础配置 */}
|
{/* 第一部分:基础配置 */}
|
||||||
<Accordion defaultExpanded>
|
<Accordion defaultExpanded>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography variant="h6">基础配置</Typography>
|
<Typography variant="h6">{t('knowledge.config.basicConfig')}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
@@ -67,7 +69,7 @@ export function NaiveConfiguration() {
|
|||||||
{/* 第二部分:页面排名和自动提取 */}
|
{/* 第二部分:页面排名和自动提取 */}
|
||||||
<Accordion defaultExpanded>
|
<Accordion defaultExpanded>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography variant="h6">页面排名和自动提取</Typography>
|
<Typography variant="h6">{t('knowledge.config.pageRankAndAutoExtract')}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
@@ -92,7 +94,7 @@ export function NaiveConfiguration() {
|
|||||||
{/* 第三部分:RAPTOR策略 */}
|
{/* 第三部分:RAPTOR策略 */}
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography variant="h6">RAPTOR策略</Typography>
|
<Typography variant="h6">{t('knowledge.config.raptorStrategy')}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
@@ -120,7 +122,7 @@ export function NaiveConfiguration() {
|
|||||||
{/* 第四部分:知识图谱 */}
|
{/* 第四部分:知识图谱 */}
|
||||||
<Accordion>
|
<Accordion>
|
||||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||||
<Typography variant="h6">知识图谱</Typography>
|
<Typography variant="h6">{t('knowledge.config.knowledgeGraph')}</Typography>
|
||||||
</AccordionSummary>
|
</AccordionSummary>
|
||||||
<AccordionDetails>
|
<AccordionDetails>
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ConfigurationFormContainer } from './configuration-form-container';
|
import { ConfigurationFormContainer } from './configuration-form-container';
|
||||||
import { ChunkMethodItem, EmbeddingModelItem } from './common-items';
|
import { ChunkMethodItem, EmbeddingModelItem } from './common-items';
|
||||||
import { Box, Typography } from '@mui/material';
|
import { Box, Typography } from '@mui/material';
|
||||||
|
|
||||||
export function TagConfiguration() {
|
export function TagConfiguration() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigurationFormContainer>
|
<ConfigurationFormContainer>
|
||||||
<ChunkMethodItem />
|
<ChunkMethodItem />
|
||||||
@@ -11,7 +14,7 @@ export function TagConfiguration() {
|
|||||||
|
|
||||||
<Box sx={{ mb: 2 }}>
|
<Box sx={{ mb: 2 }}>
|
||||||
<Typography variant="body2" color="text.secondary">
|
<Typography variant="body2" color="text.secondary">
|
||||||
PageRank配置 - 待实现
|
{t('knowledge.config.pageRankConfigTodo')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</ConfigurationFormContainer>
|
</ConfigurationFormContainer>
|
||||||
|
|||||||
@@ -12,12 +12,14 @@ import {
|
|||||||
Stepper,
|
Stepper,
|
||||||
Step,
|
Step,
|
||||||
StepLabel,
|
StepLabel,
|
||||||
|
CircularProgress,
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import {
|
import {
|
||||||
ArrowBack as ArrowBackIcon,
|
ArrowBack as ArrowBackIcon,
|
||||||
CheckCircle as CheckCircleIcon,
|
CheckCircle as CheckCircleIcon,
|
||||||
SkipNext as SkipNextIcon,
|
SkipNext as SkipNextIcon,
|
||||||
} from '@mui/icons-material';
|
} from '@mui/icons-material';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useKnowledgeOperations, useKnowledgeDetail } from '@/hooks/knowledge-hooks';
|
import { useKnowledgeOperations, useKnowledgeDetail } from '@/hooks/knowledge-hooks';
|
||||||
import GeneralForm from './components/GeneralForm';
|
import GeneralForm from './components/GeneralForm';
|
||||||
import ChunkMethodForm from './components/ChunkMethodForm';
|
import ChunkMethodForm from './components/ChunkMethodForm';
|
||||||
@@ -32,13 +34,14 @@ interface BaseFormData {
|
|||||||
avatar?: string;
|
avatar?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const steps = ['基础信息', '配置设置'];
|
|
||||||
|
|
||||||
function KnowledgeBaseCreate() {
|
function KnowledgeBaseCreate() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [activeStep, setActiveStep] = useState(0);
|
const [activeStep, setActiveStep] = useState(0);
|
||||||
const [createdKbId, setCreatedKbId] = useState<string | null>(null);
|
const [createdKbId, setCreatedKbId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const steps = [t('knowledgeConfiguration.basicInfo'), t('knowledgeConfiguration.configSettings')];
|
||||||
|
|
||||||
// 使用知识库操作 hooks
|
// 使用知识库操作 hooks
|
||||||
const {
|
const {
|
||||||
loading: isSubmitting,
|
loading: isSubmitting,
|
||||||
@@ -89,8 +92,6 @@ function KnowledgeBaseCreate() {
|
|||||||
// 处理表单提交
|
// 处理表单提交
|
||||||
const handleSubmit = async ({ data }: { data: any }) => {
|
const handleSubmit = async ({ data }: { data: any }) => {
|
||||||
clearError();
|
clearError();
|
||||||
console.log('提交数据:', data);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (activeStep === 0) {
|
if (activeStep === 0) {
|
||||||
// 第一步:创建知识库基础信息
|
// 第一步:创建知识库基础信息
|
||||||
@@ -110,7 +111,7 @@ function KnowledgeBaseCreate() {
|
|||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
setActiveStep(1);
|
setActiveStep(1);
|
||||||
showMessage.success('知识库创建成功,请配置解析设置');
|
showMessage.success(t('knowledgeConfiguration.createSuccess'));
|
||||||
} else {
|
} else {
|
||||||
// 第二步:配置知识库解析设置
|
// 第二步:配置知识库解析设置
|
||||||
if (!createdKbId) return;
|
if (!createdKbId) return;
|
||||||
@@ -130,23 +131,23 @@ function KnowledgeBaseCreate() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await updateKnowledgeModelConfig(configData);
|
await updateKnowledgeModelConfig(configData);
|
||||||
showMessage.success('知识库配置完成');
|
showMessage.success(t('knowledgeConfiguration.configComplete'));
|
||||||
navigate('/knowledge');
|
navigate('/knowledge');
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('操作失败:', err);
|
console.error('操作失败:', err);
|
||||||
showMessage.error(activeStep === 0 ? '创建知识库失败' : '配置知识库失败');
|
showMessage.error(activeStep === 0 ? t('knowledgeConfiguration.createFailed') : t('knowledgeConfiguration.configFailed'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 跳过配置,直接完成创建
|
// 跳过配置,直接完成创建
|
||||||
const handleSkipConfig = async () => {
|
const handleSkipConfig = async () => {
|
||||||
try {
|
try {
|
||||||
showMessage.success('知识库创建完成,您可以稍后在设置页面配置解析参数');
|
showMessage.success(t('knowledgeConfiguration.skipConfigSuccess'));
|
||||||
navigate('/knowledge');
|
navigate('/knowledge');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('跳过配置失败:', err);
|
console.error('跳过配置失败:', err);
|
||||||
showMessage.error('操作失败');
|
showMessage.error(t('common.operationFailed'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -159,10 +160,10 @@ function KnowledgeBaseCreate() {
|
|||||||
onClick={() => navigate('/knowledge')}
|
onClick={() => navigate('/knowledge')}
|
||||||
sx={{ mr: 2 }}
|
sx={{ mr: 2 }}
|
||||||
>
|
>
|
||||||
返回
|
{t('common.back')}
|
||||||
</Button>
|
</Button>
|
||||||
<Typography variant="h4" component="h1">
|
<Typography variant="h4" component="h1">
|
||||||
创建知识库
|
{t('knowledgeList.createKnowledgeBase')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -198,11 +199,11 @@ function KnowledgeBaseCreate() {
|
|||||||
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
|
||||||
<CheckCircleIcon color="success" sx={{ mr: 1 }} />
|
<CheckCircleIcon color="success" sx={{ mr: 1 }} />
|
||||||
<Typography variant="h6">
|
<Typography variant="h6">
|
||||||
知识库已创建成功,现在可以配置解析设置
|
{t('knowledgeConfiguration.createSuccessConfig')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}>
|
||||||
您可以现在配置这些设置,也可以稍后在知识库详情页面中修改
|
{t('knowledgeConfiguration.configLaterTip')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Divider sx={{ mb: 3 }} />
|
<Divider sx={{ mb: 3 }} />
|
||||||
<ChunkMethodForm form={form} />
|
<ChunkMethodForm form={form} />
|
||||||
@@ -219,7 +220,7 @@ function KnowledgeBaseCreate() {
|
|||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={() => navigate('/knowledge')}
|
onClick={() => navigate('/knowledge')}
|
||||||
>
|
>
|
||||||
取消
|
{t('common.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -231,7 +232,7 @@ function KnowledgeBaseCreate() {
|
|||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
startIcon={<SkipNextIcon />}
|
startIcon={<SkipNextIcon />}
|
||||||
>
|
>
|
||||||
跳过配置
|
{t('knowledgeConfiguration.skipConfig')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -239,19 +240,12 @@ function KnowledgeBaseCreate() {
|
|||||||
type="submit"
|
type="submit"
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
startIcon={
|
startIcon={isSubmitting ? <CircularProgress size={20} /> : null}
|
||||||
activeStep === steps.length - 1 ? (
|
|
||||||
<CheckCircleIcon />
|
|
||||||
) : (
|
|
||||||
<SkipNextIcon />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{isSubmitting
|
{isSubmitting
|
||||||
? '处理中...'
|
? (activeStep === 0 ? t('knowledgeConfiguration.creating') : t('knowledgeConfiguration.configuring'))
|
||||||
: activeStep === steps.length - 1
|
: (activeStep === 0 ? t('knowledgeConfiguration.createAndNext') : t('knowledgeConfiguration.completeCreate'))
|
||||||
? '完成创建'
|
}
|
||||||
: '下一步'}
|
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -38,6 +39,7 @@ import logger from '@/utils/logger';
|
|||||||
function KnowledgeBaseDetail() {
|
function KnowledgeBaseDetail() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
@@ -114,20 +116,20 @@ function KnowledgeBaseDetail() {
|
|||||||
refreshFiles();
|
refreshFiles();
|
||||||
fetchKnowledgeDetail();
|
fetchKnowledgeDetail();
|
||||||
} catch (err: any) {
|
} 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[]) => {
|
const handleUploadFiles = async (uploadFiles: File[]) => {
|
||||||
console.log('上传文件:', uploadFiles);
|
console.log(t('knowledgeDetails.uploadFiles'), uploadFiles);
|
||||||
const kb_id = knowledgeBase?.id || '';
|
const kb_id = knowledgeBase?.id || '';
|
||||||
try {
|
try {
|
||||||
await uploadDocuments(kb_id, uploadFiles);
|
await uploadDocuments(kb_id, uploadFiles);
|
||||||
refreshFiles();
|
refreshFiles();
|
||||||
fetchKnowledgeDetail();
|
fetchKnowledgeDetail();
|
||||||
} catch (err: any) {
|
} 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(); // 刷新列表
|
refreshFiles(); // 刷新列表
|
||||||
startPolling(); // 开始轮询
|
startPolling(); // 开始轮询
|
||||||
} catch (err: any) {
|
} 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);
|
await renameDocument(fileId, newName);
|
||||||
refreshFiles();
|
refreshFiles();
|
||||||
} catch (err: any) {
|
} 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);
|
await changeDocumentStatus(fileIds, status);
|
||||||
refreshFiles();
|
refreshFiles();
|
||||||
} catch (err: any) {
|
} 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);
|
await cancelRunDocuments(fileIds);
|
||||||
refreshFiles();
|
refreshFiles();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.response?.data?.message || err.message || '取消运行失败');
|
setError(err.response?.data?.message || err.message || t('knowledgeDetails.cancelRunFailed'));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查看详情
|
// 查看详情
|
||||||
const handleViewDetails = (file: IKnowledgeFile) => {
|
const handleViewDetails = (file: IKnowledgeFile) => {
|
||||||
console.log("查看详情:", file);
|
console.log(t('knowledgeDetails.viewDetails'), file);
|
||||||
|
|
||||||
navigate(`/chunk/parsed-result?kb_id=${id}&doc_id=${file.id}`);
|
navigate(`/chunk/parsed-result?kb_id=${id}&doc_id=${file.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 查看解析详情
|
// 查看解析详情
|
||||||
const handleViewProcessDetails = (file: IKnowledgeFile) => {
|
const handleViewProcessDetails = (file: IKnowledgeFile) => {
|
||||||
console.log("查看解析详情:", file);
|
console.log(t('knowledgeDetails.viewProcessDetails'), file);
|
||||||
setSelectedFileDetails(file);
|
setSelectedFileDetails(file);
|
||||||
setProcessDetailsDialogOpen(true);
|
setProcessDetailsDialogOpen(true);
|
||||||
};
|
};
|
||||||
@@ -262,7 +264,7 @@ function KnowledgeBaseDetail() {
|
|||||||
return (
|
return (
|
||||||
<Box sx={{ p: 3 }}>
|
<Box sx={{ p: 3 }}>
|
||||||
<LinearProgress />
|
<LinearProgress />
|
||||||
<Typography sx={{ mt: 2 }}>加载中...</Typography>
|
<Typography sx={{ mt: 2 }}>{t('common.loading')}</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -281,11 +283,11 @@ function KnowledgeBaseDetail() {
|
|||||||
<KnowledgeBreadcrumbs
|
<KnowledgeBreadcrumbs
|
||||||
kbItems={[
|
kbItems={[
|
||||||
{
|
{
|
||||||
label: '知识库',
|
label: t('knowledgeDetails.knowledgeBase'),
|
||||||
path: '/knowledge'
|
path: '/knowledge'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: knowledgeBase?.name || '知识库详情',
|
label: knowledgeBase?.name || t('knowledgeDetails.knowledgeBaseDetail'),
|
||||||
path: `/knowledge/${id}`
|
path: `/knowledge/${id}`
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
@@ -302,8 +304,8 @@ function KnowledgeBaseDetail() {
|
|||||||
onChange={(event, newValue) => setCurrentTab(newValue)}
|
onChange={(event, newValue) => setCurrentTab(newValue)}
|
||||||
sx={{ borderBottom: 1, borderColor: 'divider' }}
|
sx={{ borderBottom: 1, borderColor: 'divider' }}
|
||||||
>
|
>
|
||||||
<Tab label="Documents" />
|
<Tab label={t('knowledgeDetails.documents')} />
|
||||||
<Tab label="Graph" />
|
<Tab label={t('knowledgeDetails.graph')} />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{/* Document List 标签页内容 */}
|
{/* Document List 标签页内容 */}
|
||||||
@@ -365,7 +367,7 @@ function KnowledgeBaseDetail() {
|
|||||||
onSearchChange={setSearchKeyword}
|
onSearchChange={setSearchKeyword}
|
||||||
onReparse={(fileIds) => handleReparse(fileIds)}
|
onReparse={(fileIds) => handleReparse(fileIds)}
|
||||||
onDelete={(fileIds) => {
|
onDelete={(fileIds) => {
|
||||||
console.log('删除文件:', fileIds);
|
console.log(t('knowledgeDetails.deleteFiles'), fileIds);
|
||||||
setRowSelectionModel({
|
setRowSelectionModel({
|
||||||
type: 'include',
|
type: 'include',
|
||||||
ids: new Set(fileIds)
|
ids: new Set(fileIds)
|
||||||
@@ -379,7 +381,7 @@ function KnowledgeBaseDetail() {
|
|||||||
}}
|
}}
|
||||||
rowSelectionModel={rowSelectionModel}
|
rowSelectionModel={rowSelectionModel}
|
||||||
onRowSelectionModelChange={(newModel) => {
|
onRowSelectionModelChange={(newModel) => {
|
||||||
console.log('新的选择模型:', newModel);
|
console.log(t('knowledgeDetails.newSelectionModel'), newModel);
|
||||||
setRowSelectionModel(newModel);
|
setRowSelectionModel(newModel);
|
||||||
}}
|
}}
|
||||||
total={total}
|
total={total}
|
||||||
@@ -410,7 +412,7 @@ function KnowledgeBaseDetail() {
|
|||||||
open={uploadDialogOpen}
|
open={uploadDialogOpen}
|
||||||
onClose={() => setUploadDialogOpen(false)}
|
onClose={() => setUploadDialogOpen(false)}
|
||||||
onUpload={handleUploadFiles}
|
onUpload={handleUploadFiles}
|
||||||
title="上传文件到知识库"
|
title={t('knowledgeDetails.uploadFilesToKnowledge')}
|
||||||
acceptedFileTypes={['.pdf', '.docx', '.txt', '.md', '.png', '.jpg', '.jpeg', '.mp4', '.wav']}
|
acceptedFileTypes={['.pdf', '.docx', '.txt', '.md', '.png', '.jpg', '.jpeg', '.mp4', '.wav']}
|
||||||
maxFileSize={100}
|
maxFileSize={100}
|
||||||
maxFiles={10}
|
maxFiles={10}
|
||||||
@@ -418,15 +420,15 @@ function KnowledgeBaseDetail() {
|
|||||||
|
|
||||||
{/* 删除确认对话框 */}
|
{/* 删除确认对话框 */}
|
||||||
<Dialog open={deleteDialogOpen} onClose={() => setDeleteDialogOpen(false)}>
|
<Dialog open={deleteDialogOpen} onClose={() => setDeleteDialogOpen(false)}>
|
||||||
<DialogTitle>确认删除</DialogTitle>
|
<DialogTitle>{t('knowledgeDetails.confirmDelete')}</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Typography>
|
<Typography>
|
||||||
确定要删除选中的 {rowSelectionModel.ids.size} 个文件吗?此操作不可撤销。
|
{t('knowledgeDetails.confirmDeleteMessage', { count: rowSelectionModel.ids.size })}
|
||||||
</Typography>
|
</Typography>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
<Button onClick={() => setDeleteDialogOpen(false)}>取消</Button>
|
<Button onClick={() => setDeleteDialogOpen(false)}>{t('common.cancel')}</Button>
|
||||||
<Button color="error" onClick={handleDeleteFiles}>删除</Button>
|
<Button color="error" onClick={handleDeleteFiles}>{t('common.delete')}</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
@@ -437,7 +439,7 @@ function KnowledgeBaseDetail() {
|
|||||||
maxWidth="md"
|
maxWidth="md"
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<DialogTitle>文档处理详情</DialogTitle>
|
<DialogTitle>{t('knowledgeDetails.documentProcessDetails')}</DialogTitle>
|
||||||
<DialogContent sx={{ p: 3 }}>
|
<DialogContent sx={{ p: 3 }}>
|
||||||
{selectedFileDetails && (
|
{selectedFileDetails && (
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
@@ -445,12 +447,12 @@ function KnowledgeBaseDetail() {
|
|||||||
<Card variant="outlined">
|
<Card variant="outlined">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" gutterBottom color="primary">
|
<Typography variant="h6" gutterBottom color="primary">
|
||||||
基本信息
|
{t('knowledgeDetails.basicInfo')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
||||||
文件名
|
{t('knowledgeDetails.fileName')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
<Typography variant="body1" sx={{ fontWeight: 500 }}>
|
||||||
{selectedFileDetails.name}
|
{selectedFileDetails.name}
|
||||||
@@ -459,10 +461,10 @@ function KnowledgeBaseDetail() {
|
|||||||
<Divider />
|
<Divider />
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
||||||
解析器ID
|
{t('knowledgeDetails.parserId')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1">
|
<Typography variant="body1">
|
||||||
{selectedFileDetails.parser_id || '未指定'}
|
{selectedFileDetails.parser_id || t('knowledgeDetails.notSpecified')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
@@ -473,30 +475,30 @@ function KnowledgeBaseDetail() {
|
|||||||
<Card variant="outlined">
|
<Card variant="outlined">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" gutterBottom color="primary">
|
<Typography variant="h6" gutterBottom color="primary">
|
||||||
处理状态
|
{t('knowledgeDetails.processStatus')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Stack spacing={2}>
|
<Stack spacing={2}>
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
||||||
开始时间
|
{t('knowledgeDetails.startTime')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1">
|
<Typography variant="body1">
|
||||||
{selectedFileDetails.process_begin_at || '未开始'}
|
{selectedFileDetails.process_begin_at || t('knowledgeDetails.notStarted')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
||||||
处理时长
|
{t('knowledgeDetails.processingTime')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1">
|
<Typography variant="body1">
|
||||||
{selectedFileDetails.process_duration ? `${selectedFileDetails.process_duration}秒` : '未完成'}
|
{selectedFileDetails.process_duration ? `${selectedFileDetails.process_duration}${t('knowledgeDetails.seconds')}` : t('knowledgeDetails.notCompleted')}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
<Typography variant="subtitle2" color="text.secondary" gutterBottom>
|
||||||
进度
|
{t('knowledgeDetails.progress')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||||
<Chip
|
<Chip
|
||||||
@@ -520,7 +522,7 @@ function KnowledgeBaseDetail() {
|
|||||||
<Card variant="outlined">
|
<Card variant="outlined">
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Typography variant="h6" gutterBottom color="primary">
|
<Typography variant="h6" gutterBottom color="primary">
|
||||||
处理详情
|
{t('knowledgeDetails.processDetails')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@@ -556,7 +558,7 @@ function KnowledgeBaseDetail() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions sx={{ p: 3, pt: 0 }}>
|
<DialogActions sx={{ p: 3, pt: 0 }}>
|
||||||
<Button onClick={() => setProcessDetailsDialogOpen(false)} variant="contained">
|
<Button onClick={() => setProcessDetailsDialogOpen(false)} variant="contained">
|
||||||
关闭
|
{t('common.close')}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogActions>
|
</DialogActions>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|||||||
@@ -25,8 +25,12 @@ import KnowledgeGridView from '@/components/knowledge/KnowledgeGridView';
|
|||||||
import type { IKnowledge } from '@/interfaces/database/knowledge';
|
import type { IKnowledge } from '@/interfaces/database/knowledge';
|
||||||
import { useDialog } from '@/hooks/useDialog';
|
import { useDialog } from '@/hooks/useDialog';
|
||||||
import logger from '@/utils/logger';
|
import logger from '@/utils/logger';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
const KnowledgeBaseList: React.FC = () => {
|
const KnowledgeBaseList: React.FC = () => {
|
||||||
|
|
||||||
|
const {t} = useTranslation();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { deleteKnowledge } = useKnowledgeOperations();
|
const { deleteKnowledge } = useKnowledgeOperations();
|
||||||
@@ -95,8 +99,8 @@ const KnowledgeBaseList: React.FC = () => {
|
|||||||
const handleDeleteKnowledge = useCallback(async (kb: IKnowledge) => {
|
const handleDeleteKnowledge = useCallback(async (kb: IKnowledge) => {
|
||||||
// 需要确认删除
|
// 需要确认删除
|
||||||
dialog.confirm({
|
dialog.confirm({
|
||||||
title: '确认删除',
|
title: t('common.deleteModalTitle'),
|
||||||
content: `是否确认删除知识库 ${kb.name}?`,
|
content: `${t('knowledgeList.confirmDeleteKnowledge')} ${kb.name}?`,
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
try {
|
try {
|
||||||
await deleteKnowledge(kb.id);
|
await deleteKnowledge(kb.id);
|
||||||
@@ -128,7 +132,7 @@ const KnowledgeBaseList: React.FC = () => {
|
|||||||
// 构建团队筛选选项
|
// 构建团队筛选选项
|
||||||
const teamFilterOptions = useMemo(() => {
|
const teamFilterOptions = useMemo(() => {
|
||||||
const options = [
|
const options = [
|
||||||
{ value: 'all', label: '全部' },
|
{ value: 'all', label: t('common.all') },
|
||||||
];
|
];
|
||||||
|
|
||||||
// 添加租户选项
|
// 添加租户选项
|
||||||
@@ -149,7 +153,7 @@ const KnowledgeBaseList: React.FC = () => {
|
|||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 3 }}>
|
||||||
<Typography variant="h4" fontWeight={600}>
|
<Typography variant="h4" fontWeight={600}>
|
||||||
知识库管理
|
{t('header.knowledgeBase')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
@@ -157,14 +161,14 @@ const KnowledgeBaseList: React.FC = () => {
|
|||||||
onClick={handleCreateKnowledge}
|
onClick={handleCreateKnowledge}
|
||||||
sx={{ borderRadius: 2 }}
|
sx={{ borderRadius: 2 }}
|
||||||
>
|
>
|
||||||
新建知识库
|
{t('knowledgeList.createKnowledgeBase')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{/* 搜索和筛选区域 */}
|
{/* 搜索和筛选区域 */}
|
||||||
<Box sx={{ display: 'flex', gap: 2, mb: 3, alignItems: 'center' }}>
|
<Box sx={{ display: 'flex', gap: 2, mb: 3, alignItems: 'center' }}>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="搜索知识库..."
|
placeholder={t('knowledgeList.searchKnowledgePlaceholder')}
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => handleSearch(e.target.value)}
|
onChange={(e) => handleSearch(e.target.value)}
|
||||||
sx={{ flex: 1, maxWidth: 400 }}
|
sx={{ flex: 1, maxWidth: 400 }}
|
||||||
@@ -178,10 +182,10 @@ const KnowledgeBaseList: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControl sx={{ minWidth: 120 }}>
|
<FormControl sx={{ minWidth: 120 }}>
|
||||||
<InputLabel>团队筛选</InputLabel>
|
<InputLabel>{t('knowledgeList.teamFilter')}</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
value={teamFilter.length > 0 ? teamFilter[0] : 'all'}
|
value={teamFilter.length > 0 ? teamFilter[0] : 'all'}
|
||||||
label="团队筛选"
|
label={t('knowledgeList.teamFilter')}
|
||||||
defaultValue={'all'}
|
defaultValue={'all'}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
const value = e.target.value as string;
|
const value = e.target.value as string;
|
||||||
@@ -202,7 +206,7 @@ const KnowledgeBaseList: React.FC = () => {
|
|||||||
onClick={handleRefresh}
|
onClick={handleRefresh}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
刷新
|
{t('common.refresh')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -210,7 +214,7 @@ const KnowledgeBaseList: React.FC = () => {
|
|||||||
{error && (
|
{error && (
|
||||||
<Box sx={{ mb: 3, p: 2, bgcolor: 'error.light', borderRadius: 1 }}>
|
<Box sx={{ mb: 3, p: 2, bgcolor: 'error.light', borderRadius: 1 }}>
|
||||||
<Typography color="error">
|
<Typography color="error">
|
||||||
加载知识库列表失败: {error}
|
{t('knowledgeList.loadError')}: {error}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@@ -249,7 +253,11 @@ const KnowledgeBaseList: React.FC = () => {
|
|||||||
showLastButton
|
showLastButton
|
||||||
/>
|
/>
|
||||||
<Typography variant="body2" color="text.secondary" textAlign="center">
|
<Typography variant="body2" color="text.secondary" textAlign="center">
|
||||||
共 {knowledgeBases.length} 个知识库,第 {currentPage} 页,共 {totalPages} 页
|
{t('knowledgeList.paginationInfo', {
|
||||||
|
total: knowledgeBases.length,
|
||||||
|
current: currentPage,
|
||||||
|
totalPages: totalPages
|
||||||
|
})}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useForm, FormProvider, useWatch, Form } from 'react-hook-form';
|
import { useForm, FormProvider, useWatch, Form } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Typography,
|
Typography,
|
||||||
@@ -33,6 +34,7 @@ interface BaseFormData {
|
|||||||
function KnowledgeBaseSetting() {
|
function KnowledgeBaseSetting() {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { t } = useTranslation();
|
||||||
const [tabValue, setTabValue] = useState<'generalForm' | 'chunkMethodForm'>('generalForm');
|
const [tabValue, setTabValue] = useState<'generalForm' | 'chunkMethodForm'>('generalForm');
|
||||||
|
|
||||||
// 获取知识库详情
|
// 获取知识库详情
|
||||||
@@ -103,7 +105,7 @@ function KnowledgeBaseSetting() {
|
|||||||
|
|
||||||
const handleSubmit = async ({data}: {data: any}) => {
|
const handleSubmit = async ({data}: {data: any}) => {
|
||||||
if (!knowledge) return;
|
if (!knowledge) return;
|
||||||
console.log('提交数据:', data);
|
console.log(t('knowledgeSettings.submitData'), data);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 分别处理基础信息和配置信息
|
// 分别处理基础信息和配置信息
|
||||||
@@ -118,7 +120,7 @@ function KnowledgeBaseSetting() {
|
|||||||
} as any;
|
} as any;
|
||||||
|
|
||||||
await updateKnowledgeBasicInfo(basicData);
|
await updateKnowledgeBasicInfo(basicData);
|
||||||
showMessage.success('基础信息更新成功');
|
showMessage.success(t('knowledgeSettings.basicInfoUpdateSuccess'));
|
||||||
} else {
|
} else {
|
||||||
const configData = {
|
const configData = {
|
||||||
kb_id: knowledge.id,
|
kb_id: knowledge.id,
|
||||||
@@ -132,13 +134,15 @@ function KnowledgeBaseSetting() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await updateKnowledgeModelConfig(configData);
|
await updateKnowledgeModelConfig(configData);
|
||||||
showMessage.success('解析配置更新成功');
|
showMessage.success(t('knowledgeSettings.parseConfigUpdateSuccess'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 刷新知识库详情
|
// 刷新知识库详情
|
||||||
refresh();
|
refresh();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showMessage.error(`${tabValue === 'generalForm' ? '基础信息' : '解析配置'}更新失败`);
|
showMessage.error(t('knowledgeSettings.updateFailed', {
|
||||||
|
type: tabValue === 'generalForm' ? t('knowledgeSettings.basicInfo') : t('knowledgeSettings.parseConfig')
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -149,7 +153,7 @@ function KnowledgeBaseSetting() {
|
|||||||
if (detailLoading) {
|
if (detailLoading) {
|
||||||
return (
|
return (
|
||||||
<MainContainer>
|
<MainContainer>
|
||||||
<Typography>加载中...</Typography>
|
<Typography>{t('common.loading')}</Typography>
|
||||||
</MainContainer>
|
</MainContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -160,22 +164,22 @@ function KnowledgeBaseSetting() {
|
|||||||
<KnowledgeBreadcrumbs
|
<KnowledgeBreadcrumbs
|
||||||
kbItems={[
|
kbItems={[
|
||||||
{
|
{
|
||||||
label: '知识库',
|
label: t('knowledgeSettings.knowledgeBase'),
|
||||||
path: '/knowledge'
|
path: '/knowledge'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: knowledge?.name || '知识库详情',
|
label: knowledge?.name || t('knowledgeSettings.knowledgeBaseDetail'),
|
||||||
path: `/knowledge/${id}`
|
path: `/knowledge/${id}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '设置'
|
label: t('knowledgeSettings.settings')
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h4" gutterBottom>
|
||||||
知识库设置
|
{t('knowledgeSettings.knowledgeBaseSettings')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="subtitle1" color="text.secondary">
|
<Typography variant="subtitle1" color="text.secondary">
|
||||||
{knowledge?.name}
|
{knowledge?.name}
|
||||||
@@ -185,9 +189,9 @@ function KnowledgeBaseSetting() {
|
|||||||
<FormProvider {...form}>
|
<FormProvider {...form}>
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
|
||||||
<Tabs value={tabValue} onChange={handleTabChange} aria-label="设置选项卡">
|
<Tabs value={tabValue} onChange={handleTabChange} aria-label={t('knowledgeSettings.settingsTabs')}>
|
||||||
<Tab label="基础信息" value="generalForm" />
|
<Tab label={t('knowledgeSettings.basicInfo')} value="generalForm" />
|
||||||
<Tab label="解析配置" value="chunkMethodForm" />
|
<Tab label={t('knowledgeSettings.parseConfig')} value="chunkMethodForm" />
|
||||||
</Tabs>
|
</Tabs>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
@@ -202,7 +206,7 @@ function KnowledgeBaseSetting() {
|
|||||||
|
|
||||||
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end' }}>
|
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end' }}>
|
||||||
<Button type="submit" variant="contained">
|
<Button type="submit" variant="contained">
|
||||||
保存
|
{t('common.save')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Form>
|
</Form>
|
||||||
@@ -211,7 +215,7 @@ function KnowledgeBaseSetting() {
|
|||||||
{/* 返回按钮 */}
|
{/* 返回按钮 */}
|
||||||
<Fab
|
<Fab
|
||||||
color="primary"
|
color="primary"
|
||||||
aria-label="返回知识库详情"
|
aria-label={t('knowledgeSettings.backToKnowledgeDetail')}
|
||||||
onClick={handleNavigateBack}
|
onClick={handleNavigateBack}
|
||||||
sx={{
|
sx={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import { useParams, useNavigate } from 'react-router-dom';
|
import { useParams, useNavigate } from 'react-router-dom';
|
||||||
import { useForm, Controller } from 'react-hook-form';
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
Container,
|
Container,
|
||||||
@@ -31,27 +32,21 @@ import type { INextTestingResult } from '@/interfaces/database/knowledge';
|
|||||||
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
|
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
|
||||||
import TestChunkResult from './components/TestChunkResult';
|
import TestChunkResult from './components/TestChunkResult';
|
||||||
import { useSnackbar } from '@/components/Provider/SnackbarProvider';
|
import { useSnackbar } from '@/components/Provider/SnackbarProvider';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { toLower } from 'lodash';
|
import { toLower } from 'lodash';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
|
||||||
// 语言选项常量
|
// 语言选项常量
|
||||||
const Languages = [
|
const options = [
|
||||||
'English',
|
{ value: 'en', label: t('knowledgeTesting.languages.english') },
|
||||||
'Chinese',
|
{ value: 'zh', label: t('knowledgeTesting.languages.chinese') },
|
||||||
'Spanish',
|
{ value: 'ja', label: t('knowledgeTesting.languages.japanese') },
|
||||||
'French',
|
{ value: 'ko', label: t('knowledgeTesting.languages.korean') },
|
||||||
'German',
|
{ value: 'fr', label: t('knowledgeTesting.languages.french') },
|
||||||
'Japanese',
|
{ value: 'de', label: t('knowledgeTesting.languages.german') },
|
||||||
'Korean',
|
{ value: 'es', label: t('knowledgeTesting.languages.spanish') },
|
||||||
'Vietnamese',
|
{ value: 'vi', label: t('knowledgeTesting.languages.vietnamese') },
|
||||||
];
|
];
|
||||||
|
|
||||||
const options = Languages.map((x) => ({
|
|
||||||
label: t('language.' + toLower(x)),
|
|
||||||
value: x,
|
|
||||||
}));
|
|
||||||
|
|
||||||
// 表单数据接口
|
// 表单数据接口
|
||||||
interface TestFormData {
|
interface TestFormData {
|
||||||
question: string;
|
question: string;
|
||||||
@@ -65,9 +60,9 @@ interface TestFormData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function KnowledgeBaseTesting() {
|
function KnowledgeBaseTesting() {
|
||||||
|
const { t } = useTranslation();
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
// 状态管理
|
// 状态管理
|
||||||
const [testResult, setTestResult] = useState<INextTestingResult | null>(null);
|
const [testResult, setTestResult] = useState<INextTestingResult | null>(null);
|
||||||
@@ -151,12 +146,12 @@ function KnowledgeBaseTesting() {
|
|||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
setTestResult(response.data.data);
|
setTestResult(response.data.data);
|
||||||
setPage(1); // 重置到第一页
|
setPage(1); // 重置到第一页
|
||||||
showMessage.success('检索测试完成');
|
showMessage.success(t('knowledgeTesting.retrievalTestComplete'));
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.data.message || '检索测试失败');
|
throw new Error(response.data.message || t('knowledgeTesting.retrievalTestFailed'));
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showMessage.error(error.message || '检索测试失败');
|
showMessage.error(error.message || t('knowledgeTesting.retrievalTestFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setTesting(false);
|
setTesting(false);
|
||||||
}
|
}
|
||||||
@@ -203,10 +198,10 @@ function KnowledgeBaseTesting() {
|
|||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
setTestResult(response.data.data);
|
setTestResult(response.data.data);
|
||||||
} else {
|
} else {
|
||||||
throw new Error(response.data.message || '分页请求失败');
|
throw new Error(response.data.message || t('knowledgeTesting.paginationRequestFailed'));
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
showMessage.error(error.message || '分页请求失败');
|
showMessage.error(error.message || t('knowledgeTesting.paginationRequestFailed'));
|
||||||
} finally {
|
} finally {
|
||||||
setTesting(false);
|
setTesting(false);
|
||||||
}
|
}
|
||||||
@@ -227,7 +222,7 @@ function KnowledgeBaseTesting() {
|
|||||||
if (detailLoading) {
|
if (detailLoading) {
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="lg" sx={{ py: 4 }}>
|
<Container maxWidth="lg" sx={{ py: 4 }}>
|
||||||
<Typography>加载中...</Typography>
|
<Typography>{t('common.loading')}</Typography>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -239,22 +234,22 @@ function KnowledgeBaseTesting() {
|
|||||||
<KnowledgeBreadcrumbs
|
<KnowledgeBreadcrumbs
|
||||||
kbItems={[
|
kbItems={[
|
||||||
{
|
{
|
||||||
label: '知识库',
|
label: t('knowledgeTesting.knowledgeBase'),
|
||||||
path: '/knowledge'
|
path: '/knowledge'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: knowledgeDetail?.name || '知识库详情',
|
label: knowledgeDetail?.name || t('knowledgeTesting.knowledgeBaseDetail'),
|
||||||
path: `/knowledge/${id}`
|
path: `/knowledge/${id}`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '测试'
|
label: t('knowledgeTesting.testing')
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 3 }}>
|
||||||
<Typography variant="h4" gutterBottom>
|
<Typography variant="h4" gutterBottom>
|
||||||
知识库测试
|
{t('knowledgeTesting.knowledgeBaseTesting')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="subtitle1" color="text.secondary">
|
<Typography variant="subtitle1" color="text.secondary">
|
||||||
{knowledgeDetail?.name}
|
{knowledgeDetail?.name}
|
||||||
@@ -266,25 +261,25 @@ function KnowledgeBaseTesting() {
|
|||||||
<Grid size={4}>
|
<Grid size={4}>
|
||||||
<Paper sx={{ p: 3, position: 'sticky', top: 20 }}>
|
<Paper sx={{ p: 3, position: 'sticky', top: 20 }}>
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
测试配置
|
{t('knowledgeTesting.testConfiguration')}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<Box component="form" onSubmit={handleSubmit(handleTestSubmit)} sx={{ mt: 2 }}>
|
<Box component="form" onSubmit={handleSubmit(handleTestSubmit)} sx={{ mt: 2 }}>
|
||||||
<TextField
|
<TextField
|
||||||
{...register('question', { required: '请输入测试问题' })}
|
{...register('question', { required: t('knowledgeTesting.pleaseEnterTestQuestion') })}
|
||||||
label="测试问题"
|
label={t('knowledgeTesting.testQuestion')}
|
||||||
multiline
|
multiline
|
||||||
rows={3}
|
rows={3}
|
||||||
fullWidth
|
fullWidth
|
||||||
margin="normal"
|
margin="normal"
|
||||||
error={!!errors.question}
|
error={!!errors.question}
|
||||||
helperText={errors.question?.message}
|
helperText={errors.question?.message}
|
||||||
placeholder="请输入您想要测试的问题..."
|
placeholder={t('knowledgeTesting.testQuestionPlaceholder')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box sx={{ mt: 3 }}>
|
<Box sx={{ mt: 3 }}>
|
||||||
<Typography gutterBottom>
|
<Typography gutterBottom>
|
||||||
相似度阈值: {watch('similarity_threshold')}
|
{t('knowledgeTesting.similarityThreshold')}: {watch('similarity_threshold')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Slider
|
<Slider
|
||||||
{...register('similarity_threshold')}
|
{...register('similarity_threshold')}
|
||||||
@@ -300,7 +295,7 @@ function KnowledgeBaseTesting() {
|
|||||||
|
|
||||||
<Box sx={{ mt: 3 }}>
|
<Box sx={{ mt: 3 }}>
|
||||||
<Typography gutterBottom>
|
<Typography gutterBottom>
|
||||||
向量相似度权重: {watch('vector_similarity_weight')}
|
{t('knowledgeTesting.vectorSimilarityWeight')}: {watch('vector_similarity_weight')}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Slider
|
<Slider
|
||||||
{...register('vector_similarity_weight')}
|
{...register('vector_similarity_weight')}
|
||||||
@@ -315,18 +310,18 @@ function KnowledgeBaseTesting() {
|
|||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<FormControl fullWidth margin="normal">
|
<FormControl fullWidth margin="normal">
|
||||||
<InputLabel>重排序模型 (可选)</InputLabel>
|
<InputLabel>{t('knowledgeTesting.rerankModel')}</InputLabel>
|
||||||
<Controller
|
<Controller
|
||||||
name="rerank_id"
|
name="rerank_id"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Select
|
<Select
|
||||||
{...field}
|
{...field}
|
||||||
label="重排序模型 (可选)"
|
label={t('knowledgeTesting.rerankModel')}
|
||||||
disabled={rerankLoading}
|
disabled={rerankLoading}
|
||||||
>
|
>
|
||||||
<MenuItem value="">
|
<MenuItem value="">
|
||||||
<em>不使用重排序</em>
|
<em>{t('knowledgeTesting.noRerank')}</em>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
{rerankOptions.map((group) => [
|
{rerankOptions.map((group) => [
|
||||||
<ListSubheader key={group.label}>{group.label}</ListSubheader>,
|
<ListSubheader key={group.label}>{group.label}</ListSubheader>,
|
||||||
@@ -345,9 +340,9 @@ function KnowledgeBaseTesting() {
|
|||||||
{watch('rerank_id') && (
|
{watch('rerank_id') && (
|
||||||
<TextField
|
<TextField
|
||||||
{...register('top_k', {
|
{...register('top_k', {
|
||||||
required: '请输入返回结果数量',
|
required: t('knowledgeTesting.pleaseEnterResultCount'),
|
||||||
min: { value: 1, message: '最小值为1' },
|
min: { value: 1, message: t('knowledgeTesting.minValue1') },
|
||||||
max: { value: 2048, message: '最大值为2048' }
|
max: { value: 2048, message: t('knowledgeTesting.maxValue2048') }
|
||||||
})}
|
})}
|
||||||
label="Top-K"
|
label="Top-K"
|
||||||
type="number"
|
type="number"
|
||||||
@@ -355,12 +350,12 @@ function KnowledgeBaseTesting() {
|
|||||||
margin="normal"
|
margin="normal"
|
||||||
inputProps={{ min: 1, max: 2048 }}
|
inputProps={{ min: 1, max: 2048 }}
|
||||||
error={!!errors.top_k}
|
error={!!errors.top_k}
|
||||||
helperText={errors.top_k?.message || '与Rerank模型配合使用'}
|
helperText={errors.top_k?.message || t('knowledgeTesting.useWithRerankModel')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormControl fullWidth margin="normal">
|
<FormControl fullWidth margin="normal">
|
||||||
<InputLabel>跨语言搜索</InputLabel>
|
<InputLabel>{t('knowledgeTesting.crossLanguageSearch')}</InputLabel>
|
||||||
<Controller
|
<Controller
|
||||||
name="cross_languages"
|
name="cross_languages"
|
||||||
control={control}
|
control={control}
|
||||||
@@ -368,8 +363,8 @@ function KnowledgeBaseTesting() {
|
|||||||
<Select
|
<Select
|
||||||
{...field}
|
{...field}
|
||||||
multiple
|
multiple
|
||||||
label="跨语言搜索"
|
label={t('knowledgeTesting.crossLanguageSearch')}
|
||||||
input={<OutlinedInput label="跨语言搜索" />}
|
input={<OutlinedInput label={t('knowledgeTesting.crossLanguageSearch')} />}
|
||||||
renderValue={(selected) => (
|
renderValue={(selected) => (
|
||||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.5 }}>
|
||||||
{selected.map((value) => {
|
{selected.map((value) => {
|
||||||
@@ -399,7 +394,7 @@ function KnowledgeBaseTesting() {
|
|||||||
checked={watch('use_kg')}
|
checked={watch('use_kg')}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label="使用知识图谱"
|
label={t('knowledgeTesting.useKnowledgeGraph')}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -409,7 +404,7 @@ function KnowledgeBaseTesting() {
|
|||||||
disabled={testing}
|
disabled={testing}
|
||||||
sx={{ mt: 2 }}
|
sx={{ mt: 2 }}
|
||||||
>
|
>
|
||||||
{testing ? '测试中...' : '开始测试'}
|
{testing ? t('knowledgeTesting.testing') : t('knowledgeTesting.startTest')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
|
|||||||
@@ -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<z.infer<typeof formSchema>>({
|
|
||||||
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
|
|
||||||
<section className="p-5 h-full flex flex-col">
|
|
||||||
<TopTitle />
|
|
||||||
<div className="flex gap-14 flex-1 min-h-0">
|
|
||||||
<Form {...form}>
|
|
||||||
<form className="space-y-6 flex-1">
|
|
||||||
<Tabs>
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="generalForm">通用</TabsTrigger>
|
|
||||||
<TabsTrigger value="chunkMethodForm">解析方法</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="generalForm">
|
|
||||||
<GeneralForm />
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="chunkMethodForm">
|
|
||||||
<ChunkMethodForm />
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</form>
|
|
||||||
</Form>
|
|
||||||
<ChunkMethodLearnMore />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### 表单容器组件
|
|
||||||
```typescript
|
|
||||||
// configuration-form-container.tsx
|
|
||||||
export function ConfigurationFormContainer({ children, className }) {
|
|
||||||
return (
|
|
||||||
<FormContainer className={cn('p-10', className)}>
|
|
||||||
{children}
|
|
||||||
</FormContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function MainContainer({ children }) {
|
|
||||||
return <section className="space-y-5">{children}</section>;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 组件设计模式
|
|
||||||
|
|
||||||
### 1. 通用表单组件 (general-form.tsx)
|
|
||||||
- 使用 `FormField` 组件统一表单字段样式
|
|
||||||
- 采用 1/4 和 3/4 的标签与输入框布局比例
|
|
||||||
- 集成头像上传、权限选择等通用功能
|
|
||||||
|
|
||||||
### 2. 配置组件模式
|
|
||||||
每个配置组件遵循统一的结构:
|
|
||||||
```typescript
|
|
||||||
export function NaiveConfiguration() {
|
|
||||||
return (
|
|
||||||
<ConfigurationFormContainer>
|
|
||||||
<MainContainer>
|
|
||||||
<LayoutRecognizeFormField />
|
|
||||||
<MaxTokenNumberFormField />
|
|
||||||
<DelimiterFormField />
|
|
||||||
<AutoKeywordsFormField />
|
|
||||||
<AutoQuestionsFormField />
|
|
||||||
<TagItems />
|
|
||||||
</MainContainer>
|
|
||||||
</ConfigurationFormContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 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` 监听字段变化
|
|
||||||
- 实现数据获取钩子
|
|
||||||
- 优化表单重置和提交逻辑
|
|
||||||
|
|
||||||
这种架构设计为大型表单应用提供了良好的可维护性和扩展性基础,值得在用户项目中借鉴和应用。
|
|
||||||
Reference in New Issue
Block a user