### What problem does this PR solve? feat: add DynamicCategorize #918 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.8.0
| return ( | return ( | ||||
| <Popover content={content} trigger="click" placement="left" arrow={false}> | <Popover content={content} trigger="click" placement="left" arrow={false}> | ||||
| {/* <Button>Click me</Button> */} | |||||
| <Select | |||||
| defaultValue="lucy" | |||||
| style={{ width: '100%' }} | |||||
| dropdownStyle={{ display: 'none' }} | |||||
| /> | |||||
| <Select style={{ width: '100%' }} dropdownStyle={{ display: 'none' }} /> | |||||
| </Popover> | </Popover> | ||||
| ); | ); | ||||
| }; | }; |
| const RightToolBar = () => { | const RightToolBar = () => { | ||||
| const { t } = useTranslate('common'); | const { t } = useTranslate('common'); | ||||
| const changeLanguage = useChangeLanguage(); | const changeLanguage = useChangeLanguage(); | ||||
| const { language = '' } = useSelector((state) => state.settingModel.userInfo); | |||||
| const { language = 'en' } = useSelector( | |||||
| (state) => state.settingModel.userInfo, | |||||
| ); | |||||
| const handleItemClick: MenuProps['onClick'] = ({ key }) => { | const handleItemClick: MenuProps['onClick'] = ({ key }) => { | ||||
| changeLanguage(key); | changeLanguage(key); |
| preview: '預覽', | preview: '預覽', | ||||
| fileError: '文件錯誤', | fileError: '文件錯誤', | ||||
| }, | }, | ||||
| flow: { cite: '引用', citeTip: 'citeTip' }, | |||||
| footer: { | footer: { | ||||
| profile: '“保留所有權利 @ react”', | profile: '“保留所有權利 @ react”', | ||||
| }, | }, |
| preview: '预览', | preview: '预览', | ||||
| fileError: '文件错误', | fileError: '文件错误', | ||||
| }, | }, | ||||
| flow: { cite: '引用', citeTip: 'citeTip' }, | |||||
| footer: { | footer: { | ||||
| profile: 'All rights reserved @ React', | profile: 'All rights reserved @ React', | ||||
| }, | }, |
| import { useCallback } from 'react'; | import { useCallback } from 'react'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { Operator, operatorMap } from '../../constant'; | import { Operator, operatorMap } from '../../constant'; | ||||
| import { NodeData } from '../../interface'; | |||||
| import OperatorIcon from '../../operator-icon'; | import OperatorIcon from '../../operator-icon'; | ||||
| import useGraphStore from '../../store'; | import useGraphStore from '../../store'; | ||||
| import styles from './index.less'; | import styles from './index.less'; | ||||
| data, | data, | ||||
| isConnectable = true, | isConnectable = true, | ||||
| selected, | selected, | ||||
| }: NodeProps<{ label: string }>) { | |||||
| }: NodeProps<NodeData>) { | |||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const deleteNodeById = useGraphStore((store) => store.deleteNodeById); | const deleteNodeById = useGraphStore((store) => store.deleteNodeById); | ||||
| const duplicateNodeById = useGraphStore((store) => store.duplicateNode); | const duplicateNodeById = useGraphStore((store) => store.duplicateNode); | ||||
| name={data.label as Operator} | name={data.label as Operator} | ||||
| fontSize={12} | fontSize={12} | ||||
| ></OperatorIcon> | ></OperatorIcon> | ||||
| <span>{data.label}</span> | |||||
| <span>{id}</span> | |||||
| </Space> | </Space> | ||||
| <OperateDropdown | <OperateDropdown | ||||
| iconFontSize={14} | iconFontSize={14} |
| import { CloseOutlined } from '@ant-design/icons'; | |||||
| import { Button, Card, Form, Input, Select, Typography } from 'antd'; | |||||
| import { useBuildCategorizeToOptions } from './hooks'; | |||||
| const DynamicCategorize = () => { | |||||
| const form = Form.useFormInstance(); | |||||
| const options = useBuildCategorizeToOptions(); | |||||
| return ( | |||||
| <> | |||||
| <Form.List name="items"> | |||||
| {(fields, { add, remove }) => ( | |||||
| <div style={{ display: 'flex', rowGap: 16, flexDirection: 'column' }}> | |||||
| {fields.map((field) => ( | |||||
| <Card | |||||
| size="small" | |||||
| key={field.key} | |||||
| extra={ | |||||
| <CloseOutlined | |||||
| onClick={() => { | |||||
| remove(field.name); | |||||
| }} | |||||
| /> | |||||
| } | |||||
| > | |||||
| <Form.Item | |||||
| label="name" | |||||
| name={[field.name, 'name']} | |||||
| initialValue={`Categorize ${field.name + 1}`} | |||||
| rules={[ | |||||
| { required: true, message: 'Please input your name!' }, | |||||
| ]} | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| <Form.Item | |||||
| label="description" | |||||
| name={[field.name, 'description']} | |||||
| > | |||||
| <Input.TextArea rows={3} /> | |||||
| </Form.Item> | |||||
| <Form.Item label="examples" name={[field.name, 'examples']}> | |||||
| <Input.TextArea rows={3} /> | |||||
| </Form.Item> | |||||
| <Form.Item label="to" name={[field.name, 'to']}> | |||||
| <Select options={options} /> | |||||
| </Form.Item> | |||||
| </Card> | |||||
| ))} | |||||
| <Button type="dashed" onClick={() => add()} block> | |||||
| + Add Item | |||||
| </Button> | |||||
| </div> | |||||
| )} | |||||
| </Form.List> | |||||
| <Form.Item noStyle shouldUpdate> | |||||
| {() => ( | |||||
| <Typography> | |||||
| <pre>{JSON.stringify(form.getFieldsValue(), null, 2)}</pre> | |||||
| </Typography> | |||||
| )} | |||||
| </Form.Item> | |||||
| </> | |||||
| ); | |||||
| }; | |||||
| export default DynamicCategorize; |
| import { Operator } from '../constant'; | |||||
| import useGraphStore from '../store'; | |||||
| // exclude some nodes downstream of the classification node | |||||
| const excludedNodes = [Operator.Categorize, Operator.Answer, Operator.Begin]; | |||||
| export const useBuildCategorizeToOptions = () => { | |||||
| const nodes = useGraphStore((state) => state.nodes); | |||||
| return nodes | |||||
| .filter((x) => excludedNodes.every((y) => y !== x.data.label)) | |||||
| .map((x) => ({ label: x.id, value: x.id })); | |||||
| }; |
| import LLMSelect from '@/components/llm-select'; | import LLMSelect from '@/components/llm-select'; | ||||
| import { useTranslate } from '@/hooks/commonHooks'; | |||||
| import { Form } from 'antd'; | |||||
| import { IOperatorForm } from '../interface'; | |||||
| import DynamicCategorize from './dynamic-categorize'; | |||||
| const CategorizeForm = ({ form, onValuesChange }: IOperatorForm) => { | |||||
| const { t } = useTranslate('flow'); | |||||
| const CategorizeForm = () => { | |||||
| return ( | return ( | ||||
| <section> | |||||
| <LLMSelect></LLMSelect> | |||||
| </section> | |||||
| <Form | |||||
| name="basic" | |||||
| labelCol={{ span: 9 }} | |||||
| wrapperCol={{ span: 15 }} | |||||
| autoComplete="off" | |||||
| form={form} | |||||
| onValuesChange={onValuesChange} | |||||
| initialValues={{ items: [{}] }} | |||||
| // layout={'vertical'} | |||||
| > | |||||
| <Form.Item name={['cite']} label={t('cite')} tooltip={t('citeTip')}> | |||||
| <LLMSelect></LLMSelect> | |||||
| </Form.Item> | |||||
| <DynamicCategorize></DynamicCategorize> | |||||
| </Form> | |||||
| ); | ); | ||||
| }; | }; | ||||
| [Operator.Retrieval]: initialRetrievalValues, | [Operator.Retrieval]: initialRetrievalValues, | ||||
| [Operator.Generate]: initialGenerateValues, | [Operator.Generate]: initialGenerateValues, | ||||
| [Operator.Answer]: {}, | [Operator.Answer]: {}, | ||||
| [Operator.Categorize]: {}, | |||||
| }; | }; |