import { useEffect, useState } from "react"; import type { Dataset, DatasetFile } from "@/pages/DataManagement/dataset.model"; import { Steps, Card, Select, Input, Checkbox, Button, Form, message } from "antd"; import { Eye, ArrowLeft, ArrowRight, Play, Search, MoreHorizontal } from "lucide-react"; import { Link, useNavigate } from "react-router"; import { queryDatasetsUsingGet } from "../DataManagement/dataset.api"; import DatasetFileTransfer from "@/components/business/DatasetFileTransfer"; import { createSynthesisTaskUsingPost, getPromptByTypeUsingGet } from "./synthesis-api"; import { queryModelListUsingGet } from "@/pages/SettingsPage/settings.apis"; import type { ModelI } from "@/pages/SettingsPage/ModelAccess"; const { TextArea } = Input; interface CreateTaskFormValues { name: string; sourceDataset: string; description?: string; } interface CreateTaskApiResponse { code?: string | number; message?: string; data?: unknown; success?: boolean; } export default function SynthesisTaskCreate() { const navigate = useNavigate(); const [form] = Form.useForm(); const [createStep, setCreateStep] = useState(1); const [selectedFiles, setSelectedFiles] = useState([]); const [selectedMap, setSelectedMap] = useState>({}); const [selectedDataset, setSelectedDataset] = useState(null); const [selectedSynthesisTypes, setSelectedSynthesisTypes] = useState(["qa"]); const [taskType, setTaskType] = useState<"qa" | "cot">("qa"); const [promptTemplate, setPromptTemplate] = useState(""); const [submitting, setSubmitting] = useState(false); const [modelOptions, setModelOptions] = useState<{ label: string; value: string }[]>([]); const [modelsLoading, setModelsLoading] = useState(false); const [selectedModel, setSelectedModel] = useState(undefined); const [sliceConfig, setSliceConfig] = useState({ processType: "DEFAULT_CHUNK" as | "DEFAULT_CHUNK" | "CHAPTER_CHUNK" | "PARAGRAPH_CHUNK" | "FIXED_LENGTH_CHUNK" | "CUSTOM_SEPARATOR_CHUNK", chunkSize: 500, overlapSize: 50, delimiter: "", }); const sliceOptions = [ { label: "默认分块", value: "DEFAULT_CHUNK" }, { label: "按章节分块", value: "CHAPTER_CHUNK" }, { label: "按段落分块", value: "PARAGRAPH_CHUNK" }, { label: "固定长度分块", value: "FIXED_LENGTH_CHUNK" }, { label: "自定义分隔符分块", value: "CUSTOM_SEPARATOR_CHUNK" }, ]; const fetchDatasets = async () => { const { data } = await queryDatasetsUsingGet({ page: 1, size: 1000 }); return data; }; const fetchPrompt = async (type: "qa" | "cot") => { try { const synthTypeParam = type.toUpperCase(); const res = await getPromptByTypeUsingGet(synthTypeParam); const prompt = typeof res === "string" ? res : (res as { data?: string })?.data ?? ""; setPromptTemplate(prompt || ""); } catch (e) { console.error(e); message.error("获取提示词模板失败"); setPromptTemplate(""); } }; useEffect(() => { fetchDatasets(); }, []); useEffect(() => { fetchPrompt(taskType); }, [taskType]); useEffect(() => { const loadModels = async () => { setModelsLoading(true); try { const { data } = await queryModelListUsingGet({ page: 0, size: 1000 }); const options = (data?.content || []).map((model: ModelI) => ({ label: `${model.modelName} (${model.provider})`, value: model.id, })); setModelOptions(options); } catch (error) { console.error("加载模型列表失败", error); } finally { setModelsLoading(false); } }; loadModels(); }, []); useEffect(() => { if (!selectedModel && modelOptions.length > 0) { setSelectedModel(modelOptions[0].value); } }, [modelOptions, selectedModel]); // 表单数据 const [formValues, setFormValues] = useState({ name: "", sourceDataset: "", description: "", }); const handleValuesChange: NonNullable[0]["onValuesChange"]> = ( _changed, allValues ) => { setFormValues(allValues as CreateTaskFormValues); }; // 当选择文件变化时,同步 selectedFiles 为 ID 列表 useEffect(() => { const ids = Object.values(selectedMap).map((f) => String(f.id)); setSelectedFiles(ids); }, [selectedMap]); const handleCreateTask = async () => { try { const values = (await form.validateFields()) as CreateTaskFormValues; // precise validation if (!(taskType === "qa" || taskType === "cot")) { message.error("请选择一个合成类型"); return; } if (!selectedModel) { message.error("请选择模型"); return; } if (selectedFiles.length === 0) { message.error("请至少选择一个文件"); return; } // 构造后端要求的参数格式 const payload = { name: values.name || form.getFieldValue("name"), // 必选,确保传递 description: values.description ?? "", // 可选,始终传递 model_id: selectedModel, source_file_id: selectedFiles, text_split_config: { chunk_size: sliceConfig.chunkSize, chunk_overlap: sliceConfig.overlapSize, }, synthesis_config: { prompt_template: promptTemplate, }, synthesis_type: taskType === "qa" ? "QA" : "COT", }; setSubmitting(true); const res = (await createSynthesisTaskUsingPost( payload as unknown as Record )) as CreateTaskApiResponse; const ok = res?.success === true || res?.code === "0" || res?.code === 0 || typeof res?.data !== "undefined"; if (ok) { message.success("合成任务创建成功"); navigate("/data/synthesis/task"); } else { message.error(res?.message || "合成任务创建失败"); } } catch (error) { if (typeof error === "object" && error && "errorFields" in error) { message.error("请填写所有必填项"); return; } console.error(error); message.error((error instanceof Error ? error.message : "合成任务创建失败")); } finally { setSubmitting(false); } }; // 仅两个一级类型,无二级目录 const synthesisTypes = [ { id: "qa", name: "生成问答对" }, { id: "cot", name: "生成COT链式推理" }, ] as const; const handleSynthesisTypeSelect = (typeId: "qa" | "cot") => { setSelectedSynthesisTypes((prev) => { const next = prev.includes(typeId) ? [] : [typeId]; if (next[0] === "qa") setTaskType("qa"); if (next[0] === "cot") setTaskType("cot"); return next; }); }; useEffect(() => { // 进入第二步时,若未选择类型,默认选择 QA,避免误报 if (createStep === 2 && !(taskType === "qa" || taskType === "cot")) { setTaskType("qa"); setSelectedSynthesisTypes(["qa"]); } }, [createStep, taskType]); const renderCreateTaskPage = () => { if (createStep === 1) { return (

基本信息