mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-22 14:16:42 +08:00
Feat: Allows the extractor operator's prompt to reference the output of an upstream operator #9869 (#10279)
### What problem does this PR solve? Feat: Allows the extractor operator's prompt to reference the output of an upstream operator #9869 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
@ -1705,9 +1705,9 @@ This delimiter is used to split the input text into several text pieces echo of
|
|||||||
exportJson: 'Export JSON',
|
exportJson: 'Export JSON',
|
||||||
viewResult: 'View Result',
|
viewResult: 'View Result',
|
||||||
running: 'Running',
|
running: 'Running',
|
||||||
context: 'Context Generator',
|
extractor: 'Extractor',
|
||||||
contextDescription: 'Context Generator',
|
extractorDescription: 'Extractor',
|
||||||
summary: 'Summary',
|
summary: 'Augmented Context',
|
||||||
keywords: 'Keywords',
|
keywords: 'Keywords',
|
||||||
questions: 'Questions',
|
questions: 'Questions',
|
||||||
metadata: 'Metadata',
|
metadata: 'Metadata',
|
||||||
|
|||||||
@ -1623,9 +1623,9 @@ General:实体和关系提取提示来自 GitHub - microsoft/graphrag:基于
|
|||||||
exportJson: '导出 JSON',
|
exportJson: '导出 JSON',
|
||||||
viewResult: '查看结果',
|
viewResult: '查看结果',
|
||||||
running: '运行中',
|
running: '运行中',
|
||||||
context: '上下文生成器',
|
extractor: '提取器',
|
||||||
contextDescription: '上下文生成器',
|
extractorDescription: '提取器',
|
||||||
summary: '摘要',
|
summary: '增强上下文',
|
||||||
keywords: '关键词',
|
keywords: '关键词',
|
||||||
questions: '问题',
|
questions: '问题',
|
||||||
metadata: '元数据',
|
metadata: '元数据',
|
||||||
|
|||||||
@ -55,7 +55,7 @@ type IProps = {
|
|||||||
onChange?: (value?: string) => void;
|
onChange?: (value?: string) => void;
|
||||||
placeholder?: ReactNode;
|
placeholder?: ReactNode;
|
||||||
} & PromptContentProps &
|
} & PromptContentProps &
|
||||||
Pick<VariablePickerMenuPluginProps, 'extraOptions'>;
|
Pick<VariablePickerMenuPluginProps, 'extraOptions' | 'baseOptions'>;
|
||||||
|
|
||||||
function PromptContent({
|
function PromptContent({
|
||||||
showToolbar = true,
|
showToolbar = true,
|
||||||
@ -126,6 +126,7 @@ export function PromptEditor({
|
|||||||
showToolbar,
|
showToolbar,
|
||||||
multiLine = true,
|
multiLine = true,
|
||||||
extraOptions,
|
extraOptions,
|
||||||
|
baseOptions,
|
||||||
}: IProps) {
|
}: IProps) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const initialConfig: InitialConfigType = {
|
const initialConfig: InitialConfigType = {
|
||||||
@ -177,6 +178,7 @@ export function PromptEditor({
|
|||||||
<VariablePickerMenuPlugin
|
<VariablePickerMenuPlugin
|
||||||
value={value}
|
value={value}
|
||||||
extraOptions={extraOptions}
|
extraOptions={extraOptions}
|
||||||
|
baseOptions={baseOptions}
|
||||||
></VariablePickerMenuPlugin>
|
></VariablePickerMenuPlugin>
|
||||||
<PasteHandlerPlugin />
|
<PasteHandlerPlugin />
|
||||||
<VariableOnChangePlugin
|
<VariableOnChangePlugin
|
||||||
|
|||||||
@ -109,17 +109,26 @@ function VariablePickerMenuItem({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type VariablePickerMenuOptionType = {
|
||||||
|
label: string;
|
||||||
|
title: string;
|
||||||
|
value?: string;
|
||||||
|
options: Array<{
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
icon: ReactNode;
|
||||||
|
}>;
|
||||||
|
};
|
||||||
|
|
||||||
export type VariablePickerMenuPluginProps = {
|
export type VariablePickerMenuPluginProps = {
|
||||||
value?: string;
|
value?: string;
|
||||||
extraOptions?: Array<{
|
extraOptions?: VariablePickerMenuOptionType[];
|
||||||
label: string;
|
baseOptions?: VariablePickerMenuOptionType[];
|
||||||
title: string;
|
|
||||||
options: Array<{ label: string; value: string; icon?: ReactNode }>;
|
|
||||||
}>;
|
|
||||||
};
|
};
|
||||||
export default function VariablePickerMenuPlugin({
|
export default function VariablePickerMenuPlugin({
|
||||||
value,
|
value,
|
||||||
extraOptions,
|
extraOptions,
|
||||||
|
baseOptions,
|
||||||
}: VariablePickerMenuPluginProps): JSX.Element {
|
}: VariablePickerMenuPluginProps): JSX.Element {
|
||||||
const [editor] = useLexicalComposerContext();
|
const [editor] = useLexicalComposerContext();
|
||||||
const isFirstRender = useRef(true);
|
const isFirstRender = useRef(true);
|
||||||
@ -132,6 +141,10 @@ export default function VariablePickerMenuPlugin({
|
|||||||
|
|
||||||
let options = useBuildQueryVariableOptions();
|
let options = useBuildQueryVariableOptions();
|
||||||
|
|
||||||
|
if (baseOptions) {
|
||||||
|
options = baseOptions as typeof options;
|
||||||
|
}
|
||||||
|
|
||||||
const buildNextOptions = useCallback(() => {
|
const buildNextOptions = useCallback(() => {
|
||||||
let filteredOptions = [...options, ...(extraOptions ?? [])];
|
let filteredOptions = [...options, ...(extraOptions ?? [])];
|
||||||
if (queryString) {
|
if (queryString) {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
|
import { buildOutputOptions } from '@/utils/canvas-util';
|
||||||
import { isEmpty } from 'lodash';
|
import { isEmpty } from 'lodash';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { Operator } from '../../constant';
|
import { Operator } from '../../constant';
|
||||||
import { buildOutputOptions } from '../../hooks/use-get-begin-query';
|
|
||||||
import useGraphStore from '../../store';
|
import useGraphStore from '../../store';
|
||||||
|
|
||||||
export function useBuildSubNodeOutputOptions(nodeId?: string) {
|
export function useBuildSubNodeOutputOptions(nodeId?: string) {
|
||||||
|
|||||||
@ -1,19 +1,11 @@
|
|||||||
import { AgentGlobals } from '@/constants/agent';
|
import { AgentGlobals } from '@/constants/agent';
|
||||||
import { useFetchAgent } from '@/hooks/use-agent-request';
|
import { useFetchAgent } from '@/hooks/use-agent-request';
|
||||||
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
import { RAGFlowNodeType } from '@/interfaces/database/flow';
|
||||||
import { Edge } from '@xyflow/react';
|
import { buildNodeOutputOptions } from '@/utils/canvas-util';
|
||||||
import { DefaultOptionType } from 'antd/es/select';
|
import { DefaultOptionType } from 'antd/es/select';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { isEmpty } from 'lodash';
|
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
import {
|
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||||
ReactNode,
|
|
||||||
useCallback,
|
|
||||||
useContext,
|
|
||||||
useEffect,
|
|
||||||
useMemo,
|
|
||||||
useState,
|
|
||||||
} from 'react';
|
|
||||||
import {
|
import {
|
||||||
AgentDialogueMode,
|
AgentDialogueMode,
|
||||||
BeginId,
|
BeginId,
|
||||||
@ -83,72 +75,18 @@ export const useGetBeginNodeDataQueryIsSafe = () => {
|
|||||||
return isBeginNodeDataQuerySafe;
|
return isBeginNodeDataQuerySafe;
|
||||||
};
|
};
|
||||||
|
|
||||||
function filterAllUpstreamNodeIds(edges: Edge[], nodeIds: string[]) {
|
|
||||||
return nodeIds.reduce<string[]>((pre, nodeId) => {
|
|
||||||
const currentEdges = edges.filter((x) => x.target === nodeId);
|
|
||||||
|
|
||||||
const upstreamNodeIds: string[] = currentEdges.map((x) => x.source);
|
|
||||||
|
|
||||||
const ids = upstreamNodeIds.concat(
|
|
||||||
filterAllUpstreamNodeIds(edges, upstreamNodeIds),
|
|
||||||
);
|
|
||||||
|
|
||||||
ids.forEach((x) => {
|
|
||||||
if (pre.every((y) => y !== x)) {
|
|
||||||
pre.push(x);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return pre;
|
|
||||||
}, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildOutputOptions(
|
|
||||||
outputs: Record<string, any> = {},
|
|
||||||
nodeId?: string,
|
|
||||||
parentLabel?: string | ReactNode,
|
|
||||||
icon?: ReactNode,
|
|
||||||
) {
|
|
||||||
return Object.keys(outputs).map((x) => ({
|
|
||||||
label: x,
|
|
||||||
value: `${nodeId}@${x}`,
|
|
||||||
parentLabel,
|
|
||||||
icon,
|
|
||||||
type: outputs[x]?.type,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useBuildNodeOutputOptions(nodeId?: string) {
|
export function useBuildNodeOutputOptions(nodeId?: string) {
|
||||||
const nodes = useGraphStore((state) => state.nodes);
|
const nodes = useGraphStore((state) => state.nodes);
|
||||||
const edges = useGraphStore((state) => state.edges);
|
const edges = useGraphStore((state) => state.edges);
|
||||||
|
|
||||||
const nodeOutputOptions = useMemo(() => {
|
return useMemo(() => {
|
||||||
if (!nodeId) {
|
return buildNodeOutputOptions({
|
||||||
return [];
|
nodes,
|
||||||
}
|
edges,
|
||||||
const upstreamIds = filterAllUpstreamNodeIds(edges, [nodeId]);
|
nodeId,
|
||||||
|
Icon: ({ name }) => <OperatorIcon name={name as Operator}></OperatorIcon>,
|
||||||
const nodeWithOutputList = nodes.filter(
|
});
|
||||||
(x) =>
|
|
||||||
upstreamIds.some((y) => y === x.id) && !isEmpty(x.data?.form?.outputs),
|
|
||||||
);
|
|
||||||
|
|
||||||
return nodeWithOutputList
|
|
||||||
.filter((x) => x.id !== nodeId)
|
|
||||||
.map((x) => ({
|
|
||||||
label: x.data.name,
|
|
||||||
value: x.id,
|
|
||||||
title: x.data.name,
|
|
||||||
options: buildOutputOptions(
|
|
||||||
x.data.form.outputs,
|
|
||||||
x.id,
|
|
||||||
x.data.name,
|
|
||||||
<OperatorIcon name={x.data.label as Operator} />,
|
|
||||||
),
|
|
||||||
}));
|
|
||||||
}, [edges, nodeId, nodes]);
|
}, [edges, nodeId, nodes]);
|
||||||
|
|
||||||
return nodeOutputOptions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// exclude nodes with branches
|
// exclude nodes with branches
|
||||||
|
|||||||
@ -124,7 +124,7 @@ function AccordionOperators({
|
|||||||
Operator.Tokenizer,
|
Operator.Tokenizer,
|
||||||
Operator.Splitter,
|
Operator.Splitter,
|
||||||
Operator.HierarchicalMerger,
|
Operator.HierarchicalMerger,
|
||||||
Operator.Context,
|
Operator.Extractor,
|
||||||
]}
|
]}
|
||||||
isCustomDropdown={isCustomDropdown}
|
isCustomDropdown={isCustomDropdown}
|
||||||
mousePosition={mousePosition}
|
mousePosition={mousePosition}
|
||||||
|
|||||||
@ -119,7 +119,7 @@ export enum Operator {
|
|||||||
Tokenizer = 'Tokenizer',
|
Tokenizer = 'Tokenizer',
|
||||||
Splitter = 'Splitter',
|
Splitter = 'Splitter',
|
||||||
HierarchicalMerger = 'HierarchicalMerger',
|
HierarchicalMerger = 'HierarchicalMerger',
|
||||||
Context = 'Context',
|
Extractor = 'Extractor',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SwitchLogicOperatorOptions = ['and', 'or'];
|
export const SwitchLogicOperatorOptions = ['and', 'or'];
|
||||||
@ -291,7 +291,7 @@ export const initialHierarchicalMergerValues = {
|
|||||||
|
|
||||||
export const initialContextValues = {
|
export const initialContextValues = {
|
||||||
...initialLlmBaseValues,
|
...initialLlmBaseValues,
|
||||||
field_name: [ContextGeneratorFieldName.Summary],
|
field_name: ContextGeneratorFieldName.Summary,
|
||||||
outputs: {},
|
outputs: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -327,7 +327,7 @@ export const NodeMap = {
|
|||||||
[Operator.Tokenizer]: 'tokenizerNode',
|
[Operator.Tokenizer]: 'tokenizerNode',
|
||||||
[Operator.Splitter]: 'splitterNode',
|
[Operator.Splitter]: 'splitterNode',
|
||||||
[Operator.HierarchicalMerger]: 'hierarchicalMergerNode',
|
[Operator.HierarchicalMerger]: 'hierarchicalMergerNode',
|
||||||
[Operator.Context]: 'contextNode',
|
[Operator.Extractor]: 'contextNode',
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum BeginQueryType {
|
export enum BeginQueryType {
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { Operator } from '../constant';
|
import { Operator } from '../constant';
|
||||||
import ContextForm from '../form/context-form';
|
import ExtractorForm from '../form/extractor-form';
|
||||||
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
|
import HierarchicalMergerForm from '../form/hierarchical-merger-form';
|
||||||
import ParserForm from '../form/parser-form';
|
import ParserForm from '../form/parser-form';
|
||||||
import SplitterForm from '../form/splitter-form';
|
import SplitterForm from '../form/splitter-form';
|
||||||
@ -24,7 +24,7 @@ export const FormConfigMap = {
|
|||||||
[Operator.HierarchicalMerger]: {
|
[Operator.HierarchicalMerger]: {
|
||||||
component: HierarchicalMergerForm,
|
component: HierarchicalMergerForm,
|
||||||
},
|
},
|
||||||
[Operator.Context]: {
|
[Operator.Extractor]: {
|
||||||
component: ContextForm,
|
component: ExtractorForm,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import { LargeModelFormField } from '@/components/large-model-form-field';
|
import { LargeModelFormField } from '@/components/large-model-form-field';
|
||||||
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
|
||||||
|
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||||
import { Form } from '@/components/ui/form';
|
import { Form } from '@/components/ui/form';
|
||||||
import { MultiSelect } from '@/components/ui/multi-select';
|
|
||||||
import { useBuildPromptExtraPromptOptions } from '@/pages/agent/form/agent-form/use-build-prompt-options';
|
|
||||||
import { PromptEditor } from '@/pages/agent/form/components/prompt-editor';
|
import { PromptEditor } from '@/pages/agent/form/components/prompt-editor';
|
||||||
import { buildOptions } from '@/utils/form';
|
import { buildOptions } from '@/utils/form';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
@ -15,15 +14,11 @@ import {
|
|||||||
ContextGeneratorFieldName,
|
ContextGeneratorFieldName,
|
||||||
initialContextValues,
|
initialContextValues,
|
||||||
} from '../../constant';
|
} from '../../constant';
|
||||||
|
import { useBuildNodeOutputOptions } from '../../hooks/use-build-options';
|
||||||
import { useFormValues } from '../../hooks/use-form-values';
|
import { useFormValues } from '../../hooks/use-form-values';
|
||||||
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
import { useWatchFormChange } from '../../hooks/use-watch-form-change';
|
||||||
import { INextOperatorForm } from '../../interface';
|
import { INextOperatorForm } from '../../interface';
|
||||||
import useGraphStore from '../../store';
|
|
||||||
import { buildOutputList } from '../../utils/build-output-list';
|
|
||||||
import { FormWrapper } from '../components/form-wrapper';
|
import { FormWrapper } from '../components/form-wrapper';
|
||||||
import { Output } from '../components/output';
|
|
||||||
|
|
||||||
const outputList = buildOutputList(initialContextValues.outputs);
|
|
||||||
|
|
||||||
export const FormSchema = z.object({
|
export const FormSchema = z.object({
|
||||||
sys_prompt: z.string(),
|
sys_prompt: z.string(),
|
||||||
@ -32,20 +27,18 @@ export const FormSchema = z.object({
|
|||||||
field_name: z.array(z.string()),
|
field_name: z.array(z.string()),
|
||||||
});
|
});
|
||||||
|
|
||||||
export type ContextFormSchemaType = z.infer<typeof FormSchema>;
|
export type ExtractorFormSchemaType = z.infer<typeof FormSchema>;
|
||||||
|
|
||||||
const ContextForm = ({ node }: INextOperatorForm) => {
|
const ExtractorForm = ({ node }: INextOperatorForm) => {
|
||||||
const defaultValues = useFormValues(initialContextValues, node);
|
const defaultValues = useFormValues(initialContextValues, node);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const form = useForm<ContextFormSchemaType>({
|
const form = useForm<ExtractorFormSchemaType>({
|
||||||
defaultValues,
|
defaultValues,
|
||||||
resolver: zodResolver(FormSchema),
|
resolver: zodResolver(FormSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { edges } = useGraphStore((state) => state);
|
const promptOptions = useBuildNodeOutputOptions(node?.id);
|
||||||
|
|
||||||
const { extraOptions } = useBuildPromptExtraPromptOptions(edges, node?.id);
|
|
||||||
|
|
||||||
const options = buildOptions(ContextGeneratorFieldName, t, 'dataflow');
|
const options = buildOptions(ContextGeneratorFieldName, t, 'dataflow');
|
||||||
|
|
||||||
@ -55,32 +48,31 @@ const ContextForm = ({ node }: INextOperatorForm) => {
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<FormWrapper>
|
<FormWrapper>
|
||||||
<LargeModelFormField></LargeModelFormField>
|
<LargeModelFormField></LargeModelFormField>
|
||||||
|
<RAGFlowFormItem label={t('dataflow.fieldName')} name="field_name">
|
||||||
|
{(field) => (
|
||||||
|
<SelectWithSearch
|
||||||
|
{...field}
|
||||||
|
placeholder={t('dataFlowPlaceholder')}
|
||||||
|
options={options}
|
||||||
|
></SelectWithSearch>
|
||||||
|
)}
|
||||||
|
</RAGFlowFormItem>
|
||||||
<RAGFlowFormItem label={t('flow.systemPrompt')} name="sys_prompt">
|
<RAGFlowFormItem label={t('flow.systemPrompt')} name="sys_prompt">
|
||||||
<PromptEditor
|
<PromptEditor
|
||||||
placeholder={t('flow.messagePlaceholder')}
|
placeholder={t('flow.messagePlaceholder')}
|
||||||
showToolbar={true}
|
showToolbar={true}
|
||||||
extraOptions={extraOptions}
|
baseOptions={promptOptions}
|
||||||
></PromptEditor>
|
></PromptEditor>
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
<RAGFlowFormItem label={t('flow.userPrompt')} name="prompts">
|
<RAGFlowFormItem label={t('flow.userPrompt')} name="prompts">
|
||||||
<PromptEditor showToolbar={true}></PromptEditor>
|
<PromptEditor
|
||||||
</RAGFlowFormItem>
|
showToolbar={true}
|
||||||
<RAGFlowFormItem label={t('dataflow.fieldName')} name="field_name">
|
baseOptions={promptOptions}
|
||||||
{(field) => (
|
></PromptEditor>
|
||||||
<MultiSelect
|
|
||||||
onValueChange={field.onChange}
|
|
||||||
placeholder={t('dataFlowPlaceholder')}
|
|
||||||
defaultValue={field.value}
|
|
||||||
options={options}
|
|
||||||
></MultiSelect>
|
|
||||||
)}
|
|
||||||
</RAGFlowFormItem>
|
</RAGFlowFormItem>
|
||||||
</FormWrapper>
|
</FormWrapper>
|
||||||
<div className="p-5">
|
|
||||||
<Output list={outputList}></Output>
|
|
||||||
</div>
|
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(ContextForm);
|
export default memo(ExtractorForm);
|
||||||
@ -33,7 +33,7 @@ export const useInitializeOperatorParams = () => {
|
|||||||
[Operator.Tokenizer]: initialTokenizerValues,
|
[Operator.Tokenizer]: initialTokenizerValues,
|
||||||
[Operator.Splitter]: initialSplitterValues,
|
[Operator.Splitter]: initialSplitterValues,
|
||||||
[Operator.HierarchicalMerger]: initialHierarchicalMergerValues,
|
[Operator.HierarchicalMerger]: initialHierarchicalMergerValues,
|
||||||
[Operator.Context]: { ...initialContextValues, llm_id: llmId },
|
[Operator.Extractor]: { ...initialContextValues, llm_id: llmId },
|
||||||
};
|
};
|
||||||
}, [llmId]);
|
}, [llmId]);
|
||||||
|
|
||||||
|
|||||||
19
web/src/pages/data-flow/hooks/use-build-options.tsx
Normal file
19
web/src/pages/data-flow/hooks/use-build-options.tsx
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { buildNodeOutputOptions } from '@/utils/canvas-util';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { Operator } from '../constant';
|
||||||
|
import OperatorIcon from '../operator-icon';
|
||||||
|
import useGraphStore from '../store';
|
||||||
|
|
||||||
|
export function useBuildNodeOutputOptions(nodeId?: string) {
|
||||||
|
const nodes = useGraphStore((state) => state.nodes);
|
||||||
|
const edges = useGraphStore((state) => state.edges);
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
return buildNodeOutputOptions({
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
nodeId,
|
||||||
|
Icon: ({ name }) => <OperatorIcon name={name as Operator}></OperatorIcon>,
|
||||||
|
});
|
||||||
|
}, [edges, nodeId, nodes]);
|
||||||
|
}
|
||||||
@ -25,7 +25,7 @@ export const SVGIconMap = {
|
|||||||
[Operator.Tokenizer]: ListMinus,
|
[Operator.Tokenizer]: ListMinus,
|
||||||
[Operator.Splitter]: Blocks,
|
[Operator.Splitter]: Blocks,
|
||||||
[Operator.HierarchicalMerger]: Heading,
|
[Operator.HierarchicalMerger]: Heading,
|
||||||
[Operator.Context]: FileStack,
|
[Operator.Extractor]: FileStack,
|
||||||
};
|
};
|
||||||
|
|
||||||
const Empty = () => {
|
const Empty = () => {
|
||||||
|
|||||||
75
web/src/utils/canvas-util.tsx
Normal file
75
web/src/utils/canvas-util.tsx
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { BaseNode } from '@/interfaces/database/agent';
|
||||||
|
import { Edge } from '@xyflow/react';
|
||||||
|
import { isEmpty } from 'lodash';
|
||||||
|
import { ComponentType, ReactNode } from 'react';
|
||||||
|
|
||||||
|
export function filterAllUpstreamNodeIds(edges: Edge[], nodeIds: string[]) {
|
||||||
|
return nodeIds.reduce<string[]>((pre, nodeId) => {
|
||||||
|
const currentEdges = edges.filter((x) => x.target === nodeId);
|
||||||
|
|
||||||
|
const upstreamNodeIds: string[] = currentEdges.map((x) => x.source);
|
||||||
|
|
||||||
|
const ids = upstreamNodeIds.concat(
|
||||||
|
filterAllUpstreamNodeIds(edges, upstreamNodeIds),
|
||||||
|
);
|
||||||
|
|
||||||
|
ids.forEach((x) => {
|
||||||
|
if (pre.every((y) => y !== x)) {
|
||||||
|
pre.push(x);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return pre;
|
||||||
|
}, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildOutputOptions(
|
||||||
|
outputs: Record<string, any> = {},
|
||||||
|
nodeId?: string,
|
||||||
|
parentLabel?: string | ReactNode,
|
||||||
|
icon?: ReactNode,
|
||||||
|
) {
|
||||||
|
return Object.keys(outputs).map((x) => ({
|
||||||
|
label: x,
|
||||||
|
value: `${nodeId}@${x}`,
|
||||||
|
parentLabel,
|
||||||
|
icon,
|
||||||
|
type: outputs[x]?.type,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildNodeOutputOptions({
|
||||||
|
nodes,
|
||||||
|
edges,
|
||||||
|
nodeId,
|
||||||
|
Icon,
|
||||||
|
}: {
|
||||||
|
nodes: BaseNode[];
|
||||||
|
edges: Edge[];
|
||||||
|
nodeId?: string;
|
||||||
|
Icon: ComponentType<{ name: string }>;
|
||||||
|
}) {
|
||||||
|
if (!nodeId) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const upstreamIds = filterAllUpstreamNodeIds(edges, [nodeId]);
|
||||||
|
|
||||||
|
const nodeWithOutputList = nodes.filter(
|
||||||
|
(x) =>
|
||||||
|
upstreamIds.some((y) => y === x.id) && !isEmpty(x.data?.form?.outputs),
|
||||||
|
);
|
||||||
|
|
||||||
|
return nodeWithOutputList
|
||||||
|
.filter((x) => x.id !== nodeId)
|
||||||
|
.map((x) => ({
|
||||||
|
label: x.data.name,
|
||||||
|
value: x.id,
|
||||||
|
title: x.data.name,
|
||||||
|
options: buildOutputOptions(
|
||||||
|
x.data.form.outputs,
|
||||||
|
x.id,
|
||||||
|
x.data.name,
|
||||||
|
<Icon name={x.data.name} />,
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user