mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-26 08:56:47 +08:00
### 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)
This commit is contained in:
@ -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,
|
||||
|
||||
@ -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: {
|
||||
|
||||
@ -1500,6 +1500,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
||||
createFromBlank: '从空白创建',
|
||||
createFromTemplate: '从模板创建',
|
||||
importJsonFile: '导入 JSON 文件',
|
||||
chooseAgentType: '选择智能体类型',
|
||||
},
|
||||
footer: {
|
||||
profile: 'All rights reserved @ React',
|
||||
|
||||
@ -22,7 +22,7 @@ export function CreateAgentDialog({
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={hideModal}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('flow.createGraph')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
@ -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) {
|
||||
) : (
|
||||
<Route className="size-6" />
|
||||
)}
|
||||
<p>{val}</p>
|
||||
<p>
|
||||
{t(
|
||||
`flow.${val === FlowType.Agent ? 'createAgent' : 'createPipeline'}`,
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
{isActive && <Check />}
|
||||
</CardContent>
|
||||
@ -106,7 +111,11 @@ export function CreateAgentForm({
|
||||
id={TagRenameId}
|
||||
>
|
||||
{shouldChooseAgent && (
|
||||
<RAGFlowFormItem required name="type" label={t('common.type')}>
|
||||
<RAGFlowFormItem
|
||||
required
|
||||
name="type"
|
||||
label={t('flow.chooseAgentType')}
|
||||
>
|
||||
<FlowTypeCards></FlowTypeCards>
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
|
||||
@ -16,12 +16,7 @@ export const NameFormSchema = {
|
||||
export function NameFormField() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<RAGFlowFormItem
|
||||
name="name"
|
||||
required
|
||||
label={t('common.name')}
|
||||
tooltip={t('flow.sqlStatementTip')}
|
||||
>
|
||||
<RAGFlowFormItem name="name" required label={t('common.name')}>
|
||||
<Input placeholder={t('common.namePlaceholder')} autoComplete="off" />
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
|
||||
@ -52,6 +52,7 @@ export const HandleContext = createContext<HandleContextType>(
|
||||
export type LogContextType = {
|
||||
messageId: string;
|
||||
setMessageId: (messageId: string) => void;
|
||||
setUploadedFileData: (data: Record<string, any>) => void;
|
||||
};
|
||||
|
||||
export const LogContext = createContext<LogContextType>({} as LogContextType);
|
||||
|
||||
@ -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 };
|
||||
|
||||
@ -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<Record<string, any>>();
|
||||
|
||||
const handleRunAgent = useCallback(() => {
|
||||
if (isParsing) {
|
||||
// show log sheet
|
||||
@ -184,7 +187,9 @@ export default function DataFlow() {
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</PageHeader>
|
||||
<LogContext.Provider value={{ messageId, setMessageId }}>
|
||||
<LogContext.Provider
|
||||
value={{ messageId, setMessageId, setUploadedFileData }}
|
||||
>
|
||||
<ReactFlowProvider>
|
||||
<DropdownProvider>
|
||||
<DataFlowCanvas
|
||||
@ -211,6 +216,8 @@ export default function DataFlow() {
|
||||
isLogEmpty={isLogEmpty}
|
||||
logs={logs}
|
||||
handleCancel={handleCancel}
|
||||
messageId={messageId}
|
||||
uploadedFileData={uploadedFileData}
|
||||
></LogSheet>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@ -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<any> & {
|
||||
handleCancel(): void;
|
||||
uploadedFileData?: Record<string, any>;
|
||||
} & 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 (
|
||||
<Sheet open onOpenChange={hideModal} modal={false}>
|
||||
<SheetContent
|
||||
@ -57,14 +65,16 @@ export function LogSheet({
|
||||
variant={'ghost'}
|
||||
disabled={!isCompleted}
|
||||
onClick={navigateToDataflowResult({
|
||||
id: 'cfc28d6c9c4911f088bf047c16ec874f', // 'log_id',
|
||||
[PipelineResultSearchParams.AgentId]:
|
||||
'cfc28d6c9c4911f088bf047c16ec874f', // 'agent_id',
|
||||
[PipelineResultSearchParams.DocumentId]:
|
||||
'05b0e19a9d9d11f0b674047c16ec874f', //'doc_id',
|
||||
[PipelineResultSearchParams.AgentTitle]: 'full', //'title',
|
||||
id: messageId, // 'log_id',
|
||||
[PipelineResultSearchParams.AgentId]: id, // 'agent_id',
|
||||
[PipelineResultSearchParams.DocumentId]: uploadedFileData?.id, //'doc_id',
|
||||
[PipelineResultSearchParams.AgentTitle]: agent.title, //'title',
|
||||
[PipelineResultSearchParams.IsReadOnly]: 'true',
|
||||
[PipelineResultSearchParams.Type]: 'dataflow',
|
||||
[PipelineResultSearchParams.CreatedBy]:
|
||||
uploadedFileData?.created_by,
|
||||
[PipelineResultSearchParams.DocumentExtension]:
|
||||
uploadedFileData?.extension,
|
||||
})}
|
||||
>
|
||||
{t('dataflow.viewResult')} <ArrowUpRight />
|
||||
|
||||
@ -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<number>();
|
||||
@ -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;
|
||||
};
|
||||
|
||||
@ -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',
|
||||
}
|
||||
|
||||
@ -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<ReturnType<typeof useGetPipelineResultSearchParams>, '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 };
|
||||
}
|
||||
|
||||
@ -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<number | string>(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,
|
||||
|
||||
@ -77,4 +77,6 @@ export interface NavigateToDataflowResultProps {
|
||||
[PipelineResultSearchParams.AgentTitle]?: string;
|
||||
[PipelineResultSearchParams.IsReadOnly]?: string;
|
||||
[PipelineResultSearchParams.Type]: string;
|
||||
[PipelineResultSearchParams.CreatedBy]: string;
|
||||
[PipelineResultSearchParams.DocumentExtension]: string;
|
||||
}
|
||||
|
||||
@ -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`,
|
||||
|
||||
Reference in New Issue
Block a user