Feat: Add VariablePickerMenuPlugin to select variables in the prompt text box by menu #4764 (#4765)

### What problem does this PR solve?

Feat: Add VariablePickerMenuPlugin to select variables in the prompt
text box by menu #4764

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-02-08 18:09:13 +08:00
committed by GitHub
parent f64ae9dc33
commit bfcc2abe47
19 changed files with 1058 additions and 126 deletions

View File

@ -2,11 +2,8 @@ import LLMLabel from '@/components/llm-select/llm-label';
import { useTheme } from '@/components/theme-provider';
import { IGenerateNode } from '@/interfaces/database/flow';
import { Handle, NodeProps, Position } from '@xyflow/react';
import { Flex } from 'antd';
import classNames from 'classnames';
import { get } from 'lodash';
import { useGetComponentLabelByValue } from '../../hooks/use-get-begin-query';
import { IGenerateParameter } from '../../interface';
import { LeftHandleStyle, RightHandleStyle } from './handle-icon';
import styles from './index.less';
import NodeHeader from './node-header';
@ -17,8 +14,6 @@ export function GenerateNode({
isConnectable = true,
selected,
}: NodeProps<IGenerateNode>) {
const parameters: IGenerateParameter[] = get(data, 'form.parameters', []);
const getLabel = useGetComponentLabelByValue(id);
const { theme } = useTheme();
return (
<section
@ -57,21 +52,6 @@ export function GenerateNode({
<div className={styles.nodeText}>
<LLMLabel value={get(data, 'form.llm_id')}></LLMLabel>
</div>
<Flex gap={8} vertical className={styles.generateParameters}>
{parameters.map((x) => (
<Flex
key={x.id}
align="center"
gap={6}
className={styles.conditionBlock}
>
<label htmlFor="">{x.key}</label>
<span className={styles.parameterValue}>
{getLabel(x.component_id)}
</span>
</Flex>
))}
</Flex>
</section>
);
}

View File

@ -671,7 +671,6 @@ export const RestrictedUpstreamMap = {
Operator.Message,
Operator.Generate,
Operator.RewriteQuestion,
Operator.Categorize,
Operator.Relevant,
],
[Operator.KeywordExtract]: [

View File

@ -0,0 +1,6 @@
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { createContext } from 'react';
export const FlowFormContext = createContext<RAGFlowNodeType | undefined>(
undefined,
);

View File

@ -2,7 +2,7 @@ import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { CloseOutlined } from '@ant-design/icons';
import { Drawer, Flex, Form, Input } from 'antd';
import { lowerFirst } from 'lodash';
import { get, isPlainObject, lowerFirst } from 'lodash';
import { Play } from 'lucide-react';
import { useEffect, useRef } from 'react';
import { BeginId, Operator, operatorMap } from '../constant';
@ -40,10 +40,15 @@ import WikipediaForm from '../form/wikipedia-form';
import YahooFinanceForm from '../form/yahoo-finance-form';
import { useHandleFormValuesChange, useHandleNodeNameChange } from '../hooks';
import OperatorIcon from '../operator-icon';
import { getDrawerWidth, needsSingleStepDebugging } from '../utils';
import {
buildCategorizeListFromObject,
getDrawerWidth,
needsSingleStepDebugging,
} from '../utils';
import SingleDebugDrawer from './single-debug-drawer';
import { RAGFlowNodeType } from '@/interfaces/database/flow';
import { FlowFormContext } from '../context';
import { RunTooltip } from '../flow-tooltip';
import IterationForm from '../form/iteration-from';
import styles from './index.less';
@ -122,10 +127,21 @@ const FormDrawer = ({
if (node?.id !== previousId.current) {
form.resetFields();
}
form.setFieldsValue(node?.data?.form);
if (operatorName === Operator.Categorize) {
const items = buildCategorizeListFromObject(
get(node, 'data.form.category_description', {}),
);
const formData = node?.data?.form;
if (isPlainObject(formData)) {
form.setFieldsValue({ ...formData, items });
}
} else {
form.setFieldsValue(node?.data?.form);
}
previousId.current = node?.id;
}
}, [visible, form, node?.data?.form, node?.id]);
}, [visible, form, node?.data?.form, node?.id, node, operatorName]);
return (
<Drawer
@ -176,11 +192,13 @@ const FormDrawer = ({
>
<section className={styles.formWrapper}>
{visible && (
<OperatorForm
onValuesChange={handleValuesChange}
form={form}
node={node}
></OperatorForm>
<FlowFormContext.Provider value={node}>
<OperatorForm
onValuesChange={handleValuesChange}
form={form}
node={node}
></OperatorForm>
</FlowFormContext.Provider>
)}
</section>
{singleDebugDrawerVisible && (

View File

@ -1,38 +1,10 @@
import get from 'lodash/get';
import omit from 'lodash/omit';
import { useCallback, useEffect } from 'react';
import {
ICategorizeItem,
ICategorizeItemResult,
IOperatorForm,
} from '../../interface';
import useGraphStore from '../../store';
/**
* 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"
}
}
*/
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<Array<ICategorizeItem>>((pre, cur) => {
// synchronize edge data to the to field
pre.push({ name: cur, ...categorizeItem[cur] });
return pre;
}, [])
.sort((a, b) => a.index - b.index);
};
} from '@/interfaces/database/flow';
import omit from 'lodash/omit';
import { useCallback } from 'react';
import { IOperatorForm } from '../../interface';
/**
* Convert the list in the following form into an object
@ -58,12 +30,7 @@ const buildCategorizeObjectFromList = (list: Array<ICategorizeItem>) => {
export const useHandleFormValuesChange = ({
onValuesChange,
form,
nodeId,
}: IOperatorForm) => {
const getNode = useGraphStore((state) => state.getNode);
const node = getNode(nodeId);
const handleValuesChange = useCallback(
(changedValues: any, values: any) => {
onValuesChange?.(changedValues, {
@ -74,14 +41,5 @@ export const useHandleFormValuesChange = ({
[onValuesChange],
);
useEffect(() => {
const items = buildCategorizeListFromObject(
get(node, 'data.form.category_description', {}),
);
form?.setFieldsValue({
items,
});
}, [form, node]);
return { handleValuesChange };
};

View File

@ -1,11 +1,11 @@
import LLMSelect from '@/components/llm-select';
import MessageHistoryWindowSizeItem from '@/components/message-history-window-size-item';
import { PromptEditor } from '@/components/prompt-editor';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input, Switch } from 'antd';
import { Form, Switch } from 'antd';
import { IOperatorForm } from '../../interface';
import DynamicParameters from './dynamic-parameters';
const GenerateForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
const { t } = useTranslate('flow');
return (
@ -35,7 +35,7 @@ const GenerateForm = ({ onValuesChange, form, node }: IOperatorForm) => {
},
]}
>
<Input.TextArea rows={8} />
<PromptEditor></PromptEditor>
</Form.Item>
<Form.Item
name={['cite']}
@ -49,7 +49,6 @@ const GenerateForm = ({ onValuesChange, form, node }: IOperatorForm) => {
<MessageHistoryWindowSizeItem
initialValue={12}
></MessageHistoryWindowSizeItem>
<DynamicParameters node={node}></DynamicParameters>
</Form>
);
};

View File

@ -1,9 +1,9 @@
import { Form, Input } from 'antd';
import { PromptEditor } from '@/components/prompt-editor';
import { Form } from 'antd';
import { useTranslation } from 'react-i18next';
import { IOperatorForm } from '../../interface';
import DynamicParameters from '../generate-form/dynamic-parameters';
const TemplateForm = ({ onValuesChange, form, node }: IOperatorForm) => {
const TemplateForm = ({ onValuesChange, form }: IOperatorForm) => {
const { t } = useTranslation();
return (
@ -15,10 +15,8 @@ const TemplateForm = ({ onValuesChange, form, node }: IOperatorForm) => {
layout={'vertical'}
>
<Form.Item name={['content']} label={t('flow.content')}>
<Input.TextArea rows={8} placeholder={t('flow.blank')} />
<PromptEditor></PromptEditor>
</Form.Item>
<DynamicParameters node={node}></DynamicParameters>
</Form>
);
};

View File

@ -1,5 +1,6 @@
import {
DSLComponents,
ICategorizeItem,
ICategorizeItemResult,
RAGFlowNodeType,
} from '@/interfaces/database/flow';
@ -389,3 +390,29 @@ export const generateDuplicateNode = (
dragHandle: getNodeDragHandle(label),
};
};
/**
* 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<Array<ICategorizeItem>>((pre, cur) => {
// synchronize edge data to the to field
pre.push({ name: cur, ...categorizeItem[cur] });
return pre;
}, [])
.sort((a, b) => a.index - b.index);
};