### What problem does this PR solve? feat: add CreateFlowModal #918 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.8.0
| import { DSL, IFlow } from '@/interfaces/database/flow'; | |||||
| import { ResponseType } from '@/interfaces/database/base'; | |||||
| import { DSL, IFlow, IFlowTemplate } from '@/interfaces/database/flow'; | |||||
| import i18n from '@/locales/config'; | import i18n from '@/locales/config'; | ||||
| import flowService from '@/services/flow-service'; | import flowService from '@/services/flow-service'; | ||||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | ||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||
| import { useParams } from 'umi'; | import { useParams } from 'umi'; | ||||
| export const useFetchFlowTemplates = () => { | |||||
| export const useFetchFlowTemplates = (): ResponseType<IFlowTemplate[]> => { | |||||
| const { data } = useQuery({ | const { data } = useQuery({ | ||||
| queryKey: ['fetchFlowTemplates'], | queryKey: ['fetchFlowTemplates'], | ||||
| initialData: [], | initialData: [], |
| }; | }; | ||||
| // #endregion | // #endregion | ||||
| /** | |||||
| * | |||||
| * @param defaultId | |||||
| * used to switch between different items, similar to radio | |||||
| * @returns | |||||
| */ | |||||
| export const useSelectItem = (defaultId?: string) => { | |||||
| const [selectedId, setSelectedId] = useState(''); | |||||
| const handleItemClick = useCallback( | |||||
| (id: string) => () => { | |||||
| setSelectedId(id); | |||||
| }, | |||||
| [], | |||||
| ); | |||||
| useEffect(() => { | |||||
| if (defaultId) { | |||||
| setSelectedId(defaultId); | |||||
| } | |||||
| }, [defaultId]); | |||||
| return { selectedId, handleItemClick }; | |||||
| }; |
| export interface ResponseType { | |||||
| export interface ResponseType<T = any> { | |||||
| retcode: number; | retcode: number; | ||||
| data: any; | |||||
| data: T; | |||||
| retmsg: string; | retmsg: string; | ||||
| status: number; | status: number; | ||||
| } | } |
| update_time: number; | update_time: number; | ||||
| user_id: string; | user_id: string; | ||||
| } | } | ||||
| export interface IFlowTemplate { | |||||
| avatar: string; | |||||
| canvas_type: string; | |||||
| create_date: string; | |||||
| create_time: number; | |||||
| description: string; | |||||
| dsl: DSL; | |||||
| id: string; | |||||
| title: string; | |||||
| update_date: string; | |||||
| update_time: number; | |||||
| } |
| messageMsg: 'Please input message or delete this field.', | messageMsg: 'Please input message or delete this field.', | ||||
| addField: 'Add field', | addField: 'Add field', | ||||
| loop: 'Loop', | loop: 'Loop', | ||||
| createFlow: 'Create a workflow', | |||||
| }, | }, | ||||
| footer: { | footer: { | ||||
| profile: 'All rights reserved @ React', | profile: 'All rights reserved @ React', |
| fileError: '文件错误', | fileError: '文件错误', | ||||
| }, | }, | ||||
| flow: { | flow: { | ||||
| flow: '工作流', | |||||
| cite: '引用', | cite: '引用', | ||||
| citeTip: 'citeTip', | citeTip: 'citeTip', | ||||
| name: '名称', | name: '名称', |
| import { useSetModalState } from '@/hooks/commonHooks'; | import { useSetModalState } from '@/hooks/commonHooks'; | ||||
| import { | |||||
| useFetchFlow, | |||||
| useFetchFlowTemplates, | |||||
| useSetFlow, | |||||
| } from '@/hooks/flow-hooks'; | |||||
| import { useFetchFlow, useSetFlow } from '@/hooks/flow-hooks'; | |||||
| import { useFetchLlmList } from '@/hooks/llmHooks'; | import { useFetchLlmList } from '@/hooks/llmHooks'; | ||||
| import { IGraph } from '@/interfaces/database/flow'; | import { IGraph } from '@/interfaces/database/flow'; | ||||
| import { useIsFetching } from '@tanstack/react-query'; | import { useIsFetching } from '@tanstack/react-query'; | ||||
| useWatchGraphChange(); | useWatchGraphChange(); | ||||
| useFetchFlowTemplates(); | |||||
| useFetchLlmList(); | useFetchLlmList(); | ||||
| return { loading, flowDetail: data }; | return { loading, flowDetail: data }; |
| import { IModalManagerChildrenProps } from '@/components/modal-manager'; | |||||
| import { useTranslate } from '@/hooks/commonHooks'; | |||||
| import { useFetchFlowTemplates } from '@/hooks/flow-hooks'; | |||||
| import { useSelectItem } from '@/hooks/logicHooks'; | |||||
| import { UserOutlined } from '@ant-design/icons'; | |||||
| import { | |||||
| Avatar, | |||||
| Card, | |||||
| Flex, | |||||
| Form, | |||||
| Input, | |||||
| Modal, | |||||
| Space, | |||||
| Typography, | |||||
| } from 'antd'; | |||||
| import classNames from 'classnames'; | |||||
| import { useEffect } from 'react'; | |||||
| import styles from './index.less'; | |||||
| const { Title } = Typography; | |||||
| interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> { | |||||
| loading: boolean; | |||||
| initialName: string; | |||||
| onOk: (name: string, templateId: string) => void; | |||||
| showModal?(): void; | |||||
| } | |||||
| const CreateFlowModal = ({ | |||||
| visible, | |||||
| hideModal, | |||||
| loading, | |||||
| initialName, | |||||
| onOk, | |||||
| }: IProps) => { | |||||
| const [form] = Form.useForm(); | |||||
| const { t } = useTranslate('common'); | |||||
| const { data: list } = useFetchFlowTemplates(); | |||||
| const { selectedId, handleItemClick } = useSelectItem(list?.at(0)?.id); | |||||
| type FieldType = { | |||||
| name?: string; | |||||
| }; | |||||
| const handleOk = async () => { | |||||
| const ret = await form.validateFields(); | |||||
| return onOk(ret.name, selectedId); | |||||
| }; | |||||
| useEffect(() => { | |||||
| if (visible) { | |||||
| form.setFieldValue('name', initialName); | |||||
| } | |||||
| }, [initialName, form, visible]); | |||||
| return ( | |||||
| <Modal | |||||
| title={t('createFlow', { keyPrefix: 'flow' })} | |||||
| open={visible} | |||||
| onOk={handleOk} | |||||
| width={600} | |||||
| onCancel={hideModal} | |||||
| okButtonProps={{ loading }} | |||||
| confirmLoading={loading} | |||||
| > | |||||
| <Form | |||||
| name="basic" | |||||
| labelCol={{ span: 4 }} | |||||
| wrapperCol={{ span: 20 }} | |||||
| autoComplete="off" | |||||
| layout={'vertical'} | |||||
| form={form} | |||||
| > | |||||
| <Form.Item<FieldType> | |||||
| label={<b>{t('name')}</b>} | |||||
| name="name" | |||||
| rules={[{ required: true, message: t('namePlaceholder') }]} | |||||
| > | |||||
| <Input /> | |||||
| </Form.Item> | |||||
| </Form> | |||||
| <Title level={5}>Choose from templates</Title> | |||||
| <Flex vertical gap={16}> | |||||
| {list?.map((x) => ( | |||||
| <Card | |||||
| key={x.id} | |||||
| className={classNames(styles.flowTemplateCard, { | |||||
| [styles.selectedFlowTemplateCard]: selectedId === x.id, | |||||
| })} | |||||
| onClick={handleItemClick(x.id)} | |||||
| > | |||||
| <Space size={'middle'}> | |||||
| <Avatar size={40} icon={<UserOutlined />} src={x.avatar} /> | |||||
| <b>{x.title}</b> | |||||
| </Space> | |||||
| <p>{x.description}</p> | |||||
| </Card> | |||||
| ))} | |||||
| </Flex> | |||||
| </Modal> | |||||
| ); | |||||
| }; | |||||
| export default CreateFlowModal; |
| import { useSetModalState } from '@/hooks/commonHooks'; | import { useSetModalState } from '@/hooks/commonHooks'; | ||||
| import { useFetchFlowList, useSetFlow } from '@/hooks/flow-hooks'; | |||||
| import { | |||||
| useFetchFlowList, | |||||
| useFetchFlowTemplates, | |||||
| useSetFlow, | |||||
| } from '@/hooks/flow-hooks'; | |||||
| import { useCallback, useState } from 'react'; | import { useCallback, useState } from 'react'; | ||||
| import { useNavigate } from 'umi'; | import { useNavigate } from 'umi'; | ||||
| // import { dsl } from '../mock'; | // import { dsl } from '../mock'; | ||||
| import headhunterZhComponents from '../../../../../graph/test/dsl_examples/headhunter_zh.json'; | |||||
| import headhunter_zh from '../headhunter_zh.json'; | |||||
| // import headhunterZhComponents from '../../../../../graph/test/dsl_examples/headhunter_zh.json'; | |||||
| import dslJson from '../../../../../dls.json'; | |||||
| export const useFetchDataOnMount = () => { | export const useFetchDataOnMount = () => { | ||||
| const { data, loading } = useFetchFlowList(); | const { data, loading } = useFetchFlowList(); | ||||
| } = useSetModalState(); | } = useSetModalState(); | ||||
| const { loading, setFlow } = useSetFlow(); | const { loading, setFlow } = useSetFlow(); | ||||
| const navigate = useNavigate(); | const navigate = useNavigate(); | ||||
| const { data: list } = useFetchFlowTemplates(); | |||||
| const onFlowOk = useCallback( | const onFlowOk = useCallback( | ||||
| async (title: string) => { | |||||
| async (title: string, templateId: string) => { | |||||
| const templateItem = list.find((x) => x.id === templateId); | |||||
| let dsl = templateItem?.dsl; | |||||
| // if (dsl) { | |||||
| // dsl.graph = headhunter_zh; | |||||
| // } | |||||
| const ret = await setFlow({ | const ret = await setFlow({ | ||||
| title, | title, | ||||
| dsl: { ...headhunterZhComponents, graph: headhunter_zh }, | |||||
| dsl: dslJson, | |||||
| }); | }); | ||||
| if (ret?.retcode === 0) { | if (ret?.retcode === 0) { | ||||
| navigate(`/flow/${ret.data.id}`); | navigate(`/flow/${ret.data.id}`); | ||||
| } | } | ||||
| }, | }, | ||||
| [setFlow, hideFlowSettingModal, navigate], | |||||
| [setFlow, hideFlowSettingModal, navigate, list], | |||||
| ); | ); | ||||
| const handleShowFlowSettingModal = useCallback( | const handleShowFlowSettingModal = useCallback( |
| width: 100%; | width: 100%; | ||||
| } | } | ||||
| } | } | ||||
| .flowTemplateCard { | |||||
| cursor: pointer; | |||||
| } | |||||
| .selectedFlowTemplateCard { | |||||
| background-color: @selectedBackgroundColor; | |||||
| } |
| import RenameModal from '@/components/rename-modal'; | |||||
| import { PlusOutlined } from '@ant-design/icons'; | import { PlusOutlined } from '@ant-design/icons'; | ||||
| import { Button, Empty, Flex, Spin } from 'antd'; | import { Button, Empty, Flex, Spin } from 'antd'; | ||||
| import CreateFlowModal from './create-flow-modal'; | |||||
| import FlowCard from './flow-card'; | import FlowCard from './flow-card'; | ||||
| import { useFetchDataOnMount, useSaveFlow } from './hooks'; | import { useFetchDataOnMount, useSaveFlow } from './hooks'; | ||||
| )} | )} | ||||
| </Flex> | </Flex> | ||||
| </Spin> | </Spin> | ||||
| <RenameModal | |||||
| visible={flowSettingVisible} | |||||
| onOk={onFlowOk} | |||||
| loading={flowSettingLoading} | |||||
| hideModal={hideFlowSettingModal} | |||||
| initialName="" | |||||
| ></RenameModal> | |||||
| {flowSettingVisible && ( | |||||
| <CreateFlowModal | |||||
| visible={flowSettingVisible} | |||||
| onOk={onFlowOk} | |||||
| loading={flowSettingLoading} | |||||
| hideModal={hideFlowSettingModal} | |||||
| initialName="" | |||||
| ></CreateFlowModal> | |||||
| )} | |||||
| </Flex> | </Flex> | ||||
| ); | ); | ||||
| }; | }; |