diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9ecd8785..f48ffc74 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -188,7 +188,6 @@ "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -2137,7 +2136,6 @@ "integrity": "sha512-DRh5K+ka5eJic8CjH7td8QpYEV6Zo10gfRkjHCO3weqZHWDtAaSTFtl4+VMqOJ4N5jcuhZ9/l+yy8rVgw7BQeQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.10.0" } @@ -2155,7 +2153,6 @@ "integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -2223,7 +2220,6 @@ "integrity": "sha512-pUXGCuHnnKw6PyYq93lLRiZm3vjuslIy7tus1lIQTYVK9bL8XBgJnCWm8a0KcTtHC84Yya1Q6rtll+duSMj0dg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.39.1", "@typescript-eslint/types": "8.39.1", @@ -2522,7 +2518,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2771,7 +2766,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001733", "electron-to-chromium": "^1.5.199", @@ -3168,7 +3162,6 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", - "peer": true, "engines": { "node": ">=12" } @@ -3257,8 +3250,7 @@ "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/debug": { "version": "4.4.1", @@ -3485,7 +3477,6 @@ "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -4193,7 +4184,6 @@ "resolved": "https://registry.npmjs.org/immer/-/immer-11.0.1.tgz", "integrity": "sha512-naDCyggtcBWANtIrjQEajhhBEuL9b0Zg4zmlWK2CzS6xCWSE39/vvf4LqnMjUAWHBhot4m9MHCM/Z+mfWhUkiA==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/immer" @@ -6025,7 +6015,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -6038,7 +6027,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -6058,7 +6046,6 @@ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", "license": "MIT", - "peer": true, "dependencies": { "@types/use-sync-external-store": "^0.0.6", "use-sync-external-store": "^1.4.0" @@ -6189,8 +6176,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/redux-thunk": { "version": "3.1.0", @@ -6774,7 +6760,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -6878,7 +6863,6 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -7046,7 +7030,6 @@ "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", @@ -7137,7 +7120,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, diff --git a/frontend/src/pages/SynthesisTask/SynthDataDetail.tsx b/frontend/src/pages/SynthesisTask/SynthDataDetail.tsx index 1165f04b..eae232b0 100644 --- a/frontend/src/pages/SynthesisTask/SynthDataDetail.tsx +++ b/frontend/src/pages/SynthesisTask/SynthDataDetail.tsx @@ -1,8 +1,16 @@ import { useEffect, useMemo, useState } from "react"; import { useLocation, useNavigate, useParams } from "react-router"; -import { Badge, Button, Empty, List, Pagination, Spin, Typography } from "antd"; +import { Badge, Button, Empty, List, Pagination, Spin, Typography, Popconfirm, message, Dropdown, Input } from "antd"; import type { PaginationProps } from "antd"; -import { queryChunksByFileUsingGet, querySynthesisDataByChunkUsingGet, querySynthesisTaskByIdUsingGet } from "@/pages/SynthesisTask/synthesis-api"; +import { MoreOutlined, EditOutlined, DeleteOutlined } from "@ant-design/icons"; +import { + queryChunksByFileUsingGet, + querySynthesisDataByChunkUsingGet, + querySynthesisTaskByIdUsingGet, + deleteChunkWithDataUsingDelete, + batchDeleteSynthesisDataUsingDelete, + updateSynthesisDataUsingPatch, +} from "@/pages/SynthesisTask/synthesis-api"; import { formatDateTime } from "@/utils/unit"; interface LocationState { @@ -107,6 +115,24 @@ export default function SynthDataDetail() { fetchChunks(page, pageSize || 10); }; + // 删除当前选中的 Chunk 及其合成数据 + const handleDeleteCurrentChunk = async () => { + if (!selectedChunkId) return; + try { + const res = await deleteChunkWithDataUsingDelete(selectedChunkId); + if (res?.data?.code === 200 || res?.code === 200) { + message.success("删除成功"); + } else { + message.success("删除成功"); + } + setSelectedChunkId(null); + fetchChunks(1, chunkPagination.size); + } catch (error) { + console.error("Failed to delete chunk", error); + message.error("删除失败,请稍后重试"); + } + }; + // 加载选中 chunk 的所有合成数据 const fetchSynthData = async (chunkId: string) => { setDataLoading(true); @@ -137,6 +163,80 @@ export default function SynthDataDetail() { return Object.entries(data || {}); }; + // 单条合成数据删除 + const handleDeleteSingleSynthesisData = async (dataId: string) => { + try { + await batchDeleteSynthesisDataUsingDelete({ ids: [dataId] }); + message.success("删除成功"); + if (selectedChunkId) { + fetchSynthData(selectedChunkId); + } + } catch (error) { + console.error("Failed to delete synthesis data", error); + message.error("删除失败,请稍后重试"); + } + }; + + // 编辑状态:仅编辑各个 key 的 value + const [editingId, setEditingId] = useState(null); + const [editingMap, setEditingMap] = useState>({}); + + const startEdit = (item: SynthesisDataItem) => { + setEditingId(item.id); + const map: Record = {}; + Object.entries(item.data || {}).forEach(([k, v]) => { + if (typeof v === "string" || typeof v === "number" || typeof v === "boolean") { + map[k] = String(v); + } else { + map[k] = JSON.stringify(v); + } + }); + setEditingMap(map); + }; + + const cancelEdit = () => { + setEditingId(null); + setEditingMap({}); + }; + + const handleSaveEdit = async (item: SynthesisDataItem) => { + if (editingId !== item.id) return; + try { + const newData: Record = { ...item.data }; + Object.entries(editingMap).forEach(([k, v]) => { + const original = item.data?.[k]; + if (typeof original === "object" && original !== null) { + try { + newData[k] = JSON.parse(v); + } catch { + newData[k] = v; + } + } else if (typeof original === "number") { + const n = Number(v); + newData[k] = Number.isNaN(n) ? v : n; + } else if (typeof original === "boolean") { + if (v === "true" || v === "false") { + newData[k] = v === "true"; + } else { + newData[k] = v; + } + } else { + newData[k] = v; + } + }); + + await updateSynthesisDataUsingPatch(item.id, { data: newData }); + message.success("保存成功"); + cancelEdit(); + if (selectedChunkId) { + fetchSynthData(selectedChunkId); + } + } catch (error) { + console.error("Failed to update synthesis data", error); + message.error("保存失败,请稍后重试"); + } + }; + return (
{/* 顶部信息和返回 */} @@ -198,23 +298,63 @@ export default function SynthDataDetail() { return ( setSelectedChunkId(item.id)} >
-
+
setSelectedChunkId(item.id)} + > Chunk #{item.chunk_index}
- {/* 展示 chunk 全部内容,不截断 */} -
+
setSelectedChunkId(item.id)} + > {item.chunk_content}
+
+ { + setSelectedChunkId(item.id); + handleDeleteCurrentChunk(); + }} + okText="删除" + cancelText="取消" + > + + + 删除该 Chunk 及合成数据 + + + ), + }, + ], + }} + trigger={["click"]} + > +
); @@ -256,38 +396,127 @@ export default function SynthDataDetail() { ) : (
- {synthDataList.map((item, index) => ( -
-
- 记录 {index + 1} - ID:{item.id} -
- {/* 淡化表格样式的 key-value 展示 */} -
- {getDataEntries(item.data).map(([key, value], rowIdx) => ( -
{ + const isEditing = editingId === item.id; + return ( +
+
+ 记录 {index + 1} + ID:{item.id} +
+ + {/* 右下角更多操作按钮:编辑 & 删除 */} +
+ + + 编辑 + + ), + onClick: (info) => { + info.domEvent.stopPropagation(); + startEdit(item); + }, + }, + { + key: "delete-data", + danger: true, + label: ( + { + e?.stopPropagation(); + handleDeleteSingleSynthesisData(item.id); + }} + okText="删除" + cancelText="取消" + > + + + 删除 + + + ), + }, + ], + }} + trigger={["click"]} > -
- {key} -
-
- {typeof value === "string" || typeof value === "number" +
+ + {/* 表格形式的 key-value 展示 + 可编辑 value */} +
+ {getDataEntries(item.data).map(([key, value], rowIdx) => { + const displayValue = + typeof value === "string" || + typeof value === "number" || + typeof value === "boolean" ? String(value) - : JSON.stringify(value, null, 2)} -
+ : JSON.stringify(value, null, 2); + + return ( +
+
+ {key} +
+
+ {isEditing ? ( + { + const v = e.target.value; + setEditingMap((prev) => ({ ...prev, [key]: v })); + }} + autoSize={{ minRows: 1, maxRows: 4 }} + /> + ) : ( + displayValue + )} +
+
+ ); + })} +
+ + {isEditing && ( +
+ +
- ))} + )}
-
- ))} + ); + })}
)}
diff --git a/frontend/src/pages/SynthesisTask/components/SynthesisTaskTab.tsx b/frontend/src/pages/SynthesisTask/components/SynthesisTaskTab.tsx index 8a43e3b3..832fa323 100644 --- a/frontend/src/pages/SynthesisTask/components/SynthesisTaskTab.tsx +++ b/frontend/src/pages/SynthesisTask/components/SynthesisTaskTab.tsx @@ -1,15 +1,12 @@ -import { useState, useEffect, ElementType } from "react"; -import { Card, Button, Badge, Table, Modal, message, Tooltip } from "antd"; +import { useState, useEffect, useCallback } from "react"; +import { Card, Button, Table, Modal, message, Tooltip, Form, Input, Select } from "antd"; import { Plus, ArrowUp, ArrowDown, - Pause, - Play, - CheckCircle, Sparkles, } from "lucide-react"; -import { DeleteOutlined, EyeOutlined } from "@ant-design/icons"; +import { FolderOpenOutlined, DeleteOutlined, EyeOutlined, ExperimentOutlined } from "@ant-design/icons"; import { Link, useNavigate } from "react-router"; import { SearchControls } from "@/components/SearchControls"; import { formatDateTime } from "@/utils/unit"; @@ -19,6 +16,9 @@ import { archiveSynthesisTaskToDatasetUsingPost, } from "@/pages/SynthesisTask/synthesis-api"; import { createDatasetUsingPost } from "@/pages/DataManagement/dataset.api"; +import { createEvaluationTaskUsingPost } from "@/pages/DataEvaluation/evaluation.api"; +import { queryModelListUsingGet } from "@/pages/SettingsPage/settings.apis"; +import { ModelI } from "@/pages/SettingsPage/ModelAccess"; interface SynthesisTask { id: string; @@ -50,6 +50,11 @@ interface SynthesisTask { updated_by?: string; } +interface SynthesisDataItem { + id: string; + [key: string]: any; +} + export default function SynthesisTaskTab() { const navigate = useNavigate(); const [searchQuery, setSearchQuery] = useState(""); @@ -61,6 +66,18 @@ export default function SynthesisTaskTab() { const [pageSize, setPageSize] = useState(10); const [total, setTotal] = useState(0); const [loading, setLoading] = useState(false); + const [evalModalVisible, setEvalModalVisible] = useState(false); + const [currentEvalTask, setCurrentEvalTask] = useState(null); + const [evalLoading, setEvalLoading] = useState(false); + const [models, setModels] = useState([]); + const [modelLoading, setModelLoading] = useState(false); + + const [evalForm] = Form.useForm(); + + // 合成数据相关状态 + const [activeChunkId, setActiveChunkId] = useState(null); + const [synthesisData, setSynthesisData] = useState([]); + const [selectedDataIds, setSelectedDataIds] = useState([]); // 获取任务列表 const loadTasks = async () => { @@ -94,18 +111,6 @@ export default function SynthesisTaskTab() { // eslint-disable-next-line }, [searchQuery, filterStatus, page, pageSize]); - // 状态徽章 - const getStatusBadge = (status: string) => { - const statusConfig: Record = { - pending: { label: "等待中", color: "#F59E0B", icon: Pause }, - running: { label: "运行中", color: "#3B82F6", icon: Play }, - completed: { label: "已完成", color: "#10B981", icon: CheckCircle }, - failed: { label: "失败", color: "#EF4444", icon: Pause }, - paused: { label: "已暂停", color: "#E5E7EB", icon: Pause }, - }; - return statusConfig[status] ?? statusConfig["pending"]; - }; - // 类型映射 const typeMap: Record = { QA: "问答对生成", @@ -176,19 +181,28 @@ export default function SynthesisTaskTab() { key: "actions", fixed: "right" as const, render: (_: unknown, task: SynthesisTask) => ( -
+
+ />