import { useEffect, useMemo, useState } from "react"; import { useLocation, useNavigate, useParams } from "react-router"; import { Badge, Button, Empty, List, Pagination, Spin, Typography, Popconfirm, message, Dropdown, Input } from "antd"; import type { PaginationProps } from "antd"; 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 { fileName?: string; taskId?: string; } interface ChunkItem { id: string; synthesis_file_instance_id: string; chunk_index: number; chunk_content: string; chunk_metadata?: Record; } interface PagedChunkResponse { content: ChunkItem[]; totalElements: number; totalPages: number; page: number; size: number; } interface SynthesisDataItem { id: string; data: Record; synthesis_file_instance_id: string; chunk_instance_id: string; } interface SynthesisTaskInfo { id: string; name: string; synthesis_type: string; status: string; created_at: string; model_id: string; } const { Title, Text } = Typography; export default function SynthDataDetail() { const { id: fileId = "" } = useParams(); const navigate = useNavigate(); const location = useLocation(); const state = (location.state || {}) as LocationState; const [taskInfo, setTaskInfo] = useState(null); const [chunks, setChunks] = useState([]); const [chunkPagination, setChunkPagination] = useState<{ page: number; size: number; total: number; }>({ page: 1, size: 10, total: 0 }); const [selectedChunkId, setSelectedChunkId] = useState(null); const [chunkLoading, setChunkLoading] = useState(false); const [dataLoading, setDataLoading] = useState(false); const [synthDataList, setSynthDataList] = useState([]); // 加载任务信息(用于顶部展示) useEffect(() => { if (!state.taskId) return; querySynthesisTaskByIdUsingGet(state.taskId).then((res) => { setTaskInfo(res?.data?.data || null); }); }, [state.taskId]); const fetchChunks = async (page = 1, size = 10) => { if (!fileId) return; setChunkLoading(true); try { const res = await queryChunksByFileUsingGet(fileId, { page, page_size: size }); const payload: PagedChunkResponse = res?.data?.data ?? res?.data ?? { content: [], totalElements: 0, totalPages: 0, page, size, }; setChunks(payload.content || []); setChunkPagination({ page: payload.page ?? page, size: payload.size ?? size, total: payload.totalElements ?? payload.content?.length ?? 0, }); // 默认选中第一个 chunk if (!selectedChunkId && payload.content && payload.content.length > 0) { setSelectedChunkId(payload.content[0].id); } } finally { setChunkLoading(false); } }; useEffect(() => { fetchChunks(1, chunkPagination.size); // eslint-disable-next-line react-hooks/exhaustive-deps }, [fileId]); const handleChunkPageChange: PaginationProps["onChange"] = (page, pageSize) => { 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); try { const res = await querySynthesisDataByChunkUsingGet(chunkId); const list: SynthesisDataItem[] = res?.data?.data ?? res?.data ?? []; setSynthDataList(list || []); } finally { setDataLoading(false); } }; useEffect(() => { if (selectedChunkId) { fetchSynthData(selectedChunkId); } else { setSynthDataList([]); } }, [selectedChunkId]); const currentChunk = useMemo( () => chunks.find((c) => c.id === selectedChunkId) || null, [chunks, selectedChunkId] ); // 将合成数据的 data 转换成键值对数组,方便以表格形式展示 const getDataEntries = (data: Record) => { 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 (
{/* 顶部信息和返回 */}
合成数据详情 {state.fileName && ( 文件:{state.fileName} )}
{taskInfo && (
任务:{taskInfo.name} 类型: {taskInfo.synthesis_type === "QA" ? "问答对生成" : taskInfo.synthesis_type === "COT" ? "链式推理生成" : taskInfo.synthesis_type} 创建时间:{formatDateTime(taskInfo.created_at)} 模型ID:{taskInfo.model_id}
)}
{/* 主体左右布局 */}
{/* 左侧 Chunk 列表:占比 2/5 */}
Chunk 列表
{chunkLoading ? (
) : chunks.length === 0 ? ( ) : ( { const active = item.id === selectedChunkId; return (
setSelectedChunkId(item.id)} > Chunk #{item.chunk_index}
setSelectedChunkId(item.id)} > {item.chunk_content}
{ setSelectedChunkId(item.id); handleDeleteCurrentChunk(); }} okText="删除" cancelText="取消" > 删除该 Chunk 及合成数据 ), }, ], }} trigger={["click"]} >
); }} /> )}
`共 ${total} 条`} />
{/* 右侧合成数据展示:占比 3/5 */}
合成数据 {currentChunk && ( 当前 Chunk #{currentChunk.chunk_index} )}
{dataLoading ? (
) : !selectedChunkId ? ( ) : synthDataList.length === 0 ? ( ) : (
{synthDataList.map((item, index) => { 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-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); return (
{key}
{isEditing ? ( { const v = e.target.value; setEditingMap((prev) => ({ ...prev, [key]: v })); }} autoSize={{ minRows: 1, maxRows: 4 }} /> ) : ( displayValue )}
); })}
{isEditing && (
)}
); })}
)}
); }