perf: app edit

This commit is contained in:
archer
2023-07-25 15:48:59 +08:00
parent c16e2b8dd6
commit 35718b1f26
8 changed files with 413 additions and 441 deletions

View File

@@ -1 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683436459815" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1278" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64"><path d="M377.04830749 512.02677874l395.88826112-382.68981248c23.33363769-22.59430969 23.33363769-59.16132466 0-81.68344804-23.33247261-22.5582171-61.18836224-22.5582171-84.52083485 0L250.30081877 471.19378659c-23.29754397 22.5582171-23.29754397 59.14385977 0 81.63105451l438.11491385 423.52280349c11.70233003 11.27968995 26.99767353 16.91837099 42.29534607 16.91837098 15.29883648 0 30.59418112-5.63984498 42.22548878-16.95446471 23.33363769-22.5582171 23.33363769-59.07283854 0-81.63105451L377.04830749 512.02677874" p-id="1279"></path></svg>
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1683436459815" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1278" xmlns:xlink="http://www.w3.org/1999/xlink" ><path d="M377.04830749 512.02677874l395.88826112-382.68981248c23.33363769-22.59430969 23.33363769-59.16132466 0-81.68344804-23.33247261-22.5582171-61.18836224-22.5582171-84.52083485 0L250.30081877 471.19378659c-23.29754397 22.5582171-23.29754397 59.14385977 0 81.63105451l438.11491385 423.52280349c11.70233003 11.27968995 26.99767353 16.91837099 42.29534607 16.91837098 15.29883648 0 30.59418112-5.63984498 42.22548878-16.95446471 23.33363769-22.5582171 23.33363769-59.07283854 0-81.63105451L377.04830749 512.02677874" p-id="1279"></path></svg>

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 844 B

View File

@@ -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: '内容生成',

View File

@@ -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<AppModuleItemType>[];
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 (
<>
<Box
@@ -49,7 +81,7 @@ const ModuleTemplateList = ({
</Box>
<Box flex={'1 0 0'} overflow={'overlay'}>
<Box w={['100%', '330px']} mx={'auto'}>
{ModuleTemplates.map((item) =>
{filterTemplates.map((item) =>
item.list.map((item) => (
<Flex
key={item.name}
@@ -68,11 +100,11 @@ const ModuleTemplateList = ({
}}
onClick={(e) => {
if (isPc) return;
onClose();
onAddNode({
template: item,
position: { x: e.clientX, y: e.clientY }
});
onClose();
}}
>
<Avatar src={item.logo} w={'34px'} objectFit={'contain'} borderRadius={'0'} />

View File

@@ -95,12 +95,12 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
const { x, y, zoom } = useViewport();
const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
const [edges, setEdges, onEdgesChange] = useEdgesState([]);
const [loaded, setLoaded] = useState(false);
const {
isOpen: isOpenTemplate,
onOpen: onOpenTemplate,
onClose: onCloseTemplate
} = useDisclosure();
const [testModules, setTestModules] = useState<AppModuleItemType[]>();
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 ? (
<>
<MyTooltip label={'取消全屏'} offset={[10, 10]}>
<MyTooltip label={'返回'} offset={[10, 10]}>
<IconButton
size={'sm'}
icon={<MyIcon name={'fullScreenLight'} w={['14px', '16px']} />}
icon={<MyIcon name={'back'} w={'14px'} />}
borderRadius={'md'}
borderColor={'myGray.300'}
variant={'base'}
@@ -425,7 +424,12 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
<Controls position={'bottom-right'} style={{ display: 'flex' }} showInteractive={false} />
</ReactFlow>
<TemplateList isOpen={isOpenTemplate} onAddNode={onAddNode} onClose={onCloseTemplate} />
<TemplateList
isOpen={isOpenTemplate}
nodes={nodes}
onAddNode={onAddNode}
onClose={onCloseTemplate}
/>
<ChatTest
ref={ChatTestRef}
modules={testModules}

View File

@@ -40,6 +40,10 @@ import { useRequest } from '@/hooks/useRequest';
import { useConfirm } from '@/hooks/useConfirm';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { streamFetch } from '@/api/fetch';
import { useRouter } from 'next/router';
import { useToast } from '@/hooks/useToast';
import { AppSchema } from '@/types/mongoSchema';
import { delModelById } from '@/api/app';
import dynamic from 'next/dynamic';
import MySelect from '@/components/Select';
@@ -56,15 +60,17 @@ import { addVariable } from '../VariableEditModal';
import { KBSelectModal, KbParamsModal } from '../KBSelectModal';
const VariableEditModal = dynamic(() => 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<VariableItemType>();
useQuery(['initkb', appId], () => loadKbList());
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
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 (
<Box
display={['block', 'flex']}
flexDirection={'column'}
h={'100%'}
borderRight={'1.5px solid'}
borderColor={'myGray.200'}
pt={4}
pl={4}
p={4}
pt={[0, 4]}
overflow={'overlay'}
>
<Flex pr={4} justifyContent={'space-between'}>
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
</Box>
{/* basic info */}
<Box
border={theme.borders.base}
borderRadius={'lg'}
mt={2}
px={5}
py={4}
bg={'myBlue.100'}
position={'relative'}
>
<Flex alignItems={'center'} py={2}>
<Avatar src={appDetail.avatar} borderRadius={'md'} w={'28px'} />
<Box ml={3} fontWeight={'bold'} fontSize={'lg'}>
{appDetail.name}
</Box>
<IconButton
className="delete"
position={'absolute'}
top={4}
right={4}
size={'sm'}
icon={<MyIcon name={'delete'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
_hover={{
bg: 'myGray.100',
color: 'red.600'
}}
isLoading={isLoading}
onClick={openConfirm(handleDelModel)}
/>
</Flex>
<Box
flex={1}
my={2}
className={'textEllipsis3'}
wordBreak={'break-all'}
color={'myGray.600'}
>
{appDetail.intro || '快来给应用一个介绍~'}
</Box>
<Flex>
<Button
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'chatLight'} w={'16px'} />}
onClick={() => router.push(`/chat?appId=${appId}`)}
>
</Button>
<Button
mx={3}
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'shareLight'} w={'16px'} />}
onClick={() => {
router.replace({
query: {
appId,
currentTab: 'share'
}
});
}}
>
</Button>
<Button
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'settingLight'} w={'16px'} />}
onClick={() => setSettingAppInfo(appDetail)}
>
</Button>
</Flex>
</Box>
<Flex mt={5} justifyContent={'space-between'} alignItems={'center'}>
<Box fontSize={['md', 'xl']} fontWeight={'bold'}>
<MyTooltip label={'仅包含基础功能,复杂 agent 功能请使用高级编排。'} forceShow>
@@ -181,221 +287,219 @@ const Settings = ({ appId }: { appId: string }) => {
<Button
isLoading={isSaving}
fontSize={'sm'}
size={['sm', 'md']}
onClick={openConfirm(handleSubmit((data) => onSubmitSave(data)))}
>
{isPc ? '保存并预览' : '保存'}
</Button>
</Flex>
<Box flex={'1 0 0'} my={4} pr={4} overflowY={'auto'}>
{/* variable */}
<Box {...BoxStyles}>
<Flex alignItems={'center'}>
<Avatar src={'/imgs/module/variable.png'} objectFit={'contain'} w={'18px'} />
<Box ml={2} flex={1}>
</Box>
<Flex {...BoxBtnStyles} onClick={() => setEditVariable(addVariable())}>
+&ensp;
</Flex>
</Flex>
<Box
mt={2}
borderRadius={'lg'}
overflow={'hidden'}
borderWidth={'1px'}
borderBottom="none"
>
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th></Th>
<Th> key</Th>
<Th></Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{variables.map((item, index) => (
<Tr key={item.id}>
<Td>{item.label} </Td>
<Td>{item.key}</Td>
<Td>{item.required ? '✔' : ''}</Td>
<Td>
<MyIcon
mr={3}
name={'settingLight'}
w={'16px'}
cursor={'pointer'}
onClick={() => setEditVariable(item)}
/>
<MyIcon
name={'delete'}
w={'16px'}
cursor={'pointer'}
onClick={() => removeVariable(index)}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
{/* variable */}
<Box mt={2} {...BoxStyles}>
<Flex alignItems={'center'}>
<Avatar src={'/imgs/module/variable.png'} objectFit={'contain'} w={'18px'} />
<Box ml={2} flex={1}>
</Box>
</Box>
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<Avatar src={'/imgs/module/AI.png'} w={'18px'} />
<Box ml={2}>AI </Box>
<Flex {...BoxBtnStyles} onClick={() => setEditVariable(addVariable())}>
+&ensp;
</Flex>
<Flex alignItems={'center'} mt={5}>
<Box {...LabelStyles}></Box>
<MySelect
width={['100%', '300px']}
value={getValues('chatModel.model')}
list={chatModelSelectList}
onchange={(val: any) => {
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);
}}
/>
</Flex>
<Flex alignItems={'center'} my={10}>
<Box {...LabelStyles}></Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: '严谨', value: 0 },
{ label: '发散', value: 10 }
]}
width={'95%'}
min={0}
max={10}
value={getValues('chatModel.temperature')}
onChange={(e) => {
setValue('chatModel.temperature', e);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex alignItems={'center'} mt={12} mb={10}>
<Box {...LabelStyles}></Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: `${tokenLimit}`, value: tokenLimit }
]}
width={'95%'}
min={100}
max={tokenLimit}
step={50}
value={getValues('chatModel.maxToken')}
onChange={(val) => {
setValue('chatModel.maxToken', val);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex mt={10} alignItems={'flex-start'}>
<Box {...LabelStyles}>
<MyTooltip label={ChatModelSystemTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<Textarea
rows={5}
placeholder={ChatModelSystemTip}
borderColor={'myGray.100'}
{...register('chatModel.systemPrompt')}
></Textarea>
</Flex>
<Flex mt={5} alignItems={'flex-start'}>
<Box {...LabelStyles}>
<MyTooltip label={ChatModelLimitTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<Textarea
rows={5}
placeholder={ChatModelLimitTip}
borderColor={'myGray.100'}
{...register('chatModel.limitPrompt')}
></Textarea>
</Flex>
</Box>
{/* kb */}
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} flex={1}>
<Avatar src={'/imgs/module/db.png'} w={'18px'} />
<Box ml={2}></Box>
</Flex>
<Flex alignItems={'center'} mr={3} {...BoxBtnStyles} onClick={onOpenKbSelect}>
<SmallAddIcon />
</Flex>
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbParams}>
<MyIcon name={'edit'} w={'14px'} mr={1} />
</Flex>
</Flex>
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
: {getValues('kb.searchSimilarity')}, : {getValues('kb.searchLimit')},
: {getValues('kb.searchEmptyText') !== '' ? 'true' : 'false'}
</Flex>
<Grid templateColumns={['1fr', 'repeat(2,1fr)']} my={2} gridGap={[2, 4]}>
{selectedKbList.map((item) => (
<Flex
key={item._id}
alignItems={'center'}
p={2}
bg={'white'}
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
borderRadius={'md'}
border={theme.borders.base}
>
<Avatar src={item.avatar} w={'18px'} mr={1} />
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
{item.name}
</Box>
</Flex>
))}
</Grid>
</Box>
{/* welcome */}
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<Avatar src={'/imgs/module/userGuide.png'} w={'18px'} />
<Box mx={2}></Box>
<MyTooltip label={welcomeTextTip} forceShow>
<QuestionOutlineIcon />
</MyTooltip>
</Flex>
<Textarea
mt={2}
rows={5}
placeholder={welcomeTextTip}
borderColor={'myGray.100'}
{...register('guide.welcome.text')}
/>
</Flex>
<Box mt={2} borderRadius={'lg'} overflow={'hidden'} borderWidth={'1px'} borderBottom="none">
<TableContainer>
<Table bg={'white'}>
<Thead>
<Tr>
<Th></Th>
<Th> key</Th>
<Th></Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{variables.map((item, index) => (
<Tr key={item.id}>
<Td>{item.label} </Td>
<Td>{item.key}</Td>
<Td>{item.required ? '✔' : ''}</Td>
<Td>
<MyIcon
mr={3}
name={'settingLight'}
w={'16px'}
cursor={'pointer'}
onClick={() => setEditVariable(item)}
/>
<MyIcon
name={'delete'}
w={'16px'}
cursor={'pointer'}
onClick={() => removeVariable(index)}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
</Box>
</Box>
{/* model */}
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<Avatar src={'/imgs/module/AI.png'} w={'18px'} />
<Box ml={2}>AI </Box>
</Flex>
<Flex alignItems={'center'} mt={5}>
<Box {...LabelStyles}></Box>
<MySelect
width={['100%', '300px']}
value={getValues('chatModel.model')}
list={chatModelSelectList}
onchange={(val: any) => {
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);
}}
/>
</Flex>
<Flex alignItems={'center'} my={10}>
<Box {...LabelStyles}></Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: '严谨', value: 0 },
{ label: '发散', value: 10 }
]}
width={'95%'}
min={0}
max={10}
value={getValues('chatModel.temperature')}
onChange={(e) => {
setValue('chatModel.temperature', e);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex alignItems={'center'} mt={12} mb={10}>
<Box {...LabelStyles}></Box>
<Box flex={1} ml={'10px'}>
<MySlider
markList={[
{ label: '100', value: 100 },
{ label: `${tokenLimit}`, value: tokenLimit }
]}
width={'95%'}
min={100}
max={tokenLimit}
step={50}
value={getValues('chatModel.maxToken')}
onChange={(val) => {
setValue('chatModel.maxToken', val);
setRefresh(!refresh);
}}
/>
</Box>
</Flex>
<Flex mt={10} alignItems={'flex-start'}>
<Box {...LabelStyles}>
<MyTooltip label={ChatModelSystemTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<Textarea
rows={5}
placeholder={ChatModelSystemTip}
borderColor={'myGray.100'}
{...register('chatModel.systemPrompt')}
></Textarea>
</Flex>
<Flex mt={5} alignItems={'flex-start'}>
<Box {...LabelStyles}>
<MyTooltip label={ChatModelLimitTip} forceShow>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Box>
<Textarea
rows={5}
placeholder={ChatModelLimitTip}
borderColor={'myGray.100'}
{...register('chatModel.limitPrompt')}
></Textarea>
</Flex>
</Box>
{/* kb */}
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<Flex alignItems={'center'} flex={1}>
<Avatar src={'/imgs/module/db.png'} w={'18px'} />
<Box ml={2}></Box>
</Flex>
<Flex alignItems={'center'} mr={3} {...BoxBtnStyles} onClick={onOpenKbSelect}>
<SmallAddIcon />
</Flex>
<Flex alignItems={'center'} {...BoxBtnStyles} onClick={onOpenKbParams}>
<MyIcon name={'edit'} w={'14px'} mr={1} />
</Flex>
</Flex>
<Flex mt={1} color={'myGray.600'} fontSize={['sm', 'md']}>
: {getValues('kb.searchSimilarity')}, : {getValues('kb.searchLimit')},
: {getValues('kb.searchEmptyText') !== '' ? 'true' : 'false'}
</Flex>
<Grid templateColumns={['1fr', 'repeat(2,1fr)']} my={2} gridGap={[2, 4]}>
{selectedKbList.map((item) => (
<Flex
key={item._id}
alignItems={'center'}
p={2}
bg={'white'}
boxShadow={'0 4px 8px -2px rgba(16,24,40,.1),0 2px 4px -2px rgba(16,24,40,.06)'}
borderRadius={'md'}
border={theme.borders.base}
>
<Avatar src={item.avatar} w={'18px'} mr={1} />
<Box flex={'1 0 0'} w={0} className={'textEllipsis'} fontSize={'sm'}>
{item.name}
</Box>
</Flex>
))}
</Grid>
</Box>
{/* welcome */}
<Box mt={5} {...BoxStyles}>
<Flex alignItems={'center'}>
<Avatar src={'/imgs/module/userGuide.png'} w={'18px'} />
<Box mx={2}></Box>
<MyTooltip label={welcomeTextTip} forceShow>
<QuestionOutlineIcon />
</MyTooltip>
</Flex>
<Textarea
mt={2}
rows={5}
placeholder={welcomeTextTip}
borderColor={'myGray.100'}
{...register('guide.welcome.text')}
/>
</Box>
<ConfirmChild />
{settingAppInfo && (
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
)}
{editVariable && (
<VariableEditModal
defaultVariable={editVariable}

View File

@@ -1,165 +0,0 @@
import React, { useCallback, useState } from 'react';
import { Box, Flex, Button, Grid, useTheme, IconButton } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import { useUserStore } from '@/store/user';
import { useToast } from '@/hooks/useToast';
import { useLoading } from '@/hooks/useLoading';
import { delModelById } from '@/api/app';
import { useConfirm } from '@/hooks/useConfirm';
import { AppSchema } from '@/types/mongoSchema';
import dynamic from 'next/dynamic';
import Avatar from '@/components/Avatar';
import MyIcon from '@/components/Icon';
import TotalUsage from './Charts/TotalUsage';
import MyTooltip from '@/components/MyTooltip';
const InfoModal = dynamic(() => 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<AppSchema>();
/* 点击删除 */
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 (
<Box h={'100%'} display={['block', 'flex']} flexDirection={'column'} position={'relative'}>
<Grid
gridTemplateColumns={['1fr', 'repeat(2,1fr)']}
gridGap={[2, 4, 6]}
pt={[0, 7]}
px={[3, 5, 8]}
>
<Box>
<Box mb={2} fontSize={['md', 'xl']}>
</Box>
<Box
border={theme.borders.base}
borderRadius={'lg'}
px={5}
py={4}
bg={'myBlue.100'}
position={'relative'}
>
<Flex alignItems={'center'} py={2}>
<MyTooltip label={'选择头像'}>
<Avatar src={appDetail.avatar} borderRadius={'md'} w={'28px'} />
</MyTooltip>
<Box ml={3} fontWeight={'bold'} fontSize={'lg'}>
{appDetail.name}
</Box>
<IconButton
className="delete"
position={'absolute'}
top={4}
right={4}
size={'sm'}
icon={<MyIcon name={'delete'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
_hover={{
bg: 'myGray.100',
color: 'red.600'
}}
onClick={openConfirm(handleDelModel)}
/>
</Flex>
<Box
h={'68px'}
flex={1}
my={2}
className={'textEllipsis3'}
wordBreak={'break-all'}
color={'myGray.600'}
>
{appDetail.intro || '快来给应用一个介绍~'}
</Box>
<Flex>
<Button
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'chatLight'} w={'16px'} />}
onClick={() => router.push(`/chat?appId=${appId}`)}
>
</Button>
<Button
mx={3}
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'shareLight'} w={'16px'} />}
onClick={() => {
router.replace({
query: {
appId,
currentTab: 'share'
}
});
}}
>
</Button>
<Button
size={['sm', 'md']}
variant={'base'}
leftIcon={<MyIcon name={'settingLight'} w={'16px'} />}
onClick={() => setSettingAppInfo(appDetail)}
>
</Button>
</Flex>
</Box>
</Box>
<Flex flexDirection={'column'}>
<Box mb={2} fontSize={['md', 'xl']}>
14
</Box>
<Box flex={'1 0 0'} boxShadow={theme.shadows.base} borderRadius={'lg'} px={5} py={4}>
<TotalUsage appId={appId} />
</Box>
</Flex>
</Grid>
{settingAppInfo && (
<InfoModal defaultApp={settingAppInfo} onClose={() => setSettingAppInfo(undefined)} />
)}
<ConfirmChild />
<Loading fixed={false} />
</Box>
);
};
export default OverView;

View File

@@ -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: () => <Loading />
});
const AdEdit = dynamic(() => import('./components/AdEdit'), {
ssr: false,
loading: () => <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}` }) => {
/>
</Box>
<Box flex={'1 0 0'} h={[0, '100%']} overflow={['overlay', '']}>
{currentTab === TabEnum.overview && <OverView appId={appId} />}
{currentTab === TabEnum.basicEdit && <BasicEdit appId={appId} />}
{currentTab === TabEnum.adEdit && appDetail && (
<AdEdit
app={appDetail}
fullScreen={true}
onFullScreen={() => setCurrentTab(TabEnum.overview)}
onFullScreen={() => setCurrentTab(TabEnum.basicEdit)}
/>
)}
{currentTab === TabEnum.API && <API appId={appId} />}
@@ -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 }

View File

@@ -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()}
>
<IconButton
mr={3}