From 35718b1f2683e35eccc717f9b391e63751ee58e3 Mon Sep 17 00:00:00 2001 From: archer <545436317@qq.com> Date: Tue, 25 Jul 2023 15:48:59 +0800 Subject: [PATCH] perf: app edit --- client/src/components/Icon/icons/back.svg | 2 +- client/src/constants/flow/ModuleTemplate.ts | 6 +- .../AdEdit/components/TemplateList.tsx | 40 +- .../app/detail/components/AdEdit/index.tsx | 98 ++-- .../app/detail/components/BasicEdit/index.tsx | 526 +++++++++++------- .../pages/app/detail/components/OverView.tsx | 165 ------ client/src/pages/app/detail/index.tsx | 15 +- .../src/pages/chat/components/SliderApps.tsx | 2 +- 8 files changed, 413 insertions(+), 441 deletions(-) delete mode 100644 client/src/pages/app/detail/components/OverView.tsx diff --git a/client/src/components/Icon/icons/back.svg b/client/src/components/Icon/icons/back.svg index 311943fbf..90a53a4e8 100644 --- a/client/src/components/Icon/icons/back.svg +++ b/client/src/components/Icon/icons/back.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/client/src/constants/flow/ModuleTemplate.ts b/client/src/constants/flow/ModuleTemplate.ts index 3dece5a93..ff2371dcf 100644 --- a/client/src/constants/flow/ModuleTemplate.ts +++ b/client/src/constants/flow/ModuleTemplate.ts @@ -371,7 +371,11 @@ export const EmptyModule: FlowModuleTemplateType = { export const ModuleTemplates = [ { label: '输入模块', - list: [UserInputModule, HistoryModule, VariableModule, UserGuideModule] + list: [UserInputModule, HistoryModule] + }, + { + label: '引导模块', + list: [UserGuideModule, VariableModule] }, { label: '内容生成', diff --git a/client/src/pages/app/detail/components/AdEdit/components/TemplateList.tsx b/client/src/pages/app/detail/components/AdEdit/components/TemplateList.tsx index 22502cc07..fbd272de9 100644 --- a/client/src/pages/app/detail/components/AdEdit/components/TemplateList.tsx +++ b/client/src/pages/app/detail/components/AdEdit/components/TemplateList.tsx @@ -1,21 +1,53 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import { Box, Flex } from '@chakra-ui/react'; import { ModuleTemplates } from '@/constants/flow/ModuleTemplate'; import { FlowModuleTemplateType } from '@/types/flow'; -import type { XYPosition } from 'reactflow'; +import type { Node, XYPosition } from 'reactflow'; import { useGlobalStore } from '@/store/global'; +import type { AppModuleItemType } from '@/types/app'; import Avatar from '@/components/Avatar'; +import { FlowModuleTypeEnum } from '@/constants/flow'; const ModuleTemplateList = ({ + nodes, isOpen, onAddNode, onClose }: { + nodes?: Node[]; isOpen: boolean; onAddNode: (e: { template: FlowModuleTemplateType; position: XYPosition }) => void; onClose: () => void; }) => { const { isPc } = useGlobalStore(); + + const filterTemplates = useMemo(() => { + const guideModulesIndex = ModuleTemplates.findIndex((item) => item.label === '引导模块'); + const guideModule: { + label: string; + list: FlowModuleTemplateType[]; + } = JSON.parse(JSON.stringify(ModuleTemplates[guideModulesIndex])); + + if (nodes?.find((item) => item.type === FlowModuleTypeEnum.userGuide)) { + const index = guideModule.list.findIndex( + (item) => item.flowType === FlowModuleTypeEnum.userGuide + ); + guideModule.list.splice(index, 1); + } + if (nodes?.find((item) => item.type === FlowModuleTypeEnum.variable)) { + const index = guideModule.list.findIndex( + (item) => item.flowType === FlowModuleTypeEnum.variable + ); + guideModule.list.splice(index, 1); + } + + return [ + ...ModuleTemplates.slice(0, guideModulesIndex), + guideModule, + ...ModuleTemplates.slice(guideModulesIndex + 1) + ]; + }, [nodes]); + return ( <> - {ModuleTemplates.map((item) => + {filterTemplates.map((item) => item.list.map((item) => ( { if (isPc) return; + onClose(); onAddNode({ template: item, position: { x: e.clientX, y: e.clientY } }); - onClose(); }} > diff --git a/client/src/pages/app/detail/components/AdEdit/index.tsx b/client/src/pages/app/detail/components/AdEdit/index.tsx index 381fe1c49..52ba0076f 100644 --- a/client/src/pages/app/detail/components/AdEdit/index.tsx +++ b/client/src/pages/app/detail/components/AdEdit/index.tsx @@ -95,12 +95,12 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { const { x, y, zoom } = useViewport(); const [nodes, setNodes, onNodesChange] = useNodesState([]); const [edges, setEdges, onEdgesChange] = useEdgesState([]); - const [loaded, setLoaded] = useState(false); const { isOpen: isOpenTemplate, onOpen: onOpenTemplate, onClose: onCloseTemplate } = useDisclosure(); + const [testModules, setTestModules] = useState(); const onFixView = useCallback(() => { @@ -111,6 +111,48 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { }, 100); }, []); + const flow2AppModules = useCallback(() => { + const modules: AppModuleItemType[] = nodes.map((item) => ({ + moduleId: item.data.moduleId, + position: item.position, + flowType: item.data.flowType, + inputs: item.data.inputs.map((item) => ({ + key: item.key, + value: item.value, + connected: item.type !== FlowInputItemTypeEnum.target + })), + outputs: item.data.outputs.map((item) => ({ + key: item.key, + targets: [] as FlowOutputTargetItemType[] + })) + })); + + // update inputs and outputs + modules.forEach((module) => { + module.inputs.forEach((input) => { + input.connected = + input.connected || + !!edges.find( + (edge) => edge.target === module.moduleId && edge.targetHandle === input.key + ); + }); + module.outputs.forEach((output) => { + output.targets = edges + .filter( + (edge) => + edge.source === module.moduleId && + edge.sourceHandle === output.key && + edge.targetHandle + ) + .map((edge) => ({ + moduleId: edge.target, + key: edge.targetHandle || '' + })); + }); + }); + return modules; + }, [edges, nodes]); + const onChangeNode = useCallback( ({ moduleId, key, type = 'inputs', value, valueKey = 'value' }: FlowModuleItemChangeProps) => { setNodes((nodes) => @@ -176,48 +218,6 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { }, [onChangeNode, onDelNode, setNodes, x, y, zoom] ); - const flow2AppModules = useCallback(() => { - const modules: AppModuleItemType[] = nodes.map((item) => ({ - moduleId: item.data.moduleId, - position: item.position, - flowType: item.data.flowType, - inputs: item.data.inputs.map((item) => ({ - key: item.key, - value: item.value, - connected: item.type !== FlowInputItemTypeEnum.target - })), - outputs: item.data.outputs.map((item) => ({ - key: item.key, - targets: [] as FlowOutputTargetItemType[] - })) - })); - - // update inputs and outputs - modules.forEach((module) => { - module.inputs.forEach((input) => { - input.connected = - input.connected || - !!edges.find( - (edge) => edge.target === module.moduleId && edge.targetHandle === input.key - ); - }); - module.outputs.forEach((output) => { - output.targets = edges - .filter( - (edge) => - edge.source === module.moduleId && - edge.sourceHandle === output.key && - edge.targetHandle - ) - .map((edge) => ({ - moduleId: edge.target, - key: edge.targetHandle || '' - })); - }); - }); - return modules; - }, [edges, nodes]); - const onDelConnect = useCallback( (id: string) => { setEdges((state) => state.filter((item) => item.id !== id)); @@ -274,7 +274,6 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { ) ); - setLoaded(true); onFixView(); }, [onDelConnect, setEdges, setNodes, onFixView, onChangeNode, onDelNode] @@ -296,10 +295,10 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { > {fullScreen ? ( <> - + } + icon={} borderRadius={'md'} borderColor={'myGray.300'} variant={'base'} @@ -425,7 +424,12 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { - + import('../VariableEditModal')); +const InfoModal = dynamic(() => import('../InfoModal')); const Settings = ({ appId }: { appId: string }) => { const theme = useTheme(); + const router = useRouter(); + const { toast } = useToast(); const { appDetail, updateAppDetail, loadKbList, myKbList } = useUserStore(); const { isPc } = useGlobalStore(); const [editVariable, setEditVariable] = useState(); - - useQuery(['initkb', appId], () => loadKbList()); + const [settingAppInfo, setSettingAppInfo] = useState(); const [refresh, setRefresh] = useState(false); @@ -117,6 +123,24 @@ const Settings = ({ appId }: { appId: string }) => { [myKbList, kbList] ); + /* 点击删除 */ + const { mutate: handleDelModel, isLoading } = useRequest({ + mutationFn: async () => { + if (!appDetail) return null; + await delModelById(appDetail._id); + return 'success'; + }, + onSuccess(res) { + if (!res) return; + toast({ + title: '删除成功', + status: 'success' + }); + router.replace(`/app/list`); + }, + errorToast: '删除失败' + }); + const appModule2Form = useCallback(() => { const formVal = appModules2Form(appDetail.modules); reset(formVal); @@ -139,6 +163,8 @@ const Settings = ({ appId }: { appId: string }) => { appModule2Form(); }, [appModule2Form]); + useQuery(['initkb', appId], () => loadKbList()); + const BoxStyles: BoxProps = { bg: 'myWhite.200', px: 4, @@ -163,15 +189,95 @@ const Settings = ({ appId }: { appId: string }) => { return ( - + + 基础信息 + + {/* basic info */} + + + + + {appDetail.name} + + } + variant={'base'} + borderRadius={'md'} + aria-label={'delete'} + _hover={{ + bg: 'myGray.100', + color: 'red.600' + }} + isLoading={isLoading} + onClick={openConfirm(handleDelModel)} + /> + + + {appDetail.intro || '快来给应用一个介绍~'} + + + } + onClick={() => router.push(`/chat?appId=${appId}`)} + > + 对话 + + } + onClick={() => { + router.replace({ + query: { + appId, + currentTab: 'share' + } + }); + }} + > + 分享 + + } + onClick={() => setSettingAppInfo(appDetail)} + > + 设置 + + + + + 应用配置 @@ -181,221 +287,219 @@ const Settings = ({ appId }: { appId: string }) => { onSubmitSave(data)))} > {isPc ? '保存并预览' : '保存'} - - {/* variable */} - - - - - 变量 - - setEditVariable(addVariable())}> - + 新增 - - - - - - - - 变量名 - 变量 key - 必填 - - - - - {variables.map((item, index) => ( - - {item.label} - {item.key} - {item.required ? '✔' : ''} - - setEditVariable(item)} - /> - removeVariable(index)} - /> - - - ))} - - - + + {/* variable */} + + + + + 变量 - - - - - - AI 配置 + setEditVariable(addVariable())}> + + 新增 - - - 对话模型 - { - setValue('chatModel.model', val); - const maxToken = - chatModelList.find((item) => item.model === getValues('chatModel.model')) - ?.contextMaxToken || 4000; - const token = maxToken / 2; - setValue('chatModel.maxToken', token); - setRefresh(!refresh); - }} - /> - - - 温度 - - { - setValue('chatModel.temperature', e); - setRefresh(!refresh); - }} - /> - - - - 回复上限 - - { - setValue('chatModel.maxToken', val); - setRefresh(!refresh); - }} - /> - - - - - 提示词 - - - - - - - - - 限定词 - - - - - - - - - {/* kb */} - - - - - 知识库 - - - - 选择 - - - - 参数 - - - - 相似度: {getValues('kb.searchSimilarity')}, 单次搜索数量: {getValues('kb.searchLimit')}, - 空搜索时拒绝回复: {getValues('kb.searchEmptyText') !== '' ? 'true' : 'false'} - - - {selectedKbList.map((item) => ( - - - - {item.name} - - - ))} - - - - {/* welcome */} - - - - 对话开场白 - - - - - + + + + + + + 变量名 + 变量 key + 必填 + + + + + {variables.map((item, index) => ( + + {item.label} + {item.key} + {item.required ? '✔' : ''} + + setEditVariable(item)} + /> + removeVariable(index)} + /> + + + ))} + + + + {/* model */} + + + + AI 配置 + + + + 对话模型 + { + setValue('chatModel.model', val); + const maxToken = + chatModelList.find((item) => item.model === getValues('chatModel.model')) + ?.contextMaxToken || 4000; + const token = maxToken / 2; + setValue('chatModel.maxToken', token); + setRefresh(!refresh); + }} + /> + + + 温度 + + { + setValue('chatModel.temperature', e); + setRefresh(!refresh); + }} + /> + + + + 回复上限 + + { + setValue('chatModel.maxToken', val); + setRefresh(!refresh); + }} + /> + + + + + 提示词 + + + + + + + + + 限定词 + + + + + + + + + {/* kb */} + + + + + 知识库 + + + + 选择 + + + + 参数 + + + + 相似度: {getValues('kb.searchSimilarity')}, 单次搜索数量: {getValues('kb.searchLimit')}, + 空搜索时拒绝回复: {getValues('kb.searchEmptyText') !== '' ? 'true' : 'false'} + + + {selectedKbList.map((item) => ( + + + + {item.name} + + + ))} + + + + {/* welcome */} + + + + 对话开场白 + + + + + + + + {settingAppInfo && ( + setSettingAppInfo(undefined)} /> + )} {editVariable && ( import('./InfoModal')); - -const OverView = ({ appId }: { appId: string }) => { - const theme = useTheme(); - const { toast } = useToast(); - const router = useRouter(); - const { Loading, setIsLoading } = useLoading(); - const { appDetail, loadAppDetail } = useUserStore(); - const { openConfirm, ConfirmChild } = useConfirm({ - content: '确认删除该应用?' - }); - const [settingAppInfo, setSettingAppInfo] = useState(); - - /* 点击删除 */ - const handleDelModel = useCallback(async () => { - if (!appDetail) return; - setIsLoading(true); - try { - await delModelById(appDetail._id); - toast({ - title: '删除成功', - status: 'success' - }); - router.replace(`/app/list`); - } catch (err: any) { - toast({ - title: err?.message || '删除失败', - status: 'error' - }); - } - setIsLoading(false); - }, [appDetail, setIsLoading, toast, router]); - - // load app data - useQuery([appId], () => loadAppDetail(appId, true), { - enabled: false - }); - - return ( - - - - - 基本信息 - - - - - - - - {appDetail.name} - - } - variant={'base'} - borderRadius={'md'} - aria-label={'delete'} - _hover={{ - bg: 'myGray.100', - color: 'red.600' - }} - onClick={openConfirm(handleDelModel)} - /> - - - {appDetail.intro || '快来给应用一个介绍~'} - - - } - onClick={() => router.push(`/chat?appId=${appId}`)} - > - 对话 - - } - onClick={() => { - router.replace({ - query: { - appId, - currentTab: 'share' - } - }); - }} - > - 分享 - - } - onClick={() => setSettingAppInfo(appDetail)} - > - 设置 - - - - - - - 近 14 日消费 - - - - - - - - {settingAppInfo && ( - setSettingAppInfo(undefined)} /> - )} - - - - ); -}; - -export default OverView; diff --git a/client/src/pages/app/detail/index.tsx b/client/src/pages/app/detail/index.tsx index 387034ebb..b97b1672e 100644 --- a/client/src/pages/app/detail/index.tsx +++ b/client/src/pages/app/detail/index.tsx @@ -9,16 +9,12 @@ import { useQuery } from '@tanstack/react-query'; import Tabs from '@/components/Tabs'; import SideTabs from '@/components/SideTabs'; -import OverView from './components/OverView'; import Avatar from '@/components/Avatar'; import MyIcon from '@/components/Icon'; import PageContainer from '@/components/PageContainer'; import Loading from '@/components/Loading'; +import BasicEdit from './components/BasicEdit'; -const BasicEdit = dynamic(() => import('./components/BasicEdit'), { - ssr: false, - loading: () => -}); const AdEdit = dynamic(() => import('./components/AdEdit'), { ssr: false, loading: () => @@ -31,7 +27,6 @@ const API = dynamic(() => import('./components/API'), { }); enum TabEnum { - 'overview' = 'overview', 'basicEdit' = 'basicEdit', 'adEdit' = 'adEdit', 'share' = 'share', @@ -59,8 +54,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { const tabList = useMemo( () => [ - { label: '概览', id: TabEnum.overview, icon: 'overviewLight' }, - { label: '简易编排', id: TabEnum.basicEdit, icon: 'edit' }, + { label: '简易配置', id: TabEnum.basicEdit, icon: 'overviewLight' }, { label: '高级编排', id: TabEnum.adEdit, icon: 'settingLight' }, { label: '链接分享', id: TabEnum.share, icon: 'shareLight' }, { label: 'API访问', id: TabEnum.API, icon: 'apiLight' }, @@ -174,13 +168,12 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { /> - {currentTab === TabEnum.overview && } {currentTab === TabEnum.basicEdit && } {currentTab === TabEnum.adEdit && appDetail && ( setCurrentTab(TabEnum.overview)} + onFullScreen={() => setCurrentTab(TabEnum.basicEdit)} /> )} {currentTab === TabEnum.API && } @@ -192,7 +185,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => { }; export async function getServerSideProps(context: any) { - const currentTab = context?.query?.currentTab || TabEnum.overview; + const currentTab = context?.query?.currentTab || TabEnum.basicEdit; return { props: { currentTab } diff --git a/client/src/pages/chat/components/SliderApps.tsx b/client/src/pages/chat/components/SliderApps.tsx index 24949645f..091c9f8b1 100644 --- a/client/src/pages/chat/components/SliderApps.tsx +++ b/client/src/pages/chat/components/SliderApps.tsx @@ -21,7 +21,7 @@ const SliderApps = ({ appId }: { appId: string }) => { px={3} borderRadius={'md'} _hover={{ bg: 'myGray.200' }} - onClick={() => router.push('/app/list')} + onClick={() => router.back()} >