This commit is contained in:
archer
2023-07-11 15:57:01 +08:00
parent cd77d81135
commit eb768d9c04
47 changed files with 1949 additions and 1280 deletions

View File

@@ -8,7 +8,7 @@ const APIKeyModal = dynamic(() => import('@/components/APIKeyModal'), {
ssr: false
});
const API = ({ modelId }: { modelId: string }) => {
const API = ({ appId }: { appId: string }) => {
const theme = useTheme();
const { copyData } = useCopyData();
const [baseUrl, setBaseUrl] = useState('https://fastgpt.run/api/openapi');
@@ -33,9 +33,9 @@ const API = ({ modelId }: { modelId: string }) => {
ml={2}
fontWeight={'bold'}
cursor={'pointer'}
onClick={() => copyData(modelId, '已复制 AppId')}
onClick={() => copyData(appId, '已复制 AppId')}
>
{modelId}
{appId}
</Box>
</Box>
<Flex

View File

@@ -16,7 +16,7 @@ import type { AppSchema } from '@/types/mongoSchema';
import Avatar from '@/components/Avatar';
const Settings = ({ modelId }: { modelId: string }) => {
const Settings = ({ appId }: { appId: string }) => {
const { toast } = useToast();
const router = useRouter();
const { Loading, setIsLoading } = useLoading();
@@ -136,10 +136,10 @@ const Settings = ({ modelId }: { modelId: string }) => {
);
// load model data
const { isLoading } = useQuery([modelId], () => loadAppDetail(modelId, true), {
const { isLoading } = useQuery([appId], () => loadAppDetail(appId, true), {
onSuccess(res) {
res && reset(res);
modelId && setLastModelId(modelId);
appId && setLastModelId(appId);
setRefresh(!refresh);
},
onError(err: any) {
@@ -240,7 +240,7 @@ const Settings = ({ modelId }: { modelId: string }) => {
router.prefetch('/chat');
await saveUpdateModel();
} catch (error) {}
router.push(`/chat?appId=${modelId}`);
router.push(`/chat?appId=${appId}`);
}}
>

View File

@@ -38,7 +38,7 @@ import { defaultShareChat } from '@/constants/model';
import type { ShareChatEditType } from '@/types/app';
import MyTooltip from '@/components/MyTooltip';
const Share = ({ modelId }: { modelId: string }) => {
const Share = ({ appId }: { appId: string }) => {
const { toast } = useToast();
const { Loading, setIsLoading } = useLoading();
const { copyData } = useCopyData();
@@ -63,7 +63,7 @@ const Share = ({ modelId }: { modelId: string }) => {
isFetching,
data: shareChatList = [],
refetch: refetchShareChatList
} = useQuery(['initShareChatList', modelId], () => getShareChatList(modelId));
} = useQuery(['initShareChatList', appId], () => getShareChatList(appId));
const onclickCreateShareChat = useCallback(
async (e: ShareChatEditType) => {
@@ -71,13 +71,12 @@ const Share = ({ modelId }: { modelId: string }) => {
setIsLoading(true);
const id = await createShareChat({
...e,
modelId
appId
});
onCloseCreateShareChat();
refetchShareChatList();
const url = `对话地址为:${location.origin}/chat/share?shareId=${id}
${e.password ? `密码为: ${e.password}` : ''}`;
const url = `对话地址为:${location.origin}/chat/share?shareId=${id}`;
copyData(url, '已复制分享地址');
resetShareChat(defaultShareChat);
@@ -91,8 +90,8 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
setIsLoading(false);
},
[
appId,
copyData,
modelId,
onCloseCreateShareChat,
refetchShareChatList,
resetShareChat,
@@ -136,7 +135,6 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
<Thead>
<Tr>
<Th></Th>
<Th></Th>
<Th></Th>
<Th>tokens消耗</Th>
<Th>使</Th>
@@ -147,7 +145,6 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
{shareChatList.map((item) => (
<Tr key={item._id}>
<Td>{item.name}</Td>
<Td>{item.password === '1' ? '已开启' : '未使用'}</Td>
<Td>{item.maxContext}</Td>
<Td>{formatTokens(item.tokens)}</Td>
<Td>{item.lastTime ? formatTimeToChatTime(item.lastTime) : '未使用'}</Td>
@@ -160,7 +157,7 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
cursor={'pointer'}
_hover={{ color: 'myBlue.600' }}
onClick={() => {
const url = `${location.origin}/chat/share?shareId=${item._id}`;
const url = `${location.origin}/chat/share?shareId=${item.shareId}`;
copyData(url, '已复制分享地址');
}}
/>
@@ -216,17 +213,6 @@ ${e.password ? `密码为: ${e.password}` : ''}`;
/>
</Flex>
</FormControl>
<FormControl mt={4}>
<Flex alignItems={'center'}>
<Box flex={'0 0 60px'} w={0}>
:
</Box>
<Input placeholder={'不设置密码,可直接访问'} {...registerShareChat('password')} />
</Flex>
<Box fontSize={'xs'} ml={'60px'} color={'myGray.600'}>
</Box>
</FormControl>
<FormControl mt={9}>
<Flex alignItems={'center'}>
<Box flex={'0 0 120px'} w={0}>

View File

@@ -1,66 +1,157 @@
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 React, {
useMemo,
useCallback,
useRef,
forwardRef,
useImperativeHandle,
ForwardedRef
} from 'react';
import { Box, Flex, IconButton, useOutsideClick } from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import { useChat } from '@/hooks/useChat';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app';
import { streamFetch } from '@/api/fetch';
import MyTooltip from '@/components/MyTooltip';
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox';
const ChatTest = ({
app,
modules,
onClose
}: {
app: AppSchema;
modules?: AppModuleItemType[];
onClose: () => void;
}) => {
const isOpen = useMemo(() => !!modules, [modules]);
export type ChatTestComponentRef = {
resetChatTest: () => void;
};
const { ChatBox, ChatInput, ChatBoxParentRef, setChatHistory } = useChat({
appId: app._id
const ChatTest = (
{
app,
modules = [],
onClose
}: {
app: AppSchema;
modules?: AppModuleItemType[];
onClose: () => void;
},
ref: ForwardedRef<ChatTestComponentRef>
) => {
const BoxRef = useRef(null);
const ChatBoxRef = useRef<ComponentRef>(null);
const isOpen = useMemo(() => modules && modules.length > 0, [modules]);
const variableModules = useMemo(
() =>
modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs.find((item) => item.key === SystemInputEnum.variables)?.value,
[modules]
);
const welcomeText = useMemo(
() =>
modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
[modules]
);
const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const historyMaxLen =
modules
?.find((item) => item.flowType === FlowModuleTypeEnum.historyNode)
?.inputs?.find((item) => item.key === 'maxContext')?.value || 0;
const history = messages.slice(-historyMaxLen - 2, -2);
console.log(history, 'history====');
// 流请求,获取数据
const { responseText } = await streamFetch({
url: '/api/chat/chatTest',
data: {
history,
prompt: messages[messages.length - 2].content,
modules,
variables
},
onMessage: generatingMessage,
abortSignal: controller
});
return { responseText };
},
[modules]
);
useOutsideClick({
ref: BoxRef,
handler: () => {
onClose();
}
});
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>
useImperativeHandle(ref, () => ({
resetChatTest() {
console.log(ChatBoxRef.current, '===');
<Box px={5}>
<ChatInput />
</Box>
</Flex>
ChatBoxRef.current?.resetHistory([]);
ChatBoxRef.current?.resetVariables();
}
}));
return (
<>
<Box
zIndex={2}
display={isOpen ? 'block' : 'none'}
position={'fixed'}
top={0}
left={0}
bottom={0}
right={0}
/>
<Flex
ref={BoxRef}
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>
<MyTooltip label={'重置'}>
<IconButton
className="chat"
size={'sm'}
icon={<MyIcon name={'clearLight'} w={'14px'} />}
variant={'base'}
borderRadius={'md'}
aria-label={'delete'}
onClick={(e) => {
e.stopPropagation();
ChatBoxRef.current?.resetHistory([]);
ChatBoxRef.current?.resetVariables();
}}
/>
</MyTooltip>
</Flex>
<Box flex={1}>
<ChatBox
ref={ChatBoxRef}
appAvatar={app.avatar}
variableModules={variableModules}
welcomeText={welcomeText}
onStartChat={startChat}
/>
</Box>
</Flex>
</>
);
};
export default ChatTest;
export default React.memo(forwardRef(ChatTest));

View File

@@ -12,13 +12,7 @@ const NodeKbSearch = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
return (
<NodeCard
minW={'400px'}
logo={'/icon/logo.png'}
name={'知识库搜索'}
moduleId={moduleId}
{...props}
>
<NodeCard minW={'400px'} moduleId={moduleId} {...props}>
<Divider text="Input" />
<Container>
<RenderInput

View File

@@ -0,0 +1,339 @@
import React, { useCallback, useMemo, useState } from 'react';
import { NodeProps } from 'reactflow';
import {
Box,
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalFooter,
ModalBody,
NumberInput,
NumberInputField,
NumberInputStepper,
NumberIncrementStepper,
NumberDecrementStepper,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
TableContainer,
Flex,
Switch,
Input,
useDisclosure,
useTheme,
Grid,
FormControl,
Textarea
} from '@chakra-ui/react';
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons';
import NodeCard from './modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import Container from './modules/Container';
import { SystemInputEnum, VariableInputEnum } from '@/constants/app';
import type { VariableItemType } from '@/types/app';
import MyIcon from '@/components/Icon';
import { useForm } from 'react-hook-form';
import { useFieldArray } from 'react-hook-form';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import { Label } from './render/RenderInput';
import MyTooltip from '@/components/MyTooltip';
const VariableTypeList = [
{ label: '文本', icon: 'settingLight', key: VariableInputEnum.input },
{ label: '下拉单选', icon: 'settingLight', key: VariableInputEnum.select }
];
const defaultVariable: VariableItemType = {
id: nanoid(),
key: 'key',
label: 'label',
type: VariableInputEnum.input,
required: true,
maxLen: 50,
enums: [{ value: '' }]
};
const NodeUserGuide = ({
data: { inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const theme = useTheme();
const variables = useMemo(
() =>
(inputs.find((item) => item.key === SystemInputEnum.variables)
?.value as VariableItemType[]) || [],
[inputs]
);
const welcomeText = useMemo(
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
[inputs]
);
const [refresh, setRefresh] = useState(false);
const { isOpen, onClose, onOpen } = useDisclosure();
const { reset, getValues, setValue, register, control, handleSubmit } = useForm<{
variable: VariableItemType;
}>({
defaultValues: {
variable: defaultVariable
}
});
const {
fields: selectEnums,
append: appendEnums,
remove: removeEnums
} = useFieldArray({
control,
name: 'variable.enums'
});
const updateVariables = useCallback(
(value: VariableItemType[]) => {
onChangeNode({
moduleId: props.moduleId,
key: SystemInputEnum.variables,
type: 'inputs',
value
});
},
[onChangeNode, props.moduleId]
);
const onclickSubmit = useCallback(
({ variable }: { variable: VariableItemType }) => {
updateVariables(variables.map((item) => (item.id === variable.id ? variable : item)));
onClose();
},
[onClose, updateVariables, variables]
);
return (
<>
<NodeCard minW={'300px'} {...props}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<>
<Flex mb={1} alignItems={'center'}>
<MyIcon name={'welcomeText'} mr={2} w={'16px'} color={'#E74694'} />
<Box></Box>
</Flex>
<Textarea
className="nodrag"
rows={3}
resize={'both'}
defaultValue={welcomeText}
bg={'myWhite.600'}
onChange={(e) => {
onChangeNode({
moduleId: props.moduleId,
key: SystemInputEnum.welcomeText,
type: 'inputs',
value: e.target.value
});
}}
/>
</>
<Box mt={4}>
<Flex alignItems={'center'}>
<MyIcon name={'variable'} mr={2} w={'20px'} color={'#FF8A4C'} />
<Box></Box>
<MyTooltip
label={`变量会在开始对话前输入,仅会在本次对话中生效。\n你可以在任何字符串模块系统提示词、限定词等中使用 {{变量key}} 来代表变量输入。`}
>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<TableContainer>
<Table>
<Thead>
<Tr>
<Th></Th>
<Th> key</Th>
<Th></Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{variables.map((item, index) => (
<Tr key={index}>
<Td>{item.label} </Td>
<Td>{item.key}</Td>
<Td>{item.required ? '✔' : ''}</Td>
<Td>
<MyIcon
mr={3}
name={'settingLight'}
w={'16px'}
cursor={'pointer'}
onClick={() => {
onOpen();
reset({ variable: item });
}}
/>
<MyIcon
name={'delete'}
w={'16px'}
cursor={'pointer'}
onClick={() =>
updateVariables(variables.filter((variable) => variable.id !== item.id))
}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
</TableContainer>
<Box mt={2} textAlign={'right'}>
<Button
variant={'base'}
onClick={() => {
const newVariable = { ...defaultVariable, id: nanoid() };
updateVariables(variables.concat(newVariable));
reset({ variable: newVariable });
onOpen();
}}
>
+
</Button>
</Box>
</Box>
</Container>
</NodeCard>
<Modal isOpen={isOpen} onClose={() => {}}>
<ModalOverlay />
<ModalContent maxW={'Min(400px,90vw)'}>
<ModalHeader display={'flex'}>
<MyIcon name={'variable'} mr={2} w={'24px'} color={'#FF8A4C'} />
</ModalHeader>
<ModalBody>
<Flex alignItems={'center'}>
<Box w={'70px'}></Box>
<Switch {...register('variable.required')} />
</Flex>
<Flex mt={5} alignItems={'center'}>
<Box w={'80px'}></Box>
<Input {...register('variable.label', { required: '变量名不能为空' })} />
</Flex>
<Flex mt={5} alignItems={'center'}>
<Box w={'80px'}> key</Box>
<Input {...register('variable.key', { required: '变量 key 不能为空' })} />
</Flex>
<Box mt={5} mb={2}>
</Box>
<Grid gridTemplateColumns={'repeat(2,130px)'} gridGap={4}>
{VariableTypeList.map((item) => (
<Flex
key={item.key}
px={4}
py={1}
border={theme.borders.base}
borderRadius={'md'}
cursor={'pointer'}
{...(item.key === getValues('variable.type')
? {
bg: 'myWhite.600'
}
: {
_hover: {
boxShadow: 'md'
},
onClick: () => {
setValue('variable.type', item.key);
setRefresh(!refresh);
}
})}
>
<MyIcon name={item.icon as any} w={'16px'} />
<Box ml={3}>{item.label}</Box>
</Flex>
))}
</Grid>
{getValues('variable.type') === VariableInputEnum.input && (
<>
<Box mt={5} mb={2}>
</Box>
<Box>
<NumberInput max={100} min={1} step={1} position={'relative'}>
<NumberInputField
{...register('variable.maxLen', {
min: 1,
max: 100,
valueAsNumber: true
})}
max={100}
/>
<NumberInputStepper>
<NumberIncrementStepper />
<NumberDecrementStepper />
</NumberInputStepper>
</NumberInput>
</Box>
</>
)}
{getValues('variable.type') === VariableInputEnum.select && (
<>
<Box mt={5} mb={2}>
</Box>
<Box>
{selectEnums.map((item, i) => (
<Flex key={item.id} mb={2} alignItems={'center'}>
<FormControl>
<Input
{...register(`variable.enums.${i}.value`, {
required: '选项内容不能为空'
})}
/>
</FormControl>
<MyIcon
ml={3}
name={'delete'}
w={'16px'}
cursor={'pointer'}
p={2}
borderRadius={'lg'}
_hover={{ bg: 'red.100' }}
onClick={() => removeEnums(i)}
/>
</Flex>
))}
</Box>
<Button
variant={'solid'}
w={'100%'}
textAlign={'left'}
leftIcon={<SmallAddIcon />}
bg={'myGray.100 !important'}
onClick={() => appendEnums({ value: '' })}
>
</Button>
</>
)}
</ModalBody>
<ModalFooter>
<Button variant={'base'} mr={3} onClick={onClose}>
</Button>
<Button onClick={handleSubmit(onclickSubmit)}></Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
export default React.memo(NodeUserGuide);

View File

@@ -14,10 +14,10 @@ const ModuleStoreList = ({
onAddNode: (e: { template: AppModuleTemplateItemType; position: XYPosition }) => void;
onClose: () => void;
}) => {
const ContextMenuRef = useRef(null);
const BoxRef = useRef(null);
useOutsideClick({
ref: ContextMenuRef,
ref: BoxRef,
handler: () => {
onClose();
}
@@ -36,7 +36,7 @@ const ModuleStoreList = ({
></Box>
<Flex
zIndex={3}
ref={ContextMenuRef}
ref={BoxRef}
flexDirection={'column'}
position={'absolute'}
top={'65px'}
@@ -52,7 +52,7 @@ const ModuleStoreList = ({
userSelect={'none'}
>
<Box w={'330px'} py={4} fontSize={'xl'} fontWeight={'bold'}>
</Box>
<Box w={'330px'} flex={'1 0 0'} overflow={'overlay'}>
{ModuleTemplates.map((item) =>

View File

@@ -3,12 +3,15 @@ import { Box, Flex, useTheme } from '@chakra-ui/react';
import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
import type { FlowModuleItemType } from '@/types/flow';
import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
type Props = {
children: React.ReactNode | React.ReactNode[] | string;
logo?: string;
name?: string;
intro?: string;
logo: string;
name: string;
description?: string;
intro: string;
minW?: string | number;
moduleId: string;
onDelNode: FlowModuleItemType['onDelNode'];
@@ -18,6 +21,7 @@ const NodeCard = ({
children,
logo = '/icon/logo.png',
name = '未知模块',
description,
minW = '300px',
onDelNode,
moduleId
@@ -28,9 +32,15 @@ const NodeCard = ({
<Box minW={minW} bg={'white'} border={theme.borders.md} borderRadius={'md'} boxShadow={'sm'}>
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
<Avatar src={logo} borderRadius={'md'} w={'30px'} h={'30px'} />
<Box ml={3} flex={1} fontSize={'lg'} color={'myGray.600'}>
<Box ml={3} fontSize={'lg'} color={'myGray.600'}>
{name}
</Box>
{description && (
<MyTooltip label={description}>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
)}
<Box flex={1} />
<MyIcon
className={'nodrag'}
name="delete"

View File

@@ -17,7 +17,7 @@ import MySelect from '@/components/Select';
import MySlider from '@/components/Slider';
import MyTooltip from '@/components/MyTooltip';
const Label = ({
export const Label = ({
required = false,
children,
description

View File

@@ -28,6 +28,9 @@ import dynamic from 'next/dynamic';
import MyIcon from '@/components/Icon';
import ButtonEdge from './components/modules/ButtonEdge';
import MyTooltip from '@/components/MyTooltip';
import TemplateList from './components/TemplateList';
import ChatTest, { type ChatTestComponentRef } from './components/ChatTest';
const NodeChat = dynamic(() => import('./components/NodeChat'), {
ssr: false
});
@@ -46,15 +49,12 @@ const NodeAnswer = dynamic(() => import('./components/NodeAnswer'), {
const NodeQuestionInput = dynamic(() => import('./components/NodeQuestionInput'), {
ssr: false
});
const TemplateList = dynamic(() => import('./components/TemplateList'), {
ssr: false
});
const ChatTest = dynamic(() => import('./components/ChatTest'), {
ssr: false
});
const NodeCQNode = dynamic(() => import('./components/NodeCQNode'), {
ssr: false
});
const NodeUserGuide = dynamic(() => import('./components/NodeUserGuide'), {
ssr: false
});
import 'reactflow/dist/style.css';
import styles from './index.module.scss';
@@ -63,6 +63,7 @@ import { AppModuleItemType, AppModuleTemplateItemType } from '@/types/app';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
const nodeTypes = {
[FlowModuleTypeEnum.userGuide]: NodeUserGuide,
[FlowModuleTypeEnum.questionInputNode]: NodeQuestionInput,
[FlowModuleTypeEnum.historyNode]: NodeHistory,
[FlowModuleTypeEnum.chatNode]: NodeChat,
@@ -78,6 +79,7 @@ type Props = { app: AppSchema; onBack: () => void };
const AppEdit = ({ app, onBack }: Props) => {
const reactFlowWrapper = useRef<HTMLDivElement>(null);
const ChatTestRef = useRef<ChatTestComponentRef>(null);
const theme = useTheme();
const { x, y, zoom } = useViewport();
const [nodes, setNodes, onNodesChange] = useNodesState<FlowModuleItemType>([]);
@@ -222,7 +224,10 @@ const AppEdit = ({ app, onBack }: Props) => {
});
},
successToast: '保存配置成功',
errorToast: '保存配置异常'
errorToast: '保存配置异常',
onSuccess() {
ChatTestRef.current?.resetChatTest();
}
});
const initData = useCallback(
@@ -289,8 +294,6 @@ const AppEdit = ({ app, onBack }: Props) => {
aria-label={'save'}
variant={'base'}
onClick={() => {
// @ts-ignore
onclickSave();
setTestModules(flow2Modules());
}}
/>
@@ -366,7 +369,12 @@ const AppEdit = ({ app, onBack }: Props) => {
</ReactFlow>
<TemplateList isOpen={isOpenTemplate} onAddNode={onAddNode} onClose={onCloseTemplate} />
<ChatTest modules={testModules} app={app} onClose={() => setTestModules(undefined)} />
<ChatTest
ref={ChatTestRef}
modules={testModules}
app={app}
onClose={() => setTestModules(undefined)}
/>
</Box>
</Flex>
);
@@ -378,4 +386,4 @@ const Flow = (data: Props) => (
</ReactFlowProvider>
);
export default Flow;
export default React.memo(Flow);

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react';
import React, { useEffect, useMemo, useCallback } from 'react';
import { useRouter } from 'next/router';
import { Box, Flex, IconButton, useTheme } from '@chakra-ui/react';
import { useUserStore } from '@/store/user';
@@ -155,14 +155,14 @@ const AppDetail = ({ currentTab }: { currentTab: `${TabEnum}` }) => {
/>
</Box>
<Box flex={1}>
{currentTab === TabEnum.settings && <Settings modelId={appId} />}
{currentTab === TabEnum.settings && <Settings appId={appId} />}
{currentTab === TabEnum.edit && (
<Box position={'fixed'} zIndex={999} top={0} left={0} right={0} bottom={0}>
<EditApp app={appDetail} onBack={() => setCurrentTab(TabEnum.settings)} />
</Box>
)}
{currentTab === TabEnum.API && <API modelId={appId} />}
{currentTab === TabEnum.share && <Share modelId={appId} />}
{currentTab === TabEnum.API && <API appId={appId} />}
{currentTab === TabEnum.share && <Share appId={appId} />}
</Box>
</Box>
</PageContainer>