From 977d16339b392a2929dcf7d473c80cbbaae7d8b0 Mon Sep 17 00:00:00 2001 From: Dallas98 <40557804+Dallas98@users.noreply.github.com> Date: Thu, 4 Dec 2025 14:27:01 +0800 Subject: [PATCH] feat(synthesis): add chunk-level synthesis data detail page & refine APIs/routing (#130) * feat: implement synthesis data detail view with chunk selection and data display --- .../pages/SynthesisTask/SynthDataDetail.tsx | 298 ++++++++++++++++++ .../src/pages/SynthesisTask/SynthFileTask.tsx | 8 + .../src/pages/SynthesisTask/synthesis-api.ts | 20 +- frontend/src/routes/routes.ts | 10 +- 4 files changed, 330 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/SynthesisTask/SynthDataDetail.tsx b/frontend/src/pages/SynthesisTask/SynthDataDetail.tsx index e69de29..1165f04 100644 --- a/frontend/src/pages/SynthesisTask/SynthDataDetail.tsx +++ b/frontend/src/pages/SynthesisTask/SynthDataDetail.tsx @@ -0,0 +1,298 @@ +import { useEffect, useMemo, useState } from "react"; +import { useLocation, useNavigate, useParams } from "react-router"; +import { Badge, Button, Empty, List, Pagination, Spin, Typography } from "antd"; +import type { PaginationProps } from "antd"; +import { queryChunksByFileUsingGet, querySynthesisDataByChunkUsingGet, querySynthesisTaskByIdUsingGet } 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 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 || {}); + }; + + 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} + +
+ {/* 展示 chunk 全部内容,不截断 */} +
+ {item.chunk_content} +
+
+
+ ); + }} + /> + )} +
+
+ `共 ${total} 条`} + /> +
+
+ + {/* 右侧合成数据展示:占比 3/5 */} +
+
+ 合成数据 + {currentChunk && ( + + 当前 Chunk #{currentChunk.chunk_index} + + )} +
+
+ {dataLoading ? ( +
+ +
+ ) : !selectedChunkId ? ( + + ) : synthDataList.length === 0 ? ( + + ) : ( +
+ {synthDataList.map((item, index) => ( +
+
+ 记录 {index + 1} + ID:{item.id} +
+ {/* 淡化表格样式的 key-value 展示 */} +
+ {getDataEntries(item.data).map(([key, value], rowIdx) => ( +
+
+ {key} +
+
+ {typeof value === "string" || typeof value === "number" + ? String(value) + : JSON.stringify(value, null, 2)} +
+
+ ))} +
+
+ ))} +
+ )} +
+
+
+
+ ); +} diff --git a/frontend/src/pages/SynthesisTask/SynthFileTask.tsx b/frontend/src/pages/SynthesisTask/SynthFileTask.tsx index 9fe18f4..a71f84b 100644 --- a/frontend/src/pages/SynthesisTask/SynthFileTask.tsx +++ b/frontend/src/pages/SynthesisTask/SynthFileTask.tsx @@ -97,6 +97,14 @@ export default function SynthFileTask() { title: "文件名", dataIndex: "file_name", key: "file_name", + render: (text: string, record) => ( + + ), }, { title: "状态", diff --git a/frontend/src/pages/SynthesisTask/synthesis-api.ts b/frontend/src/pages/SynthesisTask/synthesis-api.ts index 86f4463..6ee3c22 100644 --- a/frontend/src/pages/SynthesisTask/synthesis-api.ts +++ b/frontend/src/pages/SynthesisTask/synthesis-api.ts @@ -1,8 +1,8 @@ import { get, post, del } from "@/utils/request"; // 创建数据合成任务 -export function createSynthesisTaskUsingPost(data: unknown) { - return post("/api/synthesis/gen/task", data); +export function createSynthesisTaskUsingPost(data: Record) { + return post("/api/synthesis/gen/task", data as unknown as Record); } // 获取数据合成任务详情 @@ -18,7 +18,7 @@ export function querySynthesisTasksUsingGet(params: { status?: string; name?: string; }) { - return get(`/api/synthesis/gen/tasks`, params as any); + return get(`/api/synthesis/gen/tasks`, params); } // 删除整个数据合成任务 @@ -28,10 +28,20 @@ export function deleteSynthesisTaskByIdUsingDelete(taskId: string) { // 分页查询某个任务下的文件任务列表 export function querySynthesisFileTasksUsingGet(taskId: string, params: { page?: number; page_size?: number }) { - return get(`/api/synthesis/gen/task/${taskId}/files`, params as any); + return get(`/api/synthesis/gen/task/${taskId}/files`, params); +} + +// 根据文件任务 ID 分页查询 chunk 记录 +export function queryChunksByFileUsingGet(fileId: string, params: { page?: number; page_size?: number }) { + return get(`/api/synthesis/gen/file/${fileId}/chunks`, params); +} + +// 根据 chunk ID 查询所有合成结果数据 +export function querySynthesisDataByChunkUsingGet(chunkId: string) { + return get(`/api/synthesis/gen/chunk/${chunkId}/data`); } // 获取不同合成类型对应的 Prompt export function getPromptByTypeUsingGet(synthType: string) { - return get(`/api/synthesis/gen/prompt`, { synth_type: synthType } as any); + return get(`/api/synthesis/gen/prompt`, { synth_type: synthType }); } diff --git a/frontend/src/routes/routes.ts b/frontend/src/routes/routes.ts index 3912c5d..a0c23ed 100644 --- a/frontend/src/routes/routes.ts +++ b/frontend/src/routes/routes.ts @@ -42,6 +42,7 @@ import RatioTaskDetail from "@/pages/RatioTask/Detail/RatioTaskDetail"; import CleansingTemplateDetail from "@/pages/DataCleansing/Detail/TemplateDetail"; import SynthFileTask from "@/pages/SynthesisTask/SynthFileTask.tsx"; import EvaluationDetailPage from "@/pages/DataEvaluation/Detail/TaskDetail.tsx"; +import SynthDataDetail from "@/pages/SynthesisTask/SynthDataDetail.tsx"; const router = createBrowserRouter([ { @@ -161,7 +162,14 @@ const router = createBrowserRouter([ path: "create", Component: SynthesisTaskCreate, }, - {path: ":id", Component: SynthFileTask}, + { + path: ":id", + Component: SynthFileTask + }, + { + path: "file/:id/detail", + Component: SynthDataDetail, + } ], }, {