balibabu commited on
Commit
4a1cf8b
·
1 Parent(s): 64b1da0

feat: add DynamicParameters #918 (#1346)

Browse files

### What problem does this PR solve?

feat: add DynamicParameters #918

### Type of change


- [x] New Feature (non-breaking change which adds functionality)

web/src/components/editable-cell.tsx ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Form, FormInstance, Input, InputRef } from 'antd';
2
+ import React, { useContext, useEffect, useRef, useState } from 'react';
3
+
4
+ const EditableContext = React.createContext<FormInstance<any> | null>(null);
5
+
6
+ interface EditableRowProps {
7
+ index: number;
8
+ }
9
+
10
+ interface Item {
11
+ key: string;
12
+ name: string;
13
+ age: string;
14
+ address: string;
15
+ }
16
+
17
+ export const EditableRow: React.FC<EditableRowProps> = ({
18
+ index,
19
+ ...props
20
+ }) => {
21
+ const [form] = Form.useForm();
22
+ return (
23
+ <Form form={form} component={false}>
24
+ <EditableContext.Provider value={form}>
25
+ <tr {...props} />
26
+ </EditableContext.Provider>
27
+ </Form>
28
+ );
29
+ };
30
+
31
+ interface EditableCellProps {
32
+ title: React.ReactNode;
33
+ editable: boolean;
34
+ children: React.ReactNode;
35
+ dataIndex: keyof Item;
36
+ record: Item;
37
+ handleSave: (record: Item) => void;
38
+ }
39
+
40
+ export const EditableCell: React.FC<EditableCellProps> = ({
41
+ title,
42
+ editable,
43
+ children,
44
+ dataIndex,
45
+ record,
46
+ handleSave,
47
+ ...restProps
48
+ }) => {
49
+ const [editing, setEditing] = useState(false);
50
+ const inputRef = useRef<InputRef>(null);
51
+ const form = useContext(EditableContext)!;
52
+
53
+ useEffect(() => {
54
+ if (editing) {
55
+ inputRef.current!.focus();
56
+ }
57
+ }, [editing]);
58
+
59
+ const toggleEdit = () => {
60
+ setEditing(!editing);
61
+ form.setFieldsValue({ [dataIndex]: record[dataIndex] });
62
+ };
63
+
64
+ const save = async () => {
65
+ try {
66
+ const values = await form.validateFields();
67
+
68
+ toggleEdit();
69
+ handleSave({ ...record, ...values });
70
+ } catch (errInfo) {
71
+ console.log('Save failed:', errInfo);
72
+ }
73
+ };
74
+
75
+ let childNode = children;
76
+
77
+ if (editable) {
78
+ childNode = editing ? (
79
+ <Form.Item
80
+ style={{ margin: 0 }}
81
+ name={dataIndex}
82
+ rules={[
83
+ {
84
+ required: true,
85
+ message: `${title} is required.`,
86
+ },
87
+ ]}
88
+ >
89
+ <Input ref={inputRef} onPressEnter={save} onBlur={save} />
90
+ </Form.Item>
91
+ ) : (
92
+ <div
93
+ className="editable-cell-value-wrap"
94
+ style={{ paddingRight: 24 }}
95
+ onClick={toggleEdit}
96
+ >
97
+ {children}
98
+ </div>
99
+ );
100
+ }
101
+
102
+ return <td {...restProps}>{childNode}</td>;
103
+ };
web/src/locales/en.ts CHANGED
@@ -560,6 +560,10 @@ The above is the content you need to summarize.`,
560
  createFlow: 'Create a workflow',
561
  yes: 'Yes',
562
  no: 'No',
 
 
 
 
563
  },
564
  footer: {
565
  profile: 'All rights reserved @ React',
 
560
  createFlow: 'Create a workflow',
561
  yes: 'Yes',
562
  no: 'No',
563
+ key: 'key',
564
+ componentId: 'component id',
565
+ add: 'Add',
566
+ operation: 'operation',
567
  },
568
  footer: {
569
  profile: 'All rights reserved @ React',
web/src/pages/flow/form-hooks.ts CHANGED
@@ -6,6 +6,7 @@ const ExcludedNodesMap = {
6
  // exclude some nodes downstream of the classification node
7
  [Operator.Categorize]: [Operator.Categorize, Operator.Answer, Operator.Begin],
8
  [Operator.Relevant]: [Operator.Begin],
 
9
  };
10
 
11
  export const useBuildFormSelectOptions = (
 
6
  // exclude some nodes downstream of the classification node
7
  [Operator.Categorize]: [Operator.Categorize, Operator.Answer, Operator.Begin],
8
  [Operator.Relevant]: [Operator.Begin],
9
+ [Operator.Generate]: [Operator.Begin],
10
  };
11
 
12
  export const useBuildFormSelectOptions = (
web/src/pages/flow/generate-form/dynamic-parameters.tsx ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useTranslate } from '@/hooks/commonHooks';
2
+ import { CloseOutlined } from '@ant-design/icons';
3
+ import { Button, Card, Form, Input, Select, Typography } from 'antd';
4
+ import { useUpdateNodeInternals } from 'reactflow';
5
+ import { Operator } from '../constant';
6
+ import {
7
+ useBuildFormSelectOptions,
8
+ useHandleFormSelectChange,
9
+ } from '../form-hooks';
10
+ import { IGenerateParameter } from '../interface';
11
+
12
+ interface IProps {
13
+ nodeId?: string;
14
+ }
15
+
16
+ const DynamicParameters = ({ nodeId }: IProps) => {
17
+ const updateNodeInternals = useUpdateNodeInternals();
18
+ const form = Form.useFormInstance();
19
+ const buildCategorizeToOptions = useBuildFormSelectOptions(
20
+ Operator.Categorize,
21
+ nodeId,
22
+ );
23
+ const { handleSelectChange } = useHandleFormSelectChange(nodeId);
24
+ const { t } = useTranslate('flow');
25
+
26
+ return (
27
+ <>
28
+ <Form.List name="parameters">
29
+ {(fields, { add, remove }) => {
30
+ const handleAdd = () => {
31
+ const idx = fields.length;
32
+ add({ name: `parameter ${idx + 1}` });
33
+ if (nodeId) updateNodeInternals(nodeId);
34
+ };
35
+ return (
36
+ <div
37
+ style={{ display: 'flex', rowGap: 10, flexDirection: 'column' }}
38
+ >
39
+ {fields.map((field) => (
40
+ <Card
41
+ size="small"
42
+ key={field.key}
43
+ extra={
44
+ <CloseOutlined
45
+ onClick={() => {
46
+ remove(field.name);
47
+ }}
48
+ />
49
+ }
50
+ >
51
+ <Form.Item
52
+ label={t('key')} // TODO: repeatability check
53
+ name={[field.name, 'key']}
54
+ rules={[{ required: true, message: t('nameMessage') }]}
55
+ >
56
+ <Input />
57
+ </Form.Item>
58
+ <Form.Item
59
+ label={t('componentId')}
60
+ name={[field.name, 'component_id']}
61
+ >
62
+ <Select
63
+ allowClear
64
+ options={buildCategorizeToOptions(
65
+ (form.getFieldValue(['parameters']) ?? [])
66
+ .map((x: IGenerateParameter) => x.component_id)
67
+ .filter(
68
+ (x: string) =>
69
+ x !==
70
+ form.getFieldValue([
71
+ 'parameters',
72
+ field.name,
73
+ 'component_id',
74
+ ]),
75
+ ),
76
+ )}
77
+ onChange={handleSelectChange(
78
+ form.getFieldValue(['parameters', field.name, 'key']),
79
+ )}
80
+ />
81
+ </Form.Item>
82
+ </Card>
83
+ ))}
84
+
85
+ <Button type="dashed" onClick={handleAdd} block>
86
+ + Add Item
87
+ </Button>
88
+ </div>
89
+ );
90
+ }}
91
+ </Form.List>
92
+
93
+ <Form.Item noStyle shouldUpdate>
94
+ {() => (
95
+ <Typography>
96
+ <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre>
97
+ </Typography>
98
+ )}
99
+ </Form.Item>
100
+ </>
101
+ );
102
+ };
103
+
104
+ export default DynamicParameters;
web/src/pages/flow/generate-form/index.less ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .variableTable {
2
+ margin-top: 14px;
3
+ }
4
+ .editableRow {
5
+ :global(.editable-cell) {
6
+ position: relative;
7
+ }
8
+
9
+ :global(.editable-cell-value-wrap) {
10
+ padding: 5px 12px;
11
+ cursor: pointer;
12
+ height: 22px !important;
13
+ }
14
+ &:hover {
15
+ :global(.editable-cell-value-wrap) {
16
+ padding: 4px 11px;
17
+ border: 1px solid #d9d9d9;
18
+ border-radius: 2px;
19
+ }
20
+ }
21
+ }
web/src/pages/flow/generate-form/index.tsx CHANGED
@@ -1,8 +1,9 @@
1
- import LlmSettingItems from '@/components/llm-setting-items';
2
  import { useTranslate } from '@/hooks/commonHooks';
3
  import { Form, Input, Switch } from 'antd';
4
  import { useSetLlmSetting } from '../hooks';
5
  import { IOperatorForm } from '../interface';
 
6
 
7
  const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
8
  const { t } = useTranslate('flow');
@@ -18,7 +19,13 @@ const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
18
  form={form}
19
  onValuesChange={onValuesChange}
20
  >
21
- <LlmSettingItems></LlmSettingItems>
 
 
 
 
 
 
22
  <Form.Item
23
  name={['prompt']}
24
  label={t('prompt', { keyPrefix: 'knowledgeConfiguration' })}
@@ -42,6 +49,7 @@ const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
42
  >
43
  <Switch />
44
  </Form.Item>
 
45
  </Form>
46
  );
47
  };
 
1
+ import LLMSelect from '@/components/llm-select';
2
  import { useTranslate } from '@/hooks/commonHooks';
3
  import { Form, Input, Switch } from 'antd';
4
  import { useSetLlmSetting } from '../hooks';
5
  import { IOperatorForm } from '../interface';
6
+ import DynamicParameters from './next-dynamic-parameters';
7
 
8
  const GenerateForm = ({ onValuesChange, form }: IOperatorForm) => {
9
  const { t } = useTranslate('flow');
 
19
  form={form}
20
  onValuesChange={onValuesChange}
21
  >
22
+ <Form.Item
23
+ name={'llm_id'}
24
+ label={t('model', { keyPrefix: 'chat' })}
25
+ tooltip={t('modelTip', { keyPrefix: 'chat' })}
26
+ >
27
+ <LLMSelect></LLMSelect>
28
+ </Form.Item>
29
  <Form.Item
30
  name={['prompt']}
31
  label={t('prompt', { keyPrefix: 'knowledgeConfiguration' })}
 
49
  >
50
  <Switch />
51
  </Form.Item>
52
+ <DynamicParameters></DynamicParameters>
53
  </Form>
54
  );
55
  };
web/src/pages/flow/generate-form/next-dynamic-parameters.tsx ADDED
@@ -0,0 +1,135 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { EditableCell, EditableRow } from '@/components/editable-cell';
2
+ import { useTranslate } from '@/hooks/commonHooks';
3
+ import { DeleteOutlined } from '@ant-design/icons';
4
+ import { Button, Flex, Select, Table, TableProps } from 'antd';
5
+ import { useEffect, useState } from 'react';
6
+ import { v4 as uuid } from 'uuid';
7
+ import { IGenerateParameter } from '../interface';
8
+
9
+ import { Operator } from '../constant';
10
+ import { useBuildFormSelectOptions } from '../form-hooks';
11
+ import styles from './index.less';
12
+
13
+ interface IProps {
14
+ nodeId?: string;
15
+ }
16
+
17
+ const components = {
18
+ body: {
19
+ row: EditableRow,
20
+ cell: EditableCell,
21
+ },
22
+ };
23
+
24
+ const DynamicParameters = ({ nodeId }: IProps) => {
25
+ const [dataSource, setDataSource] = useState<IGenerateParameter[]>([]);
26
+ const { t } = useTranslate('flow');
27
+
28
+ const buildCategorizeToOptions = useBuildFormSelectOptions(
29
+ Operator.Generate,
30
+ nodeId,
31
+ );
32
+
33
+ const handleRemove = (id?: string) => () => {
34
+ const newData = dataSource.filter((item) => item.id !== id);
35
+ setDataSource(newData);
36
+ };
37
+
38
+ const handleAdd = () => {
39
+ setDataSource((state) => [
40
+ ...state,
41
+ {
42
+ id: uuid(),
43
+ key: '',
44
+ component_id: undefined,
45
+ },
46
+ ]);
47
+ };
48
+
49
+ const handleSave = (row: IGenerateParameter) => {
50
+ const newData = [...dataSource];
51
+ const index = newData.findIndex((item) => row.id === item.id);
52
+ const item = newData[index];
53
+ newData.splice(index, 1, {
54
+ ...item,
55
+ ...row,
56
+ });
57
+ setDataSource(newData);
58
+ };
59
+
60
+ useEffect(() => {}, [dataSource]);
61
+
62
+ const handleOptionalChange = (row: IGenerateParameter) => (value: string) => {
63
+ const newData = [...dataSource];
64
+ const index = newData.findIndex((item) => row.id === item.id);
65
+ const item = newData[index];
66
+ newData.splice(index, 1, {
67
+ ...item,
68
+ component_id: value,
69
+ });
70
+ setDataSource(newData);
71
+ };
72
+
73
+ const columns: TableProps<IGenerateParameter>['columns'] = [
74
+ {
75
+ title: t('key'),
76
+ dataIndex: 'key',
77
+ key: 'key',
78
+ onCell: (record: IGenerateParameter) => ({
79
+ record,
80
+ editable: true,
81
+ dataIndex: 'key',
82
+ title: 'key',
83
+ handleSave,
84
+ }),
85
+ },
86
+ {
87
+ title: t('componentId'),
88
+ dataIndex: 'component_id',
89
+ key: 'component_id',
90
+ align: 'center',
91
+ render(text, record) {
92
+ return (
93
+ <Select
94
+ style={{ width: '100%' }}
95
+ allowClear
96
+ options={buildCategorizeToOptions([])}
97
+ // onChange={handleSelectChange(
98
+ // form.getFieldValue(['parameters', field.name, 'key']),
99
+ // )}
100
+ />
101
+ );
102
+ },
103
+ },
104
+ {
105
+ title: t('operation'),
106
+ dataIndex: 'operation',
107
+ width: 20,
108
+ key: 'operation',
109
+ align: 'center',
110
+ render(_, record) {
111
+ return <DeleteOutlined onClick={handleRemove(record.id)} />;
112
+ },
113
+ },
114
+ ];
115
+
116
+ return (
117
+ <section>
118
+ <Flex justify="end">
119
+ <Button size="small" onClick={handleAdd}>
120
+ {t('add')}
121
+ </Button>
122
+ </Flex>
123
+ <Table
124
+ dataSource={dataSource}
125
+ columns={columns}
126
+ rowKey={'id'}
127
+ className={styles.variableTable}
128
+ components={components}
129
+ rowClassName={() => styles.editableRow}
130
+ />
131
+ </section>
132
+ );
133
+ };
134
+
135
+ export default DynamicParameters;
web/src/pages/flow/interface.ts CHANGED
@@ -45,6 +45,12 @@ export interface ICategorizeItem {
45
  to?: string;
46
  }
47
 
 
 
 
 
 
 
48
  export type ICategorizeItemResult = Record<
49
  string,
50
  Omit<ICategorizeItem, 'name'>
 
45
  to?: string;
46
  }
47
 
48
+ export interface IGenerateParameter {
49
+ id?: string;
50
+ key: string;
51
+ component_id?: string;
52
+ }
53
+
54
  export type ICategorizeItemResult = Record<
55
  string,
56
  Omit<ICategorizeItem, 'name'>