feat(knowledge): add configuration components and refactor general form

feat(hooks): add llm-related hooks and constants
docs: add architecture analysis for reference projects
This commit is contained in:
2025-10-15 16:24:53 +08:00
parent 486815d83e
commit fe8747983e
33 changed files with 2627 additions and 812 deletions

1
.env
View File

@@ -1,4 +1,5 @@
VITE_API_BASE_URL = http://150.158.121.95 VITE_API_BASE_URL = http://150.158.121.95
# VITE_API_BASE_URL = http://154.9.253.114
VITE_RSA_PUBLIC_KEY="-----BEGIN PUBLIC KEY----- VITE_RSA_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArq9XTUSeYr2+N1h3Afl/z8Dse/2yD0ZGrKwx+EEEcdsBLca9Ynmx3nIB5obmLlSfmskLpBo0UACBmB5rEjBp2Q2f3AG3Hjd4B+gNCG6BDaawuDlgANIhGnaTLrIqWrrcm4EMzJOnAOI1fgzJRsOOUEfaS318Eq9OVO3apEyCCt0lOQK6PuksduOjVxtltDav+guVAA068NrPYmRNabVKRNLJpL8w4D44sfth5RvZ3q9t+6RTArpEtc5sh5ChzvqPOzKGMXW83C95TxmXqpbK6olN4RevSfVjEAgCydH6HN6OhtOQEcnrU97r9H0iZOWwbw3pVrZiUkuRD1R56Wzs2wIDAQAB MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArq9XTUSeYr2+N1h3Afl/z8Dse/2yD0ZGrKwx+EEEcdsBLca9Ynmx3nIB5obmLlSfmskLpBo0UACBmB5rEjBp2Q2f3AG3Hjd4B+gNCG6BDaawuDlgANIhGnaTLrIqWrrcm4EMzJOnAOI1fgzJRsOOUEfaS318Eq9OVO3apEyCCt0lOQK6PuksduOjVxtltDav+guVAA068NrPYmRNabVKRNLJpL8w4D44sfth5RvZ3q9t+6RTArpEtc5sh5ChzvqPOzKGMXW83C95TxmXqpbK6olN4RevSfVjEAgCydH6HN6OhtOQEcnrU97r9H0iZOWwbw3pVrZiUkuRD1R56Wzs2wIDAQAB

View File

@@ -55,7 +55,7 @@ const Header = () => {
color: '#333', color: '#333',
}} }}
> >
RAG Dashboard RAG Empowerment System
</Box> </Box>
<Box <Box

View File

@@ -41,7 +41,7 @@ const Sidebar = () => {
letterSpacing: '0.5px', letterSpacing: '0.5px',
}} }}
> >
RAGflow Prototype T-Systems Enterprise
</Typography> </Typography>
<List> <List>
@@ -93,7 +93,7 @@ const Sidebar = () => {
opacity: 0.7, opacity: 0.7,
}} }}
> >
© 2025 RAG Demo © 2025 T-Systems
</Box> </Box>
</Box> </Box>
); );

126
src/constants/llm.ts Normal file
View File

@@ -0,0 +1,126 @@
export const LLM_FACTORY_LIST = Object.freeze({
TongYiQianWen: 'Tongyi-Qianwen',
Moonshot: 'Moonshot',
OpenAI: 'OpenAI',
ZhipuAI: 'ZHIPU-AI',
WenXinYiYan: '文心一言',
Ollama: 'Ollama',
Xinference: 'Xinference',
ModelScope: 'ModelScope',
DeepSeek: 'DeepSeek',
VolcEngine: 'VolcEngine',
BaiChuan: 'BaiChuan',
Jina: 'Jina',
MiniMax: 'MiniMax',
Mistral: 'Mistral',
AzureOpenAI: 'Azure-OpenAI',
Bedrock: 'Bedrock',
Gemini: 'Gemini',
Groq: 'Groq',
OpenRouter: 'OpenRouter',
LocalAI: 'LocalAI',
StepFun: 'StepFun',
NVIDIA: 'NVIDIA',
LMStudio: 'LM-Studio',
OpenAiAPICompatible: 'OpenAI-API-Compatible',
Cohere: 'Cohere',
LeptonAI: 'LeptonAI',
TogetherAI: 'TogetherAI',
PerfXCloud: 'PerfXCloud',
Upstage: 'Upstage',
NovitaAI: 'NovitaAI',
SILICONFLOW: 'SILICONFLOW',
PPIO: 'PPIO',
ZeroOneAI: '01.AI',
Replicate: 'Replicate',
TencentHunYuan: 'Tencent Hunyuan',
XunFeiSpark: 'XunFei Spark',
BaiduYiYan: 'BaiduYiyan',
FishAudio: 'Fish Audio',
TencentCloud: 'Tencent Cloud',
Anthropic: 'Anthropic',
VoyageAI: 'Voyage AI',
GoogleCloud: 'Google Cloud',
HuggingFace: 'HuggingFace',
YouDao: 'Youdao',
BAAI: 'BAAI',
NomicAI: 'nomic-ai',
JinaAI: 'jinaai',
SentenceTransformers: 'sentence-transformers',
GPUStack: 'GPUStack',
VLLM: 'VLLM',
GiteeAI: 'GiteeAI',
Ai302: '302.AI',
DeepInfra: 'DeepInfra',
Grok: 'Grok',
XAI: 'xAI',
TokenPony: 'TokenPony',
Meituan: 'Meituan',
CometAPI: 'CometAPI',
DeerAPI: 'DeerAPI',
} as const);
export type LLMFactory = (typeof LLM_FACTORY_LIST)[keyof typeof LLM_FACTORY_LIST];
// Please lowercase the file name
export const IconMap = {
[LLM_FACTORY_LIST.TongYiQianWen]: 'tongyi',
[LLM_FACTORY_LIST.Moonshot]: 'moonshot',
[LLM_FACTORY_LIST.OpenAI]: 'openai',
[LLM_FACTORY_LIST.ZhipuAI]: 'zhipu',
[LLM_FACTORY_LIST.WenXinYiYan]: 'wenxin',
[LLM_FACTORY_LIST.Ollama]: 'ollama',
[LLM_FACTORY_LIST.Xinference]: 'xinference',
[LLM_FACTORY_LIST.ModelScope]: 'modelscope',
[LLM_FACTORY_LIST.DeepSeek]: 'deepseek',
[LLM_FACTORY_LIST.VolcEngine]: 'volc_engine',
[LLM_FACTORY_LIST.BaiChuan]: 'baichuan',
[LLM_FACTORY_LIST.Jina]: 'jina',
[LLM_FACTORY_LIST.MiniMax]: 'chat-minimax',
[LLM_FACTORY_LIST.Mistral]: 'mistral',
[LLM_FACTORY_LIST.AzureOpenAI]: 'azure',
[LLM_FACTORY_LIST.Bedrock]: 'bedrock',
[LLM_FACTORY_LIST.Gemini]: 'gemini',
[LLM_FACTORY_LIST.Groq]: 'groq-next',
[LLM_FACTORY_LIST.OpenRouter]: 'open-router',
[LLM_FACTORY_LIST.LocalAI]: 'local-ai',
[LLM_FACTORY_LIST.StepFun]: 'stepfun',
[LLM_FACTORY_LIST.NVIDIA]: 'nvidia',
[LLM_FACTORY_LIST.LMStudio]: 'lm-studio',
[LLM_FACTORY_LIST.OpenAiAPICompatible]: 'openai-api',
[LLM_FACTORY_LIST.Cohere]: 'cohere',
[LLM_FACTORY_LIST.LeptonAI]: 'lepton-ai',
[LLM_FACTORY_LIST.TogetherAI]: 'together-ai',
[LLM_FACTORY_LIST.PerfXCloud]: 'perfx-cloud',
[LLM_FACTORY_LIST.Upstage]: 'upstage',
[LLM_FACTORY_LIST.NovitaAI]: 'novita-ai',
[LLM_FACTORY_LIST.SILICONFLOW]: 'siliconflow',
[LLM_FACTORY_LIST.PPIO]: 'ppio',
[LLM_FACTORY_LIST.ZeroOneAI]: 'yi',
[LLM_FACTORY_LIST.Replicate]: 'replicate',
[LLM_FACTORY_LIST.TencentHunYuan]: 'hunyuan',
[LLM_FACTORY_LIST.XunFeiSpark]: 'spark',
[LLM_FACTORY_LIST.BaiduYiYan]: 'yiyan',
[LLM_FACTORY_LIST.FishAudio]: 'fish-audio',
[LLM_FACTORY_LIST.TencentCloud]: 'tencent-cloud',
[LLM_FACTORY_LIST.Anthropic]: 'anthropic',
[LLM_FACTORY_LIST.VoyageAI]: 'voyage',
[LLM_FACTORY_LIST.GoogleCloud]: 'google-cloud',
[LLM_FACTORY_LIST.HuggingFace]: 'huggingface',
[LLM_FACTORY_LIST.YouDao]: 'youdao',
[LLM_FACTORY_LIST.BAAI]: 'baai',
[LLM_FACTORY_LIST.NomicAI]: 'nomic-ai',
[LLM_FACTORY_LIST.JinaAI]: 'jina',
[LLM_FACTORY_LIST.SentenceTransformers]: 'sentence-transformers',
[LLM_FACTORY_LIST.GPUStack]: 'gpustack',
[LLM_FACTORY_LIST.VLLM]: 'vllm',
[LLM_FACTORY_LIST.GiteeAI]: 'gitee-ai',
[LLM_FACTORY_LIST.Ai302]: 'ai302',
[LLM_FACTORY_LIST.DeepInfra]: 'deepinfra',
[LLM_FACTORY_LIST.Grok]: 'grok',
[LLM_FACTORY_LIST.XAI]: 'xai',
[LLM_FACTORY_LIST.TokenPony]: 'token-pony',
[LLM_FACTORY_LIST.Meituan]: 'longcat',
[LLM_FACTORY_LIST.CometAPI]: 'cometapi',
[LLM_FACTORY_LIST.DeerAPI]: 'deerapi',
};

View File

@@ -17,6 +17,7 @@ export interface UseDocumentListState {
// 文档列表Hook返回值接口 // 文档列表Hook返回值接口
export interface UseDocumentListReturn extends UseDocumentListState { export interface UseDocumentListReturn extends UseDocumentListState {
fetchDocuments: (params?: IFetchKnowledgeListRequestParams) => Promise<void>; fetchDocuments: (params?: IFetchKnowledgeListRequestParams) => Promise<void>;
fetchDocumentsFilter: () => Promise<void>;
setKeywords: (keywords: string) => void; setKeywords: (keywords: string) => void;
setCurrentPage: (page: number) => void; setCurrentPage: (page: number) => void;
setPageSize: (size: number) => void; setPageSize: (size: number) => void;
@@ -95,6 +96,27 @@ export const useDocumentList = (
} }
}, [kbId, keywords, currentPage, pageSize]); }, [kbId, keywords, currentPage, pageSize]);
/**
* 获取文档过滤器
*/
const fetchDocumentsFilter = useCallback(async () => {
if (!kbId) return;
try {
const response = await knowledgeService.getDocumentFilter({
kb_id: kbId,
});
if (response.data.code === 0) {
const data = response.data.data;
} else {
throw new Error(response.data.message || '获取文档过滤器失败');
}
} catch (error: any) {
const errorMessage = error.response?.data?.message || error.message || '获取文档过滤器失败';
console.error('Failed to fetch document filter:', error);
}
}, [kbId, keywords]);
/** /**
* 刷新当前页面数据 * 刷新当前页面数据
*/ */
@@ -139,6 +161,7 @@ export const useDocumentList = (
pageSize, pageSize,
keywords, keywords,
fetchDocuments, fetchDocuments,
fetchDocumentsFilter,
setKeywords: handleSetKeywords, setKeywords: handleSetKeywords,
setCurrentPage: handleSetCurrentPage, setCurrentPage: handleSetCurrentPage,
setPageSize: handleSetPageSize, setPageSize: handleSetPageSize,

View File

@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import knowledgeService from '@/services/knowledge_service'; import knowledgeService from '@/services/knowledge_service';
import type { IKnowledge, IKnowledgeResult } from '@/interfaces/database/knowledge'; import type { IKnowledge, IKnowledgeResult, IParserConfig } from '@/interfaces/database/knowledge';
import type { IFetchKnowledgeListRequestParams } from '@/interfaces/request/knowledge'; import type { IFetchKnowledgeListRequestParams } from '@/interfaces/request/knowledge';
/** /**
@@ -299,11 +299,9 @@ export const useKnowledgeOperations = () => {
* 包括嵌入模型、解析器配置、相似度阈值等 * 包括嵌入模型、解析器配置、相似度阈值等
*/ */
const updateKnowledgeModelConfig = useCallback(async (data: { const updateKnowledgeModelConfig = useCallback(async (data: {
id: string; kb_id: string;
embd_id?: string; embd_id?: string;
// parser_config?: Partial<ParserConfig>; parser_config?: Partial<IParserConfig>;
similarity_threshold?: number;
vector_similarity_weight?: number;
parser_id?: string; parser_id?: string;
}) => { }) => {
try { try {
@@ -311,7 +309,6 @@ export const useKnowledgeOperations = () => {
setError(null); setError(null);
const updateData = { const updateData = {
kb_id: data.id,
...data, ...data,
}; };

195
src/hooks/llm-hooks.ts Normal file
View File

@@ -0,0 +1,195 @@
import { useState, useEffect, useCallback, useMemo } from 'react';
import { LLM_MODEL_TYPES, type LlmModelType } from "@/constants/knowledge";
import type { IThirdOAIModelCollection, IThirdOAIModel } from "@/interfaces/database/llm";
import userService from "@/services/user_service";
/**
* 获取LLM模型列表的Hook
* @param modelType 可选的模型类型过滤
* @returns LLM模型数据和相关状态
*/
export function useLlmList(modelType?: LlmModelType) {
const [data, setData] = useState<IThirdOAIModelCollection>({});
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const fetchLlmList = useCallback(async () => {
try {
setLoading(true);
setError(null);
const response = await userService.llm_list({ model_type: modelType });
if (response.data?.code === 0) {
setData(response.data?.data ?? {});
} else {
setError(response.data?.message || '获取LLM列表失败');
}
} catch (err) {
setError(err instanceof Error ? err.message : '获取LLM列表失败');
} finally {
setLoading(false);
}
}, [modelType]);
useEffect(() => {
fetchLlmList();
}, [fetchLlmList]);
return {
data,
loading,
error,
refresh: fetchLlmList,
};
}
/**
* 获取可用的LLM选项列表
* @param modelType 可选的模型类型过滤
* @returns 格式化的选项列表
*/
export function useLlmOptions(modelType?: LlmModelType) {
const { data: llmInfo, loading, error } = useLlmList(modelType);
const options = useMemo(() => {
return Object.entries(llmInfo).map(([key, value]) => {
return {
label: key,
options: value
.filter((x) => x.available) // 只显示可用的模型
.map((x) => ({
label: x.llm_name,
value: `${x.llm_name}@${x.fid}`,
disabled: !x.available,
model: x,
})),
};
}).filter((group) => group.options.length > 0); // 过滤掉空组
}, [llmInfo]);
return {
options,
loading,
error,
};
}
/**
* 根据模型类型获取分组的LLM选项
* @returns 按模型类型分组的选项
*/
export function useLlmOptionsByModelType() {
const { data: llmInfo, loading, error } = useLlmList();
const getOptionsByModelType = useCallback((modelType: LlmModelType) => {
return Object.entries(llmInfo)
.filter(([, value]) =>
modelType
? value.some((x) => x.model_type.includes(modelType))
: true,
)
.map(([key, value]) => {
return {
label: key,
options: value
.filter(
(x) =>
(modelType ? x.model_type.includes(modelType) : true) &&
x.available,
)
.map((x) => ({
label: x.llm_name,
value: `${x.llm_name}@${x.fid}`,
disabled: !x.available,
model: x,
})),
};
})
.filter((x) => x.options.length > 0);
}, [llmInfo]);
return {
getOptionsByModelType,
loading,
error,
};
}
/**
* 获取嵌入模型选项
* @returns 嵌入模型选项列表
*/
export function useEmbeddingModelOptions() {
return useLlmOptions(LLM_MODEL_TYPES.Embedding);
}
/**
* 获取聊天模型选项
* @returns 聊天模型选项列表
*/
export function useChatModelOptions() {
return useLlmOptions(LLM_MODEL_TYPES.Chat);
}
/**
* 获取重排序模型选项
* @returns 重排序模型选项列表
*/
export function useRerankModelOptions() {
return useLlmOptions(LLM_MODEL_TYPES.Rerank);
}
/**
* 获取图像转文本模型选项
* @returns 图像转文本模型选项列表
*/
export function useImage2TextModelOptions() {
return useLlmOptions(LLM_MODEL_TYPES.Image2text);
}
/**
* 获取语音转文本模型选项
* @returns 语音转文本模型选项列表
*/
export function useSpeech2TextModelOptions() {
return useLlmOptions(LLM_MODEL_TYPES.Speech2text);
}
/**
* 获取文本转语音模型选项
* @returns 文本转语音模型选项列表
*/
export function useTTSModelOptions() {
return useLlmOptions(LLM_MODEL_TYPES.TTS);
}
/**
* 根据模型ID获取模型详情
* @param modelId 模型ID (格式: llm_name@fid)
* @returns 模型详情
*/
export function useLlmModelDetail(modelId?: string) {
const { data: llmInfo, loading, error } = useLlmList();
const modelDetail = useMemo(() => {
if (!modelId || !llmInfo) return null;
const [llmName, fid] = modelId.split('@');
for (const models of Object.values(llmInfo)) {
const model = models.find(
(m) => m.llm_name === llmName && m.fid === fid
);
if (model) return model;
}
return null;
}, [modelId, llmInfo]);
return {
modelDetail,
loading,
error,
};
}

View File

@@ -1,539 +1,84 @@
import React from 'react'; import React, { useMemo } from 'react';
import { type UseFormReturn } from 'react-hook-form'; import { useFormContext, useWatch } from 'react-hook-form';
import { import {
Box, Box,
Typography, Typography,
FormControl,
InputLabel,
Select,
MenuItem,
Grid,
TextField,
FormHelperText,
Button,
CircularProgress,
Switch,
FormControlLabel,
Chip,
Slider,
Accordion,
AccordionSummary,
AccordionDetails,
} from '@mui/material'; } from '@mui/material';
import {
Save as SaveIcon,
ExpandMore as ExpandMoreIcon,
Add as AddIcon,
} from '@mui/icons-material';
import { DOCUMENT_PARSER_TYPES, type DocumentParserType } from '@/constants/knowledge'; import { DOCUMENT_PARSER_TYPES, type DocumentParserType } from '@/constants/knowledge';
import { type IParserConfig } from '@/interfaces/database/knowledge'; import { type IParserConfig } from '@/interfaces/database/knowledge';
import {
NaiveConfiguration,
QAConfiguration,
PaperConfiguration,
ResumeConfiguration,
ManualConfiguration,
TableConfiguration,
BookConfiguration,
LawsConfiguration,
PresentationConfiguration,
OneConfiguration,
TagConfiguration,
ChunkMethodItem,
} from '../configuration';
/** // 配置组件映射表
{ const ConfigurationComponentMap = {
"kb_id": "dcc2871aa4cd11f08d4116ac85b1de0a", [DOCUMENT_PARSER_TYPES.Naive]: NaiveConfiguration,
"name": "k1123", [DOCUMENT_PARSER_TYPES.Qa]: QAConfiguration,
"description": " 213213", [DOCUMENT_PARSER_TYPES.Resume]: ResumeConfiguration,
"permission": "team", [DOCUMENT_PARSER_TYPES.Manual]: ManualConfiguration,
"parser_id": "naive", [DOCUMENT_PARSER_TYPES.Table]: TableConfiguration,
"embd_id": "", [DOCUMENT_PARSER_TYPES.Paper]: PaperConfiguration,
"parser_config": { [DOCUMENT_PARSER_TYPES.Book]: BookConfiguration,
"layout_recognize": "Plain Text", [DOCUMENT_PARSER_TYPES.Laws]: LawsConfiguration,
"chunk_token_num": 512, [DOCUMENT_PARSER_TYPES.Presentation]: PresentationConfiguration,
"delimiter": "\n", [DOCUMENT_PARSER_TYPES.One]: OneConfiguration,
"auto_keywords": 0, [DOCUMENT_PARSER_TYPES.Tag]: TagConfiguration,
"auto_questions": 0, [DOCUMENT_PARSER_TYPES.KnowledgeGraph]: TagConfiguration,
"html4excel": false, [DOCUMENT_PARSER_TYPES.Picture]: TagConfiguration,
"topn_tags": 3, [DOCUMENT_PARSER_TYPES.Audio]: TagConfiguration,
"raptor": { [DOCUMENT_PARSER_TYPES.Email]: TagConfiguration,
"use_raptor": true, // [DOCUMENT_PARSER_TYPES.KnowledgeGraph]: KnowledgeGraphConfiguration,
"prompt": "请总结以下段落。 小心数字,不要编造。 段落如下:\n {cluster_content}\n以上就是你需要总结的内容。", // [DOCUMENT_PARSER_TYPES.Picture]: PictureConfiguration,
"max_token": 256, // [DOCUMENT_PARSER_TYPES.Audio]: AudioConfiguration,
"threshold": 0.1, // [DOCUMENT_PARSER_TYPES.Email]: GraphragConfiguration,
"max_cluster": 64, };
"random_seed": 0
}, // 空组件
"graphrag": { function EmptyComponent() {
"use_graphrag": true, return (
"entity_types": [ <Box sx={{ textAlign: 'center', py: 4 }}>
"organization", <Typography color="text.secondary">
"person",
"geo", </Typography>
"event", </Box>
"category" );
],
"method": "light"
} }
},
"pagerank": 0
}
*/
// 解析器选项配置
const parserOptions = [
{ value: DOCUMENT_PARSER_TYPES.Naive, label: '通用解析器', description: '适用于大多数文档类型' },
{ value: DOCUMENT_PARSER_TYPES.Qa, label: 'Q&A解析器', description: '适用于问答格式文档' },
{ value: DOCUMENT_PARSER_TYPES.Resume, label: '简历解析器', description: '专门用于解析简历文档' },
{ value: DOCUMENT_PARSER_TYPES.Manual, label: '手册解析器', description: '适用于技术手册和说明书' },
{ value: DOCUMENT_PARSER_TYPES.Table, label: '表格解析器', description: '专门处理表格数据' },
{ value: DOCUMENT_PARSER_TYPES.Paper, label: '论文解析器', description: '适用于学术论文' },
{ value: DOCUMENT_PARSER_TYPES.Book, label: '书籍解析器', description: '适用于书籍和长文档' },
{ value: DOCUMENT_PARSER_TYPES.Laws, label: '法律解析器', description: '专门处理法律文档' },
{ value: DOCUMENT_PARSER_TYPES.Presentation, label: '演示文稿解析器', description: '适用于PPT等演示文档' },
{ value: DOCUMENT_PARSER_TYPES.Picture, label: '图片解析器', description: '处理图片中的文字内容' },
{ value: DOCUMENT_PARSER_TYPES.One, label: '整体解析器', description: '将整个文档作为一个块处理' },
{ value: DOCUMENT_PARSER_TYPES.Audio, label: '音频解析器', description: '处理音频文件转录' },
{ value: DOCUMENT_PARSER_TYPES.Email, label: '邮件解析器', description: '专门处理邮件格式' },
{ value: DOCUMENT_PARSER_TYPES.Tag, label: '标签解析器', description: '基于标签的文档解析' },
{ value: DOCUMENT_PARSER_TYPES.KnowledgeGraph, label: '知识图谱解析器', description: '构建知识图谱结构' },
];
export interface ConfigFormData extends IParserConfig { export interface ConfigFormData extends IParserConfig {
parser_id: DocumentParserType; parser_id: DocumentParserType;
} }
interface ChunkMethodFormProps { function ChunkMethodForm() {
form: UseFormReturn<ConfigFormData>; const { control } = useFormContext();
onSubmit: (data: ConfigFormData) => void;
isSubmitting?: boolean;
onCancel?: () => void;
disabled?: boolean;
submitButtonText?: string;
cancelButtonText?: string;
}
function ChunkMethodForm({ // 监听parser_id变化
form, const parser_id = useWatch({
onSubmit, control,
isSubmitting = false, name: 'parser_id',
onCancel, });
disabled = false,
submitButtonText = '保存',
cancelButtonText = '取消'
}: ChunkMethodFormProps) {
const selectedParser: DocumentParserType = form.watch('parser_id');
const [entityTypes, setEntityTypes] = React.useState<string[]>(['organization', 'person', 'geo', 'event', 'category']);
// 通用配置部分 // 根据parser_id动态选择配置组件
const renderGeneralConfig = () => ( const ConfigurationComponent = useMemo(() => {
<Accordion defaultExpanded> const parser = parser_id as DocumentParserType;
<AccordionSummary expandIcon={<ExpandMoreIcon />}> const component = ConfigurationComponentMap[parser] || EmptyComponent;
<Typography variant="h6"></Typography> return component || EmptyComponent;
</AccordionSummary> }, [parser_id]);
<AccordionDetails>
<Grid container spacing={3}>
{/* 切片方法 */}
<Grid size={12}>
<FormControl fullWidth disabled={disabled}>
<InputLabel></InputLabel>
<Select
label="切片方法"
defaultValue={DOCUMENT_PARSER_TYPES.Naive}
{...form.register('parser_id')}
value={form.watch('parser_id') || DOCUMENT_PARSER_TYPES.Naive}
>
{parserOptions.map((option) => (
<MenuItem key={option.value} value={option.value}>
<Box>
<Typography variant="body1">{option.label}</Typography>
<Typography variant="caption" color="text.secondary">
{option.description}
</Typography>
</Box>
</MenuItem>
))}
</Select>
<FormHelperText></FormHelperText>
</FormControl>
</Grid>
{/* PDF解析器 */}
<Grid size={12}>
<FormControl fullWidth disabled={disabled}>
<InputLabel>PDF解析器</InputLabel>
<Select
label="PDF解析器"
defaultValue="Naive"
disabled={disabled}
>
<MenuItem value="Naive">Naive</MenuItem>
</Select>
</FormControl>
</Grid>
{/* 嵌入模型 */}
<Grid size={12}>
<FormControl fullWidth disabled={disabled}>
<InputLabel></InputLabel>
<Select
label="嵌入模型"
defaultValue=""
disabled={disabled}
>
<MenuItem value=""></MenuItem>
</Select>
</FormControl>
</Grid>
{/* 建议文本块大小 */}
<Grid size={12}>
<Typography gutterBottom></Typography>
<Box sx={{ px: 2 }}>
<Slider
defaultValue={512}
min={50}
max={2048}
step={1}
valueLabelDisplay="on"
disabled={disabled}
{...form.register('chunk_token_num', { valueAsNumber: true })}
/>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ ml: 2 }}>
512
</Typography>
</Grid>
{/* 文本分段标识符 */}
<Grid size={12}>
<TextField
fullWidth
label="文本分段标识符"
placeholder="\\n"
disabled={disabled}
{...form.register('delimiter')}
helperText="用于分割文本的分隔符"
/>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
);
// 页面排名配置部分
const renderPageRankConfig = () => (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6"></Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={3}>
{/* 页面排名 */}
<Grid size={12}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography></Typography>
<Switch disabled={disabled} />
<TextField
type="number"
size="small"
defaultValue={0}
disabled={disabled}
sx={{ width: 100 }}
/>
</Box>
</Grid>
{/* 自动关键词提取 */}
<Grid size={12}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography></Typography>
<Switch disabled={disabled} />
<TextField
type="number"
size="small"
defaultValue={0}
disabled={disabled}
sx={{ width: 100 }}
{...form.register('auto_keywords', { valueAsNumber: true })}
/>
</Box>
</Grid>
{/* 自动问题提取 */}
<Grid size={12}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Typography></Typography>
<Switch disabled={disabled} />
<TextField
type="number"
size="small"
defaultValue={0}
disabled={disabled}
sx={{ width: 100 }}
{...form.register('auto_questions', { valueAsNumber: true })}
/>
</Box>
</Grid>
{/* 表格转HTML */}
<Grid size={12}>
<FormControlLabel
control={
<Switch
disabled={disabled}
{...form.register('html4excel')}
/>
}
label="表格转HTML"
/>
</Grid>
{/* 标签集 */}
<Grid size={12}>
<FormControl fullWidth disabled={disabled}>
<InputLabel></InputLabel>
<Select
label="标签集"
defaultValue=""
disabled={disabled}
>
<MenuItem value=""></MenuItem>
</Select>
</FormControl>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
);
// RAPTOR集成配置部分
const renderRaptorConfig = () => (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6">使RAPTOR集成</Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={3}>
{/* 启用RAPTOR */}
<Grid size={12}>
<FormControlLabel
control={
<Switch
disabled={disabled}
{...form.register('raptor.use_raptor')}
/>
}
label="使用召回增强RAPTOR集成"
/>
</Grid>
{/* 提示词 */}
<Grid size={12}>
<TextField
fullWidth
multiline
rows={4}
label="提示词"
placeholder="请总结以下段落。小心数字,不要编造。段落如下:&#10; {cluster_content}&#10;以上就是你需要总结的内容。"
disabled={disabled}
{...form.register('raptor.prompt')}
helperText="用于RAPTOR集成的提示词模板"
/>
</Grid>
{/* 最大token数 */}
<Grid size={{xs:12,sm:6}}>
<Typography gutterBottom>token数</Typography>
<Box sx={{ px: 2 }}>
<Slider
defaultValue={256}
min={50}
max={1000}
step={1}
valueLabelDisplay="on"
disabled={disabled}
{...form.register('raptor.max_token', { valueAsNumber: true })}
/>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ ml: 2 }}>
256
</Typography>
</Grid>
{/* 阈值 */}
<Grid size={{xs:12,sm:6}}>
<Typography gutterBottom></Typography>
<Box sx={{ px: 2 }}>
<Slider
defaultValue={0.1}
min={0 as number}
max={1}
step={0.01}
valueLabelDisplay="on"
disabled={disabled}
{...form.register('raptor.threshold', { valueAsNumber: true })}
/>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ ml: 2 }}>
0.1
</Typography>
</Grid>
{/* 最大聚类数 */}
<Grid size={{xs:12,sm:6}}>
<Typography gutterBottom></Typography>
<Box sx={{ px: 2 }}>
<Slider
defaultValue={64}
min={1}
max={200 as number}
step={1}
valueLabelDisplay="on"
disabled={disabled}
{...form.register('raptor.max_cluster', { valueAsNumber: true })}
/>
</Box>
<Typography variant="caption" color="text.secondary" sx={{ ml: 2 }}>
64
</Typography>
</Grid>
{/* 随机种子 */}
<Grid size={{xs:12,sm:6}}>
<TextField
fullWidth
type="number"
label="随机种子"
defaultValue={0}
disabled={disabled}
{...form.register('raptor.random_seed', { valueAsNumber: true })}
InputProps={{
endAdornment: (
<Button size="small" disabled={disabled}>
+
</Button>
)
}}
/>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
);
// 提取知识图谱配置部分
const renderGraphRagConfig = () => (
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6"></Typography>
</AccordionSummary>
<AccordionDetails>
<Grid container spacing={3}>
{/* 启用知识图谱 */}
<Grid size={12}>
<FormControlLabel
control={
<Switch
disabled={disabled}
{...form.register('graphrag.use_graphrag')}
/>
}
label="提取知识图谱"
/>
</Grid>
{/* 添加实体类型 */}
<Grid size={12}>
<Button
startIcon={<AddIcon />}
variant="outlined"
size="small"
disabled={disabled}
onClick={() => {
// 添加新实体类型的逻辑
}}
>
+
</Button>
</Grid>
{/* 实体类型标签 */}
<Grid size={12}>
<Typography variant="subtitle2" gutterBottom></Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{entityTypes.map((type, index) => (
<Chip
key={index}
label={type}
onDelete={disabled ? undefined : () => {
setEntityTypes(prev => prev.filter((_, i) => i !== index));
}}
disabled={disabled}
/>
))}
</Box>
</Grid>
{/* 方法 */}
<Grid size={12}>
<FormControl fullWidth disabled={disabled}>
<InputLabel></InputLabel>
<Select
label="方法"
defaultValue="Light"
{...form.register('graphrag.method')}
>
<MenuItem value="Light">Light</MenuItem>
</Select>
</FormControl>
</Grid>
{/* 实体归一化 */}
<Grid size={12}>
<FormControlLabel
control={<Switch disabled={disabled} />}
label="实体归一化"
/>
</Grid>
{/* 社区报告生成 */}
<Grid size={12}>
<FormControlLabel
control={<Switch disabled={disabled} />}
label="社区报告生成"
/>
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
);
return ( return (
<Box> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<Typography variant="h5" gutterBottom> {/* 动态配置内容 */}
<ConfigurationComponent />
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom sx={{ mb: 3 }}>
</Typography>
<Box sx={{ mb: 3 }}>
{renderGeneralConfig()}
{renderPageRankConfig()}
{renderRaptorConfig()}
{renderGraphRagConfig()}
</Box>
{/* 操作按钮 */}
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'flex-end' }}>
{onCancel && (
<Button
variant="outlined"
onClick={onCancel}
disabled={isSubmitting}
>
{cancelButtonText}
</Button>
)}
<Button
variant="contained"
onClick={form.handleSubmit(onSubmit)}
disabled={disabled || isSubmitting}
startIcon={isSubmitting ? <CircularProgress size={20} /> : <SaveIcon />}
>
{submitButtonText}
</Button>
</Box>
</Box> </Box>
); );
} }

View File

@@ -1,5 +1,5 @@
import React, { useRef } from 'react'; import React, { useRef } from 'react';
import { type UseFormReturn } from 'react-hook-form'; import { useFormContext, Controller } from 'react-hook-form';
import { import {
Box, Box,
Typography, Typography,
@@ -12,211 +12,138 @@ import {
Avatar, Avatar,
Button, Button,
IconButton, IconButton,
CircularProgress,
} from '@mui/material'; } from '@mui/material';
import { import {
PhotoCamera as PhotoCameraIcon, PhotoCamera as PhotoCameraIcon,
Delete as DeleteIcon, Delete as DeleteIcon,
Save as SaveIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
export interface BasicFormData { function GeneralForm() {
name: string; const { control, watch, setValue } = useFormContext();
description: string;
permission: string;
avatar?: string;
}
interface GeneralFormProps {
form: UseFormReturn<BasicFormData>;
onSubmit: (data: BasicFormData) => void;
isSubmitting?: boolean;
onCancel?: () => void;
disabled?: boolean;
submitButtonText?: string;
cancelButtonText?: string;
}
function GeneralForm({
form,
onSubmit,
isSubmitting = false,
onCancel,
disabled = false,
submitButtonText = '提交',
cancelButtonText = '取消'
}: GeneralFormProps) {
const fileInputRef = useRef<HTMLInputElement>(null); const fileInputRef = useRef<HTMLInputElement>(null);
// 处理头像上传
const handleAvatarUpload = (event: React.ChangeEvent<HTMLInputElement>) => { const handleAvatarUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0]; const file = event.target.files?.[0];
if (file) { if (file) {
// 检查文件类型
if (!file.type.startsWith('image/')) {
alert('请选择图片文件');
return;
}
// 检查文件大小 (限制为 2MB)
if (file.size > 2 * 1024 * 1024) {
alert('图片大小不能超过 2MB');
return;
}
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = (e) => {
const base64String = e.target?.result as string; setValue('avatar', e.target?.result as string);
form.setValue('avatar', base64String);
}; };
reader.readAsDataURL(file); reader.readAsDataURL(file);
} }
}; };
// 删除头像
const handleAvatarDelete = () => { const handleAvatarDelete = () => {
form.setValue('avatar', undefined); setValue('avatar', undefined);
if (fileInputRef.current) {
fileInputRef.current.value = '';
}
}; };
const avatarValue = form.watch('avatar'); const handleAvatarClick = () => {
fileInputRef.current?.click();
};
const avatar = watch('avatar');
return ( return (
<Box> <Box sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom> <Typography variant="h6" gutterBottom>
</Typography> </Typography>
<Grid container spacing={3}> <Grid container spacing={3}>
{/* 头像上传 */} <Grid size={{xs:12, md:6}}>
<Grid size={12}> <Box sx={{ display: 'flex', flexDirection: 'row', alignItems: 'center', gap: 2 }}>
<Typography variant="subtitle2" gutterBottom>
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<Avatar <Avatar
src={avatarValue} src={avatar}
sx={{ width: 80, height: 80 }} sx={{ width: 120, height: 120, cursor: 'pointer' }}
onClick={handleAvatarClick}
> >
{!avatarValue && form.watch('name')?.charAt(0)?.toUpperCase()} {!avatar && <PhotoCameraIcon sx={{ fontSize: 40 }} />}
</Avatar> </Avatar>
<Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
variant="outlined"
size="small"
startIcon={<PhotoCameraIcon />}
onClick={handleAvatarClick}
>
</Button>
{avatar && (
<IconButton
size="small"
color="error"
onClick={handleAvatarDelete}
>
<DeleteIcon />
</IconButton>
)}
</Box>
<input <input
ref={fileInputRef} ref={fileInputRef}
type="file" type="file"
accept="image/*" accept="image/*"
style={{ display: 'none' }} style={{ display: 'none' }}
onChange={handleAvatarUpload} onChange={handleAvatarUpload}
disabled={disabled}
/> />
<Button
variant="outlined"
startIcon={<PhotoCameraIcon />}
onClick={() => fileInputRef.current?.click()}
disabled={disabled}
size="small"
sx={{ mr: 1 }}
>
</Button>
{avatarValue && (
<IconButton
onClick={handleAvatarDelete}
disabled={disabled}
size="small"
color="error"
>
<DeleteIcon />
</IconButton>
)}
</Box> </Box>
</Box>
<Typography variant="caption" color="text.secondary">
PNGJPG 2MB
</Typography>
</Grid> </Grid>
{/* 知识库名称 */} {/* 表单字段 */}
<Grid size={12}> <Grid size={{xs:12,md:8}}>
<Grid container spacing={2}>
<Grid size={{xs:12}}>
<Controller
name="name"
control={control}
rules={{ required: '知识库名称不能为空' }}
render={({ field, fieldState: { error } }) => (
<TextField <TextField
fullWidth {...field}
label="知识库名称" label="知识库名称"
placeholder="请输入知识库名称" fullWidth
disabled={disabled} required
{...form.register('name', { error={!!error}
required: '请输入知识库名称', helperText={error?.message}
minLength: { />
value: 2, )}
message: '知识库名称至少需要2个字符',
},
maxLength: {
value: 50,
message: '知识库名称不能超过50个字符',
},
})}
error={!!form.formState.errors.name}
helperText={form.formState.errors.name?.message?.toString() || '请输入知识库名称'}
/> />
</Grid> </Grid>
{/* 描述 */} <Grid size={{xs:12}}>
<Grid size={12}> <Controller
name="description"
control={control}
render={({ field }) => (
<TextField <TextField
{...field}
label="描述"
fullWidth fullWidth
multiline multiline
rows={4} rows={3}
label="描述" placeholder="请输入知识库描述..."
placeholder="请输入知识库描述" />
disabled={disabled} )}
{...form.register('description', {
maxLength: {
value: 500,
message: '描述不能超过500个字符',
},
})}
error={!!form.formState.errors.description}
helperText={form.formState.errors.description?.message?.toString() || '请输入知识库描述'}
/> />
</Grid> </Grid>
{/* 权限设置 */} <Grid size={{xs:12}}>
<Grid size={{xs:12,sm:6}}> <Controller
<FormControl fullWidth disabled={disabled}> name="permission"
control={control}
render={({ field }) => (
<FormControl fullWidth>
<InputLabel></InputLabel> <InputLabel></InputLabel>
<Select <Select {...field} label="权限设置">
label="权限设置"
{...form.register('permission')}
value={form.watch('permission') || 'me'}
>
<MenuItem value="me"></MenuItem> <MenuItem value="me"></MenuItem>
<MenuItem value="team"></MenuItem> <MenuItem value="team"></MenuItem>
</Select> </Select>
</FormControl> </FormControl>
</Grid>
{/* 操作按钮 */}
<Grid size={12}>
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'flex-end', mt: 2 }}>
{onCancel && (
<Button
variant="outlined"
onClick={onCancel}
disabled={isSubmitting}
>
{cancelButtonText}
</Button>
)} )}
<Button />
variant="contained" </Grid>
onClick={form.handleSubmit(onSubmit)} </Grid>
disabled={disabled || isSubmitting}
startIcon={isSubmitting ? <CircularProgress size={20} /> : <SaveIcon />}
>
{submitButtonText}
</Button>
</Box>
</Grid> </Grid>
</Grid> </Grid>
</Box> </Box>

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { ConfigurationFormContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function AudioConfiguration() {
return (
<ConfigurationFormContainer>
<ChunkMethodItem />
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Raptor配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
GraphRAG配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
);
}

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function BookConfiguration() {
return (
<MainContainer>
<ConfigurationFormContainer>
<ChunkMethodItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Raptor配置 -
</Typography>
</Box>
</ConfigurationFormContainer>
<Box sx={{ mb: 2, p: 2 }}>
<Typography variant="body2" color="text.secondary">
GraphRAG配置 -
</Typography>
</Box>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
</MainContainer>
);
}

View File

@@ -0,0 +1,118 @@
import {
FormControl,
InputLabel,
Select,
MenuItem,
FormHelperText,
Box,
Typography,
ListSubheader,
} from '@mui/material';
import { useFormContext, Controller } from 'react-hook-form';
import { DOCUMENT_PARSER_TYPES } from '@/constants/knowledge';
import { useSelectChunkMethodList } from '../hooks';
import { useEmbeddingModelOptions } from '@/hooks/llm-hooks';
// 解析器选项配置
const PARSER_OPTIONS = [
{ value: DOCUMENT_PARSER_TYPES.Naive, label: 'General', description: '通用解析器' },
{ value: DOCUMENT_PARSER_TYPES.Qa, label: 'Q&A', description: 'Q&A解析器' },
{ value: DOCUMENT_PARSER_TYPES.Resume, label: 'Resume', description: 'Resume解析器' },
{ value: DOCUMENT_PARSER_TYPES.Manual, label: 'Manual', description: 'Manual解析器' },
{ value: DOCUMENT_PARSER_TYPES.Table, label: 'Table', description: 'Table解析器' },
{ value: DOCUMENT_PARSER_TYPES.Paper, label: 'Paper', description: 'Paper解析器' },
{ value: DOCUMENT_PARSER_TYPES.Book, label: 'Book', description: 'Book解析器' },
{ value: DOCUMENT_PARSER_TYPES.Laws, label: 'Laws', description: 'Laws解析器' },
{ value: DOCUMENT_PARSER_TYPES.Presentation, label: 'Presentation', description: 'Presentation解析器' },
{ value: DOCUMENT_PARSER_TYPES.One, label: 'One', description: 'One解析器' },
{ value: DOCUMENT_PARSER_TYPES.Tag, label: 'Tag', description: 'Tag解析器' },
];
export function ChunkMethodItem() {
const { control, formState: { errors } } = useFormContext();
const parserIds = useSelectChunkMethodList();
const parserOptions = parserIds.map((x) => ({
value: x,
label: PARSER_OPTIONS.find((y) => y.value === x)?.label || x,
description: PARSER_OPTIONS.find((y) => y.value === x)?.description || x,
}));
return (
<Box>
<Typography variant="h6" gutterBottom>
</Typography>
<Controller
name="parser_id"
control={control}
render={({ field }) => (
<FormControl fullWidth error={!!errors.parser_id}>
<InputLabel></InputLabel>
<Select
{...field}
label="选择切片方法"
>
{parserOptions.map((option) => (
<MenuItem key={option.value} value={option.value}>
<Box>
<Typography variant="body1">{option.label}</Typography>
<Typography variant="caption" color="text.secondary">
{option.description}
</Typography>
</Box>
</MenuItem>
))}
</Select>
{errors.parser_id && (
<FormHelperText>{errors.parser_id.message as string}</FormHelperText>
)}
</FormControl>
)}
/>
</Box>
);
}
export function EmbeddingModelItem() {
const { control, formState: { errors } } = useFormContext();
const { options: embdOptions } = useEmbeddingModelOptions();
return (
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom>
</Typography>
<Controller
name="embd_id"
control={control}
render={({ field }) => (
<FormControl fullWidth error={!!errors.embd_id}>
<InputLabel></InputLabel>
<Select
{...field}
label="选择嵌入模型"
>
{
embdOptions.map((group) => [
<ListSubheader key={`header-${group.label}`} disableSticky>
{group.label}
</ListSubheader>,
...group.options.map((option) => (
<MenuItem key={option.value} value={option.value} disabled={option.disabled}>
{option.label}
</MenuItem>
))
])
}
</Select>
{errors.embd_id && (
<FormHelperText>{errors.embd_id.message as string}</FormHelperText>
)}
</FormControl>
)}
/>
</Box>
);
}

View File

@@ -0,0 +1,34 @@
import React, { type PropsWithChildren } from 'react';
import { Box, Paper } from '@mui/material';
interface ConfigurationFormContainerProps extends PropsWithChildren {
className?: string;
}
export function ConfigurationFormContainer({
children,
className,
}: ConfigurationFormContainerProps) {
return (
<Paper
elevation={0}
sx={{
p: 3,
backgroundColor: 'transparent',
border: 'none',
boxShadow: 'none',
}}
className={className}
>
{children}
</Paper>
);
}
export function MainContainer({ children }: PropsWithChildren) {
return (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
{children}
</Box>
);
}

View File

@@ -0,0 +1,49 @@
import React from 'react';
import { ConfigurationFormContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function EmailConfiguration() {
return (
<ConfigurationFormContainer>
<ChunkMethodItem />
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Raptor配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
GraphRAG配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
);
}

View File

@@ -0,0 +1,20 @@
// 配置组件统一导出
export { NaiveConfiguration } from './naive';
export { QAConfiguration } from './qa';
export { PaperConfiguration } from './paper';
export { ChunkMethodItem, EmbeddingModelItem } from './common-item';
export { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
// 所有解析器配置组件
export { AudioConfiguration } from './audio';
export { BookConfiguration } from './book';
export { EmailConfiguration } from './email';
export { KnowledgeGraphConfiguration } from './knowledge-graph';
export { LawsConfiguration } from './laws';
export { ManualConfiguration } from './manual';
export { OneConfiguration } from './one';
export { PictureConfiguration } from './picture';
export { PresentationConfiguration } from './presentation';
export { ResumeConfiguration } from './resume';
export { TableConfiguration } from './table';
export { TagConfiguration } from './tag';

View File

@@ -0,0 +1,36 @@
import React from 'react';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function KnowledgeGraphConfiguration() {
return (
<>
<ChunkMethodItem />
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Token数量配置 (最大: 16384) -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</>
);
}

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function LawsConfiguration() {
return (
<MainContainer>
<ConfigurationFormContainer>
<ChunkMethodItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Raptor配置 -
</Typography>
</Box>
</ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
GraphRAG配置 -
</Typography>
</Box>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
</MainContainer>
);
}

View File

@@ -0,0 +1,59 @@
import React from 'react';
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function ManualConfiguration() {
return (
<MainContainer>
<ConfigurationFormContainer>
<ChunkMethodItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Raptor配置 -
</Typography>
</Box>
</ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
GraphRAG配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</MainContainer>
);
}

View File

@@ -0,0 +1,484 @@
import React from 'react';
import {
Box,
Typography,
FormControl,
InputLabel,
Select,
MenuItem,
TextField,
FormControlLabel,
Switch,
Slider,
Accordion,
AccordionSummary,
AccordionDetails,
Chip,
IconButton,
} from '@mui/material';
import { ExpandMore as ExpandMoreIcon, Add as AddIcon } from '@mui/icons-material';
import { useFormContext, Controller } from 'react-hook-form';
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
export function NaiveConfiguration() {
const { control, watch, formState: { errors } } = useFormContext();
return (
<ConfigurationFormContainer>
<MainContainer>
{/* 第一部分:基础配置 */}
<Accordion defaultExpanded>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6"></Typography>
</AccordionSummary>
<AccordionDetails>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{/* 切片方法 */}
<ChunkMethodItem />
{/* PDF解析器 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
PDF解析器
</Typography>
<Controller
name="parser_config.pdf_parser"
control={control}
render={({ field }) => (
<FormControl fullWidth>
<Select
{...field}
displayEmpty
defaultValue="Naive"
>
<MenuItem value="Naive">Naive</MenuItem>
</Select>
</FormControl>
)}
/>
</Box>
{/* 嵌入模型 */}
<EmbeddingModelItem />
{/* 建议文本块大小 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
</Typography>
<Controller
name="parser_config.chunk_token_num"
control={control}
render={({ field }) => (
<Box sx={{ px: 2 }}>
<Slider
{...field}
min={128}
max={2048}
step={64}
valueLabelDisplay="on"
defaultValue={512}
onChange={(_, value) => field.onChange(value)}
/>
</Box>
)}
/>
</Box>
{/* 文本分段标识符 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
*
</Typography>
<Controller
name="parser_config.delimiter"
control={control}
render={({ field }) => (
<TextField
{...field}
fullWidth
placeholder="\\n"
defaultValue="\\n"
/>
)}
/>
</Box>
</Box>
</AccordionDetails>
</Accordion>
{/* 第二部分:页面排名和自动提取 */}
<Accordion defaultExpanded>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6"></Typography>
</AccordionSummary>
<AccordionDetails>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{/* 页面排名 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
</Typography>
<Controller
name="parser_config.page_rank"
control={control}
render={({ field }) => (
<TextField
{...field}
type="number"
fullWidth
defaultValue={0}
inputProps={{ min: 0 }}
/>
)}
/>
</Box>
{/* 自动关键词提取 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
</Typography>
<Controller
name="parser_config.auto_keywords"
control={control}
render={({ field }) => (
<TextField
{...field}
type="number"
fullWidth
defaultValue={0}
inputProps={{ min: 0 }}
/>
)}
/>
</Box>
{/* 自动问题提取 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
</Typography>
<Controller
name="parser_config.auto_questions"
control={control}
render={({ field }) => (
<TextField
{...field}
type="number"
fullWidth
defaultValue={0}
inputProps={{ min: 0 }}
/>
)}
/>
</Box>
{/* 表格转HTML */}
<Box>
<Controller
name="parser_config.html4excel"
control={control}
render={({ field }) => (
<FormControlLabel
control={
<Switch
checked={field.value || false}
onChange={field.onChange}
/>
}
label="表格转HTML"
/>
)}
/>
</Box>
{/* 标签集 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
</Typography>
<Controller
name="parser_config.tags"
control={control}
render={({ field }) => (
<FormControl fullWidth>
<Select
{...field}
displayEmpty
defaultValue=""
>
<MenuItem value=""></MenuItem>
</Select>
</FormControl>
)}
/>
</Box>
</Box>
</AccordionDetails>
</Accordion>
{/* 第三部分RAPTOR策略 */}
<Accordion defaultExpanded>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6">RAPTOR策略</Typography>
</AccordionSummary>
<AccordionDetails>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{/* 使用召回增强RAPTOR策略 */}
<Box>
<Controller
name="parser_config.raptor.use_raptor"
control={control}
render={({ field }) => (
<FormControlLabel
control={
<Switch
checked={field.value || false}
onChange={field.onChange}
/>
}
label="使用召回增强RAPTOR策略"
/>
)}
/>
</Box>
{/* 提示词 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
</Typography>
<Controller
name="parser_config.raptor.prompt"
control={control}
render={({ field }) => (
<TextField
{...field}
multiline
rows={4}
fullWidth
defaultValue="请总结以下段落。小心数字,不要编造。段落如下:\n{cluster_content}\n以上就是你需要总结的内容。"
/>
)}
/>
</Box>
{/* 最大token数 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
token数
</Typography>
<Controller
name="parser_config.raptor.max_token"
control={control}
render={({ field }) => (
<Box sx={{ px: 2 }}>
<Slider
{...field}
min={64}
max={512}
step={32}
valueLabelDisplay="on"
defaultValue={256}
onChange={(_, value) => field.onChange(value)}
/>
</Box>
)}
/>
</Box>
{/* 阈值 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
</Typography>
<Controller
name="parser_config.raptor.threshold"
control={control}
render={({ field }) => (
<Box sx={{ px: 2 }}>
<Slider
{...field}
min={0}
max={1}
step={0.1}
valueLabelDisplay="on"
defaultValue={0.1}
onChange={(_, value) => field.onChange(value)}
/>
</Box>
)}
/>
</Box>
{/* 最大聚类数 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
</Typography>
<Controller
name="parser_config.raptor.max_cluster"
control={control}
render={({ field }) => (
<Box sx={{ px: 2 }}>
<Slider
{...field}
min={16}
max={128}
step={16}
valueLabelDisplay="on"
defaultValue={64}
onChange={(_, value) => field.onChange(value)}
/>
</Box>
)}
/>
</Box>
{/* 随机种子 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
</Typography>
<Controller
name="parser_config.raptor.random_seed"
control={control}
render={({ field }) => (
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
<TextField
{...field}
type="number"
defaultValue={0}
sx={{ flex: 1 }}
/>
<IconButton>
<AddIcon />
</IconButton>
</Box>
)}
/>
</Box>
</Box>
</AccordionDetails>
</Accordion>
{/* 第四部分:知识图谱 */}
<Accordion>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Typography variant="h6"></Typography>
</AccordionSummary>
<AccordionDetails>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
{/* 提取知识图谱 */}
<Box>
<Controller
name="parser_config.graphrag.use_graphrag"
control={control}
render={({ field }) => (
<FormControlLabel
control={
<Switch
checked={field.value || false}
onChange={field.onChange}
/>
}
label="提取知识图谱"
/>
)}
/>
</Box>
{/* 实体类型 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
*
</Typography>
<Controller
name="parser_config.graphrag.entity_types"
control={control}
render={({ field }) => (
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 2 }}>
{['organization', 'person', 'geo', 'event', 'category'].map((type) => (
<Chip
key={type}
label={type}
onDelete={() => {}}
variant="outlined"
/>
))}
<IconButton size="small">
<AddIcon />
</IconButton>
</Box>
)}
/>
</Box>
{/* 方法 */}
<Box>
<Typography variant="subtitle1" gutterBottom>
</Typography>
<Controller
name="parser_config.graphrag.method"
control={control}
render={({ field }) => (
<FormControl fullWidth>
<Select
{...field}
defaultValue="Light"
>
<MenuItem value="Light">Light</MenuItem>
<MenuItem value="General">General</MenuItem>
</Select>
</FormControl>
)}
/>
</Box>
{/* 实体归一化 */}
<Box>
<Controller
name="parser_config.graphrag.entity_normalize"
control={control}
render={({ field }) => (
<FormControlLabel
control={
<Switch
checked={field.value || false}
onChange={field.onChange}
/>
}
label="实体归一化"
/>
)}
/>
</Box>
{/* 社区报告生成 */}
<Box>
<Controller
name="parser_config.graphrag.community_report"
control={control}
render={({ field }) => (
<FormControlLabel
control={
<Switch
checked={field.value || false}
onChange={field.onChange}
/>
}
label="社区报告生成"
/>
)}
/>
</Box>
</Box>
</AccordionDetails>
</Accordion>
</MainContainer>
</ConfigurationFormContainer>
);
}

View File

@@ -0,0 +1,48 @@
import React from 'react';
import { ConfigurationFormContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function OneConfiguration() {
return (
<ConfigurationFormContainer>
<ChunkMethodItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
GraphRAG配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
);
}

View File

@@ -0,0 +1,121 @@
import React from 'react';
import {
Box,
Typography,
FormControl,
InputLabel,
Select,
MenuItem,
TextField,
Slider,
} from '@mui/material';
import { useFormContext, Controller } from 'react-hook-form';
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
export function PaperConfiguration() {
const { control } = useFormContext();
return (
<ConfigurationFormContainer>
<MainContainer>
{/* 布局识别 */}
<Box>
<Typography variant="h6" gutterBottom>
</Typography>
<Controller
name="parser_config.layout_recognize"
control={control}
render={({ field }) => (
<FormControl fullWidth>
<InputLabel></InputLabel>
<Select
{...field}
label="布局识别方式"
>
<MenuItem value="DeepDOC">DeepDOC</MenuItem>
<MenuItem value="Plain Text">Plain Text</MenuItem>
</Select>
</FormControl>
)}
/>
</Box>
{/* 自动关键词 */}
<Box>
<Typography variant="h6" gutterBottom>
</Typography>
<Controller
name="parser_config.auto_keywords"
control={control}
render={({ field }) => (
<Box sx={{ px: 2 }}>
<Slider
{...field}
min={0}
max={10}
step={1}
marks
valueLabelDisplay="auto"
onChange={(_, value) => field.onChange(value)}
/>
</Box>
)}
/>
</Box>
{/* 自动问题 */}
<Box>
<Typography variant="h6" gutterBottom>
</Typography>
<Controller
name="parser_config.auto_questions"
control={control}
render={({ field }) => (
<Box sx={{ px: 2 }}>
<Slider
{...field}
min={0}
max={10}
step={1}
marks
valueLabelDisplay="auto"
onChange={(_, value) => field.onChange(value)}
/>
</Box>
)}
/>
</Box>
{/* 标签数量 */}
<Box>
<Typography variant="h6" gutterBottom>
Top N
</Typography>
<Controller
name="parser_config.topn_tags"
control={control}
render={({ field }) => (
<TextField
{...field}
type="number"
fullWidth
label="标签数量"
inputProps={{ min: 1, max: 10 }}
onChange={(e) => field.onChange(parseInt(e.target.value))}
/>
)}
/>
</Box>
<Box>
<Typography variant="body2" color="text.secondary">
</Typography>
</Box>
</MainContainer>
</ConfigurationFormContainer>
);
}

View File

@@ -0,0 +1,37 @@
import React from 'react';
import { ConfigurationFormContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function PictureConfiguration() {
return (
<ConfigurationFormContainer>
<ChunkMethodItem />
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
);
}

View File

@@ -0,0 +1,61 @@
import React from 'react';
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function PresentationConfiguration() {
return (
<MainContainer>
<ConfigurationFormContainer>
<ChunkMethodItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
Raptor配置 -
</Typography>
</Box>
</ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
GraphRAG配置 -
</Typography>
</Box>
<ConfigurationFormContainer>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
</MainContainer>
);
}

View File

@@ -0,0 +1,45 @@
import React from 'react';
import {
Box,
Typography,
TextField,
} from '@mui/material';
import { useFormContext, Controller } from 'react-hook-form';
import { ConfigurationFormContainer, MainContainer } from './configuration-form-container';
export function QAConfiguration() {
const { control } = useFormContext();
return (
<ConfigurationFormContainer>
<MainContainer>
{/* 标签数量 */}
<Box>
<Typography variant="h6" gutterBottom>
Top N
</Typography>
<Controller
name="parser_config.topn_tags"
control={control}
render={({ field }) => (
<TextField
{...field}
type="number"
fullWidth
label="标签数量"
inputProps={{ min: 1, max: 10 }}
onChange={(e) => field.onChange(parseInt(e.target.value))}
/>
)}
/>
</Box>
<Box>
<Typography variant="body2" color="text.secondary">
Q&A解析器专门用于处理问答格式的文档
</Typography>
</Box>
</MainContainer>
</ConfigurationFormContainer>
);
}

View File

@@ -0,0 +1,25 @@
import React from 'react';
import { ConfigurationFormContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function ResumeConfiguration() {
return (
<ConfigurationFormContainer>
<ChunkMethodItem />
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
-
</Typography>
</Box>
</ConfigurationFormContainer>
);
}

View File

@@ -0,0 +1,19 @@
import React from 'react';
import { ConfigurationFormContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function TableConfiguration() {
return (
<ConfigurationFormContainer>
<ChunkMethodItem />
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
</ConfigurationFormContainer>
);
}

View File

@@ -0,0 +1,19 @@
import React from 'react';
import { ConfigurationFormContainer } from './configuration-form-container';
import { ChunkMethodItem, EmbeddingModelItem } from './common-item';
import { Box, Typography } from '@mui/material';
export function TagConfiguration() {
return (
<ConfigurationFormContainer>
<ChunkMethodItem />
<EmbeddingModelItem />
<Box sx={{ mb: 2 }}>
<Typography variant="body2" color="text.secondary">
PageRank配置 -
</Typography>
</Box>
</ConfigurationFormContainer>
);
}

View File

@@ -0,0 +1,30 @@
import { useUserData } from "@/hooks/useUserData";
const HiddenFields = ['email', 'picture', 'audio'];
/**
* 解析器ID格式为 "naive:General",我们只需要 "naive" 部分
* "parser_ids": "naive:General,qa:Q&A,resume:Resume,manual:Manual,
* table:Table,paper:Paper,book:Book,laws:Laws,presentation:Presentation,
* picture:Picture,one:One,audio:Audio,email:Email,tag:Tag",
* @returns 解析器ID列表
*/
export function useSelectChunkMethodList() {
const {tenantInfo} = useUserData();
const parserList = tenantInfo?.parser_ids?.split(',') || [];
// 解析器ID格式为 "naive:General",我们只需要 "naive" 部分
const parserIds = parserList.map((x) => x.split(':')[0]);
// const parserLabels = parserList.map((x) => x.split(':')[1]);
const filteredParserIds = parserIds.filter((x) => !HiddenFields.some((y) => y === x));
// return filteredParserIds.map((x) => ({
// value: x,
// label: parserLabels[parserIds.indexOf(x)],
// }));
return filteredParserIds
}
// export function useSelectEmbeddingModelOptions() {
// const allOptions = useSelectLlmOptionsByModelType();
// return allOptions[LlmModelType.Embedding];
// }

View File

@@ -1,64 +1,39 @@
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, type UseFormReturn } from 'react-hook-form'; import { useForm, FormProvider, useWatch, Form } from 'react-hook-form';
import { import {
Box, Box,
Container,
Typography, Typography,
Paper, Paper,
Tabs, Tabs,
Tab, Tab,
Fab, Fab,
Snackbar, Button,
Alert,
} from '@mui/material'; } from '@mui/material';
import { import {
ArrowBack as ArrowBackIcon, ArrowBack as ArrowBackIcon,
} from '@mui/icons-material'; } from '@mui/icons-material';
import { useKnowledgeDetail, useKnowledgeOperations } from '@/hooks/knowledge-hooks'; import { useKnowledgeDetail, useKnowledgeOperations } from '@/hooks/knowledge-hooks';
import GeneralForm, { type BasicFormData } from './components/GeneralForm'; import GeneralForm from './components/GeneralForm';
import ChunkMethodForm, { type ConfigFormData } from './components/ChunkMethodForm'; import ChunkMethodForm, { type ConfigFormData } from './components/ChunkMethodForm';
import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs'; import KnowledgeBreadcrumbs from './components/KnowledgeBreadcrumbs';
import { useSnackbar } from '@/components/Provider/SnackbarProvider'; import { useSnackbar } from '@/components/Provider/SnackbarProvider';
import { DOCUMENT_PARSER_TYPES } from '@/constants/knowledge';
import { MainContainer } from './configuration';
interface TabPanelProps { // 统一表单数据类型
children?: React.ReactNode; interface BaseFormData {
index: number; name: string;
value: number; description: string;
} permission: string;
avatar?: string;
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`setting-tabpanel-${index}`}
aria-labelledby={`setting-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{ p: 3 }}>
{children}
</Box>
)}
</div>
);
}
function a11yProps(index: number) {
return {
id: `setting-tab-${index}`,
'aria-controls': `setting-tabpanel-${index}`,
};
} }
function KnowledgeBaseSetting() { function KnowledgeBaseSetting() {
const { id } = useParams<{ id: string }>(); const { id } = useParams<{ id: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
const [tabValue, setTabValue] = useState(0); const [tabValue, setTabValue] = useState<'generalForm' | 'chunkMethodForm'>('generalForm');
// 获取知识库详情 // 获取知识库详情
const { knowledge, loading: detailLoading, refresh } = useKnowledgeDetail(id || ''); const { knowledge, loading: detailLoading, refresh } = useKnowledgeDetail(id || '');
@@ -71,99 +46,115 @@ function KnowledgeBaseSetting() {
loading: operationLoading loading: operationLoading
} = useKnowledgeOperations(); } = useKnowledgeOperations();
// 基础信息表单 // 统一表单管理
const basicForm = useForm<BasicFormData>({ const form = useForm<any>({
defaultValues: { defaultValues: {
name: '', name: '',
description: '', description: '',
permission: 'me', permission: 'me',
avatar: undefined, avatar: undefined,
}, }
}); });
// 解析配置表单 // 监听parser_id变化
const configForm = useForm<ConfigFormData>({ const parserId = useWatch({
defaultValues: { control: form.control,
parser_id: 'naive', name: 'parser_id',
chunk_token_count: 512,
layout_recognize: false,
task_page_size: 0,
},
}); });
// 当知识库数据加载完成时,更新表单默认值 // 当知识库数据加载完成时,更新表单默认值
useEffect(() => { useEffect(() => {
if (knowledge) { if (knowledge) {
basicForm.reset({ form.reset({
name: knowledge.name || '', name: knowledge.name || '',
description: knowledge.description || '', description: knowledge.description || '',
permission: knowledge.permission || 'me', permission: knowledge.permission || 'me',
avatar: knowledge.avatar, avatar: knowledge.avatar,
}); parser_id: knowledge.parser_id || DOCUMENT_PARSER_TYPES.Naive,
parser_config: {
configForm.reset({ chunk_token_num: knowledge.parser_config?.chunk_token_num || 512,
// parser_id: knowledge.parser_id || 'naive', delimiter: knowledge.parser_config?.delimiter || '\n',
// chunk_token_count: knowledge.chunk_token_count || 512, auto_keywords: knowledge.parser_config?.auto_keywords || 0,
// layout_recognize: knowledge.layout_recognize || false, auto_questions: knowledge.parser_config?.auto_questions || 0,
// task_page_size: knowledge.task_page_size || 0, html4excel: knowledge.parser_config?.html4excel || false,
topn_tags: knowledge.parser_config?.topn_tags || 3,
raptor: {
use_raptor: knowledge.parser_config?.raptor?.use_raptor || false,
prompt: knowledge.parser_config?.raptor?.prompt || '请总结以下段落。小心数字,不要编造。段落如下:\n {cluster_content}\n以上就是你需要总结的内容。',
max_token: knowledge.parser_config?.raptor?.max_token || 256,
threshold: knowledge.parser_config?.raptor?.threshold || 0.1,
max_cluster: knowledge.parser_config?.raptor?.max_cluster || 64,
random_seed: knowledge.parser_config?.raptor?.random_seed || 0,
},
graphrag: {
use_graphrag: knowledge.parser_config?.graphrag?.use_graphrag || false,
entity_types: knowledge.parser_config?.graphrag?.entity_types || ['organization', 'person', 'geo', 'event', 'category'],
method: knowledge.parser_config?.graphrag?.method || 'light',
},
},
}); });
} }
}, [knowledge, basicForm, configForm]); }, [knowledge, form]);
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { const handleTabChange = (event: React.SyntheticEvent, newValue: 'generalForm' | 'chunkMethodForm') => {
setTabValue(newValue); setTabValue(newValue);
}; };
const handleBasicInfoSubmit = async (data: BasicFormData) => { const handleSubmit = async ({data}: {data: any}) => {
if (!knowledge) return; if (!knowledge) return;
console.log('提交数据:', data);
try { try {
const kb = { // 分别处理基础信息和配置信息
...data, if (tabValue === 'generalForm') {
// parser_id: knowledge.parser_id, const basicData = {
name: data.name,
description: data.description,
permission: data.permission,
avatar: data.avatar,
kb_id: knowledge.id, kb_id: knowledge.id,
parser_id: knowledge.parser_id,
} as any; } as any;
await updateKnowledgeBasicInfo(kb); await updateKnowledgeBasicInfo(basicData);
showMessage.success('基础信息更新成功'); showMessage.success('基础信息更新成功');
// 刷新知识库详情 } else {
refresh(); const configData = {
} catch (error) { kb_id: knowledge.id,
// showMessage.error('基础信息更新失败'); name: knowledge.name,
} description: knowledge.description,
}; permission: knowledge.permission,
avatar: knowledge.avatar,
const handleConfigSubmit = async (data: ConfigFormData) => {
if (!id) return;
try {
await updateKnowledgeModelConfig({
id,
parser_id: data.parser_id, parser_id: data.parser_id,
// 可以根据需要添加更多配置字段 embd_id: data.embd_id,
}); parser_config: data.parser_config,
};
await updateKnowledgeModelConfig(configData);
showMessage.success('解析配置更新成功'); showMessage.success('解析配置更新成功');
}
// 刷新知识库详情 // 刷新知识库详情
refresh(); refresh();
} catch (error) { } catch (error) {
// showMessage.error('解析配置更新失败'); showMessage.error(`${tabValue === 'generalForm' ? '基础信息' : '解析配置'}更新失败`);
} }
}; };
const handleBackToDetail = () => { const handleNavigateBack = () => {
navigate(`/knowledge/${id}`); navigate(`/knowledge/${id}`);
}; };
if (detailLoading) { if (detailLoading) {
return ( return (
<Container maxWidth="lg" sx={{ py: 4 }}> <MainContainer>
<Typography>...</Typography> <Typography>...</Typography>
</Container> </MainContainer>
); );
} }
return ( return (
<Container maxWidth="lg" sx={{ py: 4 }}> <MainContainer>
{/* 面包屑导航 */} {/* 面包屑导航 */}
<KnowledgeBreadcrumbs knowledge={knowledge} /> <KnowledgeBreadcrumbs knowledge={knowledge} />
@@ -176,40 +167,37 @@ function KnowledgeBaseSetting() {
</Typography> </Typography>
</Box> </Box>
<Paper sx={{ width: '100%' }}> <FormProvider {...form}>
<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="设置选项卡">
<Tab label="基础信息" {...a11yProps(0)} /> <Tab label="基础信息" value="generalForm" />
<Tab label="解析配置" {...a11yProps(1)} /> <Tab label="解析配置" value="chunkMethodForm" />
</Tabs> </Tabs>
</Box> </Box>
<TabPanel value={tabValue} index={0}> <Box sx={{ mt: 3 }}>
<GeneralForm {tabValue === 'generalForm' && (
form={basicForm} <GeneralForm />
onSubmit={handleBasicInfoSubmit} )}
isSubmitting={operationLoading} {tabValue === 'chunkMethodForm' && (
submitButtonText="保存基础信息" <ChunkMethodForm />
disabled={detailLoading} )}
/> </Box>
</TabPanel>
<TabPanel value={tabValue} index={1}> <Box sx={{ mt: 3, display: 'flex', justifyContent: 'flex-end' }}>
<ChunkMethodForm <Button type="submit" variant="contained">
form={configForm as any}
onSubmit={handleConfigSubmit} </Button>
isSubmitting={operationLoading} </Box>
submitButtonText="保存解析配置" </Form>
disabled={detailLoading} </FormProvider>
/>
</TabPanel>
</Paper>
{/* 返回按钮 */} {/* 返回按钮 */}
<Fab <Fab
color="primary" color="primary"
aria-label="返回知识库详情" aria-label="返回知识库详情"
onClick={handleBackToDetail} onClick={handleNavigateBack}
sx={{ sx={{
position: 'fixed', position: 'fixed',
bottom: 128, bottom: 128,
@@ -218,7 +206,7 @@ function KnowledgeBaseSetting() {
> >
<ArrowBackIcon /> <ArrowBackIcon />
</Fab> </Fab>
</Container> </MainContainer>
); );
} }

View File

@@ -0,0 +1,366 @@
# 参考配置页面架构分析
## 项目概述
参考项目是一个基于 React + TypeScript 的知识库配置系统,使用 `react-hook-form` + `zod` 进行表单管理和验证,采用模块化的组件架构设计。
## 整体架构
### 1. 文件结构
```
dataset-setting/
├── index.tsx # 主配置页面入口
├── form-schema.ts # 表单数据结构和验证规则
├── general-form.tsx # 通用表单组件
├── chunk-method-form.tsx # 动态解析方法表单
├── hooks.ts # 自定义钩子函数
├── configuration-form-container.tsx # 表单容器组件
├── components/ # 通用组件
│ └── tag-item.tsx
└── configuration/ # 解析方法配置组件
├── common-item.tsx # 通用配置项
├── naive.tsx # 通用解析配置
├── paper.tsx # 论文解析配置
├── qa.tsx # 问答解析配置
├── book.tsx # 书籍解析配置
├── laws.tsx # 法律文档解析配置
├── manual.tsx # 手动解析配置
├── table.tsx # 表格解析配置
├── presentation.tsx # 演示文稿解析配置
├── picture.tsx # 图片解析配置
├── one.tsx # One解析配置
├── audio.tsx # 音频解析配置
├── email.tsx # 邮件解析配置
├── tag.tsx # 标签解析配置
└── knowledge-graph.tsx # 知识图谱解析配置
```
### 2. 核心架构模式
#### 2.1 主从架构模式
- **主页面** (`index.tsx`): 统一的表单管理和状态控制
- **子组件**: 各种专门化的表单组件,通过 `useFormContext` 共享表单状态
#### 2.2 策略模式
- **动态组件映射**: 根据选择的解析方法动态渲染对应的配置组件
- **配置组件映射表**: `ConfigurationComponentMap` 实现解析方法到组件的映射
## 核心功能模块
### 1. 表单数据结构 (`form-schema.ts`)
#### 主要数据结构
```typescript
const formSchema = z.object({
// 基础信息
parseType: z.number(), // 解析类型 (1: 内置解析, 2: 数据流水线)
name: z.string(), // 知识库名称
description: z.string(), // 描述
avatar: z.any().nullish(), // 头像
permission: z.string(), // 权限设置
// 解析器配置
parser_id: z.string(), // 解析器ID
embd_id: z.string(), // 嵌入模型ID
// 数据流水线配置
pipeline_id: z.string(), // 流水线ID
pipeline_name: z.string(), // 流水线名称
pipeline_avatar: z.string(), // 流水线头像
// 解析器详细配置
parser_config: z.object({
layout_recognize: z.string(), // 布局识别
chunk_token_num: z.number(), // 分块token数量
delimiter: z.string(), // 分隔符
auto_keywords: z.number(), // 自动关键词数量
auto_questions: z.number(), // 自动问题数量
html4excel: z.boolean(), // Excel转HTML
tag_kb_ids: z.array(z.string()), // 标签知识库ID
topn_tags: z.number(), // 顶部标签数量
toc_extraction: z.boolean(), // 目录提取
// RAPTOR配置
raptor: z.object({
use_raptor: z.boolean(),
prompt: z.string(),
max_token: z.number(),
threshold: z.number(),
max_cluster: z.number(),
random_seed: z.number(),
}),
// GraphRAG配置
graphrag: z.object({
use_graphrag: z.boolean(),
entity_types: z.array(z.string()),
method: z.string(),
resolution: z.boolean(),
community: z.boolean(),
}),
}),
pagerank: z.number(), // PageRank权重
});
```
#### 验证规则特点
1. **条件验证**: 使用 `refine` 方法实现复杂的条件验证逻辑
2. **跨字段验证**: 使用 `superRefine` 实现字段间的依赖验证
3. **国际化支持**: 错误消息支持多语言
### 2. 动态表单系统
#### 2.1 解析方法映射机制
```typescript
const ConfigurationComponentMap = {
[DocumentParserType.Naive]: NaiveConfiguration,
[DocumentParserType.Qa]: QAConfiguration,
[DocumentParserType.Resume]: ResumeConfiguration,
// ... 其他解析方法映射
};
```
#### 2.2 动态组件渲染
```typescript
const ConfigurationComponent = useMemo(() => {
return finalParserId
? ConfigurationComponentMap[finalParserId]
: EmptyComponent;
}, [finalParserId]);
```
#### 2.3 配置组件结构模式
每个配置组件都遵循统一的结构模式:
```typescript
export function XxxConfiguration() {
return (
<MainContainer>
<ConfigurationFormContainer>
{/* 第一组配置项 */}
</ConfigurationFormContainer>
<ConfigurationFormContainer>
{/* 第二组配置项 */}
</ConfigurationFormContainer>
</MainContainer>
);
}
```
### 3. 状态管理系统
#### 3.1 表单状态管理
- **统一表单实例**: 主页面创建 `useForm` 实例
- **上下文共享**: 子组件通过 `useFormContext` 访问表单状态
- **响应式更新**: 使用 `useWatch` 监听字段变化
#### 3.2 数据流管理
```typescript
// 监听解析类型变化
const parseType = useWatch({
control: form.control,
name: 'parseType',
});
// 监听解析器选择变化
const selectedTag = useWatch({
name: 'parser_id',
control: form.control,
});
```
#### 3.3 副作用处理
```typescript
useEffect(() => {
if (parseType === 1) {
form.setValue('pipeline_id', '');
}
}, [parseType, form]);
```
### 4. 数据获取和初始化
#### 4.1 自定义钩子 (`hooks.ts`)
```typescript
// 获取知识库配置并初始化表单
export const useFetchKnowledgeConfigurationOnMount = (form) => {
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration();
useEffect(() => {
// 合并默认值和服务器数据
const parser_config = {
...form.formState?.defaultValues?.parser_config,
...knowledgeDetails.parser_config,
// 特殊处理嵌套对象
};
// 重置表单数据
form.reset(formValues);
}, [form, knowledgeDetails]);
};
```
#### 4.2 数据处理策略
1. **默认值合并**: 将服务器数据与表单默认值合并
2. **嵌套对象处理**: 特殊处理 `raptor``graphrag` 等嵌套配置
3. **条件初始化**: 根据数据状态设置表单字段值
## 页面布局结构
### 1. 主页面布局
```typescript
<section className="p-5 h-full flex flex-col">
{/* 页面标题 */}
<TopTitle />
<div className="flex gap-14 flex-1 min-h-0">
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* 滚动内容区域 */}
<div className="w-[768px] h-[calc(100vh-240px)] pr-1 overflow-y-auto">
<MainContainer>
{/* 通用表单 */}
<GeneralForm />
<Divider />
{/* GraphRAG配置 */}
<GraphRagItems />
<Divider />
{/* RAPTOR配置 */}
<RaptorFormFields />
<Divider />
{/* 解析类型选择 */}
<ParseTypeItem />
{/* 条件渲染: 内置解析方法 */}
{parseType === 1 && (
<>
<ChunkMethodItem />
<ChunkMethodForm selectedTag={selectedTag} />
</>
)}
{/* 条件渲染: 数据流水线 */}
{parseType === 2 && (
<DataFlowSelect />
)}
</MainContainer>
</div>
{/* 操作按钮区域 */}
<div className="text-right items-center flex justify-end gap-3">
<Button type="reset">取消</Button>
<SavingButton />
</div>
</form>
</Form>
</div>
</section>
```
### 2. 响应式设计
- **固定宽度**: 表单区域固定 768px 宽度
- **垂直滚动**: 内容区域支持垂直滚动
- **弹性布局**: 使用 Flexbox 实现自适应高度
## 组件设计模式
### 1. 容器组件模式
```typescript
// 主容器 - 控制整体间距
export function MainContainer({ children, className }) {
return <section className={cn('space-y-5', className)}>{children}</section>;
}
// 配置容器 - 控制配置项间距
export function ConfigurationFormContainer({ children, className }) {
return <section className={cn('space-y-4', className)}>{children}</section>;
}
```
### 2. 表单字段组件模式
```typescript
export function XxxFormField() {
const form = useFormContext();
return (
<FormField
control={form.control}
name="fieldName"
render={({ field }) => (
<FormItem className="items-center space-y-0">
<div className="flex">
<FormLabel className="w-1/4">标签</FormLabel>
<FormControl className="w-3/4">
<Input {...field} />
</FormControl>
</div>
<div className="flex pt-1">
<div className="w-1/4"></div>
<FormMessage />
</div>
</FormItem>
)}
/>
);
}
```
### 3. 条件渲染模式
```typescript
// 基于状态的条件渲染
{parseType === 1 && <InternalParsingComponents />}
{parseType === 2 && <PipelineComponents />}
// 基于选择的动态组件
<ConfigurationComponent />
```
## 技术特点
### 1. 类型安全
- **TypeScript**: 全面的类型定义
- **Zod验证**: 运行时类型验证
- **表单类型推导**: 从schema自动推导表单类型
### 2. 性能优化
- **useMemo**: 缓存动态组件选择
- **useCallback**: 缓存事件处理函数
- **条件渲染**: 避免不必要的组件渲染
### 3. 可维护性
- **模块化设计**: 每个解析方法独立组件
- **统一接口**: 所有配置组件遵循相同模式
- **代码复用**: 通用组件和钩子函数
### 4. 扩展性
- **插件化架构**: 新增解析方法只需添加对应组件
- **配置驱动**: 通过配置映射控制组件渲染
- **钩子系统**: 自定义钩子支持功能扩展
## 适配建议
### 1. 保持现有布局样式
- 使用你现有的 Accordion 布局结构
- 保持四个配置分组的设计
- 适配参考项目的组件组织方式
### 2. 采用核心架构模式
- **动态组件映射**: 实现解析方法到配置组件的映射
- **统一表单管理**: 使用 `useFormContext` 共享表单状态
- **模块化组件**: 将不同配置拆分为独立组件
### 3. 数据结构对齐
- 参考 `form-schema.ts` 的数据结构设计
- 实现条件验证和跨字段验证
- 支持嵌套配置对象
### 4. 状态管理优化
- 使用 `useWatch` 监听关键字段变化
- 实现响应式的条件渲染
- 优化表单初始化和数据合并逻辑
这个架构设计具有很强的可扩展性和维护性,非常适合复杂的配置表单场景。你可以基于这个分析来改进你的配置页面实现。

View File

@@ -0,0 +1,265 @@
# 参考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` 监听字段变化
- 实现数据获取钩子
- 优化表单重置和提交逻辑
这种架构设计为大型表单应用提供了良好的可维护性和扩展性基础,值得在用户项目中借鉴和应用。

View File

@@ -11,7 +11,7 @@ import type {
IKnowledgeFile, IKnowledgeFile,
IChunk, IChunk,
IRenameTag, IRenameTag,
ParserConfig, IParserConfig,
} from '@/interfaces/database/knowledge'; } from '@/interfaces/database/knowledge';
import type { GridRowSelectionModel } from '@mui/x-data-grid'; import type { GridRowSelectionModel } from '@mui/x-data-grid';
@@ -120,8 +120,8 @@ const knowledgeService = {
return post(api.document_run, data); return post(api.document_run, data);
}, },
// 更改文档解析器 // 更改文档解析器配置
changeDocumentParser: (data: { doc_id: string; parser_config: ParserConfig }) => { changeDocumentParser: (data: { doc_id: string; parser_config: IParserConfig }) => {
return post(api.document_change_parser, data); return post(api.document_change_parser, data);
}, },

View File

@@ -2,6 +2,7 @@ import api from './api';
import request, { post } from '@/utils/request'; import request, { post } from '@/utils/request';
import type { ITenantInfo } from '@/interfaces/database/knowledge'; import type { ITenantInfo } from '@/interfaces/database/knowledge';
import type { IUserInfo, ITenant } from '@/interfaces/database/user-setting'; import type { IUserInfo, ITenant } from '@/interfaces/database/user-setting';
import type { LlmModelType } from '@/constants/knowledge';
// 用户相关API服务 // 用户相关API服务
const userService = { const userService = {
@@ -71,6 +72,11 @@ const userService = {
agreeTenant: (tenantId: string) => { agreeTenant: (tenantId: string) => {
return request.put(api.agreeTenant(tenantId)); return request.put(api.agreeTenant(tenantId));
}, },
// 获取LLM模型列表
llm_list: (data: { model_type?: LlmModelType }) => {
return request.get(api.llm_list, { params: data });
},
}; };
export default userService; export default userService;