mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-08 20:42:30 +08:00
### What problem does this PR solve? Feat: Display the pipeline operation sheet on the agent page #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -48,13 +48,3 @@ export type HandleContextType = {
|
||||
export const HandleContext = createContext<HandleContextType>(
|
||||
{} as HandleContextType,
|
||||
);
|
||||
|
||||
export type PipelineLogContextType = {
|
||||
messageId: string;
|
||||
setMessageId: (messageId: string) => void;
|
||||
setUploadedFileData: (data: Record<string, any>) => void;
|
||||
};
|
||||
|
||||
export const PipelineLogContext = createContext<PipelineLogContextType>(
|
||||
{} as PipelineLogContextType,
|
||||
);
|
||||
|
||||
55
web/src/pages/agent/hooks/use-run-dataflow.ts
Normal file
55
web/src/pages/agent/hooks/use-run-dataflow.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { useSendMessageBySSE } from '@/hooks/use-send-message';
|
||||
import api from '@/utils/api';
|
||||
import { get } from 'lodash';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useParams } from 'umi';
|
||||
import { UseFetchLogReturnType } from './use-fetch-pipeline-log';
|
||||
import { useSaveGraph } from './use-save-graph';
|
||||
|
||||
export function useRunDataflow({
|
||||
showLogSheet,
|
||||
setMessageId,
|
||||
}: {
|
||||
showLogSheet: () => void;
|
||||
} & Pick<UseFetchLogReturnType, 'setMessageId'>) {
|
||||
const { send } = useSendMessageBySSE(api.runCanvas);
|
||||
const { id } = useParams();
|
||||
const { saveGraph, loading } = useSaveGraph();
|
||||
const [uploadedFileData, setUploadedFileData] =
|
||||
useState<Record<string, any>>();
|
||||
|
||||
const run = useCallback(
|
||||
async (fileResponseData: Record<string, any>) => {
|
||||
const saveRet = await saveGraph();
|
||||
const success = saveRet?.code === 0;
|
||||
if (!success) return;
|
||||
|
||||
showLogSheet();
|
||||
const res = await send({
|
||||
id,
|
||||
query: '',
|
||||
session_id: null,
|
||||
files: [fileResponseData.file],
|
||||
});
|
||||
|
||||
if (res && res?.response.status === 200 && get(res, 'data.code') === 0) {
|
||||
// fetch canvas
|
||||
setUploadedFileData(fileResponseData.file);
|
||||
const msgId = get(res, 'data.data.message_id');
|
||||
if (msgId) {
|
||||
setMessageId(msgId);
|
||||
}
|
||||
|
||||
return msgId;
|
||||
} else {
|
||||
message.error(get(res, 'data.message', ''));
|
||||
}
|
||||
},
|
||||
[id, saveGraph, send, setMessageId, setUploadedFileData, showLogSheet],
|
||||
);
|
||||
|
||||
return { run, loading: loading, uploadedFileData };
|
||||
}
|
||||
|
||||
export type RunDataflowType = ReturnType<typeof useRunDataflow>;
|
||||
@ -32,25 +32,26 @@ import {
|
||||
Settings,
|
||||
Upload,
|
||||
} from 'lucide-react';
|
||||
import { ComponentPropsWithoutRef, useCallback, useState } from 'react';
|
||||
import { ComponentPropsWithoutRef, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'umi';
|
||||
import AgentCanvas from './canvas';
|
||||
import { DropdownProvider } from './canvas/context';
|
||||
import { Operator } from './constant';
|
||||
import { PipelineLogContext } from './context';
|
||||
import { useCancelCurrentDataflow } from './hooks/use-cancel-dataflow';
|
||||
import { useHandleExportJsonFile } from './hooks/use-export-json';
|
||||
import { useFetchDataOnMount } from './hooks/use-fetch-data';
|
||||
import { useFetchPipelineLog } from './hooks/use-fetch-pipeline-log';
|
||||
import { useGetBeginNodeDataInputs } from './hooks/use-get-begin-query';
|
||||
import { useIsPipeline } from './hooks/use-is-pipeline';
|
||||
import { useRunDataflow } from './hooks/use-run-dataflow';
|
||||
import {
|
||||
useSaveGraph,
|
||||
useSaveGraphBeforeOpeningDebugDrawer,
|
||||
useWatchAgentChange,
|
||||
} from './hooks/use-save-graph';
|
||||
import { PipelineLogSheet } from './pipeline-log-sheet';
|
||||
import PipelineRunSheet from './pipeline-run-sheet';
|
||||
import { SettingDialog } from './setting-dialog';
|
||||
import useGraphStore from './store';
|
||||
import { useAgentHistoryManager } from './use-agent-history-manager';
|
||||
@ -110,6 +111,12 @@ export default function Agent() {
|
||||
|
||||
// pipeline
|
||||
|
||||
const {
|
||||
visible: pipelineRunSheetVisible,
|
||||
hideModal: hidePipelineRunSheet,
|
||||
showModal: showPipelineRunSheet,
|
||||
} = useSetModalState();
|
||||
|
||||
const {
|
||||
visible: pipelineLogSheetVisible,
|
||||
showModal: showPipelineLogSheet,
|
||||
@ -126,8 +133,6 @@ export default function Agent() {
|
||||
isLogEmpty,
|
||||
} = useFetchPipelineLog(pipelineLogSheetVisible);
|
||||
|
||||
const [uploadedFileData, setUploadedFileData] =
|
||||
useState<Record<string, any>>();
|
||||
const findNodeByName = useGraphStore((state) => state.findNodeByName);
|
||||
|
||||
const handleRunPipeline = useCallback(() => {
|
||||
@ -141,14 +146,15 @@ export default function Agent() {
|
||||
showPipelineLogSheet();
|
||||
} else {
|
||||
hidePipelineLogSheet();
|
||||
handleRun();
|
||||
// handleRun();
|
||||
showPipelineRunSheet();
|
||||
}
|
||||
}, [
|
||||
findNodeByName,
|
||||
handleRun,
|
||||
hidePipelineLogSheet,
|
||||
isParsing,
|
||||
showPipelineLogSheet,
|
||||
showPipelineRunSheet,
|
||||
t,
|
||||
]);
|
||||
|
||||
@ -157,7 +163,7 @@ export default function Agent() {
|
||||
stopFetchTrace,
|
||||
});
|
||||
|
||||
const run = useCallback(() => {
|
||||
const handleButtonRunClick = useCallback(() => {
|
||||
if (isPipeline) {
|
||||
handleRunPipeline();
|
||||
} else {
|
||||
@ -165,6 +171,12 @@ export default function Agent() {
|
||||
}
|
||||
}, [handleRunAgent, handleRunPipeline, isPipeline]);
|
||||
|
||||
const {
|
||||
run: runPipeline,
|
||||
loading: pipelineRunning,
|
||||
uploadedFileData,
|
||||
} = useRunDataflow({ showLogSheet: showPipelineLogSheet, setMessageId });
|
||||
|
||||
return (
|
||||
<section className="h-full">
|
||||
<PageHeader>
|
||||
@ -194,7 +206,7 @@ export default function Agent() {
|
||||
>
|
||||
<LaptopMinimalCheck /> {t('flow.save')}
|
||||
</ButtonLoading>
|
||||
<Button variant={'secondary'} onClick={run}>
|
||||
<Button variant={'secondary'} onClick={handleButtonRunClick}>
|
||||
<CirclePlay />
|
||||
{t('flow.run')}
|
||||
</Button>
|
||||
@ -241,18 +253,14 @@ export default function Agent() {
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</PageHeader>
|
||||
<PipelineLogContext.Provider
|
||||
value={{ messageId, setMessageId, setUploadedFileData }}
|
||||
>
|
||||
<ReactFlowProvider>
|
||||
<DropdownProvider>
|
||||
<AgentCanvas
|
||||
drawerVisible={chatDrawerVisible}
|
||||
hideDrawer={hideChatDrawer}
|
||||
></AgentCanvas>
|
||||
</DropdownProvider>
|
||||
</ReactFlowProvider>
|
||||
</PipelineLogContext.Provider>
|
||||
<ReactFlowProvider>
|
||||
<DropdownProvider>
|
||||
<AgentCanvas
|
||||
drawerVisible={chatDrawerVisible}
|
||||
hideDrawer={hideChatDrawer}
|
||||
></AgentCanvas>
|
||||
</DropdownProvider>
|
||||
</ReactFlowProvider>
|
||||
{embedVisible && (
|
||||
<EmbedDialog
|
||||
visible={embedVisible}
|
||||
@ -284,6 +292,13 @@ export default function Agent() {
|
||||
uploadedFileData={uploadedFileData}
|
||||
></PipelineLogSheet>
|
||||
)}
|
||||
{pipelineRunSheetVisible && (
|
||||
<PipelineRunSheet
|
||||
hideModal={hidePipelineRunSheet}
|
||||
run={runPipeline}
|
||||
loading={pipelineRunning}
|
||||
></PipelineRunSheet>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
31
web/src/pages/agent/pipeline-run-sheet/index.tsx
Normal file
31
web/src/pages/agent/pipeline-run-sheet/index.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import {
|
||||
Sheet,
|
||||
SheetContent,
|
||||
SheetHeader,
|
||||
SheetTitle,
|
||||
} from '@/components/ui/sheet';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { RunDataflowType } from '../hooks/use-run-dataflow';
|
||||
import { UploaderForm } from './uploader';
|
||||
|
||||
type RunSheetProps = IModalProps<any> &
|
||||
Pick<RunDataflowType, 'run' | 'loading'>;
|
||||
|
||||
const PipelineRunSheet = ({ hideModal, run, loading }: RunSheetProps) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Sheet onOpenChange={hideModal} open modal={false}>
|
||||
<SheetContent className={cn('top-20 p-2')}>
|
||||
<SheetHeader>
|
||||
<SheetTitle>{t('flow.testRun')}</SheetTitle>
|
||||
<UploaderForm ok={run} loading={loading}></UploaderForm>
|
||||
</SheetHeader>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
);
|
||||
};
|
||||
|
||||
export default PipelineRunSheet;
|
||||
57
web/src/pages/agent/pipeline-run-sheet/uploader.tsx
Normal file
57
web/src/pages/agent/pipeline-run-sheet/uploader.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
'use client';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { ButtonLoading } from '@/components/ui/button';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { FileUploadDirectUpload } from '@/pages/agent/debug-content/uploader';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const formSchema = z.object({
|
||||
file: z.record(z.any()),
|
||||
});
|
||||
|
||||
export type FormSchemaType = z.infer<typeof formSchema>;
|
||||
|
||||
type UploaderFormProps = {
|
||||
ok: (values: FormSchemaType) => void;
|
||||
loading: boolean;
|
||||
};
|
||||
|
||||
export function UploaderForm({ ok, loading }: UploaderFormProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useForm<FormSchemaType>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {},
|
||||
});
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(ok)} className="space-y-8">
|
||||
<RAGFlowFormItem name="file">
|
||||
{(field) => {
|
||||
return (
|
||||
<FileUploadDirectUpload
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
></FileUploadDirectUpload>
|
||||
);
|
||||
}}
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<div>
|
||||
<ButtonLoading
|
||||
type="submit"
|
||||
loading={loading}
|
||||
className="w-full mt-1"
|
||||
>
|
||||
{t('flow.run')}
|
||||
</ButtonLoading>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@ -9,16 +9,30 @@ import { removeUselessFieldsFromValues } from '@/utils/form';
|
||||
import { Edge, Node, XYPosition } from '@xyflow/react';
|
||||
import { FormInstance, FormListFieldData } from 'antd';
|
||||
import { humanId } from 'human-id';
|
||||
import { curry, get, intersectionWith, isEqual, omit, sample } from 'lodash';
|
||||
import {
|
||||
curry,
|
||||
get,
|
||||
intersectionWith,
|
||||
isEmpty,
|
||||
isEqual,
|
||||
omit,
|
||||
sample,
|
||||
} from 'lodash';
|
||||
import pipe from 'lodash/fp/pipe';
|
||||
import isObject from 'lodash/isObject';
|
||||
import {
|
||||
CategorizeAnchorPointPositions,
|
||||
FileType,
|
||||
FileTypeSuffixMap,
|
||||
NoCopyOperatorsList,
|
||||
NoDebugOperatorsList,
|
||||
NodeHandleId,
|
||||
Operator,
|
||||
} from './constant';
|
||||
import { ExtractorFormSchemaType } from './form/extractor-form';
|
||||
import { HierarchicalMergerFormSchemaType } from './form/hierarchical-merger-form';
|
||||
import { ParserFormSchemaType } from './form/parser-form';
|
||||
import { SplitterFormSchemaType } from './form/splitter-form';
|
||||
import { BeginQuery, IPosition } from './interface';
|
||||
|
||||
function buildAgentExceptionGoto(edges: Edge[], nodeId: string) {
|
||||
@ -170,6 +184,92 @@ export function hasSubAgent(edges: Edge[], nodeId?: string) {
|
||||
return !!edge;
|
||||
}
|
||||
|
||||
// Because the array of react-hook-form must be object data,
|
||||
// it needs to be converted into a simple data type array required by the backend
|
||||
function transformObjectArrayToPureArray(
|
||||
list: Array<Record<string, any>>,
|
||||
field: string,
|
||||
) {
|
||||
return Array.isArray(list)
|
||||
? list.filter((x) => !isEmpty(x[field])).map((y) => y[field])
|
||||
: [];
|
||||
}
|
||||
|
||||
function transformParserParams(params: ParserFormSchemaType) {
|
||||
const setups = params.setups.reduce<
|
||||
Record<string, ParserFormSchemaType['setups'][0]>
|
||||
>((pre, cur) => {
|
||||
if (cur.fileFormat) {
|
||||
let filteredSetup: Partial<
|
||||
ParserFormSchemaType['setups'][0] & { suffix: string[] }
|
||||
> = {
|
||||
output_format: cur.output_format,
|
||||
suffix: FileTypeSuffixMap[cur.fileFormat as FileType],
|
||||
};
|
||||
|
||||
switch (cur.fileFormat) {
|
||||
case FileType.PDF:
|
||||
filteredSetup = {
|
||||
...filteredSetup,
|
||||
parse_method: cur.parse_method,
|
||||
lang: cur.lang,
|
||||
};
|
||||
break;
|
||||
case FileType.Image:
|
||||
filteredSetup = {
|
||||
...filteredSetup,
|
||||
parse_method: cur.parse_method,
|
||||
lang: cur.lang,
|
||||
system_prompt: cur.system_prompt,
|
||||
};
|
||||
break;
|
||||
case FileType.Email:
|
||||
filteredSetup = {
|
||||
...filteredSetup,
|
||||
fields: cur.fields,
|
||||
};
|
||||
break;
|
||||
case FileType.Video:
|
||||
case FileType.Audio:
|
||||
filteredSetup = {
|
||||
...filteredSetup,
|
||||
llm_id: cur.llm_id,
|
||||
};
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
pre[cur.fileFormat] = filteredSetup;
|
||||
}
|
||||
return pre;
|
||||
}, {});
|
||||
|
||||
return { ...params, setups };
|
||||
}
|
||||
|
||||
function transformSplitterParams(params: SplitterFormSchemaType) {
|
||||
return {
|
||||
...params,
|
||||
overlapped_percent: Number(params.overlapped_percent) / 100,
|
||||
delimiters: transformObjectArrayToPureArray(params.delimiters, 'value'),
|
||||
};
|
||||
}
|
||||
|
||||
function transformHierarchicalMergerParams(
|
||||
params: HierarchicalMergerFormSchemaType,
|
||||
) {
|
||||
const levels = params.levels.map((x) =>
|
||||
transformObjectArrayToPureArray(x.expressions, 'expression'),
|
||||
);
|
||||
|
||||
return { ...params, hierarchy: Number(params.hierarchy), levels };
|
||||
}
|
||||
|
||||
function transformExtractorParams(params: ExtractorFormSchemaType) {
|
||||
return { ...params, prompts: [{ content: params.prompts, role: 'user' }] };
|
||||
}
|
||||
|
||||
// construct a dsl based on the node information of the graph
|
||||
export const buildDslComponentsByGraph = (
|
||||
nodes: RAGFlowNodeType[],
|
||||
@ -202,6 +302,21 @@ export const buildDslComponentsByGraph = (
|
||||
params = buildCategorize(edges, nodes, id);
|
||||
break;
|
||||
|
||||
case Operator.Parser:
|
||||
params = transformParserParams(params);
|
||||
break;
|
||||
|
||||
case Operator.Splitter:
|
||||
params = transformSplitterParams(params);
|
||||
break;
|
||||
|
||||
case Operator.HierarchicalMerger:
|
||||
params = transformHierarchicalMergerParams(params);
|
||||
break;
|
||||
case Operator.Extractor:
|
||||
params = transformExtractorParams(params);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user