### 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
| @@ -1,11 +1,12 @@ | |||
| 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 flowService from '@/services/flow-service'; | |||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | |||
| import { message } from 'antd'; | |||
| import { useParams } from 'umi'; | |||
| export const useFetchFlowTemplates = () => { | |||
| export const useFetchFlowTemplates = (): ResponseType<IFlowTemplate[]> => { | |||
| const { data } = useQuery({ | |||
| queryKey: ['fetchFlowTemplates'], | |||
| initialData: [], | |||
| @@ -244,3 +244,28 @@ export const useHandleMessageInputChange = () => { | |||
| }; | |||
| // #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 }; | |||
| }; | |||
| @@ -1,6 +1,6 @@ | |||
| export interface ResponseType { | |||
| export interface ResponseType<T = any> { | |||
| retcode: number; | |||
| data: any; | |||
| data: T; | |||
| retmsg: string; | |||
| status: number; | |||
| } | |||
| @@ -42,3 +42,16 @@ export interface IFlow { | |||
| update_time: number; | |||
| 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; | |||
| } | |||
| @@ -557,6 +557,7 @@ The above is the content you need to summarize.`, | |||
| messageMsg: 'Please input message or delete this field.', | |||
| addField: 'Add field', | |||
| loop: 'Loop', | |||
| createFlow: 'Create a workflow', | |||
| }, | |||
| footer: { | |||
| profile: 'All rights reserved @ React', | |||
| @@ -524,6 +524,7 @@ export default { | |||
| fileError: '文件错误', | |||
| }, | |||
| flow: { | |||
| flow: '工作流', | |||
| cite: '引用', | |||
| citeTip: 'citeTip', | |||
| name: '名称', | |||
| @@ -1,9 +1,5 @@ | |||
| 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 { IGraph } from '@/interfaces/database/flow'; | |||
| import { useIsFetching } from '@tanstack/react-query'; | |||
| @@ -221,7 +217,6 @@ export const useFetchDataOnMount = () => { | |||
| useWatchGraphChange(); | |||
| useFetchFlowTemplates(); | |||
| useFetchLlmList(); | |||
| return { loading, flowDetail: data }; | |||
| @@ -0,0 +1,104 @@ | |||
| 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; | |||
| @@ -1,10 +1,14 @@ | |||
| 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 { useNavigate } from 'umi'; | |||
| // 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 = () => { | |||
| const { data, loading } = useFetchFlowList(); | |||
| @@ -21,12 +25,19 @@ export const useSaveFlow = () => { | |||
| } = useSetModalState(); | |||
| const { loading, setFlow } = useSetFlow(); | |||
| const navigate = useNavigate(); | |||
| const { data: list } = useFetchFlowTemplates(); | |||
| 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({ | |||
| title, | |||
| dsl: { ...headhunterZhComponents, graph: headhunter_zh }, | |||
| dsl: dslJson, | |||
| }); | |||
| if (ret?.retcode === 0) { | |||
| @@ -34,7 +45,7 @@ export const useSaveFlow = () => { | |||
| navigate(`/flow/${ret.data.id}`); | |||
| } | |||
| }, | |||
| [setFlow, hideFlowSettingModal, navigate], | |||
| [setFlow, hideFlowSettingModal, navigate, list], | |||
| ); | |||
| const handleShowFlowSettingModal = useCallback( | |||
| @@ -46,3 +46,11 @@ | |||
| width: 100%; | |||
| } | |||
| } | |||
| .flowTemplateCard { | |||
| cursor: pointer; | |||
| } | |||
| .selectedFlowTemplateCard { | |||
| background-color: @selectedBackgroundColor; | |||
| } | |||
| @@ -1,6 +1,6 @@ | |||
| import RenameModal from '@/components/rename-modal'; | |||
| import { PlusOutlined } from '@ant-design/icons'; | |||
| import { Button, Empty, Flex, Spin } from 'antd'; | |||
| import CreateFlowModal from './create-flow-modal'; | |||
| import FlowCard from './flow-card'; | |||
| import { useFetchDataOnMount, useSaveFlow } from './hooks'; | |||
| @@ -39,13 +39,15 @@ const FlowList = () => { | |||
| )} | |||
| </Flex> | |||
| </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> | |||
| ); | |||
| }; | |||