From ed9e780c8cdda9189d3999a2a4e726ee9adb2074 Mon Sep 17 00:00:00 2001 From: wizardchen Date: Tue, 9 Jun 2026 17:49:39 +0800 Subject: [PATCH] feat(client): enhance knowledge file upload with process configuration support - Updated the `CreateKnowledgeFromFile` function to accept an optional `processConfig` parameter for per-upload parse configuration overrides. - Introduced the `KnowledgeProcessOverrides` type to define various parsing settings, including parser engine rules and chunking configurations. - Modified the API client and related components to handle the new process configuration, improving flexibility in knowledge file uploads. - Updated documentation and API specifications to reflect the new `process_config` parameter in the upload process. - Added a new `UploadConfirmHost` component to facilitate user confirmation for uploads, integrating with the updated upload logic. - Enhanced user experience by providing detailed configuration options during the upload process. --- client/example.go | 2 +- client/knowledge.go | 27 + client/knowledgebase.go | 19 + docs/api/knowledge.md | 3 + docs/docs.go | 6 + docs/swagger.json | 6 + docs/swagger.yaml | 4 + frontend/src/App.vue | 2 + frontend/src/api/knowledge-base/index.ts | 42 +- frontend/src/components/ModelSelector.vue | 5 +- frontend/src/components/UploadConfirmHost.vue | 29 + .../components/manual-knowledge-editor.vue | 60 +- frontend/src/i18n/locales/en-US.ts | 68 + frontend/src/i18n/locales/ko-KR.ts | 67 + frontend/src/i18n/locales/ru-RU.ts | 67 + frontend/src/i18n/locales/zh-CN.ts | 67 + frontend/src/stores/uploadConfirm.ts | 85 ++ frontend/src/types/knowledgeProcess.ts | 66 + .../src/views/knowledge/KnowledgeBase.vue | 532 +++---- .../components/KbUploadSourceDropdown.vue | 251 ++++ .../components/UploadConfirmDialog.vue | 1242 +++++++++++++++++ .../knowledge/settings/GraphSettings.vue | 46 +- .../knowledge/settings/KBAdvancedSettings.vue | 58 +- .../knowledge/settings/KBChunkingSettings.vue | 145 +- .../knowledge/settings/KBParserSettings.vue | 60 +- .../views/knowledge/utils/uploadSources.ts | 70 + .../application/service/datasource_service.go | 2 + internal/application/service/extract.go | 28 +- .../application/service/image_multimodal.go | 35 +- .../application/service/knowledge_create.go | 233 ++-- .../service/knowledge_create_test.go | 49 + .../service/knowledge_post_process.go | 15 +- .../application/service/knowledge_process.go | 93 +- .../service/knowledge_process_config.go | 231 +++ .../service/knowledge_process_config_test.go | 245 ++++ internal/handler/knowledge.go | 39 +- internal/im/service.go | 2 +- internal/types/interfaces/knowledge.go | 2 + internal/types/knowledge.go | 67 +- internal/types/knowledge_process.go | 24 + internal/types/knowledge_process_test.go | 48 + 41 files changed, 3546 insertions(+), 596 deletions(-) create mode 100644 frontend/src/components/UploadConfirmHost.vue create mode 100644 frontend/src/stores/uploadConfirm.ts create mode 100644 frontend/src/types/knowledgeProcess.ts create mode 100644 frontend/src/views/knowledge/components/KbUploadSourceDropdown.vue create mode 100644 frontend/src/views/knowledge/components/UploadConfirmDialog.vue create mode 100644 frontend/src/views/knowledge/utils/uploadSources.ts create mode 100644 internal/application/service/knowledge_process_config.go create mode 100644 internal/application/service/knowledge_process_config_test.go create mode 100644 internal/types/knowledge_process.go create mode 100644 internal/types/knowledge_process_test.go diff --git a/client/example.go b/client/example.go index dcae29dd1..c9ed35e86 100644 --- a/client/example.go +++ b/client/example.go @@ -71,7 +71,7 @@ func ExampleUsage() { "source": "local", "type": "document", } - knowledge, err := apiClient.CreateKnowledgeFromFile(context.Background(), createdKB.ID, filePath, metadata, nil, "", "") + knowledge, err := apiClient.CreateKnowledgeFromFile(context.Background(), createdKB.ID, filePath, metadata, nil, "", "", nil) if err != nil { fmt.Printf("Failed to upload knowledge file: %v\n", err) } else { diff --git a/client/knowledge.go b/client/knowledge.go index 1398455ee..35455bbd2 100644 --- a/client/knowledge.go +++ b/client/knowledge.go @@ -83,6 +83,19 @@ var ErrDuplicateFile = errors.New("file already exists") // ErrDuplicateURL is returned when attempting to create a knowledge entry with a URL that already exists var ErrDuplicateURL = errors.New("URL already exists") +// KnowledgeProcessOverrides stores per-upload parse config overrides sent as process_config. +// When nil, the server uses the knowledge base defaults only. +type KnowledgeProcessOverrides struct { + ParserEngineRules []ParserEngineRule `json:"parser_engine_rules,omitempty"` + ChunkingConfig *ChunkingConfig `json:"chunking_config,omitempty"` + EnableMultimodel *bool `json:"enable_multimodel,omitempty"` + VLMConfig *VLMConfig `json:"vlm_config,omitempty"` + ASRConfig *ASRConfig `json:"asr_config,omitempty"` + QuestionGenerationConfig *QuestionGenerationConfig `json:"question_generation_config,omitempty"` + GraphEnabled *bool `json:"graph_enabled,omitempty"` + ExtractConfig *ExtractConfig `json:"extract_config,omitempty"` +} + // CreateKnowledgeFromFile creates a knowledge entry from a local file path // Parameters: // - knowledgeBaseID: The ID of the knowledge base @@ -91,8 +104,10 @@ var ErrDuplicateURL = errors.New("URL already exists") // - enableMultimodel: Optional flag to enable multimodal processing // - customFileName: Optional custom file name (useful for folder uploads with path) // - channel: Optional ingestion channel (e.g. "web", "api", "wechat"); empty defaults to "web" +// - processConfig: Optional parse config overrides (serialized as process_config form field) func (c *Client) CreateKnowledgeFromFile(ctx context.Context, knowledgeBaseID string, filePath string, metadata map[string]string, enableMultimodel *bool, customFileName string, channel string, + processConfig *KnowledgeProcessOverrides, ) (*Knowledge, error) { // Open the local file file, err := os.Open(filePath) @@ -159,6 +174,16 @@ func (c *Client) CreateKnowledgeFromFile(ctx context.Context, } } + if processConfig != nil { + processConfigBytes, err := json.Marshal(processConfig) + if err != nil { + return nil, fmt.Errorf("failed to serialize process_config: %w", err) + } + if err := writer.WriteField("process_config", string(processConfigBytes)); err != nil { + return nil, fmt.Errorf("failed to write process_config field: %w", err) + } + } + // Close the multipart writer err = writer.Close() if err != nil { @@ -209,6 +234,8 @@ type CreateKnowledgeFromURLRequest struct { TagID string `json:"tag_id,omitempty"` // Channel identifies the ingestion channel (e.g. "web", "browser_extension", "api") Channel string `json:"channel,omitempty"` + // ProcessConfig is optional per-upload parse config overrides (KnowledgeProcessOverrides). + ProcessConfig *KnowledgeProcessOverrides `json:"process_config,omitempty"` } // CreateKnowledgeFromURL creates a knowledge entry from a URL. diff --git a/client/knowledgebase.go b/client/knowledgebase.go index 85ad3762f..b2cfdf71e 100644 --- a/client/knowledgebase.go +++ b/client/knowledgebase.go @@ -108,6 +108,25 @@ type GraphRelation struct { Type string `json:"type"` } +// ParserEngineRule maps a set of file types to a specific parser engine. +type ParserEngineRule struct { + FileTypes []string `json:"file_types"` + Engine string `json:"engine"` +} + +// QuestionGenerationConfig controls LLM-generated questions per chunk during parsing. +type QuestionGenerationConfig struct { + Enabled bool `json:"enabled"` + QuestionCount int `json:"question_count"` +} + +// ASRConfig represents automatic speech recognition settings for audio files. +type ASRConfig struct { + Enabled bool `json:"enabled"` + ModelID string `json:"model_id"` + Language string `json:"language,omitempty"` +} + // UnmarshalJSON keeps backward compatibility for legacy responses that still // use `cos_config` instead of `storage_config`. func (kb *KnowledgeBase) UnmarshalJSON(data []byte) error { diff --git a/docs/api/knowledge.md b/docs/api/knowledge.md index 9a72a8f5f..44d1251cd 100644 --- a/docs/api/knowledge.md +++ b/docs/api/knowledge.md @@ -52,9 +52,12 @@ | `fileName` | string | 否 | 自定义文件名,用于"文件夹上传"时保留相对路径(如 `docs/intro.md`) | | `metadata` | string | 否 | JSON 字符串,会被反序列化为 `map[string]string` | | `enable_multimodel` | string | 否 | `"true"` / `"false"`,是否启用图文多模态解析 | +| `process_config` | string | 否 | JSON 字符串,批次解析配置覆盖(`KnowledgeProcessOverrides`);写入 `knowledge.metadata.process_overrides`。未传时行为与现网一致 | | `tag_id` | string | 否 | 标签 ID;传 `__untagged__` 或空字符串表示未分类 | | `channel` | string | 否 | 来源渠道标识(写入 `channel` 字段,默认 `web`) | +`process_config` 可选字段包括:`parser_engine_rules`、`chunking_config`、`enable_multimodel`、`vlm_config`、`asr_config`、`question_generation_config`、`graph_enabled`、`extract_config`。若同时传 `enable_multimodel` 与 `process_config.enable_multimodel`,以 `process_config` 为准。 + **请求**: ```curl diff --git a/docs/docs.go b/docs/docs.go index 17cc68d93..4b0634c8e 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -4775,6 +4775,12 @@ const docTemplate = `{ "description": "启用多模态处理", "name": "enable_multimodel", "in": "formData" + }, + { + "type": "string", + "description": "处理配置JSON(KnowledgeProcessOverrides)", + "name": "process_config", + "in": "formData" } ], "responses": { diff --git a/docs/swagger.json b/docs/swagger.json index 56fb55df9..522618343 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -4768,6 +4768,12 @@ "description": "启用多模态处理", "name": "enable_multimodel", "in": "formData" + }, + { + "type": "string", + "description": "处理配置JSON(KnowledgeProcessOverrides)", + "name": "process_config", + "in": "formData" } ], "responses": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index a916b2395..f7f7ab810 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -7746,6 +7746,10 @@ paths: in: formData name: enable_multimodel type: boolean + - description: 处理配置JSON(KnowledgeProcessOverrides) + in: formData + name: process_config + type: string produces: - application/json responses: diff --git a/frontend/src/App.vue b/frontend/src/App.vue index 92d153085..709338817 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -4,6 +4,7 @@ import { useRouter } from 'vue-router' import { useI18n } from 'vue-i18n' import { MessagePlugin, NotifyPlugin } from 'tdesign-vue-next' import ManualKnowledgeEditor from '@/components/manual-knowledge-editor.vue' +import UploadConfirmHost from '@/components/UploadConfirmHost.vue' import { useAuthStore } from '@/stores/auth' import { useSettingsStore } from '@/stores/settings' import { getCurrentUser, userInfoFromApi } from '@/api/auth' @@ -257,6 +258,7 @@ onUnmounted(() => {
+
diff --git a/frontend/src/api/knowledge-base/index.ts b/frontend/src/api/knowledge-base/index.ts index a1d555d3f..9ffdad3fe 100644 --- a/frontend/src/api/knowledge-base/index.ts +++ b/frontend/src/api/knowledge-base/index.ts @@ -1,4 +1,5 @@ import { get, post, put, del, postUpload, getDown } from "../../utils/request"; +import type { KnowledgeProcessOverrides } from '@/types/knowledgeProcess'; // 知识库管理 API(列表、创建、获取、更新、删除、复制) export function listKnowledgeBases(params?: { @@ -160,23 +161,51 @@ export function togglePinKnowledgeBase(id: string) { // 知识文件 API(基于具体知识库) // data.tag_id: 可选,指定知识所属的分类ID -export function uploadKnowledgeFile(kbId: string, data: { file: File; tag_id?: string; [key: string]: any } = { file: new File([], '') }, onProgress?: (progressEvent: any) => void) { +export function uploadKnowledgeFile( + kbId: string, + data: { + file: File + tag_id?: string + fileName?: string + process_config?: KnowledgeProcessOverrides | string + [key: string]: any + } = { file: new File([], '') }, + onProgress?: (progressEvent: any) => void, +) { const formData = new FormData(); Object.keys(data).forEach(key => { - if (data[key] !== undefined) formData.append(key, data[key]); + const value = data[key]; + if (value === undefined) return; + if (key === 'process_config' && value && typeof value !== 'string') { + formData.append(key, JSON.stringify(value)); + } else { + formData.append(key, value); + } }); return postUpload(`/api/v1/knowledge-bases/${kbId}/knowledge/file`, formData, onProgress); } // 从URL创建知识 // data.tag_id: 可选,指定知识所属的分类ID -export function createKnowledgeFromURL(kbId: string, data: { url: string; enable_multimodel?: boolean; tag_id?: string }) { +export function createKnowledgeFromURL( + kbId: string, + data: { url: string; enable_multimodel?: boolean; tag_id?: string; process_config?: KnowledgeProcessOverrides }, +) { return post(`/api/v1/knowledge-bases/${kbId}/knowledge/url`, data); } // 手工创建知识 // data.tag_id: 可选,指定知识所属的分类ID -export function createManualKnowledge(kbId: string, data: { title: string; content: string; status: string; tag_id?: string }) { +export function createManualKnowledge( + kbId: string, + data: { + title: string + content: string + status: string + tag_id?: string + process_config?: KnowledgeProcessOverrides + }, +) { return post(`/api/v1/knowledge-bases/${kbId}/knowledge/manual`, data); } @@ -215,7 +244,10 @@ export function getKnowledgeDetails(id: string, options?: { agent_id?: string }) return get(qs ? `/api/v1/knowledge/${id}?${qs}` : `/api/v1/knowledge/${id}`); } -export function updateManualKnowledge(id: string, data: { title: string; content: string; status: string }) { +export function updateManualKnowledge( + id: string, + data: { title: string; content: string; status: string; process_config?: KnowledgeProcessOverrides }, +) { return put(`/api/v1/knowledge/manual/${id}`, data); } diff --git a/frontend/src/components/ModelSelector.vue b/frontend/src/components/ModelSelector.vue index aff8b61d6..402713d09 100644 --- a/frontend/src/components/ModelSelector.vue +++ b/frontend/src/components/ModelSelector.vue @@ -6,6 +6,7 @@ :placeholder="placeholderText" :disabled="disabled" :loading="loading" + :status="status" filterable style="width: 100%;" > @@ -51,13 +52,15 @@ interface Props { selectedModelId?: string disabled?: boolean placeholder?: string + status?: 'default' | 'success' | 'warning' | 'error' // 可选:外部传入的所有模型列表,如果提供则不调用API allModels?: ModelConfig[] } const props = withDefaults(defineProps(), { disabled: false, - placeholder: '' + placeholder: '', + status: 'default', }) const emit = defineEmits<{ diff --git a/frontend/src/components/UploadConfirmHost.vue b/frontend/src/components/UploadConfirmHost.vue new file mode 100644 index 000000000..81724ab0f --- /dev/null +++ b/frontend/src/components/UploadConfirmHost.vue @@ -0,0 +1,29 @@ + + + diff --git a/frontend/src/components/manual-knowledge-editor.vue b/frontend/src/components/manual-knowledge-editor.vue index 69a120fcc..5da9628bb 100644 --- a/frontend/src/components/manual-knowledge-editor.vue +++ b/frontend/src/components/manual-knowledge-editor.vue @@ -3,7 +3,15 @@ import { ref, reactive, computed, watch, nextTick, onBeforeUnmount } from 'vue' import { marked } from 'marked' import { MessagePlugin } from 'tdesign-vue-next' import { useUIStore } from '@/stores/ui' -import { listKnowledgeBases, getKnowledgeDetails, createManualKnowledge, updateManualKnowledge } from '@/api/knowledge-base' +import { + listKnowledgeBases, + getKnowledgeDetails, + getKnowledgeBaseById, + createManualKnowledge, + updateManualKnowledge, +} from '@/api/knowledge-base' +import { useUploadConfirmStore } from '@/stores/uploadConfirm' +import type { KnowledgeProcessOverrides } from '@/types/knowledgeProcess' import { sanitizeHTML, safeMarkdownToHTML } from '@/utils/security' import { useI18n } from 'vue-i18n' @@ -24,6 +32,7 @@ interface KnowledgeDetailResponse { type ManualStatus = 'draft' | 'publish' const uiStore = useUIStore() +const uploadConfirmStore = useUploadConfirmStore() const { t } = useI18n() const visible = computed({ @@ -576,11 +585,53 @@ const handleSave = async (targetStatus: ManualStatus) => { saving.value = true savingAction.value = targetStatus try { - const payload: { title: string; content: string; status: string; tag_id?: string } = { + const tagIdToUpload = uiStore.selectedTagId !== '__untagged__' ? uiStore.selectedTagId : undefined + const payload: { + title: string + content: string + status: string + tag_id?: string + process_config?: KnowledgeProcessOverrides + } = { title: form.title.trim(), content: form.content, status: targetStatus, } + if (tagIdToUpload) { + payload.tag_id = tagIdToUpload + } + + if (targetStatus === 'publish') { + let kbInfo: any + try { + const kbRes: any = await getKnowledgeBaseById(form.kbId) + kbInfo = kbRes?.data + } catch { + MessagePlugin.error(t('manualEditor.error.fetchDetailFailed')) + return + } + if (!kbInfo) { + MessagePlugin.error(t('manualEditor.error.fetchDetailFailed')) + return + } + try { + const confirmResult = await uploadConfirmStore.open({ + mode: 'manual', + kbInfo, + manual: { + kbId: form.kbId, + knowledgeId: knowledgeId.value || undefined, + title: payload.title, + content: payload.content, + tagId: tagIdToUpload, + }, + }) + payload.process_config = confirmResult.processConfig + } catch { + return + } + } + let response: any let knowledgeID = knowledgeId.value let kbId = form.kbId @@ -588,11 +639,6 @@ const handleSave = async (targetStatus: ManualStatus) => { if (mode.value === 'edit' && knowledgeId.value) { response = await updateManualKnowledge(knowledgeId.value, payload) } else { - // 创建新知识时,从 store 获取当前选中的分类ID - const tagIdToUpload = uiStore.selectedTagId !== '__untagged__' ? uiStore.selectedTagId : undefined - if (tagIdToUpload) { - payload.tag_id = tagIdToUpload - } response = await createManualKnowledge(form.kbId, payload) knowledgeID = response?.data?.id || knowledgeID kbId = form.kbId diff --git a/frontend/src/i18n/locales/en-US.ts b/frontend/src/i18n/locales/en-US.ts index 869c56846..0af500e3b 100755 --- a/frontend/src/i18n/locales/en-US.ts +++ b/frontend/src/i18n/locales/en-US.ts @@ -636,6 +636,74 @@ export default { chunkLoadFailed: 'Failed to load chunks', }, + uploadConfirm: { + title: 'Confirm Upload', + fileList: 'Files to upload', + parseConfig: 'Parse settings', + configNav: 'Parse settings navigation', + tabOverview: 'Overview', + overviewTitle: 'Parse settings for this batch', + overviewDesc: 'Click any row to edit', + backToOverview: 'Back to overview', + summaryChunkOverlapShort: 'Overlap {overlap}', + summaryParentChildShort: 'Parent-child', + summaryParserMode: 'Mode', + summaryParserBuiltin: 'Built-in engine (default)', + summaryChunkSize: 'Chunk size', + summaryChunkOverlap: 'Overlap', + summaryStrategy: 'Strategy', + summaryStrategyDefault: 'Default', + summaryParentChild: 'Parent-child', + summaryParentChildOn: 'On (parent {parent} / child {child})', + summaryParentChildOff: 'Off', + summaryStatus: 'Status', + summaryModel: 'Model', + summaryQuestionCount: 'Questions per chunk', + summaryQuestionCountValue: '{count}', + summaryGraphTags: 'Relation types', + summaryGraphTagsValue: '{count}', + navChunkingSummary: 'Chunk {size}', + statusOn: 'On', + statusOff: 'Off', + notSet: 'Not set', + confirm: 'Upload and parse', + cancel: 'Cancel', + uploading: 'Uploading {current}/{total}', + tabParser: 'Parser', + tabChunking: 'Chunking', + tabMultimodal: 'Multimodal', + tabAsr: 'Audio', + tabQuestion: 'Question generation', + tabGraph: 'Knowledge graph', + noFiles: 'Keep at least one file to upload', + noItems: 'Add at least one file or URL', + urlItemLabel: 'URL', + urlAdded: 'URL added', + urlDuplicate: 'This URL is already in the list', + addUrl: 'Add', + vlmModelRequired: 'This batch includes images. Enable multimodal and select a VLM model.', + multimodalRequiredForImages: 'Off (batch includes images)', + asrModelRequired: 'This batch includes audio. Enable ASR and select a speech recognition model.', + asrRequiredForAudio: 'Off (batch includes audio)', + vlmModelSelectRequired: 'Multimodal is enabled. Please select a VLM model.', + asrModelSelectRequired: 'Speech recognition is enabled. Please select an ASR model.', + continueAdd: 'Add more', + addMoreFiles: 'Add more files', + addMoreFolder: 'Add more from folder', + filesAdded: 'Added {count} file(s)', + filesAllDuplicate: 'Selected files are already in the list', + titleUrl: 'Confirm URL import', + titleManual: 'Confirm online publish', + overviewDescUrl: 'The URL will be fetched and parsed after you confirm the settings', + overviewDescManual: 'The document will be published and indexed after you confirm the settings', + confirmUrl: 'Import and parse', + confirmManual: 'Publish and parse', + urlSource: 'Import source', + manualSource: 'Document to publish', + editUrl: 'Edit URL', + manualCharCount: '{count} characters', + }, + knowledgeStages: { title: 'Processing pipeline', root: 'Knowledge processing', diff --git a/frontend/src/i18n/locales/ko-KR.ts b/frontend/src/i18n/locales/ko-KR.ts index b27a85442..376bce412 100755 --- a/frontend/src/i18n/locales/ko-KR.ts +++ b/frontend/src/i18n/locales/ko-KR.ts @@ -642,6 +642,73 @@ export default { deleteSuccess: "지식이 성공적으로 삭제되었습니다!", chunkLoadFailed: "청크 로드 실패", }, + uploadConfirm: { + title: "문서 업로드 확인", + fileList: "업로드할 파일", + parseConfig: "파싱 설정", + configNav: "파싱 설정 탐색", + tabOverview: "구성 개요", + overviewTitle: "이번 배치 파싱 설정", + overviewDesc: "항목을 클릭하여 설정을 수정하세요", + backToOverview: "구성 개요로 돌아가기", + summaryChunkOverlapShort: "겹침 {overlap}", + summaryParentChildShort: "부모-자식 청킹", + summaryParserMode: "모드", + summaryParserBuiltin: "내장 엔진(기본)", + summaryChunkSize: "청크 크기", + summaryChunkOverlap: "겹침", + summaryStrategy: "전략", + summaryStrategyDefault: "기본", + summaryParentChild: "부모-자식 청킹", + summaryParentChildOn: "켜짐(부모 {parent} / 자식 {child})", + summaryParentChildOff: "꺼짐", + summaryStatus: "상태", + summaryModel: "모델", + summaryQuestionCount: "청크당 질문 수", + summaryQuestionCountValue: "{count}개", + summaryGraphTags: "관계 유형", + summaryGraphTagsValue: "{count}개", + navChunkingSummary: "청크 {size}", + statusOn: "켜짐", + statusOff: "꺼짐", + notSet: "미설정", + confirm: "업로드 및 파싱 확인", + cancel: "취소", + uploading: "업로드 중 {current}/{total}", + tabParser: "파서", + tabChunking: "청킹", + tabMultimodal: "멀티모달", + tabAsr: "오디오", + tabQuestion: "질문 생성", + tabGraph: "지식 그래프", + noFiles: "업로드할 파일을 하나 이상 남겨 두세요", + noItems: "파일 또는 URL을 하나 이상 추가하세요", + urlItemLabel: "URL", + urlAdded: "URL이 추가되었습니다", + urlDuplicate: "이 URL은 이미 목록에 있습니다", + addUrl: "추가", + vlmModelRequired: "이 배치에 이미지가 포함되어 있습니다. 멀티모달을 활성화하고 VLM 모델을 선택하세요", + multimodalRequiredForImages: "꺼짐(이 배치에 이미지 포함)", + asrModelRequired: "이 배치에 오디오가 포함되어 있습니다. ASR을 활성화하고 음성 인식 모델을 선택하세요", + asrRequiredForAudio: "꺼짐(이 배치에 오디오 포함)", + vlmModelSelectRequired: "멀티모달이 활성화되었습니다. VLM 모델을 선택하세요", + asrModelSelectRequired: "음성 인식이 활성화되었습니다. ASR 모델을 선택하세요", + continueAdd: "계속 추가", + addMoreFiles: "파일 더 추가", + addMoreFolder: "폴더에서 더 추가", + filesAdded: "{count}개 파일이 추가되었습니다", + filesAllDuplicate: "선택한 파일이 이미 목록에 있습니다", + titleUrl: "URL 가져오기 확인", + titleManual: "온라인 편집 게시 확인", + overviewDescUrl: "설정 확인 후 URL을 가져와 파싱합니다", + overviewDescManual: "설정 확인 후 문서를 게시하고 인덱싱합니다", + confirmUrl: "가져오기 및 파싱", + confirmManual: "게시 및 파싱", + urlSource: "가져오기 소스", + manualSource: "게시할 문서", + editUrl: "URL 수정", + manualCharCount: "{count}자", + }, knowledgeStages: { title: "처리 파이프라인", root: "지식 처리", diff --git a/frontend/src/i18n/locales/ru-RU.ts b/frontend/src/i18n/locales/ru-RU.ts index 41a592ebd..d13379b92 100755 --- a/frontend/src/i18n/locales/ru-RU.ts +++ b/frontend/src/i18n/locales/ru-RU.ts @@ -606,6 +606,73 @@ export default { cancelParseSubmitted: 'Разбор остановлен', cancelParseFailed: 'Не удалось остановить, попробуйте позже' }, + uploadConfirm: { + title: 'Подтверждение загрузки', + fileList: 'Файлы для загрузки', + parseConfig: 'Настройки разбора', + configNav: 'Навигация по настройкам разбора', + tabOverview: 'Обзор', + overviewTitle: 'Настройки разбора для этой партии', + overviewDesc: 'Нажмите на строку, чтобы изменить', + backToOverview: 'Вернуться к обзору', + summaryChunkOverlapShort: 'Перекрытие {overlap}', + summaryParentChildShort: 'Родительско-дочернее', + summaryParserMode: 'Режим', + summaryParserBuiltin: 'Встроенный движок (по умолчанию)', + summaryChunkSize: 'Размер чанка', + summaryChunkOverlap: 'Перекрытие', + summaryStrategy: 'Стратегия', + summaryStrategyDefault: 'По умолчанию', + summaryParentChild: 'Родительско-дочернее', + summaryParentChildOn: 'Вкл. (родитель {parent} / дочерний {child})', + summaryParentChildOff: 'Выкл.', + summaryStatus: 'Статус', + summaryModel: 'Модель', + summaryQuestionCount: 'Вопросов на чанк', + summaryQuestionCountValue: '{count}', + summaryGraphTags: 'Типы связей', + summaryGraphTagsValue: '{count}', + navChunkingSummary: 'Чанк {size}', + statusOn: 'Вкл.', + statusOff: 'Выкл.', + notSet: 'Не задано', + confirm: 'Загрузить и обработать', + cancel: 'Отмена', + uploading: 'Загрузка {current}/{total}', + tabParser: 'Парсер', + tabChunking: 'Разбиение', + tabMultimodal: 'Мультимодальность', + tabAsr: 'Аудио', + tabQuestion: 'Генерация вопросов', + tabGraph: 'Граф знаний', + noFiles: 'Оставьте хотя бы один файл для загрузки', + noItems: 'Добавьте хотя бы один файл или URL', + urlItemLabel: 'URL', + urlAdded: 'URL добавлен', + urlDuplicate: 'Этот URL уже в списке', + addUrl: 'Добавить', + vlmModelRequired: 'В партии есть изображения. Включите мультимодальность и выберите модель VLM.', + multimodalRequiredForImages: 'Выкл. (в партии есть изображения)', + asrModelRequired: 'В партии есть аудио. Включите ASR и выберите модель распознавания речи.', + asrRequiredForAudio: 'Выкл. (в партии есть аудио)', + vlmModelSelectRequired: 'Мультимодальность включена. Выберите модель VLM.', + asrModelSelectRequired: 'Распознавание речи включено. Выберите модель ASR.', + continueAdd: 'Добавить ещё', + addMoreFiles: 'Добавить файлы', + addMoreFolder: 'Добавить из папки', + filesAdded: 'Добавлено файлов: {count}', + filesAllDuplicate: 'Выбранные файлы уже в списке', + titleUrl: 'Подтверждение импорта URL', + titleManual: 'Подтверждение публикации', + overviewDescUrl: 'После подтверждения настроек URL будет загружен и обработан', + overviewDescManual: 'После подтверждения настроек документ будет опубликован и проиндексирован', + confirmUrl: 'Импортировать и обработать', + confirmManual: 'Опубликовать и обработать', + urlSource: 'Источник импорта', + manualSource: 'Документ для публикации', + editUrl: 'Изменить URL', + manualCharCount: '{count} символов', + }, knowledgeStages: { title: 'Конвейер обработки', root: 'Обработка знаний', diff --git a/frontend/src/i18n/locales/zh-CN.ts b/frontend/src/i18n/locales/zh-CN.ts index f118a911a..d8d6b317b 100755 --- a/frontend/src/i18n/locales/zh-CN.ts +++ b/frontend/src/i18n/locales/zh-CN.ts @@ -639,6 +639,73 @@ export default { deleteSuccess: "知识删除成功!", chunkLoadFailed: "分块加载失败", }, + uploadConfirm: { + title: "上传文档确认", + fileList: "待上传文件", + parseConfig: "解析配置", + configNav: "解析配置导航", + tabOverview: "配置总览", + overviewTitle: "本批次解析配置", + overviewDesc: "点击任一项可修改配置", + backToOverview: "返回配置总览", + summaryChunkOverlapShort: "重叠 {overlap}", + summaryParentChildShort: "父子分块", + summaryParserMode: "模式", + summaryParserBuiltin: "内置引擎(默认)", + summaryChunkSize: "分块大小", + summaryChunkOverlap: "重叠", + summaryStrategy: "策略", + summaryStrategyDefault: "默认", + summaryParentChild: "父子分块", + summaryParentChildOn: "已开启(父 {parent} / 子 {child})", + summaryParentChildOff: "未开启", + summaryStatus: "状态", + summaryModel: "模型", + summaryQuestionCount: "每块问题数", + summaryQuestionCountValue: "{count} 个", + summaryGraphTags: "关系类型", + summaryGraphTagsValue: "{count} 个", + navChunkingSummary: "分块 {size}", + statusOn: "已开启", + statusOff: "未开启", + notSet: "未设置", + confirm: "确认上传并解析", + cancel: "取消", + uploading: "正在上传 {current}/{total}", + tabParser: "解析引擎", + tabChunking: "分块", + tabMultimodal: "多模态", + tabAsr: "音频", + tabQuestion: "问题生成", + tabGraph: "图谱", + noFiles: "请至少保留一个待上传文件", + noItems: "请至少添加一个文件或 URL", + urlItemLabel: "URL", + urlAdded: "已添加 URL", + urlDuplicate: "该 URL 已在列表中", + addUrl: "添加", + vlmModelRequired: "本批次包含图片,请启用多模态并选择 VLM 模型", + multimodalRequiredForImages: "未开启(本批次含图片)", + asrModelRequired: "本批次包含音频,请启用 ASR 并选择语音识别模型", + asrRequiredForAudio: "未开启(本批次含音频)", + vlmModelSelectRequired: "已启用多模态,请选择 VLM 模型", + asrModelSelectRequired: "已启用语音识别,请选择 ASR 模型", + continueAdd: "继续添加", + addMoreFiles: "继续添加文件", + addMoreFolder: "继续添加文件夹", + filesAdded: "已添加 {count} 个文件", + filesAllDuplicate: "所选文件已在列表中", + titleUrl: "URL 导入确认", + titleManual: "在线编辑发布确认", + overviewDescUrl: "确认解析配置后将抓取并解析该 URL", + overviewDescManual: "确认解析配置后将发布并索引该文档", + confirmUrl: "确认导入并解析", + confirmManual: "确认发布并解析", + urlSource: "导入来源", + manualSource: "待发布文档", + editUrl: "修改 URL", + manualCharCount: "{count} 个字符", + }, knowledgeStages: { title: "处理流水线", root: "知识处理", diff --git a/frontend/src/stores/uploadConfirm.ts b/frontend/src/stores/uploadConfirm.ts new file mode 100644 index 000000000..a65ba0475 --- /dev/null +++ b/frontend/src/stores/uploadConfirm.ts @@ -0,0 +1,85 @@ +import { defineStore } from 'pinia' +import type { KnowledgeProcessOverrides } from '@/types/knowledgeProcess' + +export type UploadConfirmMode = 'file' | 'url' | 'manual' + +export interface UploadConfirmManualSource { + kbId: string + knowledgeId?: string + title: string + content: string + tagId?: string +} + +export interface UploadConfirmResult { + processConfig: KnowledgeProcessOverrides + mode: UploadConfirmMode + files?: File[] + urls?: string[] + manual?: UploadConfirmManualSource +} + +export interface OpenUploadConfirmOptions { + mode: UploadConfirmMode + kbInfo: any + files?: File[] + urls?: string[] + manual?: UploadConfirmManualSource + acceptFileTypes?: string + supportedFileTypes?: string[] +} + +export const useUploadConfirmStore = defineStore('uploadConfirm', { + state: () => ({ + visible: false, + mode: 'file' as UploadConfirmMode, + kbInfo: null as any, + files: [] as File[], + urls: [] as string[], + manual: null as UploadConfirmManualSource | null, + acceptFileTypes: '', + supportedFileTypes: [] as string[], + pendingResolve: null as ((value: UploadConfirmResult) => void) | null, + pendingReject: null as (() => void) | null, + }), + + actions: { + open(options: OpenUploadConfirmOptions): Promise { + return new Promise((resolve, reject) => { + this.visible = true + this.mode = options.mode + this.kbInfo = options.kbInfo + this.files = options.files ? [...options.files] : [] + this.urls = options.urls ? [...options.urls] : [] + this.manual = options.manual || null + this.acceptFileTypes = options.acceptFileTypes || '' + this.supportedFileTypes = options.supportedFileTypes ? [...options.supportedFileTypes] : [] + this.pendingResolve = resolve + this.pendingReject = reject + }) + }, + + resolveConfirm(payload: UploadConfirmResult) { + this.pendingResolve?.(payload) + this.reset() + }, + + rejectConfirm() { + this.pendingReject?.() + this.reset() + }, + + reset() { + this.visible = false + this.mode = 'file' + this.kbInfo = null + this.files = [] + this.urls = [] + this.manual = null + this.acceptFileTypes = '' + this.supportedFileTypes = [] + this.pendingResolve = null + this.pendingReject = null + }, + }, +}) diff --git a/frontend/src/types/knowledgeProcess.ts b/frontend/src/types/knowledgeProcess.ts new file mode 100644 index 000000000..40c6b73d0 --- /dev/null +++ b/frontend/src/types/knowledgeProcess.ts @@ -0,0 +1,66 @@ +/** Matches backend types.KnowledgeProcessOverrides (snake_case JSON). */ + +export interface ParserEngineRule { + file_types: string[] + engine: string +} + +export interface ChunkingConfigOverride { + chunk_size?: number + chunk_overlap?: number + separators?: string[] + enable_multimodal?: boolean + parser_engine_rules?: ParserEngineRule[] + enable_parent_child?: boolean + parent_chunk_size?: number + child_chunk_size?: number + strategy?: string + token_limit?: number + languages?: string[] +} + +export interface VLMConfigOverride { + enabled?: boolean + model_id?: string +} + +export interface ASRConfigOverride { + enabled?: boolean + model_id?: string + language?: string +} + +export interface QuestionGenerationConfigOverride { + enabled?: boolean + question_count?: number +} + +export interface GraphNodeOverride { + name: string + attributes?: string[] +} + +export interface GraphRelationOverride { + node1: string + node2: string + type: string +} + +export interface ExtractConfigOverride { + enabled?: boolean + text?: string + tags?: string[] + nodes?: GraphNodeOverride[] + relations?: GraphRelationOverride[] +} + +export interface KnowledgeProcessOverrides { + parser_engine_rules?: ParserEngineRule[] + chunking_config?: ChunkingConfigOverride + enable_multimodel?: boolean + vlm_config?: VLMConfigOverride + asr_config?: ASRConfigOverride + question_generation_config?: QuestionGenerationConfigOverride + graph_enabled?: boolean + extract_config?: ExtractConfigOverride +} diff --git a/frontend/src/views/knowledge/KnowledgeBase.vue b/frontend/src/views/knowledge/KnowledgeBase.vue index 3c03561e8..e4e399381 100644 --- a/frontend/src/views/knowledge/KnowledgeBase.vue +++ b/frontend/src/views/knowledge/KnowledgeBase.vue @@ -1,6 +1,6 @@ + + diff --git a/frontend/src/views/knowledge/components/UploadConfirmDialog.vue b/frontend/src/views/knowledge/components/UploadConfirmDialog.vue new file mode 100644 index 000000000..dade7bc87 --- /dev/null +++ b/frontend/src/views/knowledge/components/UploadConfirmDialog.vue @@ -0,0 +1,1242 @@ + + + + + diff --git a/frontend/src/views/knowledge/settings/GraphSettings.vue b/frontend/src/views/knowledge/settings/GraphSettings.vue index 041bab544..36b17b4a9 100644 --- a/frontend/src/views/knowledge/settings/GraphSettings.vue +++ b/frontend/src/views/knowledge/settings/GraphSettings.vue @@ -1,10 +1,9 @@