From 7f62ab8eb3326572ff0fee8671a4f337d3f2af12 Mon Sep 17 00:00:00 2001 From: balibabu Date: Tue, 30 Sep 2025 18:55:55 +0800 Subject: [PATCH] Feat: View data flow test results #9869 (#10392) ### What problem does this PR solve? Feat: View data flow test results #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/hooks/use-chunk-request.ts | 5 +- web/src/locales/en.ts | 11 +++-- web/src/locales/zh.ts | 1 + web/src/pages/agents/create-agent-dialog.tsx | 2 +- web/src/pages/agents/create-agent-form.tsx | 13 +++++- web/src/pages/agents/name-form-field.tsx | 7 +-- web/src/pages/data-flow/context.ts | 1 + .../pages/data-flow/hooks/use-run-dataflow.ts | 13 ++++-- web/src/pages/data-flow/index.tsx | 11 ++++- web/src/pages/data-flow/log-sheet/index.tsx | 24 +++++++--- .../components/document-preview/hooks.ts | 11 +++-- web/src/pages/dataflow-result/constant.ts | 2 + web/src/pages/dataflow-result/hooks.ts | 46 +++++++++++++++++-- web/src/pages/dataflow-result/index.tsx | 30 +++++++++--- web/src/pages/dataflow-result/interface.ts | 2 + web/src/utils/api.ts | 1 + 16 files changed, 141 insertions(+), 39 deletions(-) diff --git a/web/src/hooks/use-chunk-request.ts b/web/src/hooks/use-chunk-request.ts index 320f57979..c896dd304 100644 --- a/web/src/hooks/use-chunk-request.ts +++ b/web/src/hooks/use-chunk-request.ts @@ -13,7 +13,9 @@ import { } from './logic-hooks'; import { useGetKnowledgeSearchParams } from './route-hook'; -export const useFetchNextChunkList = (): ResponseGetType<{ +export const useFetchNextChunkList = ( + enabled = true, +): ResponseGetType<{ data: IChunk[]; total: number; documentInfo: IKnowledgeFile; @@ -37,6 +39,7 @@ export const useFetchNextChunkList = (): ResponseGetType<{ placeholderData: (previousData: any) => previousData ?? { data: [], total: 0, documentInfo: {} }, // https://github.com/TanStack/query/issues/8183 gcTime: 0, + enabled, queryFn: async () => { const { data } = await kbService.chunk_list({ doc_id: documentId, diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index ab07e90ca..3878aaa36 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1065,7 +1065,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s {input} The above is the content you need to summarize.`, createGraph: 'Create agent', - createFromTemplates: 'Create from templates', + createFromTemplates: 'Create from template', retrieval: 'Retrieval', generate: 'Generate', answer: 'Interact', @@ -1586,9 +1586,12 @@ This delimiter is used to split the input text into several text pieces echo of 'Write your SQL query here. You can use variables, raw SQL, or mix both using variable syntax.', frameworkPrompts: 'Framework', release: 'Publish', - createFromBlank: 'Create from Blank', - createFromTemplate: 'Create from Template', - importJsonFile: 'Import json file', + createFromBlank: 'Create from blank', + createFromTemplate: 'Create from template', + importJsonFile: 'Import JSON file', + ceateAgent: 'Agent flow', + createPipeline: 'Data pipeline', + chooseAgentType: 'Choose Agent Type', }, llmTools: { bad_calculator: { diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 76e365908..faa35320f 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -1500,6 +1500,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 createFromBlank: '从空白创建', createFromTemplate: '从模板创建', importJsonFile: '导入 JSON 文件', + chooseAgentType: '选择智能体类型', }, footer: { profile: 'All rights reserved @ React', diff --git a/web/src/pages/agents/create-agent-dialog.tsx b/web/src/pages/agents/create-agent-dialog.tsx index c4b7fd467..5c8f99320 100644 --- a/web/src/pages/agents/create-agent-dialog.tsx +++ b/web/src/pages/agents/create-agent-dialog.tsx @@ -22,7 +22,7 @@ export function CreateAgentDialog({ return ( - + {t('flow.createGraph')} diff --git a/web/src/pages/agents/create-agent-form.tsx b/web/src/pages/agents/create-agent-form.tsx index eed33fe2e..f89031d17 100644 --- a/web/src/pages/agents/create-agent-form.tsx +++ b/web/src/pages/agents/create-agent-form.tsx @@ -25,6 +25,7 @@ type FlowTypeCardProps = { onChange?: (value: FlowType) => void; }; function FlowTypeCards({ value, onChange }: FlowTypeCardProps) { + const { t } = useTranslation(); const handleChange = useCallback( (value: FlowType) => () => { onChange?.(value); @@ -59,7 +60,11 @@ function FlowTypeCards({ value, onChange }: FlowTypeCardProps) { ) : ( )} -

{val}

+

+ {t( + `flow.${val === FlowType.Agent ? 'createAgent' : 'createPipeline'}`, + )} +

{isActive && } @@ -106,7 +111,11 @@ export function CreateAgentForm({ id={TagRenameId} > {shouldChooseAgent && ( - + )} diff --git a/web/src/pages/agents/name-form-field.tsx b/web/src/pages/agents/name-form-field.tsx index f18e46924..6a17ae97c 100644 --- a/web/src/pages/agents/name-form-field.tsx +++ b/web/src/pages/agents/name-form-field.tsx @@ -16,12 +16,7 @@ export const NameFormSchema = { export function NameFormField() { const { t } = useTranslation(); return ( - + ); diff --git a/web/src/pages/data-flow/context.ts b/web/src/pages/data-flow/context.ts index 5a765921d..32f2edd86 100644 --- a/web/src/pages/data-flow/context.ts +++ b/web/src/pages/data-flow/context.ts @@ -52,6 +52,7 @@ export const HandleContext = createContext( export type LogContextType = { messageId: string; setMessageId: (messageId: string) => void; + setUploadedFileData: (data: Record) => void; }; export const LogContext = createContext({} as LogContextType); diff --git a/web/src/pages/data-flow/hooks/use-run-dataflow.ts b/web/src/pages/data-flow/hooks/use-run-dataflow.ts index d512009bf..e49f1ac56 100644 --- a/web/src/pages/data-flow/hooks/use-run-dataflow.ts +++ b/web/src/pages/data-flow/hooks/use-run-dataflow.ts @@ -13,7 +13,7 @@ export function useRunDataflow( ) { const { send } = useSendMessageBySSE(api.runCanvas); const { id } = useParams(); - const { setMessageId } = useContext(LogContext); + const { setMessageId, setUploadedFileData } = useContext(LogContext); const { handleRun: saveGraph, loading } = useSaveGraphBeforeOpeningDebugDrawer(showLogSheet!); @@ -32,7 +32,7 @@ export function useRunDataflow( if (res && res?.response.status === 200 && get(res, 'data.code') === 0) { // fetch canvas hideRunOrChatDrawer(); - + setUploadedFileData(fileResponseData.file); const msgId = get(res, 'data.data.message_id'); if (msgId) { setMessageId(msgId); @@ -43,7 +43,14 @@ export function useRunDataflow( message.error(get(res, 'data.message', '')); } }, - [hideRunOrChatDrawer, id, saveGraph, send, setMessageId], + [ + hideRunOrChatDrawer, + id, + saveGraph, + send, + setMessageId, + setUploadedFileData, + ], ); return { run, loading: loading }; diff --git a/web/src/pages/data-flow/index.tsx b/web/src/pages/data-flow/index.tsx index bf45362c8..cdca3ebae 100644 --- a/web/src/pages/data-flow/index.tsx +++ b/web/src/pages/data-flow/index.tsx @@ -26,7 +26,7 @@ import { Settings, Upload, } from 'lucide-react'; -import { ComponentPropsWithoutRef, useCallback } from 'react'; +import { ComponentPropsWithoutRef, useCallback, useState } from 'react'; import { useTranslation } from 'react-i18next'; import DataFlowCanvas from './canvas'; import { DropdownProvider } from './canvas/context'; @@ -99,6 +99,9 @@ export default function DataFlow() { isLogEmpty, } = useFetchLog(logSheetVisible); + const [uploadedFileData, setUploadedFileData] = + useState>(); + const handleRunAgent = useCallback(() => { if (isParsing) { // show log sheet @@ -184,7 +187,9 @@ export default function DataFlow() { - + )} diff --git a/web/src/pages/data-flow/log-sheet/index.tsx b/web/src/pages/data-flow/log-sheet/index.tsx index 3ef574ec6..f66bada1b 100644 --- a/web/src/pages/data-flow/log-sheet/index.tsx +++ b/web/src/pages/data-flow/log-sheet/index.tsx @@ -7,6 +7,7 @@ import { SheetTitle, } from '@/components/ui/sheet'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; +import { useFetchAgent } from '@/hooks/use-agent-request'; import { IModalProps } from '@/interfaces/common'; import { cn } from '@/lib/utils'; import { PipelineResultSearchParams } from '@/pages/dataflow-result/constant'; @@ -18,6 +19,7 @@ import { } from 'lucide-react'; import { useTranslation } from 'react-i18next'; import 'react18-json-view/src/style.css'; +import { useParams } from 'umi'; import { isEndOutputEmpty, useDownloadOutput, @@ -27,9 +29,10 @@ import { DataflowTimeline } from './dataflow-timeline'; type LogSheetProps = IModalProps & { handleCancel(): void; + uploadedFileData?: Record; } & Pick< UseFetchLogReturnType, - 'isCompleted' | 'isLogEmpty' | 'isParsing' | 'logs' + 'isCompleted' | 'isLogEmpty' | 'isParsing' | 'logs' | 'messageId' >; export function LogSheet({ @@ -39,11 +42,16 @@ export function LogSheet({ handleCancel, isCompleted, isLogEmpty, + messageId, + uploadedFileData, }: LogSheetProps) { const { t } = useTranslation(); + const { id } = useParams(); + const { data: agent } = useFetchAgent(); const { handleDownloadJson } = useDownloadOutput(logs); const { navigateToDataflowResult } = useNavigatePage(); + return ( {t('dataflow.viewResult')} diff --git a/web/src/pages/dataflow-result/components/document-preview/hooks.ts b/web/src/pages/dataflow-result/components/document-preview/hooks.ts index fcf6a01ba..30e5887d8 100644 --- a/web/src/pages/dataflow-result/components/document-preview/hooks.ts +++ b/web/src/pages/dataflow-result/components/document-preview/hooks.ts @@ -1,8 +1,9 @@ import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; -import { api_host } from '@/utils/api'; +import api, { api_host } from '@/utils/api'; import { useSize } from 'ahooks'; import { CustomTextRenderer } from 'node_modules/react-pdf/dist/esm/shared/types'; import { useCallback, useEffect, useMemo, useState } from 'react'; +import { useGetPipelineResultSearchParams } from '../../hooks'; export const useDocumentResizeObserver = () => { const [containerWidth, setContainerWidth] = useState(); @@ -44,12 +45,16 @@ export const useHighlightText = (searchText: string = '') => { return textRenderer; }; -export const useGetDocumentUrl = () => { +export const useGetDocumentUrl = (isAgent: boolean) => { const { documentId } = useGetKnowledgeSearchParams(); + const { createdBy, documentId: id } = useGetPipelineResultSearchParams(); const url = useMemo(() => { + if (isAgent) { + return api.downloadFile + `?id=${id}&created_by=${createdBy}`; + } return `${api_host}/document/get/${documentId}`; - }, [documentId]); + }, [createdBy, documentId, id, isAgent]); return url; }; diff --git a/web/src/pages/dataflow-result/constant.ts b/web/src/pages/dataflow-result/constant.ts index 783f1745f..6d30ce122 100644 --- a/web/src/pages/dataflow-result/constant.ts +++ b/web/src/pages/dataflow-result/constant.ts @@ -20,4 +20,6 @@ export enum PipelineResultSearchParams { IsReadOnly = 'is_read_only', AgentId = 'agent_id', AgentTitle = 'agent_title', + CreatedBy = 'created_by', // Who uploaded the file + DocumentExtension = 'extension', } diff --git a/web/src/pages/dataflow-result/hooks.ts b/web/src/pages/dataflow-result/hooks.ts index 9ef8c9598..8b984d8d8 100644 --- a/web/src/pages/dataflow-result/hooks.ts +++ b/web/src/pages/dataflow-result/hooks.ts @@ -3,6 +3,7 @@ import message from '@/components/ui/message'; import { useCreateChunk, useDeleteChunk } from '@/hooks/chunk-hooks'; import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks'; import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; +import { useFetchMessageTrace } from '@/hooks/use-agent-request'; import { IChunk } from '@/interfaces/database/knowledge'; import kbService from '@/services/knowledge-service'; import { formatSecondsToHumanReadable } from '@/utils/date'; @@ -10,7 +11,7 @@ import { buildChunkHighlights } from '@/utils/document-util'; import { useMutation, useQuery } from '@tanstack/react-query'; import { t } from 'i18next'; import { camelCase, upperFirst } from 'lodash'; -import { useCallback, useMemo, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { IHighlight } from 'react-pdf-highlighter'; import { useParams, useSearchParams } from 'umi'; import { ITimelineNodeObj, TimelineNodeObj } from './components/time-line'; @@ -21,11 +22,15 @@ import { } from './constant'; import { IDslComponent, IPipelineFileLogDetail } from './interface'; -export const useFetchPipelineFileLogDetail = (props?: { +export const useFetchPipelineFileLogDetail = ({ + isAgent = false, + isEdit = true, + refreshCount, +}: { isEdit?: boolean; refreshCount?: number; + isAgent: boolean; }) => { - const { isEdit = true, refreshCount } = props || { isEdit: true }; const { id } = useParams(); const [searchParams] = useSearchParams(); const logId = searchParams.get('id') || id; @@ -39,6 +44,7 @@ export const useFetchPipelineFileLogDetail = (props?: { queryKey, initialData: {} as IPipelineFileLogDetail, gcTime: 0, + enabled: !isAgent, queryFn: async () => { if (isEdit) { const { data } = await kbService.get_pipeline_detail({ @@ -287,5 +293,39 @@ export const useGetPipelineResultSearchParams = () => { currentQueryParameters.get(PipelineResultSearchParams.AgentId) || '', agentTitle: currentQueryParameters.get(PipelineResultSearchParams.AgentTitle) || '', + documentExtension: + currentQueryParameters.get( + PipelineResultSearchParams.DocumentExtension, + ) || '', + createdBy: + currentQueryParameters.get(PipelineResultSearchParams.CreatedBy) || '', }; }; + +export function useFetchPipelineResult({ + agentId, +}: Pick, 'agentId'>) { + const [searchParams] = useSearchParams(); + const messageId = searchParams.get('id'); + + const { data, setMessageId, setISStopFetchTrace } = + useFetchMessageTrace(agentId); + + useEffect(() => { + if (messageId) { + setMessageId(messageId); + setISStopFetchTrace(true); + } + }, [agentId, messageId, setISStopFetchTrace, setMessageId]); + + const pipelineResult = useMemo(() => { + if (Array.isArray(data)) { + const latest = data?.at(-1); + if (latest?.component_id === 'END' && Array.isArray(latest.trace)) { + return latest.trace.at(0); + } + } + }, [data]); + + return { pipelineResult }; +} diff --git a/web/src/pages/dataflow-result/index.tsx b/web/src/pages/dataflow-result/index.tsx index 3da8581be..9da4a910e 100644 --- a/web/src/pages/dataflow-result/index.tsx +++ b/web/src/pages/dataflow-result/index.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import DocumentPreview from './components/document-preview'; import { useFetchPipelineFileLogDetail, + useFetchPipelineResult, useGetChunkHighlights, useGetPipelineResultSearchParams, useHandleChunkCardClick, @@ -26,28 +27,38 @@ import { } from '@/components/ui/breadcrumb'; import { Button } from '@/components/ui/button'; import { Modal } from '@/components/ui/modal/modal'; +import { Images } from '@/constants/common'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useGetKnowledgeSearchParams } from '@/hooks/route-hook'; import { useGetDocumentUrl } from './components/document-preview/hooks'; import TimelineDataFlow from './components/time-line'; import { TimelineNodeType } from './constant'; import styles from './index.less'; -import { IDslComponent } from './interface'; +import { IDslComponent, IPipelineFileLogDetail } from './interface'; import ParserContainer from './parser'; const Chunk = () => { - const { isReadOnly, knowledgeId, agentId, agentTitle } = + const { isReadOnly, knowledgeId, agentId, agentTitle, documentExtension } = useGetPipelineResultSearchParams(); + const isAgent = !!agentId; + + const { pipelineResult } = useFetchPipelineResult({ agentId }); + const { data: { documentInfo }, - } = useFetchNextChunkList(); + } = useFetchNextChunkList(!isAgent); + const { selectedChunk, handleChunkCardClick } = useHandleChunkCardClick(); const [activeStepId, setActiveStepId] = useState(2); - const { data: dataset } = useFetchPipelineFileLogDetail(); + const { data: dataset } = useFetchPipelineFileLogDetail({ + isAgent, + }); const { t } = useTranslation(); - const { timelineNodes } = useTimelineDataFlow(dataset); + const { timelineNodes } = useTimelineDataFlow( + agentId ? (pipelineResult as IPipelineFileLogDetail) : dataset, + ); const { navigateToDataset, @@ -55,12 +66,17 @@ const Chunk = () => { navigateToAgents, navigateToDataflow, } = useNavigatePage(); - const fileUrl = useGetDocumentUrl(); + let fileUrl = useGetDocumentUrl(isAgent); const { highlights, setWidthAndHeight } = useGetChunkHighlights(selectedChunk); const fileType = useMemo(() => { + if (isAgent) { + return Images.some((x) => x === documentExtension) + ? 'visual' + : documentExtension; + } switch (documentInfo?.type) { case 'doc': return documentInfo?.name.split('.').pop() || 'doc'; @@ -72,7 +88,7 @@ const Chunk = () => { return documentInfo?.type; } return 'unknown'; - }, [documentInfo]); + }, [documentExtension, documentInfo?.name, documentInfo?.type, isAgent]); const { handleReRunFunc, diff --git a/web/src/pages/dataflow-result/interface.ts b/web/src/pages/dataflow-result/interface.ts index 81dd55b65..865044ce2 100644 --- a/web/src/pages/dataflow-result/interface.ts +++ b/web/src/pages/dataflow-result/interface.ts @@ -77,4 +77,6 @@ export interface NavigateToDataflowResultProps { [PipelineResultSearchParams.AgentTitle]?: string; [PipelineResultSearchParams.IsReadOnly]?: string; [PipelineResultSearchParams.Type]: string; + [PipelineResultSearchParams.CreatedBy]: string; + [PipelineResultSearchParams.DocumentExtension]: string; } diff --git a/web/src/utils/api.ts b/web/src/utils/api.ts index 6dffd4672..273501a2a 100644 --- a/web/src/utils/api.ts +++ b/web/src/utils/api.ts @@ -177,6 +177,7 @@ export default { `${ExternalApi}${api_host}/agentbots/${canvasId}/inputs`, prompt: `${api_host}/canvas/prompts`, cancelDataflow: (id: string) => `${api_host}/canvas/cancel/${id}`, + downloadFile: `${api_host}/canvas/download`, // mcp server listMcpServer: `${api_host}/mcp_server/list`,