diff --git a/web/src/components/layout-recognize-form-field.tsx b/web/src/components/layout-recognize-form-field.tsx index c1a91796e..0e5b660bb 100644 --- a/web/src/components/layout-recognize-form-field.tsx +++ b/web/src/components/layout-recognize-form-field.tsx @@ -3,7 +3,7 @@ import { useTranslate } from '@/hooks/common-hooks'; import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks'; import { cn } from '@/lib/utils'; import { camelCase } from 'lodash'; -import { useMemo } from 'react'; +import { ReactNode, useMemo } from 'react'; import { useFormContext } from 'react-hook-form'; import { SelectWithSearch } from './originui/select-with-search'; import { @@ -23,10 +23,12 @@ export function LayoutRecognizeFormField({ name = 'parser_config.layout_recognize', horizontal = true, optionsWithoutLLM, + label, }: { name?: string; horizontal?: boolean; optionsWithoutLLM?: { value: string; label: string }[]; + label?: ReactNode; }) { const form = useFormContext(); @@ -81,7 +83,7 @@ export function LayoutRecognizeFormField({ ['w-1/4']: horizontal, })} > - {t('layoutRecognize')} + {label || t('layoutRecognize')}
diff --git a/web/src/components/originui/select-with-search.tsx b/web/src/components/originui/select-with-search.tsx index cffe160e5..031fdddb1 100644 --- a/web/src/components/originui/select-with-search.tsx +++ b/web/src/components/originui/select-with-search.tsx @@ -49,6 +49,22 @@ export type SelectWithSearchFlagProps = { placeholder?: string; }; +function findLabelWithoutOptions( + options: SelectWithSearchFlagOptionType[], + value: string, +) { + return options.find((opt) => opt.value === value)?.label || ''; +} + +function findLabelWithOptions( + options: SelectWithSearchFlagOptionType[], + value: string, +) { + return options + .map((group) => group?.options?.find((item) => item.value === value)) + .filter(Boolean)[0]?.label; +} + export const SelectWithSearch = forwardRef< React.ElementRef, SelectWithSearchFlagProps @@ -69,6 +85,28 @@ export const SelectWithSearch = forwardRef< const [open, setOpen] = useState(false); const [value, setValue] = useState(''); + const selectLabel = useMemo(() => { + if (options.every((x) => x.options === undefined)) { + return findLabelWithoutOptions(options, value); + } else if (options.every((x) => Array.isArray(x.options))) { + return findLabelWithOptions(options, value); + } else { + // Some have options, some don't + const optionsWithOptions = options.filter((x) => + Array.isArray(x.options), + ); + const optionsWithoutOptions = options.filter( + (x) => x.options === undefined, + ); + + const label = findLabelWithOptions(optionsWithOptions, value); + if (label) { + return label; + } + return findLabelWithoutOptions(optionsWithoutOptions, value); + } + }, [options, value]); + const handleSelect = useCallback( (val: string) => { setValue(val); @@ -90,16 +128,7 @@ export const SelectWithSearch = forwardRef< useEffect(() => { setValue(val); }, [val]); - const selectLabel = useMemo(() => { - const optionTemp = options[0]; - if (optionTemp?.options) { - return options - .map((group) => group?.options?.find((item) => item.value === value)) - .filter(Boolean)[0]?.label; - } else { - return options.find((opt) => opt.value === value)?.label || ''; - } - }, [options, value]); + return ( diff --git a/web/src/locales/en.ts b/web/src/locales/en.ts index e9065be8b..e9e120b37 100644 --- a/web/src/locales/en.ts +++ b/web/src/locales/en.ts @@ -1694,6 +1694,7 @@ This delimiter is used to split the input text into several text pieces echo of searchMethod: 'Search method', filenameEmbdWeight: 'Filename embd weight', begin: 'File', + parserMethod: 'Parser method', }, }, }; diff --git a/web/src/locales/zh.ts b/web/src/locales/zh.ts index 7ee4cab8e..e4ce6935c 100644 --- a/web/src/locales/zh.ts +++ b/web/src/locales/zh.ts @@ -1604,6 +1604,7 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于 searchMethod: '搜索方法', filenameEmbdWeight: '文件名嵌入权重', begin: '文件', + parserMethod: '解析方法', }, }, }; diff --git a/web/src/pages/data-flow/canvas/index.tsx b/web/src/pages/data-flow/canvas/index.tsx index bd252406c..8a8da4b93 100644 --- a/web/src/pages/data-flow/canvas/index.tsx +++ b/web/src/pages/data-flow/canvas/index.tsx @@ -30,6 +30,7 @@ import { useBeforeDelete } from '../hooks/use-before-delete'; import { useMoveNote } from '../hooks/use-move-note'; import { useDropdownManager } from './context'; +import { useRunDataflow } from '../hooks/use-run-dataflow'; import { useHideFormSheetOnNodeDeletion, useShowDrawer, @@ -152,6 +153,8 @@ function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) { hideModal: hideLogSheet, } = useSetModalState(); + const { run, loading: running, messageId } = useRunDataflow(showLogSheet!); + const onConnect = (connection: Connection) => { originalOnConnect(connection); isConnectedRef.current = true; @@ -300,10 +303,13 @@ function DataFlowCanvas({ drawerVisible, hideDrawer }: IProps) { {runVisible && ( )} - {logSheetVisible && } + {logSheetVisible && ( + + )}
); } diff --git a/web/src/pages/data-flow/constant.tsx b/web/src/pages/data-flow/constant.tsx index 3f04c5b00..a181cfc1f 100644 --- a/web/src/pages/data-flow/constant.tsx +++ b/web/src/pages/data-flow/constant.tsx @@ -227,7 +227,7 @@ export enum FileType { Email = 'email', TextMarkdown = 'text&markdown', Docx = 'word', - PowerPoint = 'ppt', + PowerPoint = 'slides', Video = 'video', Audio = 'audio', } diff --git a/web/src/pages/data-flow/form/parser-form/common-form-fields.tsx b/web/src/pages/data-flow/form/parser-form/common-form-fields.tsx index c63ad6e92..d9b51bd7a 100644 --- a/web/src/pages/data-flow/form/parser-form/common-form-fields.tsx +++ b/web/src/pages/data-flow/form/parser-form/common-form-fields.tsx @@ -1,3 +1,4 @@ +import { crossLanguageOptions } from '@/components/cross-language-form-field'; import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field'; import { LLMFormField } from '@/components/llm-setting-items/llm-form-field'; import { @@ -46,11 +47,13 @@ export function ParserMethodFormField({ prefix, optionsWithoutLLM, }: CommonProps & { optionsWithoutLLM?: { value: string; label: string }[] }) { + const { t } = useTranslation(); return ( ); } @@ -62,3 +65,22 @@ export function LargeModelFormField({ prefix }: CommonProps) { > ); } + +export function LanguageFormField({ prefix }: CommonProps) { + const { t } = useTranslation(); + + return ( + + {(field) => ( + + )} + + ); +} diff --git a/web/src/pages/data-flow/form/parser-form/image-form-fields.tsx b/web/src/pages/data-flow/form/parser-form/image-form-fields.tsx index 8ee2b09f2..7b15eda19 100644 --- a/web/src/pages/data-flow/form/parser-form/image-form-fields.tsx +++ b/web/src/pages/data-flow/form/parser-form/image-form-fields.tsx @@ -1,10 +1,11 @@ import { buildOptions } from '@/utils/form'; import { isEmpty } from 'lodash'; -import { useEffect } from 'react'; -import { useFormContext } from 'react-hook-form'; +import { useEffect, useMemo } from 'react'; +import { useFormContext, useWatch } from 'react-hook-form'; import { ImageParseMethod } from '../../constant'; -import { ParserMethodFormField } from './common-form-fields'; +import { LanguageFormField, ParserMethodFormField } from './common-form-fields'; import { CommonProps } from './interface'; +import { useSetInitialLanguage } from './use-set-initial-language'; import { buildFieldNameWithPrefix } from './utils'; const options = buildOptions(ImageParseMethod); @@ -13,6 +14,14 @@ export function ImageFormFields({ prefix }: CommonProps) { const form = useFormContext(); const parseMethodName = buildFieldNameWithPrefix('parse_method', prefix); + const parseMethod = useWatch({ + name: parseMethodName, + }); + + const languageShown = useMemo(() => { + return !isEmpty(parseMethod) && parseMethod !== ImageParseMethod.OCR; + }, [parseMethod]); + useEffect(() => { if (isEmpty(form.getValues(parseMethodName))) { form.setValue(parseMethodName, ImageParseMethod.OCR, { @@ -22,12 +31,15 @@ export function ImageFormFields({ prefix }: CommonProps) { } }, [form, parseMethodName]); + useSetInitialLanguage({ prefix, languageShown }); + return ( <> + {languageShown && } ); } diff --git a/web/src/pages/data-flow/form/parser-form/index.tsx b/web/src/pages/data-flow/form/parser-form/index.tsx index baf7a88a4..3e08301f1 100644 --- a/web/src/pages/data-flow/form/parser-form/index.tsx +++ b/web/src/pages/data-flow/form/parser-form/index.tsx @@ -33,7 +33,9 @@ import { VideoFormFields } from './video-form-fields'; const outputList = buildOutputList(initialParserValues.outputs); -const FileFormatOptions = buildOptions(FileType); +const FileFormatOptions = buildOptions(FileType).filter( + (x) => x.value !== FileType.Video, // Temporarily hide the video option +); const FileFormatWidgetMap = { [FileType.PDF]: PdfFormFields, @@ -105,7 +107,7 @@ function ParserItem({ name, index, fieldLength, remove }: ParserItemProps) { return (
diff --git a/web/src/pages/data-flow/form/parser-form/pdf-form-fields.tsx b/web/src/pages/data-flow/form/parser-form/pdf-form-fields.tsx index b38669019..d6c3eb7f4 100644 --- a/web/src/pages/data-flow/form/parser-form/pdf-form-fields.tsx +++ b/web/src/pages/data-flow/form/parser-form/pdf-form-fields.tsx @@ -1,17 +1,13 @@ -import { crossLanguageOptions } from '@/components/cross-language-form-field'; import { ParseDocumentType } from '@/components/layout-recognize-form-field'; -import { SelectWithSearch } from '@/components/originui/select-with-search'; -import { RAGFlowFormItem } from '@/components/ragflow-form'; import { isEmpty } from 'lodash'; import { useEffect, useMemo } from 'react'; import { useFormContext, useWatch } from 'react-hook-form'; -import { useTranslation } from 'react-i18next'; -import { ParserMethodFormField } from './common-form-fields'; +import { LanguageFormField, ParserMethodFormField } from './common-form-fields'; import { CommonProps } from './interface'; +import { useSetInitialLanguage } from './use-set-initial-language'; import { buildFieldNameWithPrefix } from './utils'; export function PdfFormFields({ prefix }: CommonProps) { - const { t } = useTranslation(); const form = useFormContext(); const parseMethodName = buildFieldNameWithPrefix('parse_method', prefix); @@ -19,7 +15,6 @@ export function PdfFormFields({ prefix }: CommonProps) { const parseMethod = useWatch({ name: parseMethodName, }); - const lang = form.getValues(buildFieldNameWithPrefix('lang', prefix)); const languageShown = useMemo(() => { return ( @@ -29,18 +24,7 @@ export function PdfFormFields({ prefix }: CommonProps) { ); }, [parseMethod]); - useEffect(() => { - if (languageShown && isEmpty(lang)) { - form.setValue( - buildFieldNameWithPrefix('lang', prefix), - crossLanguageOptions[0].value, - { - shouldValidate: true, - shouldDirty: true, - }, - ); - } - }, [form, lang, languageShown, prefix]); + useSetInitialLanguage({ prefix, languageShown }); useEffect(() => { if (isEmpty(form.getValues(parseMethodName))) { @@ -54,20 +38,7 @@ export function PdfFormFields({ prefix }: CommonProps) { return ( <> - {languageShown && ( - - {(field) => ( - - )} - - )} + {languageShown && } ); } diff --git a/web/src/pages/data-flow/form/parser-form/use-set-initial-language.ts b/web/src/pages/data-flow/form/parser-form/use-set-initial-language.ts new file mode 100644 index 000000000..0eb56cfa9 --- /dev/null +++ b/web/src/pages/data-flow/form/parser-form/use-set-initial-language.ts @@ -0,0 +1,29 @@ +import { crossLanguageOptions } from '@/components/cross-language-form-field'; +import { isEmpty } from 'lodash'; +import { useEffect } from 'react'; +import { useFormContext } from 'react-hook-form'; +import { buildFieldNameWithPrefix } from './utils'; + +export function useSetInitialLanguage({ + prefix, + languageShown, +}: { + prefix: string; + languageShown: boolean; +}) { + const form = useFormContext(); + const lang = form.getValues(buildFieldNameWithPrefix('lang', prefix)); + + useEffect(() => { + if (languageShown && isEmpty(lang)) { + form.setValue( + buildFieldNameWithPrefix('lang', prefix), + crossLanguageOptions[0].value, + { + shouldValidate: true, + shouldDirty: true, + }, + ); + } + }, [form, lang, languageShown, prefix]); +} diff --git a/web/src/pages/data-flow/hooks/use-change-node-name.ts b/web/src/pages/data-flow/hooks/use-change-node-name.ts index 61a5653d7..da7316a84 100644 --- a/web/src/pages/data-flow/hooks/use-change-node-name.ts +++ b/web/src/pages/data-flow/hooks/use-change-node-name.ts @@ -9,7 +9,6 @@ import { useMemo, useState, } from 'react'; -import { Operator } from '../constant'; import useGraphStore from '../store'; import { getAgentNodeTools } from '../utils'; @@ -77,13 +76,10 @@ export const useHandleNodeNameChange = ({ data: any; }) => { const [name, setName] = useState(''); - const { updateNodeName, nodes, getOperatorTypeFromId } = useGraphStore( - (state) => state, - ); + const { updateNodeName, nodes } = useGraphStore((state) => state); const previousName = data?.name; - const isToolNode = getOperatorTypeFromId(id) === Operator.Tool; - const { handleToolNameBlur, previousToolName } = useHandleTooNodeNameChange({ + const { previousToolName } = useHandleTooNodeNameChange({ id, name, setName, @@ -109,12 +105,12 @@ export const useHandleNodeNameChange = ({ }, []); useEffect(() => { - setName(isToolNode ? previousToolName : previousName); - }, [isToolNode, previousName, previousToolName]); + setName(previousName); + }, [previousName, previousToolName]); return { name, - handleNameBlur: isToolNode ? handleToolNameBlur : handleNameBlur, + handleNameBlur: handleNameBlur, handleNameChange, }; }; 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 38bac8c7d..f6aa116b2 100644 --- a/web/src/pages/data-flow/hooks/use-run-dataflow.ts +++ b/web/src/pages/data-flow/hooks/use-run-dataflow.ts @@ -1,12 +1,14 @@ import { useSendMessageBySSE } from '@/hooks/use-send-message'; import api from '@/utils/api'; -import { useCallback } from 'react'; +import { get } from 'lodash'; +import { useCallback, useState } from 'react'; import { useParams } from 'umi'; import { useSaveGraphBeforeOpeningDebugDrawer } from './use-save-graph'; export function useRunDataflow(showLogSheet: () => void) { const { send } = useSendMessageBySSE(api.runCanvas); const { id } = useParams(); + const [messageId, setMessageId] = useState(); const { handleRun: saveGraph, loading } = useSaveGraphBeforeOpeningDebugDrawer(showLogSheet!); @@ -22,12 +24,21 @@ export function useRunDataflow(showLogSheet: () => void) { files: [fileResponseData.file], }); - if (res && res?.response.status === 200 && res?.data?.code === 0) { + if (res && res?.response.status === 200 && get(res, 'data.code') === 0) { // fetch canvas + + const msgId = get(res, 'data.data.message_id'); + if (msgId) { + setMessageId(msgId); + } + + return msgId; } }, [id, saveGraph, send], ); - return { run, loading: loading }; + return { run, loading: loading, messageId }; } + +export type RunDataflowType = ReturnType; diff --git a/web/src/pages/data-flow/log-sheet/dataflow-timeline.tsx b/web/src/pages/data-flow/log-sheet/dataflow-timeline.tsx index 9008f847d..b2ada516f 100644 --- a/web/src/pages/data-flow/log-sheet/dataflow-timeline.tsx +++ b/web/src/pages/data-flow/log-sheet/dataflow-timeline.tsx @@ -8,7 +8,9 @@ import { TimelineSeparator, TimelineTitle, } from '@/components/originui/timeline'; +import { useFetchMessageTrace } from '@/hooks/use-agent-request'; import { Aperture } from 'lucide-react'; +import { useEffect } from 'react'; const items = [ { @@ -48,7 +50,24 @@ const items = [ }, ]; -export function DataflowTimeline() { +export type DataflowTimelineProps = { messageId: string }; + +interface DataflowTrace { + datetime: string; + elapsed_time: number; + message: string; + progress: number; + timestamp: number; +} +export function DataflowTimeline({ messageId }: DataflowTimelineProps) { + const { setMessageId, data } = useFetchMessageTrace(false); + + useEffect(() => { + if (messageId) { + setMessageId(messageId); + } + }, [messageId, setMessageId]); + return ( {items.map((item) => ( diff --git a/web/src/pages/data-flow/log-sheet/index.tsx b/web/src/pages/data-flow/log-sheet/index.tsx index c268fc97b..b4aafa837 100644 --- a/web/src/pages/data-flow/log-sheet/index.tsx +++ b/web/src/pages/data-flow/log-sheet/index.tsx @@ -8,18 +8,18 @@ import { IModalProps } from '@/interfaces/common'; import { cn } from '@/lib/utils'; import { NotebookText } from 'lucide-react'; import 'react18-json-view/src/style.css'; -import { DataflowTimeline } from './dataflow-timeline'; +import { DataflowTimeline, DataflowTimelineProps } from './dataflow-timeline'; -type LogSheetProps = IModalProps; +type LogSheetProps = IModalProps & DataflowTimelineProps; -export function LogSheet({ hideModal }: LogSheetProps) { +export function LogSheet({ hideModal, messageId }: LogSheetProps) { return ( - +
diff --git a/web/src/pages/data-flow/run-sheet/index.tsx b/web/src/pages/data-flow/run-sheet/index.tsx index 4a2c70681..7ee9adc35 100644 --- a/web/src/pages/data-flow/run-sheet/index.tsx +++ b/web/src/pages/data-flow/run-sheet/index.tsx @@ -7,13 +7,14 @@ import { import { IModalProps } from '@/interfaces/common'; import { cn } from '@/lib/utils'; import { useTranslation } from 'react-i18next'; -import { useRunDataflow } from '../hooks/use-run-dataflow'; +import { RunDataflowType } from '../hooks/use-run-dataflow'; import { UploaderForm } from './uploader'; -const RunSheet = ({ hideModal, showModal: showLogSheet }: IModalProps) => { - const { t } = useTranslation(); +type RunSheetProps = IModalProps & + Pick; - const { run, loading } = useRunDataflow(showLogSheet!); +const RunSheet = ({ hideModal, run, loading }: RunSheetProps) => { + const { t } = useTranslation(); return ( diff --git a/web/src/pages/data-flow/utils.ts b/web/src/pages/data-flow/utils.ts index f1d0a6393..32cbb8772 100644 --- a/web/src/pages/data-flow/utils.ts +++ b/web/src/pages/data-flow/utils.ts @@ -1,22 +1,10 @@ -import { - IAgentForm, - ICategorizeItem, - ICategorizeItemResult, -} from '@/interfaces/database/agent'; +import { IAgentForm } from '@/interfaces/database/agent'; import { DSLComponents, RAGFlowNodeType } from '@/interfaces/database/flow'; 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, - isEmpty, - isEqual, - omit, - sample, -} from 'lodash'; +import { curry, get, intersectionWith, isEmpty, isEqual, sample } from 'lodash'; import pipe from 'lodash/fp/pipe'; import isObject from 'lodash/isObject'; import { @@ -30,7 +18,7 @@ import { import { HierarchicalMergerFormSchemaType } from './form/hierarchical-merger-form'; import { ParserFormSchemaType } from './form/parser-form'; import { SplitterFormSchemaType } from './form/splitter-form'; -import { BeginQuery, IPosition } from './interface'; +import { IPosition } from './interface'; const buildComponentDownstreamOrUpstream = ( edges: Edge[], @@ -122,6 +110,7 @@ function transformParserParams(params: ParserFormSchemaType) { filteredSetup = { ...filteredSetup, parse_method: cur.parse_method, + lang: cur.lang, }; break; case FileType.Email: @@ -308,10 +297,6 @@ export const getOtherFieldValues = ( x !== form.getFieldValue([formListName, field.name, latestField]), ); -export const generateSwitchHandleText = (idx: number) => { - return `Case ${idx + 1}`; -}; - export const getNodeDragHandle = (nodeType?: string) => { return nodeType === Operator.Note ? '.note-drag-handle' : undefined; }; @@ -400,40 +385,6 @@ export const needsSingleStepDebugging = (label: string) => { return !NoDebugOperatorsList.some((x) => (label as Operator) === x); }; -// Get the coordinates of the node relative to the Iteration node -export function getRelativePositionToIterationNode( - nodes: RAGFlowNodeType[], - position?: XYPosition, // relative position -) { - if (!position) { - return; - } - - const iterationNodes = nodes.filter( - (node) => node.data.label === Operator.Iteration, - ); - - for (const iterationNode of iterationNodes) { - const { - position: { x, y }, - width, - height, - } = iterationNode; - const halfWidth = (width || 0) / 2; - if ( - position.x >= x - halfWidth && - position.x <= x + halfWidth && - position.y >= y && - position.y <= y + (height || 0) - ) { - return { - parentId: iterationNode.id, - position: { x: position.x - x + halfWidth, y: position.y - y }, - }; - } - } -} - export const generateDuplicateNode = ( position?: XYPosition, label?: string, @@ -468,71 +419,11 @@ export function convertToObjectArray(list: Array) { return list.map((x) => ({ value: x })); } -/** - * convert the following object into a list - * - * { - "product_related": { - "description": "The question is about product usage, appearance and how it works.", - "examples": "Why it always beaming?\nHow to install it onto the wall?\nIt leaks, what to do?", - "to": "generate:0" - } - } -*/ -export const buildCategorizeListFromObject = ( - categorizeItem: ICategorizeItemResult, -) => { - // Categorize's to field has two data sources, with edges as the data source. - // Changes in the edge or to field need to be synchronized to the form field. - return Object.keys(categorizeItem) - .reduce>((pre, cur) => { - // synchronize edge data to the to field - - pre.push({ - name: cur, - ...categorizeItem[cur], - examples: convertToObjectArray(categorizeItem[cur].examples), - }); - return pre; - }, []) - .sort((a, b) => a.index - b.index); -}; - -/** - * Convert the list in the following form into an object - * { - "items": [ - { - "name": "Categorize 1", - "description": "111", - "examples": ["ddd"], - "to": "Retrieval:LazyEelsStick" - } - ] - } -*/ -export const buildCategorizeObjectFromList = (list: Array) => { - return list.reduce((pre, cur) => { - if (cur?.name) { - pre[cur.name] = { - ...omit(cur, 'name', 'examples'), - examples: convertToStringArray(cur.examples) as string[], - }; - } - return pre; - }, {}); -}; - export function getAgentNodeTools(agentNode?: RAGFlowNodeType) { const tools: IAgentForm['tools'] = get(agentNode, 'data.form.tools', []); return tools; } -export function getAgentNodeMCP(agentNode?: RAGFlowNodeType) { - const tools: IAgentForm['mcp'] = get(agentNode, 'data.form.mcp', []); - return tools; -} - export function mapEdgeMouseEvent( edges: Edge[], edgeId: string, @@ -552,21 +443,3 @@ export function mapEdgeMouseEvent( return nextEdges; } - -export function buildBeginQueryWithObject( - inputs: Record, - values: BeginQuery[], -) { - const nextInputs = Object.keys(inputs).reduce>( - (pre, key) => { - const item = values.find((x) => x.key === key); - if (item) { - pre[key] = { ...item }; - } - return pre; - }, - {}, - ); - - return nextInputs; -}