guide node

This commit is contained in:
archer 2023-07-18 11:07:46 +08:00
parent d346d38677
commit f9d83c481f
No known key found for this signature in database
GPG Key ID: 569A5660D2379E28
25 changed files with 313 additions and 279 deletions

View File

@ -1,3 +0,0 @@
[ZoneTransfer]
ZoneId=3
HostUrl=about:internet

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -3,7 +3,7 @@ import type { ChatItemType } from '@/types/chat';
import { VariableItemType } from '@/types/app'; import { VariableItemType } from '@/types/app';
export interface InitChatResponse { export interface InitChatResponse {
historyId: string; chatId: string;
appId: string; appId: string;
app: { app: {
variableModules?: VariableItemType[]; variableModules?: VariableItemType[];
@ -13,6 +13,7 @@ export interface InitChatResponse {
intro: string; intro: string;
canUse: boolean; canUse: boolean;
}; };
customTitle?: string;
title: string; title: string;
variables: Record<string, any>; variables: Record<string, any>;
history: ChatItemType[]; history: ChatItemType[];

View File

@ -22,8 +22,8 @@ import Avatar from '@/components/Avatar';
import { adaptChatItem_openAI } from '@/utils/plugin/openai'; import { adaptChatItem_openAI } from '@/utils/plugin/openai';
import { useMarkdown } from '@/hooks/useMarkdown'; import { useMarkdown } from '@/hooks/useMarkdown';
import { VariableItemType } from '@/types/app'; import { AppModuleItemType, VariableItemType } from '@/types/app';
import { VariableInputEnum } from '@/constants/app'; import { SystemInputEnum, VariableInputEnum } from '@/constants/app';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import MySelect from '@/components/Select'; import MySelect from '@/components/Select';
import { MessageItemType } from '@/pages/api/openapi/v1/chat/completions'; import { MessageItemType } from '@/pages/api/openapi/v1/chat/completions';
@ -36,6 +36,7 @@ const QuoteModal = dynamic(() => import('./QuoteModal'));
import styles from './index.module.scss'; import styles from './index.module.scss';
import { QuoteItemType } from '@/pages/api/app/modules/kb/search'; import { QuoteItemType } from '@/pages/api/app/modules/kb/search';
import { FlowModuleTypeEnum } from '@/constants/flow';
const textareaMinH = '22px'; const textareaMinH = '22px';
export type StartChatFnProps = { export type StartChatFnProps = {
@ -94,6 +95,18 @@ const Empty = () => {
); );
}; };
const ChatAvatar = ({
src,
order,
ml,
mr
}: {
src: string;
order: number;
ml?: (string | number) | (string | number)[];
mr?: (string | number) | (string | number)[];
}) => <Avatar src={src} w={['24px', '34px']} h={['24px', '34px']} order={order} ml={ml} mr={mr} />;
const ChatBox = ( const ChatBox = (
{ {
showEmptyIntro = false, showEmptyIntro = false,
@ -375,80 +388,79 @@ const ChatBox = (
w: '100%' w: '100%'
}; };
const hasVariableInput = useMemo( const showEmpty = useMemo(
() => variableModules || welcomeText, () => showEmptyIntro && chatHistory.length === 0 && !(variableModules || welcomeText),
[variableModules, welcomeText] [chatHistory.length, showEmptyIntro, variableModules, welcomeText]
); );
return ( return (
<Flex flexDirection={'column'} h={'100%'}> <Flex flexDirection={'column'} h={'100%'}>
<Box ref={ChatBoxRef} flex={'1 0 0'} h={0} overflow={'overlay'} px={[2, 5, 8]} py={[0, 5]}> <Box ref={ChatBoxRef} flex={'1 0 0'} h={0} overflow={'overlay'} px={[2, 5, 7]} py={[0, 5]}>
<Box maxW={['100%', '1000px', '1200px']} h={'100%'} mx={'auto'}> <Box maxW={['100%', '1000px', '1200px']} h={'100%'} mx={'auto'}>
{/* variable input */} {!!welcomeText && (
{hasVariableInput && (
<Flex alignItems={'flex-start'} py={2}> <Flex alignItems={'flex-start'} py={2}>
{/* avatar */} {/* avatar */}
<Avatar <ChatAvatar src={appAvatar} order={1} mr={['6px', 2]} />
src={appAvatar}
w={['24px', '34px']}
h={['24px', '34px']}
order={1}
mr={['6px', 2]}
/>
{/* message */} {/* message */}
<Flex order={2} pt={2} maxW={`calc(100% - ${isLargeWidth ? '75px' : '58px'})`}> <Flex order={2} pt={2} maxW={`calc(100% - ${isLargeWidth ? '75px' : '58px'})`}>
<Card bg={'white'} px={4} py={3} borderRadius={'0 8px 8px 8px'}> <Card bg={'white'} px={4} py={3} borderRadius={'0 8px 8px 8px'}>
{welcomeText && ( {welcomeText}
<Box mb={2} pb={2} borderBottom={theme.borders.base}> </Card>
{welcomeText} </Flex>
</Box> </Flex>
)} )}
{variableModules && ( {/* variable input */}
<Box> {variableModules && (
{variableModules.map((item) => ( <Flex alignItems={'flex-start'} py={2}>
<Box w={'min(100%,300px)'} key={item.id} mb={4}> {/* avatar */}
<VariableLabel required={item.required}>{item.label}</VariableLabel> <ChatAvatar src={appAvatar} order={1} mr={['6px', 2]} />
{item.type === VariableInputEnum.input && ( {/* message */}
<Input <Flex order={2} pt={2} maxW={`calc(100% - ${isLargeWidth ? '75px' : '58px'})`}>
isDisabled={variableIsFinish} <Card bg={'white'} px={4} py={3} borderRadius={'0 8px 8px 8px'}>
{...register(item.key, { <Box>
required: item.required {variableModules.map((item) => (
})} <Box w={'min(100%,300px)'} key={item.id} mb={4}>
/> <VariableLabel required={item.required}>{item.label}</VariableLabel>
)} {item.type === VariableInputEnum.input && (
{item.type === VariableInputEnum.select && ( <Input
<MySelect isDisabled={variableIsFinish}
width={'100%'} {...register(item.key, {
isDisabled={variableIsFinish} required: item.required
list={(item.enums || []).map((item) => ({ })}
label: item.value, />
value: item.value )}
}))} {item.type === VariableInputEnum.select && (
value={getValues(item.key)} <MySelect
onchange={(e) => { width={'100%'}
setValue(item.key, e); isDisabled={variableIsFinish}
setRefresh(!refresh); list={(item.enums || []).map((item) => ({
}} label: item.value,
/> value: item.value
)} }))}
</Box> value={getValues(item.key)}
))} onchange={(e) => {
{!variableIsFinish && ( setValue(item.key, e);
<Button setRefresh(!refresh);
leftIcon={<MyIcon name={'chatFill'} w={'16px'} />} }}
size={'sm'} />
maxW={'100px'} )}
borderRadius={'lg'} </Box>
onClick={handleSubmit((data) => { ))}
onUpdateVariable?.(data); {!variableIsFinish && (
setVariables(data); <Button
})} leftIcon={<MyIcon name={'chatFill'} w={'16px'} />}
> size={'sm'}
{'开始对话'} maxW={'100px'}
</Button> borderRadius={'lg'}
)} onClick={handleSubmit((data) => {
</Box> onUpdateVariable?.(data);
)} setVariables(data);
})}
>
{'开始对话'}
</Button>
)}
</Box>
</Card> </Card>
</Flex> </Flex>
</Flex> </Flex>
@ -469,10 +481,8 @@ const ChatBox = (
> >
{item.obj === 'Human' && <Box flex={1} />} {item.obj === 'Human' && <Box flex={1} />}
{/* avatar */} {/* avatar */}
<Avatar <ChatAvatar
src={item.obj === 'Human' ? userInfo?.avatar || HUMAN_ICON : appAvatar} src={item.obj === 'Human' ? userInfo?.avatar || HUMAN_ICON : appAvatar}
w={['24px', '34px']}
h={['24px', '34px']}
{...(item.obj === 'AI' {...(item.obj === 'AI'
? { ? {
order: 1, order: 1,
@ -597,7 +607,7 @@ const ChatBox = (
))} ))}
</Box> </Box>
{showEmptyIntro && chatHistory.length === 0 && !hasVariableInput && <Empty />} {showEmpty && <Empty />}
</Box> </Box>
</Box> </Box>
{/* input */} {/* input */}
@ -770,3 +780,20 @@ export const useChatBox = () => {
onExportChat onExportChat
}; };
}; };
export const getSpecialModule = (modules: AppModuleItemType[]) => {
const welcomeText: string =
modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value || '';
const variableModules: VariableItemType[] =
modules
.find((item) => item.flowType === FlowModuleTypeEnum.variable)
?.inputs.find((item) => item.key === SystemInputEnum.variables)?.value || [];
return {
welcomeText,
variableModules
};
};

View File

@ -1,10 +1,8 @@
import type { AppItemType } from '@/types/app';
import { rawSearchKey } from './chat';
/* app */ /* app */
export enum AppModuleItemTypeEnum { export enum AppModuleItemTypeEnum {
'userGuide' = 'userGuide', // default chat input: userChatInput, history 'variable' = 'variable',
'initInput' = 'initInput', // default chat input: userChatInput, history 'userGuide' = 'userGuide',
'initInput' = 'initInput',
'http' = 'http', // send a http request 'http' = 'http', // send a http request
'switch' = 'switch', // one input and two outputs 'switch' = 'switch', // one input and two outputs
'answer' = 'answer' // redirect response 'answer' = 'answer' // redirect response

View File

@ -9,10 +9,28 @@ import {
} from './inputTemplate'; } from './inputTemplate';
import { rawSearchKey } from '../chat'; import { rawSearchKey } from '../chat';
export const VariableInputModule: AppModuleTemplateItemType = { export const VariableModule: AppModuleTemplateItemType = {
logo: '/imgs/module/variable.png',
name: '全局变量',
intro: '可以在对话开始前,要求用户填写一些内容作为本轮对话的变量。该模块位于开场引导之后。',
description:
'全局变量可以通过 {{变量key}} 的形式注入到其他模块的文本中。目前支持:提示词、限定词。',
type: AppModuleItemTypeEnum.variable,
flowType: FlowModuleTypeEnum.variable,
inputs: [
{
key: SystemInputEnum.variables,
type: FlowInputItemTypeEnum.systemInput,
label: '变量输入',
value: []
}
],
outputs: []
};
export const UserGuideModule: AppModuleTemplateItemType = {
logo: '/imgs/module/userGuide.png', logo: '/imgs/module/userGuide.png',
name: '开场引导', name: '用户引导',
intro: '可以在每个新对话开始前,给用户发送一段开场白,或要求用户填写一些内容作为本轮对话的变量。', intro: '可以添加特殊的对话前后引导模块,更好的让用户进行对话',
type: AppModuleItemTypeEnum.userGuide, type: AppModuleItemTypeEnum.userGuide,
flowType: FlowModuleTypeEnum.userGuide, flowType: FlowModuleTypeEnum.userGuide,
inputs: [ inputs: [
@ -20,12 +38,6 @@ export const VariableInputModule: AppModuleTemplateItemType = {
key: SystemInputEnum.welcomeText, key: SystemInputEnum.welcomeText,
type: FlowInputItemTypeEnum.input, type: FlowInputItemTypeEnum.input,
label: '开场白' label: '开场白'
},
{
key: SystemInputEnum.variables,
type: FlowInputItemTypeEnum.systemInput,
label: '变量输入',
value: []
} }
], ],
outputs: [] outputs: []
@ -350,20 +362,16 @@ export const ClassifyQuestionModule: AppModuleTemplateItemType = {
export const ModuleTemplates = [ export const ModuleTemplates = [
{ {
label: '输入模块', label: '输入模块',
list: [UserInputModule, HistoryModule, VariableInputModule] list: [UserInputModule, HistoryModule, VariableModule, UserGuideModule]
}, },
{ {
label: '对话模块', label: '内容生成',
list: [ChatModule] list: [ChatModule, AnswerModule]
}, },
{ {
label: '知识库模块', label: '知识库模块',
list: [KBSearchModule] list: [KBSearchModule]
}, },
{
label: '工具',
list: [AnswerModule]
},
{ {
label: 'Agent', label: 'Agent',
list: [ClassifyQuestionModule] list: [ClassifyQuestionModule]
@ -1105,7 +1113,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
moduleId: 'xj0c9p' moduleId: 'xj0c9p'
}, },
{ {
...VariableInputModule, ...VariableModule,
inputs: [ inputs: [
{ {
key: 'welcomeText', key: 'welcomeText',

View File

@ -19,6 +19,7 @@ export enum FlowOutputItemTypeEnum {
} }
export enum FlowModuleTypeEnum { export enum FlowModuleTypeEnum {
variable = 'variable',
userGuide = 'userGuide', userGuide = 'userGuide',
questionInputNode = 'questionInput', questionInputNode = 'questionInput',
historyNode = 'historyNode', historyNode = 'historyNode',

View File

@ -7,18 +7,17 @@ import { ChatItemType } from '@/types/chat';
import { authApp } from '@/service/utils/auth'; import { authApp } from '@/service/utils/auth';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import type { AppSchema, ChatSchema } from '@/types/mongoSchema'; import type { AppSchema, ChatSchema } from '@/types/mongoSchema';
import { FlowModuleTypeEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app';
import { quoteLenKey, rawSearchKey } from '@/constants/chat'; import { quoteLenKey, rawSearchKey } from '@/constants/chat';
import { getSpecialModule } from '@/components/ChatBox';
/* 初始化我的聊天框,需要身份验证 */ /* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try { try {
const { userId } = await authUser({ req, authToken: true }); const { userId } = await authUser({ req, authToken: true });
let { appId, historyId } = req.query as { let { appId, chatId } = req.query as {
appId: '' | string; appId: '' | string;
historyId: '' | string; chatId: '' | string;
}; };
await connectToDatabase(); await connectToDatabase();
@ -53,10 +52,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
// 历史记录 // 历史记录
const { chat, history = [] }: { chat?: ChatSchema; history?: ChatItemType[] } = const { chat, history = [] }: { chat?: ChatSchema; history?: ChatItemType[] } =
await (async () => { await (async () => {
if (historyId) { if (chatId) {
// auth historyId // auth historyId
const chat = await Chat.findOne({ const chat = await Chat.findOne({
_id: historyId, _id: chatId,
userId userId
}); });
if (!chat) { if (!chat) {
@ -66,7 +65,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
const history = await Chat.aggregate([ const history = await Chat.aggregate([
{ {
$match: { $match: {
_id: new mongoose.Types.ObjectId(historyId), _id: new mongoose.Types.ObjectId(chatId),
userId: new mongoose.Types.ObjectId(userId) userId: new mongoose.Types.ObjectId(userId)
} }
}, },
@ -96,20 +95,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
jsonRes<InitChatResponse>(res, { jsonRes<InitChatResponse>(res, {
data: { data: {
historyId, chatId,
appId, appId,
app: { app: {
variableModules: app.modules ...getSpecialModule(app.modules),
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.variables)?.value,
welcomeText: app.modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
name: app.name, name: app.name,
avatar: app.avatar, avatar: app.avatar,
intro: app.intro, intro: app.intro,
canUse: app.share.isShare || isOwner canUse: app.share.isShare || isOwner
}, },
customTitle: chat?.customTitle,
title: chat?.title || '新对话', title: chat?.title || '新对话',
variables: chat?.variables || {}, variables: chat?.variables || {},
history history

View File

@ -7,6 +7,7 @@ import { hashPassword } from '@/service/utils/tools';
import { HUMAN_ICON } from '@/constants/chat'; import { HUMAN_ICON } from '@/constants/chat';
import { FlowModuleTypeEnum } from '@/constants/flow'; import { FlowModuleTypeEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app'; import { SystemInputEnum } from '@/constants/app';
import { getSpecialModule } from '@/components/ChatBox';
/* 初始化我的聊天框,需要身份验证 */ /* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) { export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@ -48,12 +49,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
?.find((item) => item.flowType === FlowModuleTypeEnum.historyNode) ?.find((item) => item.flowType === FlowModuleTypeEnum.historyNode)
?.inputs?.find((item) => item.key === 'maxContext')?.value || 0, ?.inputs?.find((item) => item.key === 'maxContext')?.value || 0,
app: { app: {
variableModules: app.modules ...getSpecialModule(app.modules),
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.variables)?.value,
welcomeText: app.modules
.find((item) => item.flowType === FlowModuleTypeEnum.userGuide)
?.inputs?.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
name: app.name, name: app.name,
avatar: app.avatar, avatar: app.avatar,
intro: app.intro intro: app.intro

View File

@ -28,7 +28,7 @@ const Settings = ({ appId }: { appId: string }) => {
content: '确认删除该应用?' content: '确认删除该应用?'
}); });
const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>(); const [settingAppInfo, setSettingAppInfo] = useState<AppSchema>();
const [fullScreen, setFullScreen] = useState(false); const [fullScreen, setFullScreen] = useState(true);
/* 点击删除 */ /* 点击删除 */
const handleDelModel = useCallback(async () => { const handleDelModel = useCallback(async () => {

View File

@ -11,12 +11,13 @@ import React, {
import { Box, Flex, IconButton, useOutsideClick } from '@chakra-ui/react'; import { Box, Flex, IconButton, useOutsideClick } from '@chakra-ui/react';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import { FlowModuleTypeEnum } from '@/constants/flow'; import { FlowModuleTypeEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app';
import { streamFetch } from '@/api/fetch'; import { streamFetch } from '@/api/fetch';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import ChatBox, { type ComponentRef, type StartChatFnProps } from '@/components/ChatBox'; import ChatBox, {
import { useToast } from '@/hooks/useToast'; getSpecialModule,
import { getErrText } from '@/utils/tools'; type ComponentRef,
type StartChatFnProps
} from '@/components/ChatBox';
export type ChatTestComponentRef = { export type ChatTestComponentRef = {
resetChatTest: () => void; resetChatTest: () => void;
@ -36,24 +37,8 @@ const ChatTest = (
) => { ) => {
const BoxRef = useRef(null); const BoxRef = useRef(null);
const ChatBoxRef = useRef<ComponentRef>(null); const ChatBoxRef = useRef<ComponentRef>(null);
const { toast } = useToast();
const isOpen = useMemo(() => modules && modules.length > 0, [modules]); 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( const startChat = useCallback(
async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => { async ({ messages, controller, generatingMessage, variables }: StartChatFnProps) => {
const historyMaxLen = const historyMaxLen =
@ -137,8 +122,7 @@ const ChatTest = (
<ChatBox <ChatBox
ref={ChatBoxRef} ref={ChatBoxRef}
appAvatar={app.avatar} appAvatar={app.avatar}
variableModules={variableModules} {...getSpecialModule(modules)}
welcomeText={welcomeText}
onStartChat={startChat} onStartChat={startChat}
onDelMessage={() => {}} onDelMessage={() => {}}
/> />

View File

@ -1,10 +1,10 @@
import React from 'react'; import React from 'react';
import { NodeProps } from 'reactflow'; import { NodeProps } from 'reactflow';
import NodeCard from './modules/NodeCard'; import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow'; import { FlowModuleItemType } from '@/types/flow';
import Divider from './modules/Divider'; import Divider from '../modules/Divider';
import Container from './modules/Container'; import Container from '../modules/Container';
import RenderInput from './render/RenderInput'; import RenderInput from '../render/RenderInput';
const NodeAnswer = ({ const NodeAnswer = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props } data: { moduleId, inputs, outputs, onChangeNode, ...props }

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { NodeProps } from 'reactflow'; import { NodeProps } from 'reactflow';
import { Box, Input, Button, Flex } from '@chakra-ui/react'; import { Box, Input, Button, Flex } from '@chakra-ui/react';
import NodeCard from './modules/NodeCard'; import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow'; import { FlowModuleItemType } from '@/types/flow';
import Divider from './modules/Divider'; import Divider from '../modules/Divider';
import Container from './modules/Container'; import Container from '../modules/Container';
import RenderInput from './render/RenderInput'; import RenderInput from '../render/RenderInput';
import type { ClassifyQuestionAgentItemType } from '@/types/app'; import type { ClassifyQuestionAgentItemType } from '@/types/app';
import { Handle, Position } from 'reactflow'; import { Handle, Position } from 'reactflow';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
@ -13,7 +13,7 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 4);
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import { FlowOutputItemTypeEnum } from '@/constants/flow'; import { FlowOutputItemTypeEnum } from '@/constants/flow';
const NodeRINode = ({ const NodeCQNode = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props } data: { moduleId, inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => { }: NodeProps<FlowModuleItemType>) => {
return ( return (
@ -133,4 +133,4 @@ const NodeRINode = ({
</NodeCard> </NodeCard>
); );
}; };
export default React.memo(NodeRINode); export default React.memo(NodeCQNode);

View File

@ -1,11 +1,11 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { NodeProps } from 'reactflow'; import { NodeProps } from 'reactflow';
import NodeCard from './modules/NodeCard'; import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow'; import { FlowModuleItemType } from '@/types/flow';
import Divider from './modules/Divider'; import Divider from '../modules/Divider';
import Container from './modules/Container'; import Container from '../modules/Container';
import RenderInput from './render/RenderInput'; import RenderInput from '../render/RenderInput';
import RenderOutput from './render/RenderOutput'; import RenderOutput from '../render/RenderOutput';
import { FlowOutputItemTypeEnum } from '@/constants/flow'; import { FlowOutputItemTypeEnum } from '@/constants/flow';
import MySelect from '@/components/Select'; import MySelect from '@/components/Select';
import { chatModelList } from '@/store/static'; import { chatModelList } from '@/store/static';

View File

@ -1,11 +1,11 @@
import React from 'react'; import React from 'react';
import { NodeProps } from 'reactflow'; import { NodeProps } from 'reactflow';
import NodeCard from './modules/NodeCard'; import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow'; import { FlowModuleItemType } from '@/types/flow';
import Divider from './modules/Divider'; import Divider from '../modules/Divider';
import Container from './modules/Container'; import Container from '../modules/Container';
import RenderInput from './render/RenderInput'; import RenderInput from '../render/RenderInput';
import RenderOutput from './render/RenderOutput'; import RenderOutput from '../render/RenderOutput';
const NodeHistory = ({ const NodeHistory = ({
data: { inputs, outputs, onChangeNode, ...props } data: { inputs, outputs, onChangeNode, ...props }

View File

@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { NodeProps } from 'reactflow'; import { NodeProps } from 'reactflow';
import NodeCard from './modules/NodeCard'; import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow'; import { FlowModuleItemType } from '@/types/flow';
import Divider from './modules/Divider'; import Divider from '../modules/Divider';
import Container from './modules/Container'; import Container from '../modules/Container';
import RenderInput from './render/RenderInput'; import RenderInput from '../render/RenderInput';
import RenderOutput from './render/RenderOutput'; import RenderOutput from '../render/RenderOutput';
import KBSelect from './Plugins/KBSelect'; import KBSelect from '../Plugins/KBSelect';
const NodeKbSearch = ({ const NodeKbSearch = ({
data: { moduleId, inputs, outputs, onChangeNode, ...props } data: { moduleId, inputs, outputs, onChangeNode, ...props }

View File

@ -1,9 +1,9 @@
import React from 'react'; import React from 'react';
import { Handle, Position, NodeProps } from 'reactflow'; import { Handle, Position, NodeProps } from 'reactflow';
import { Box } from '@chakra-ui/react'; import { Box } from '@chakra-ui/react';
import NodeCard from './modules/NodeCard'; import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow'; import { FlowModuleItemType } from '@/types/flow';
import Container from './modules/Container'; import Container from '../modules/Container';
import { SystemInputEnum } from '@/constants/app'; import { SystemInputEnum } from '@/constants/app';
const QuestionInputNode = ({ const QuestionInputNode = ({

View File

@ -1,16 +1,16 @@
import React from 'react'; import React from 'react';
import { Handle, Position, NodeProps } from 'reactflow'; import { Handle, Position, NodeProps } from 'reactflow';
import { Flex, Box } from '@chakra-ui/react'; import { Flex, Box } from '@chakra-ui/react';
import NodeCard from './modules/NodeCard'; import NodeCard from '../modules/NodeCard';
import { SystemInputEnum } from '@/constants/app'; import { SystemInputEnum } from '@/constants/app';
import { FlowModuleItemType } from '@/types/flow'; import { FlowModuleItemType } from '@/types/flow';
import Divider from './modules/Divider'; import Divider from '../modules/Divider';
import Container from './modules/Container'; import Container from '../modules/Container';
import Label from './modules/Label'; import Label from '../modules/Label';
const NodeTFSwitch = ({ data: { inputs, outputs, ...props } }: NodeProps<FlowModuleItemType>) => { const NodeTFSwitch = ({ data: { inputs, outputs, ...props } }: NodeProps<FlowModuleItemType>) => {
return ( return (
<NodeCard minW={'220px'} logo={'/icon/logo.png'} name={'TF 开关'} {...props}> <NodeCard minW={'220px'} {...props}>
<Divider text="输入输出" /> <Divider text="输入输出" />
<Container h={'100px'} py={0} px={0} display={'flex'} alignItems={'center'}> <Container h={'100px'} py={0} px={0} display={'flex'} alignItems={'center'}>
<Box flex={1} pl={'12px'}> <Box flex={1} pl={'12px'}>

View File

@ -0,0 +1,59 @@
import React, { useMemo } from 'react';
import { NodeProps } from 'reactflow';
import { Box, Flex, Textarea } from '@chakra-ui/react';
import { QuestionOutlineIcon } from '@chakra-ui/icons';
import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow';
import Container from '../modules/Container';
import { SystemInputEnum } from '@/constants/app';
import MyIcon from '@/components/Icon';
import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import MyTooltip from '@/components/MyTooltip';
const welcomePlaceholder =
'每次对话开始前,发送一个初始内容。可使用的特殊标记:\n[快捷按键]: 用户点击后可以直接发送该问题';
const NodeUserGuide = ({
data: { inputs, outputs, onChangeNode, ...props }
}: NodeProps<FlowModuleItemType>) => {
const welcomeText = useMemo(
() => inputs.find((item) => item.key === SystemInputEnum.welcomeText)?.value,
[inputs]
);
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>
<MyTooltip label={welcomePlaceholder}>
<QuestionOutlineIcon display={['none', 'inline']} ml={1} />
</MyTooltip>
</Flex>
<Textarea
className="nodrag"
rows={6}
resize={'both'}
defaultValue={welcomeText}
bg={'myWhite.500'}
placeholder={welcomePlaceholder}
onChange={(e) => {
onChangeNode({
moduleId: props.moduleId,
key: SystemInputEnum.welcomeText,
type: 'inputs',
value: e.target.value
});
}}
/>
</>
</Container>
</NodeCard>
</>
);
};
export default React.memo(NodeUserGuide);

View File

@ -27,13 +27,12 @@ import {
useDisclosure, useDisclosure,
useTheme, useTheme,
Grid, Grid,
FormControl, FormControl
Textarea
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { QuestionOutlineIcon, SmallAddIcon } from '@chakra-ui/icons'; import { AddIcon, SmallAddIcon } from '@chakra-ui/icons';
import NodeCard from './modules/NodeCard'; import NodeCard from '../modules/NodeCard';
import { FlowModuleItemType } from '@/types/flow'; import { FlowModuleItemType } from '@/types/flow';
import Container from './modules/Container'; import Container from '../modules/Container';
import { SystemInputEnum, VariableInputEnum } from '@/constants/app'; import { SystemInputEnum, VariableInputEnum } from '@/constants/app';
import type { VariableItemType } from '@/types/app'; import type { VariableItemType } from '@/types/app';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
@ -41,8 +40,6 @@ import { useForm } from 'react-hook-form';
import { useFieldArray } from 'react-hook-form'; import { useFieldArray } from 'react-hook-form';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6); const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
import { Label } from './render/RenderInput';
import MyTooltip from '@/components/MyTooltip';
const VariableTypeList = [ const VariableTypeList = [
{ label: '文本', icon: 'settingLight', key: VariableInputEnum.input }, { label: '文本', icon: 'settingLight', key: VariableInputEnum.input },
@ -118,91 +115,60 @@ const NodeUserGuide = ({
<> <>
<NodeCard minW={'300px'} {...props}> <NodeCard minW={'300px'} {...props}>
<Container borderTop={'2px solid'} borderTopColor={'myGray.200'}> <Container borderTop={'2px solid'} borderTopColor={'myGray.200'}>
<> <TableContainer>
<Flex mb={1} alignItems={'center'}> <Table>
<MyIcon name={'welcomeText'} mr={2} w={'16px'} color={'#E74694'} /> <Thead>
<Box></Box> <Tr>
</Flex> <Th></Th>
<Textarea <Th> key</Th>
className="nodrag" <Th></Th>
rows={3} <Th></Th>
resize={'both'} </Tr>
defaultValue={welcomeText} </Thead>
bg={'myWhite.600'} <Tbody>
onChange={(e) => { {variables.map((item, index) => (
onChangeNode({ <Tr key={index}>
moduleId: props.moduleId, <Td>{item.label} </Td>
key: SystemInputEnum.welcomeText, <Td>{item.key}</Td>
type: 'inputs', <Td>{item.required ? '✔' : ''}</Td>
value: e.target.value <Td>
}); <MyIcon
}} mr={3}
/> name={'settingLight'}
</> w={'16px'}
<Box mt={4}> cursor={'pointer'}
<Flex alignItems={'center'}> onClick={() => {
<MyIcon name={'variable'} mr={2} w={'20px'} color={'#FF8A4C'} /> onOpen();
<Box></Box> reset({ variable: item });
<MyTooltip }}
label={`变量会在开始对话前输入,仅会在本次对话中生效。\n你可以在任何字符串模块系统提示词、限定词等中使用 {{变量key}} 来代表变量输入。`} />
> <MyIcon
<QuestionOutlineIcon display={['none', 'inline']} ml={1} /> name={'delete'}
</MyTooltip> w={'16px'}
</Flex> cursor={'pointer'}
<TableContainer> onClick={() =>
<Table> updateVariables(variables.filter((variable) => variable.id !== item.id))
<Thead> }
<Tr> />
<Th></Th> </Td>
<Th> key</Th>
<Th></Th>
<Th></Th>
</Tr> </Tr>
</Thead> ))}
<Tbody> </Tbody>
{variables.map((item, index) => ( </Table>
<Tr key={index}> </TableContainer>
<Td>{item.label} </Td> <Box mt={2} textAlign={'right'}>
<Td>{item.key}</Td> <Button
<Td>{item.required ? '✔' : ''}</Td> variant={'base'}
<Td> leftIcon={<AddIcon fontSize={'10px'} />}
<MyIcon onClick={() => {
mr={3} const newVariable = { ...defaultVariable, id: nanoid() };
name={'settingLight'} updateVariables(variables.concat(newVariable));
w={'16px'} reset({ variable: newVariable });
cursor={'pointer'} onOpen();
onClick={() => { }}
onOpen(); >
reset({ variable: item });
}} </Button>
/>
<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> </Box>
</Container> </Container>
</NodeCard> </NodeCard>

View File

@ -74,7 +74,7 @@ const ModuleStoreList = ({
}); });
}} }}
> >
<Avatar src={item.logo} w={'34px'} borderRadius={'0'} /> <Avatar src={item.logo} w={'34px'} objectFit={'contain'} borderRadius={'0'} />
<Box ml={5} flex={'1 0 0'}> <Box ml={5} flex={'1 0 0'}>
<Box color={'black'}>{item.name}</Box> <Box color={'black'}>{item.name}</Box>
<Box color={'myGray.500'} fontSize={'sm'}> <Box color={'myGray.500'} fontSize={'sm'}>

View File

@ -31,7 +31,7 @@ const NodeCard = ({
return ( return (
<Box minW={minW} bg={'white'} border={theme.borders.md} borderRadius={'md'} boxShadow={'sm'}> <Box minW={minW} bg={'white'} border={theme.borders.md} borderRadius={'md'} boxShadow={'sm'}>
<Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}> <Flex className="custom-drag-handle" px={4} py={3} alignItems={'center'}>
<Avatar src={logo} borderRadius={'md'} w={'30px'} h={'30px'} /> <Avatar src={logo} borderRadius={'md'} objectFit={'contain'} w={'30px'} h={'30px'} />
<Box ml={3} fontSize={'lg'} color={'myGray.600'}> <Box ml={3} fontSize={'lg'} color={'myGray.600'}>
{name} {name}
</Box> </Box>

View File

@ -31,28 +31,31 @@ import MyTooltip from '@/components/MyTooltip';
import TemplateList from './components/TemplateList'; import TemplateList from './components/TemplateList';
import ChatTest, { type ChatTestComponentRef } from './components/ChatTest'; import ChatTest, { type ChatTestComponentRef } from './components/ChatTest';
const NodeChat = dynamic(() => import('./components/NodeChat'), { const NodeChat = dynamic(() => import('./components/Nodes/NodeChat'), {
ssr: false ssr: false
}); });
const NodeKbSearch = dynamic(() => import('./components/NodeKbSearch'), { const NodeKbSearch = dynamic(() => import('./components/Nodes/NodeKbSearch'), {
ssr: false ssr: false
}); });
const NodeHistory = dynamic(() => import('./components/NodeHistory'), { const NodeHistory = dynamic(() => import('./components/Nodes/NodeHistory'), {
ssr: false ssr: false
}); });
const NodeTFSwitch = dynamic(() => import('./components/NodeTFSwitch'), { const NodeTFSwitch = dynamic(() => import('./components/Nodes/NodeTFSwitch'), {
ssr: false ssr: false
}); });
const NodeAnswer = dynamic(() => import('./components/NodeAnswer'), { const NodeAnswer = dynamic(() => import('./components/Nodes/NodeAnswer'), {
ssr: false ssr: false
}); });
const NodeQuestionInput = dynamic(() => import('./components/NodeQuestionInput'), { const NodeQuestionInput = dynamic(() => import('./components/Nodes/NodeQuestionInput'), {
ssr: false ssr: false
}); });
const NodeRINode = dynamic(() => import('./components/NodeRINode'), { const NodeCQNode = dynamic(() => import('./components/Nodes/NodeCQNode'), {
ssr: false ssr: false
}); });
const NodeUserGuide = dynamic(() => import('./components/NodeUserGuide'), { const NodeVariable = dynamic(() => import('./components/Nodes/NodeVariable'), {
ssr: false
});
const NodeUserGuide = dynamic(() => import('./components/Nodes/NodeUserGuide'), {
ssr: false ssr: false
}); });
@ -64,13 +67,14 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 6);
const nodeTypes = { const nodeTypes = {
[FlowModuleTypeEnum.userGuide]: NodeUserGuide, [FlowModuleTypeEnum.userGuide]: NodeUserGuide,
[FlowModuleTypeEnum.variable]: NodeVariable,
[FlowModuleTypeEnum.questionInputNode]: NodeQuestionInput, [FlowModuleTypeEnum.questionInputNode]: NodeQuestionInput,
[FlowModuleTypeEnum.historyNode]: NodeHistory, [FlowModuleTypeEnum.historyNode]: NodeHistory,
[FlowModuleTypeEnum.chatNode]: NodeChat, [FlowModuleTypeEnum.chatNode]: NodeChat,
[FlowModuleTypeEnum.kbSearchNode]: NodeKbSearch, [FlowModuleTypeEnum.kbSearchNode]: NodeKbSearch,
[FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch, [FlowModuleTypeEnum.tfSwitchNode]: NodeTFSwitch,
[FlowModuleTypeEnum.answerNode]: NodeAnswer, [FlowModuleTypeEnum.answerNode]: NodeAnswer,
[FlowModuleTypeEnum.classifyQuestion]: NodeRINode [FlowModuleTypeEnum.classifyQuestion]: NodeCQNode
}; };
const edgeTypes = { const edgeTypes = {
buttonedge: ButtonEdge buttonedge: ButtonEdge

View File

@ -161,7 +161,7 @@ const ChatHistorySlider = ({
{item.top ? '取消置顶' : '置顶'} {item.top ? '取消置顶' : '置顶'}
</MenuItem> </MenuItem>
)} )}
{/* {onUpdateTitle && ( {onUpdateTitle && (
<MenuItem <MenuItem
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
@ -174,7 +174,7 @@ const ChatHistorySlider = ({
<MyIcon mr={2} name={'customTitle'} w={'16px'}></MyIcon> <MyIcon mr={2} name={'customTitle'} w={'16px'}></MyIcon>
</MenuItem> </MenuItem>
)} */} )}
<MenuItem <MenuItem
_hover={{ color: 'red.500' }} _hover={{ color: 'red.500' }}
onClick={(e) => { onClick={(e) => {

View File

@ -301,9 +301,7 @@ const Chat = () => {
appAvatar={chatData.app.avatar} appAvatar={chatData.app.avatar}
variableModules={chatData.app.variableModules} variableModules={chatData.app.variableModules}
welcomeText={chatData.app.welcomeText} welcomeText={chatData.app.welcomeText}
onUpdateVariable={(e) => { onUpdateVariable={(e) => {}}
console.log(e);
}}
onStartChat={startChat} onStartChat={startChat}
onDelMessage={delOneHistoryItem} onDelMessage={delOneHistoryItem}
/> />