feat: app ui

This commit is contained in:
archer
2023-07-10 08:56:11 +08:00
parent aef42cef9d
commit cd77d81135
33 changed files with 661 additions and 547 deletions

View File

@@ -24,7 +24,7 @@ const API = ({ modelId }: { modelId: string }) => {
}, []);
return (
<Flex flexDirection={'column'} h={'100%'}>
<Flex flexDirection={'column'} pt={[0, 5]} h={'100%'}>
<Box display={['none', 'flex']} px={5} alignItems={'center'}>
<Box flex={1}>
AppId:

View File

@@ -161,7 +161,9 @@ const Settings = ({ modelId }: { modelId: string }) => {
position={'relative'}
maxW={['auto', '800px']}
>
<Box fontSize={['md', 'xl']}></Box>
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
</Box>
<Flex mt={5} alignItems={'center'}>
<Box w={['60px', '100px', '140px']} flexShrink={0}>

View File

@@ -108,7 +108,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
};
return (
<Box position={'relative'} px={5} minH={'50vh'}>
<Box position={'relative'} pt={[0, 5]} px={5} minH={'50vh'}>
<Flex justifyContent={'space-between'}>
<Box fontWeight={'bold'}>

View File

@@ -0,0 +1,66 @@
import { AppModuleItemType } from '@/types/app';
import { AppSchema } from '@/types/mongoSchema';
import React, { useState, useEffect, useRef, useMemo } from 'react';
import { Box, useOutsideClick, Flex, IconButton } from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import { useChat } from '@/hooks/useChat';
const ChatTest = ({
app,
modules,
onClose
}: {
app: AppSchema;
modules?: AppModuleItemType[];
onClose: () => void;
}) => {
const isOpen = useMemo(() => !!modules, [modules]);
const { ChatBox, ChatInput, ChatBoxParentRef, setChatHistory } = useChat({
appId: app._id
});
return (
<Flex
zIndex={3}
flexDirection={'column'}
position={'absolute'}
top={5}
right={0}
h={isOpen ? '95%' : '0'}
w={isOpen ? '460px' : '0'}
bg={'white'}
boxShadow={'3px 0 20px rgba(0,0,0,0.2)'}
borderRadius={'md'}
overflow={'hidden'}
transition={'.2s ease'}
>
<Flex py={4} px={5} whiteSpace={'nowrap'}>
<Box fontSize={'xl'} fontWeight={'bold'} flex={1}>
</Box>
<IconButton
className="chat"
size={'sm'}
icon={<MyIcon name={'clearLight'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
onClick={(e) => {
e.stopPropagation();
setChatHistory([]);
}}
/>
</Flex>
<Box ref={ChatBoxParentRef} flex={1} px={5} overflow={'overlay'}>
<ChatBox appAvatar={app.avatar} />
</Box>
<Box px={5}>
<ChatInput />
</Box>
</Flex>
);
};
export default ChatTest;

View File

@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useRef } from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import ReactFlow, {
Background,
Controls,
@@ -27,6 +27,7 @@ import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
import ButtonEdge from './components/modules/ButtonEdge';
import MyTooltip from '@/components/MyTooltip';
const NodeChat = dynamic(() => import('./components/NodeChat'), {
ssr: false
});
@@ -48,13 +49,16 @@ const NodeQuestionInput = dynamic(() => import('./components/NodeQuestionInput')
const TemplateList = dynamic(() => import('./components/TemplateList'), {
ssr: false
});
const ChatTest = dynamic(() => import('./components/ChatTest'), {
ssr: false
});
const NodeCQNode = dynamic(() => import('./components/NodeCQNode'), {
ssr: false
});
import 'reactflow/dist/style.css';
import styles from './index.module.scss';
import { AppModuleTemplateItemType } from '@/types/app';
import { AppModuleItemType, AppModuleTemplateItemType } from '@/types/app';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
@@ -83,6 +87,7 @@ const AppEdit = ({ app, onBack }: Props) => {
onOpen: onOpenTemplate,
onClose: onCloseTemplate
} = useDisclosure();
const [testModules, setTestModules] = useState<AppModuleItemType[]>();
const onChangeNode = useCallback(
({ moduleId, key, type = 'inputs', value, valueKey = 'value' }: FlowModuleItemChangeProps) => {
@@ -149,6 +154,41 @@ const AppEdit = ({ app, onBack }: Props) => {
},
[onChangeNode, onDelNode, setNodes, x, y, zoom]
);
const flow2Modules = useCallback(() => {
const modules: AppModuleItemType[] = nodes.map((item) => ({
...item.data,
position: item.position,
onChangeNode: undefined,
onDelNode: undefined,
outputs: item.data.outputs.map((output) => ({
...output,
targets: [] as FlowOutputTargetItemType[]
}))
}));
// update inputs and outputs
modules.forEach((module) => {
module.inputs.forEach((input) => {
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) => {
@@ -177,42 +217,8 @@ const AppEdit = ({ app, onBack }: Props) => {
const { mutate: onclickSave, isLoading } = useRequest({
mutationFn: () => {
const modules = nodes.map((item) => ({
...item.data,
position: item.position,
onChangeNode: undefined,
onDelNode: undefined,
outputs: item.data.outputs.map((output) => ({
...output,
targets: [] as FlowOutputTargetItemType[]
}))
}));
console.log(modules);
// update inputs and outputs
modules.forEach((module) => {
module.inputs.forEach((input) => {
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 putAppById(app._id, {
modules
modules: flow2Modules()
});
},
successToast: '保存配置成功',
@@ -244,10 +250,11 @@ const AppEdit = ({ app, onBack }: Props) => {
useEffect(() => {
initData(JSON.parse(JSON.stringify(app)));
}, [app]);
}, [app, initData]);
return (
<Flex h={'100%'} flexDirection={'column'} bg={'#fff'}>
{/* header */}
<Flex py={3} px={5} borderBottom={theme.borders.base} alignItems={'center'}>
<IconButton
size={'sm'}
@@ -263,20 +270,42 @@ const AppEdit = ({ app, onBack }: Props) => {
<Box ml={5} fontSize={'xl'} flex={1}>
{app.name}
</Box>
<IconButton
icon={<MyIcon name={'save'} w={'16px'} />}
borderRadius={'lg'}
isLoading={isLoading}
aria-label={'save'}
bg={'myBlue.200'}
variant={'base'}
border={'none'}
color={'myGray.900'}
_hover={{
bg: 'myBlue.300'
}}
onClick={onclickSave}
/>
{testModules ? (
<IconButton
mr={6}
icon={<SmallCloseIcon fontSize={'25px'} />}
variant={'base'}
color={'myGray.600'}
borderRadius={'lg'}
aria-label={''}
onClick={() => setTestModules(undefined)}
/>
) : (
<MyTooltip label={'测试对话'}>
<IconButton
mr={6}
icon={<MyIcon name={'chatLight'} w={'16px'} />}
borderRadius={'lg'}
aria-label={'save'}
variant={'base'}
onClick={() => {
// @ts-ignore
onclickSave();
setTestModules(flow2Modules());
}}
/>
</MyTooltip>
)}
<MyTooltip label={'保存配置'}>
<IconButton
icon={<MyIcon name={'save'} w={'16px'} />}
borderRadius={'lg'}
isLoading={isLoading}
aria-label={'save'}
onClick={onclickSave}
/>
</MyTooltip>
</Flex>
<Box
flex={'1 0 0'}
@@ -288,6 +317,7 @@ const AppEdit = ({ app, onBack }: Props) => {
return false;
}}
>
{/* open module template */}
<IconButton
position={'absolute'}
top={5}
@@ -334,7 +364,9 @@ const AppEdit = ({ app, onBack }: Props) => {
showInteractive={false}
/>
</ReactFlow>
<TemplateList isOpen={isOpenTemplate} onAddNode={onAddNode} onClose={onCloseTemplate} />
<ChatTest modules={testModules} app={app} onClose={() => setTestModules(undefined)} />
</Box>
</Flex>
);

View File

@@ -1,14 +1,15 @@
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, useTheme } from '@chakra-ui/react';
import { Box, Flex, IconButton, useTheme } from '@chakra-ui/react';
import { useUserStore } from '@/store/user';
import dynamic from 'next/dynamic';
import { defaultApp } from '@/constants/model';
import Tabs from '@/components/Tabs';
import SlideTabs from '@/components/SlideTabs';
import Settings from './components/Settings';
import { defaultApp } from '@/constants/model';
import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon';
import PageContainer from '@/components/PageContainer';
const EditApp = dynamic(() => import('./components/edit'), {
@@ -54,11 +55,11 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
const tabList = useMemo(
() => [
{ label: '基础信息', id: TabEnum.settings, icon: 'text' },
...(isOwner ? [{ label: '编排', id: TabEnum.edit, icon: 'edit' }] : []),
{ label: '分享', id: TabEnum.share, icon: 'edit' },
{ label: 'API', id: TabEnum.API, icon: 'edit' },
{ label: '立即对话', id: 'startChat', icon: 'chat' }
{ label: '概览', id: TabEnum.settings, icon: 'overviewLight' },
...(isOwner ? [{ label: '高级设置', id: TabEnum.edit, icon: 'settingLight' }] : []),
{ label: '链接分享', id: TabEnum.share, icon: 'shareLight' },
{ label: 'API访问', id: TabEnum.API, icon: 'apiLight' },
{ label: '立即对话', id: 'startChat', icon: 'chatLight' }
],
[isOwner]
);
@@ -82,7 +83,13 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
<PageContainer>
<Box display={['block', 'flex']} h={'100%'}>
{/* pc tab */}
<Box display={['none', 'block']} p={4} w={'200px'} borderRight={theme.borders.base}>
<Box
display={['none', 'flex']}
flexDirection={'column'}
p={4}
w={'200px'}
borderRight={theme.borders.base}
>
<Flex mb={4} alignItems={'center'}>
<Avatar src={appDetail.avatar} w={'34px'} borderRadius={'lg'} />
<Box ml={2} fontWeight={'bold'}>
@@ -90,6 +97,7 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
</Box>
</Flex>
<SlideTabs
flex={1}
mx={'auto'}
mt={2}
w={'100%'}
@@ -103,6 +111,27 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
}
}}
/>
<Flex
alignItems={'center'}
cursor={'pointer'}
py={2}
px={3}
borderRadius={'md'}
_hover={{ bg: 'myGray.100' }}
onClick={() => router.replace('/app/list')}
>
<IconButton
mr={3}
icon={<MyIcon name={'backFill'} w={'18px'} color={'myBlue.600'} />}
bg={'white'}
boxShadow={'1px 1px 9px rgba(0,0,0,0.15)'}
h={'28px'}
size={'sm'}
borderRadius={'50%'}
aria-label={''}
/>
</Flex>
</Box>
{/* phone tab */}
<Box display={['block', 'none']} textAlign={'center'} px={5} py={3}>