feat: chat status

This commit is contained in:
archer
2023-08-04 18:14:36 +08:00
parent c7bfd773e3
commit 9a31407a01
16 changed files with 205 additions and 46 deletions

View File

@@ -3,6 +3,7 @@
"Cancel": "No", "Cancel": "No",
"Confirm": "Yes", "Confirm": "Yes",
"Warning": "Warning", "Warning": "Warning",
"Running": "Running",
"app": { "app": {
"App Detail": "App Detail", "App Detail": "App Detail",
"Confirm Del App Tip": "Confirm to delete the app and all its chats", "Confirm Del App Tip": "Confirm to delete the app and all its chats",

View File

@@ -3,6 +3,7 @@
"Cancel": "取消", "Cancel": "取消",
"Confirm": "确认", "Confirm": "确认",
"Warning": "提示", "Warning": "提示",
"Running": "运行中",
"app": { "app": {
"App Detail": "应用详情", "App Detail": "应用详情",
"Confirm Del App Tip": "确认删除该应用及其所有聊天记录?", "Confirm Del App Tip": "确认删除该应用及其所有聊天记录?",

View File

@@ -2,11 +2,12 @@ import { sseResponseEventEnum, TaskResponseKeyEnum } from '@/constants/chat';
import { getErrText } from '@/utils/tools'; import { getErrText } from '@/utils/tools';
import { parseStreamChunk, SSEParseData } from '@/utils/sse'; import { parseStreamChunk, SSEParseData } from '@/utils/sse';
import type { ChatHistoryItemResType } from '@/types/chat'; import type { ChatHistoryItemResType } from '@/types/chat';
import { StartChatFnProps } from '@/components/ChatBox';
interface StreamFetchProps { interface StreamFetchProps {
url?: string; url?: string;
data: Record<string, any>; data: Record<string, any>;
onMessage: (text: string) => void; onMessage: StartChatFnProps['generatingMessage'];
abortSignal: AbortController; abortSignal: AbortController;
} }
export const streamFetch = ({ export const streamFetch = ({
@@ -71,8 +72,14 @@ export const streamFetch = ({
if (eventName === sseResponseEventEnum.answer && data !== '[DONE]') { if (eventName === sseResponseEventEnum.answer && data !== '[DONE]') {
const answer: string = data?.choices?.[0].delta.content || ''; const answer: string = data?.choices?.[0].delta.content || '';
onMessage(answer); onMessage({ text: answer });
responseText += answer; responseText += answer;
} else if (
eventName === sseResponseEventEnum.moduleStatus &&
data?.name &&
data?.status
) {
onMessage(data);
} else if ( } else if (
eventName === sseResponseEventEnum.appStreamResponse && eventName === sseResponseEventEnum.appStreamResponse &&
Array.isArray(data) Array.isArray(data)

View File

@@ -28,3 +28,16 @@
} }
} }
} }
.statusAnimation {
animation: statusBox 0.8s linear infinite alternate;
}
@keyframes statusBox {
0% {
opacity: 1;
}
100% {
opacity: 0.11;
}
}

View File

@@ -39,6 +39,7 @@ import { htmlTemplate } from '@/constants/common';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import { useGlobalStore } from '@/store/global'; import { useGlobalStore } from '@/store/global';
import { TaskResponseKeyEnum, getDefaultChatVariables } from '@/constants/chat'; import { TaskResponseKeyEnum, getDefaultChatVariables } from '@/constants/chat';
import { useTranslation } from 'react-i18next';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
@@ -51,11 +52,12 @@ const ResponseDetailModal = dynamic(() => import('./ResponseDetailModal'));
import styles from './index.module.scss'; import styles from './index.module.scss';
const textareaMinH = '22px'; const textareaMinH = '22px';
type generatingMessageProps = { text?: string; name?: string; status?: 'running' | 'finish' };
export type StartChatFnProps = { export type StartChatFnProps = {
messages: MessageItemType[]; messages: MessageItemType[];
controller: AbortController; controller: AbortController;
variables: Record<string, any>; variables: Record<string, any>;
generatingMessage: (text: string) => void; generatingMessage: (e: generatingMessageProps) => void;
}; };
export type ComponentRef = { export type ComponentRef = {
@@ -153,6 +155,7 @@ const ChatBox = (
const ChatBoxRef = useRef<HTMLDivElement>(null); const ChatBoxRef = useRef<HTMLDivElement>(null);
const theme = useTheme(); const theme = useTheme();
const router = useRouter(); const router = useRouter();
const { t } = useTranslation();
const { copyData } = useCopyData(); const { copyData } = useCopyData();
const { toast } = useToast(); const { toast } = useToast();
const { isPc } = useGlobalStore(); const { isPc } = useGlobalStore();
@@ -164,7 +167,9 @@ const ChatBox = (
const [chatHistory, setChatHistory] = useState<ChatSiteItemType[]>([]); const [chatHistory, setChatHistory] = useState<ChatSiteItemType[]>([]);
const isChatting = useMemo( const isChatting = useMemo(
() => chatHistory[chatHistory.length - 1]?.status === 'loading', () =>
chatHistory[chatHistory.length - 1] &&
chatHistory[chatHistory.length - 1]?.status !== 'finish',
[chatHistory] [chatHistory]
); );
const variableIsFinish = useMemo(() => { const variableIsFinish = useMemo(() => {
@@ -209,13 +214,23 @@ const ChatBox = (
); );
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
const generatingMessage = useCallback( const generatingMessage = useCallback(
(text: string) => { ({ text = '', status, name }: generatingMessageProps) => {
setChatHistory((state) => setChatHistory((state) =>
state.map((item, index) => { state.map((item, index) => {
if (index !== state.length - 1) return item; if (index !== state.length - 1) return item;
return { return {
...item, ...item,
value: item.value + text ...(text
? {
value: item.value + text
}
: {}),
...(status && name
? {
status,
moduleName: name
}
: {})
}; };
}) })
); );
@@ -418,6 +433,21 @@ const ChatBox = (
!welcomeText, !welcomeText,
[chatHistory.length, showEmptyIntro, variableModules, welcomeText] [chatHistory.length, showEmptyIntro, variableModules, welcomeText]
); );
const statusBoxData = useMemo(() => {
const colorMap = {
loading: '#67c13b',
running: '#67c13b',
finish: 'myBlue.600'
};
if (!isChatting) return;
const chatContent = chatHistory[chatHistory.length - 1];
if (!chatContent) return;
return {
bg: colorMap[chatContent.status] || colorMap.loading,
name: t(chatContent.moduleName || 'Running')
};
}, [chatHistory, isChatting, t]);
useEffect(() => { useEffect(() => {
return () => { return () => {
@@ -595,7 +625,7 @@ const ChatBox = (
)} )}
{item.obj === 'AI' && ( {item.obj === 'AI' && (
<> <>
<Flex w={'100%'} alignItems={'center'}> <Flex w={'100%'} alignItems={'flex-end'}>
<ChatAvatar src={appAvatar} type={'AI'} /> <ChatAvatar src={appAvatar} type={'AI'} />
<Flex {...controlContainerStyle} ml={3}> <Flex {...controlContainerStyle} ml={3}>
<MyTooltip label={'复制'}> <MyTooltip label={'复制'}>
@@ -635,6 +665,28 @@ const ChatBox = (
</MyTooltip> </MyTooltip>
)} )}
</Flex> </Flex>
{statusBoxData && index === chatHistory.length - 1 && (
<Flex
ml={3}
alignItems={'center'}
px={3}
py={'1px'}
borderRadius="md"
border={theme.borders.base}
>
<Box
className={styles.statusAnimation}
bg={statusBoxData.bg}
w="8px"
h="8px"
borderRadius={'50%'}
mt={'1px'}
></Box>
<Box ml={2} color={'myGray.600'}>
{statusBoxData.name}
</Box>
</Flex>
)}
</Flex> </Flex>
<Box position={'relative'} maxW={messageCardMaxW} mt={['6px', 2]}> <Box position={'relative'} maxW={messageCardMaxW} mt={['6px', 2]}>
<Card bg={'white'} {...MessageCardStyle}> <Card bg={'white'} {...MessageCardStyle}>

View File

@@ -1,4 +1,4 @@
import React, { useMemo, useRef } from 'react'; import React, { useMemo } from 'react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import RemarkGfm from 'remark-gfm'; import RemarkGfm from 'remark-gfm';
import RemarkMath from 'remark-math'; import RemarkMath from 'remark-math';

View File

@@ -3,9 +3,8 @@ import dayjs from 'dayjs';
export enum sseResponseEventEnum { export enum sseResponseEventEnum {
error = 'error', error = 'error',
answer = 'answer', answer = 'answer',
chatResponse = 'chatResponse', // moduleStatus = 'moduleStatus',
appStreamResponse = 'appStreamResponse', // sse response request appStreamResponse = 'appStreamResponse' // sse response request
moduleFetchResponse = 'moduleFetchResponse' // http module sse response
} }
export enum ChatRoleEnum { export enum ChatRoleEnum {

View File

@@ -114,6 +114,7 @@ export const ChatModule: FlowModuleTemplateType = {
name: 'AI 对话', name: 'AI 对话',
intro: 'AI 大模型对话', intro: 'AI 大模型对话',
flowType: FlowModuleTypeEnum.chatNode, flowType: FlowModuleTypeEnum.chatNode,
showStatus: true,
inputs: [ inputs: [
{ {
key: 'model', key: 'model',
@@ -203,6 +204,7 @@ export const KBSearchModule: FlowModuleTemplateType = {
name: '知识库搜索', name: '知识库搜索',
intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。', intro: '去知识库中搜索对应的答案。可作为 AI 对话引用参考。',
flowType: FlowModuleTypeEnum.kbSearchNode, flowType: FlowModuleTypeEnum.kbSearchNode,
showStatus: true,
inputs: [ inputs: [
{ {
key: 'kbList', key: 'kbList',
@@ -321,6 +323,7 @@ export const ClassifyQuestionModule: FlowModuleTemplateType = {
description: description:
'根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:\n类型1: 打招呼\n类型2: 关于 laf 通用问题\n类型3: 关于 laf 代码问题\n类型4: 其他问题', '根据用户的历史记录和当前问题判断该次提问的类型。可以添加多组问题类型,下面是一个模板例子:\n类型1: 打招呼\n类型2: 关于 laf 通用问题\n类型3: 关于 laf 代码问题\n类型4: 其他问题',
flowType: FlowModuleTypeEnum.classifyQuestion, flowType: FlowModuleTypeEnum.classifyQuestion,
showStatus: true,
inputs: [ inputs: [
{ {
key: 'systemPrompt', key: 'systemPrompt',
@@ -381,6 +384,7 @@ export const ContextExtractModule: FlowModuleTemplateType = {
intro: '从文本中提取出指定格式的数据', intro: '从文本中提取出指定格式的数据',
description: '可从文本中提取指定的数据例如sql语句、搜索关键词、代码等', description: '可从文本中提取指定的数据例如sql语句、搜索关键词、代码等',
flowType: FlowModuleTypeEnum.contentExtract, flowType: FlowModuleTypeEnum.contentExtract,
showStatus: true,
inputs: [ inputs: [
Input_Template_TFSwitch, Input_Template_TFSwitch,
{ {
@@ -441,6 +445,7 @@ export const HttpModule: FlowModuleTemplateType = {
intro: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)', intro: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
description: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)', description: '可以发出一个 HTTP POST 请求,实现更为复杂的操作(联网搜索、数据库查询等)',
flowType: FlowModuleTypeEnum.httpRequest, flowType: FlowModuleTypeEnum.httpRequest,
showStatus: true,
inputs: [ inputs: [
{ {
key: HttpPropsEnum.url, key: HttpPropsEnum.url,
@@ -507,10 +512,11 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
modules: [ modules: [
{ {
moduleId: 'userChatInput', moduleId: 'userChatInput',
name: '用户问题(对话入口)',
flowType: 'questionInput', flowType: 'questionInput',
position: { position: {
x: 506.7143912167368, x: 464.32198615344566,
y: 1601.0230108651226 y: 1602.2698463081606
}, },
inputs: [ inputs: [
{ {
@@ -537,6 +543,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'history', moduleId: 'history',
name: '聊天记录',
flowType: 'historyNode', flowType: 'historyNode',
position: { position: {
x: 452.5466249541586, x: 452.5466249541586,
@@ -576,17 +583,19 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'chatModule', moduleId: 'chatModule',
name: 'AI 对话',
flowType: 'chatNode', flowType: 'chatNode',
showStatus: true,
position: { position: {
x: 998.0312473867093, x: 1150.8317145593148,
y: 803.8586941051353 y: 957.9676672880053
}, },
inputs: [ inputs: [
{ {
key: 'model', key: 'model',
type: 'custom', type: 'custom',
label: '对话模型', label: '对话模型',
value: 'gpt-3.5-turbo', value: 'gpt-3.5-turbo-16k',
list: [], list: [],
connected: true connected: true
}, },
@@ -614,9 +623,9 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
key: 'maxToken', key: 'maxToken',
type: 'custom', type: 'custom',
label: '回复上限', label: '回复上限',
value: 2000, value: 8000,
min: 100, min: 100,
max: 4000, max: 16000,
step: 50, step: 50,
markList: [ markList: [
{ {
@@ -624,8 +633,8 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
value: 100 value: 100
}, },
{ {
label: '4000', label: '16000',
value: 4000 value: 16000
} }
], ],
connected: true connected: true
@@ -712,6 +721,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
modules: [ modules: [
{ {
moduleId: 'userGuide', moduleId: 'userGuide',
name: '用户引导',
flowType: 'userGuide', flowType: 'userGuide',
position: { position: {
x: 454.98510354678695, x: 454.98510354678695,
@@ -730,6 +740,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'userChatInput', moduleId: 'userChatInput',
name: '用户问题(对话入口)',
flowType: 'questionInput', flowType: 'questionInput',
position: { position: {
x: 464.32198615344566, x: 464.32198615344566,
@@ -764,6 +775,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'history', moduleId: 'history',
name: '聊天记录',
flowType: 'historyNode', flowType: 'historyNode',
position: { position: {
x: 452.5466249541586, x: 452.5466249541586,
@@ -803,7 +815,9 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'kbSearch', moduleId: 'kbSearch',
name: '知识库搜索',
flowType: 'kbSearchNode', flowType: 'kbSearchNode',
showStatus: true,
position: { position: {
x: 956.0838440206068, x: 956.0838440206068,
y: 887.462827870246 y: 887.462827870246
@@ -916,7 +930,9 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'chatModule', moduleId: 'chatModule',
name: 'AI 对话',
flowType: 'chatNode', flowType: 'chatNode',
showStatus: true,
position: { position: {
x: 1546.0823206390796, x: 1546.0823206390796,
y: 1008.9827344021824 y: 1008.9827344021824
@@ -926,7 +942,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
key: 'model', key: 'model',
type: 'custom', type: 'custom',
label: '对话模型', label: '对话模型',
value: 'gpt-3.5-turbo', value: 'gpt-3.5-turbo-16k',
list: [], list: [],
connected: true connected: true
}, },
@@ -954,9 +970,9 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
key: 'maxToken', key: 'maxToken',
type: 'custom', type: 'custom',
label: '回复上限', label: '回复上限',
value: 2000, value: 8000,
min: 100, min: 100,
max: 4000, max: 16000,
step: 50, step: 50,
markList: [ markList: [
{ {
@@ -964,8 +980,8 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
value: 100 value: 100
}, },
{ {
label: '4000', label: '16000',
value: 4000 value: 16000
} }
], ],
connected: true connected: true
@@ -1044,6 +1060,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: '2752oj', moduleId: '2752oj',
name: '指定回复',
flowType: 'answerNode', flowType: 'answerNode',
position: { position: {
x: 1542.9271243684725, x: 1542.9271243684725,
@@ -1080,6 +1097,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
modules: [ modules: [
{ {
moduleId: 'userGuide', moduleId: 'userGuide',
name: '用户引导',
flowType: 'userGuide', flowType: 'userGuide',
position: { position: {
x: 447.98520778293346, x: 447.98520778293346,
@@ -1098,6 +1116,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'variable', moduleId: 'variable',
name: '全局变量',
flowType: 'variable', flowType: 'variable',
position: { position: {
x: 444.0369195277651, x: 444.0369195277651,
@@ -1146,6 +1165,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'userChatInput', moduleId: 'userChatInput',
name: '用户问题(对话入口)',
flowType: 'questionInput', flowType: 'questionInput',
position: { position: {
x: 464.32198615344566, x: 464.32198615344566,
@@ -1176,6 +1196,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'history', moduleId: 'history',
name: '聊天记录',
flowType: 'historyNode', flowType: 'historyNode',
position: { position: {
x: 452.5466249541586, x: 452.5466249541586,
@@ -1215,7 +1236,9 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'chatModule', moduleId: 'chatModule',
name: 'AI 对话',
flowType: 'chatNode', flowType: 'chatNode',
showStatus: true,
position: { position: {
x: 981.9682828103937, x: 981.9682828103937,
y: 890.014595014464 y: 890.014595014464
@@ -1225,7 +1248,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
key: 'model', key: 'model',
type: 'custom', type: 'custom',
label: '对话模型', label: '对话模型',
value: 'gpt-3.5-turbo', value: 'gpt-3.5-turbo-16k',
list: [], list: [],
connected: true connected: true
}, },
@@ -1253,9 +1276,9 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
key: 'maxToken', key: 'maxToken',
type: 'custom', type: 'custom',
label: '回复上限', label: '回复上限',
value: 2000, value: 8000,
min: 100, min: 100,
max: 4000, max: 16000,
step: 50, step: 50,
markList: [ markList: [
{ {
@@ -1263,8 +1286,8 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
value: 100 value: 100
}, },
{ {
label: '4000', label: '16000',
value: 4000 value: 16000
} }
], ],
connected: true connected: true
@@ -1351,6 +1374,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
modules: [ modules: [
{ {
moduleId: '7z5g5h', moduleId: '7z5g5h',
name: '用户问题(对话入口)',
flowType: 'questionInput', flowType: 'questionInput',
position: { position: {
x: 198.56612928723575, x: 198.56612928723575,
@@ -1389,6 +1413,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'xj0c9p', moduleId: 'xj0c9p',
name: '聊天记录',
flowType: 'historyNode', flowType: 'historyNode',
position: { position: {
x: 194.99102398958047, x: 194.99102398958047,
@@ -1428,7 +1453,9 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'remuj3', moduleId: 'remuj3',
name: '问题分类',
flowType: 'classifyQuestion', flowType: 'classifyQuestion',
showStatus: true,
position: { position: {
x: 672.9092284362648, x: 672.9092284362648,
y: 1077.557793775116 y: 1077.557793775116
@@ -1535,6 +1562,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'a99p6z', moduleId: 'a99p6z',
name: '指定回复',
flowType: 'answerNode', flowType: 'answerNode',
position: { position: {
x: 1304.2886011902247, x: 1304.2886011902247,
@@ -1563,6 +1591,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'iejcou', moduleId: 'iejcou',
name: '指定回复',
flowType: 'answerNode', flowType: 'answerNode',
position: { position: {
x: 1294.2531189034548, x: 1294.2531189034548,
@@ -1591,7 +1620,9 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'nlfwkc', moduleId: 'nlfwkc',
name: 'AI 对话',
flowType: 'chatNode', flowType: 'chatNode',
showStatus: true,
position: { position: {
x: 1821.979893659983, x: 1821.979893659983,
y: 1104.6583548423682 y: 1104.6583548423682
@@ -1720,6 +1751,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 's4v9su', moduleId: 's4v9su',
name: '聊天记录',
flowType: 'historyNode', flowType: 'historyNode',
position: { position: {
x: 193.3803955457983, x: 193.3803955457983,
@@ -1759,22 +1791,20 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'fljhzy', moduleId: 'fljhzy',
name: '知识库搜索',
flowType: 'kbSearchNode', flowType: 'kbSearchNode',
showStatus: true,
position: { position: {
x: 1305.5374262228029, x: 1305.5374262228029,
y: 1120.0404921820218 y: 1120.0404921820218
}, },
inputs: [ inputs: [
{ {
key: 'kbList',
type: 'custom', type: 'custom',
label: '关联的知识库', label: '关联的知识库',
value: [
{
kbId: '646627f4f7b896cfd8910e24'
}
],
list: [], list: [],
key: 'kbList',
value: [],
connected: true connected: true
}, },
{ {
@@ -1876,6 +1906,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'q9equb', moduleId: 'q9equb',
name: '用户引导',
flowType: 'userGuide', flowType: 'userGuide',
position: { position: {
x: 191.4857498376603, x: 191.4857498376603,
@@ -1895,6 +1926,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: 'tc90wz', moduleId: 'tc90wz',
name: '指定回复',
flowType: 'answerNode', flowType: 'answerNode',
position: { position: {
x: 1828.4596416688908, x: 1828.4596416688908,
@@ -1923,6 +1955,7 @@ export const appTemplates: (AppItemType & { avatar: string; intro: string })[] =
}, },
{ {
moduleId: '5v78ap', moduleId: '5v78ap',
name: '指定回复',
flowType: 'answerNode', flowType: 'answerNode',
position: { position: {
x: 1294.814522053934, x: 1294.814522053934,

View File

@@ -116,10 +116,12 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
} }
// 创建响应流 // 创建响应流
res.setHeader('Content-Type', 'text/event-stream;charset=utf-8'); if (stream) {
res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Content-Type', 'text/event-stream;charset=utf-8');
res.setHeader('X-Accel-Buffering', 'no'); res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Cache-Control', 'no-cache, no-transform'); res.setHeader('X-Accel-Buffering', 'no');
res.setHeader('Cache-Control', 'no-cache, no-transform');
}
/* start process */ /* start process */
const { responseData, answerText } = await dispatchModules({ const { responseData, answerText } = await dispatchModules({
@@ -320,6 +322,14 @@ export async function dispatchModules({
if (res.closed) return Promise.resolve(); if (res.closed) return Promise.resolve();
console.log('run=========', module.flowType); console.log('run=========', module.flowType);
if (stream && module.showStatus) {
responseStatus({
res,
name: module.name,
status: 'running'
});
}
// get fetch params // get fetch params
const params: Record<string, any> = {}; const params: Record<string, any> = {};
module.inputs.forEach((item: any) => { module.inputs.forEach((item: any) => {
@@ -370,7 +380,9 @@ function loadModules(
return modules.map((module) => { return modules.map((module) => {
return { return {
moduleId: module.moduleId, moduleId: module.moduleId,
name: module.name,
flowType: module.flowType, flowType: module.flowType,
showStatus: module.showStatus,
inputs: module.inputs inputs: module.inputs
.filter((item) => item.connected) // filter unconnected target input .filter((item) => item.connected) // filter unconnected target input
.map((item) => { .map((item) => {
@@ -401,3 +413,23 @@ function loadModules(
}; };
}); });
} }
function responseStatus({
res,
status,
name
}: {
res: NextApiResponse;
status: 'running' | 'finish';
name?: string;
}) {
if (!name) return;
sseResponse({
res,
event: sseResponseEventEnum.moduleStatus,
data: JSON.stringify({
status,
name
})
});
}

View File

@@ -1,10 +1,9 @@
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { ModuleTemplates } from '@/constants/flow/ModuleTemplate'; import { ModuleTemplates } from '@/constants/flow/ModuleTemplate';
import { FlowModuleTemplateType } from '@/types/flow'; import { FlowModuleItemType, FlowModuleTemplateType } from '@/types/flow';
import type { Node, XYPosition } from 'reactflow'; import type { Node, XYPosition } from 'reactflow';
import { useGlobalStore } from '@/store/global'; import { useGlobalStore } from '@/store/global';
import type { AppModuleItemType } from '@/types/app';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import { FlowModuleTypeEnum } from '@/constants/flow'; import { FlowModuleTypeEnum } from '@/constants/flow';
@@ -14,7 +13,7 @@ const ModuleTemplateList = ({
onAddNode, onAddNode,
onClose onClose
}: { }: {
nodes?: Node<AppModuleItemType>[]; nodes?: Node<FlowModuleItemType>[];
isOpen: boolean; isOpen: boolean;
onAddNode: (e: { template: FlowModuleTemplateType; position: XYPosition }) => void; onAddNode: (e: { template: FlowModuleTemplateType; position: XYPosition }) => void;
onClose: () => void; onClose: () => void;

View File

@@ -158,7 +158,9 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
const flow2AppModules = useCallback(() => { const flow2AppModules = useCallback(() => {
const modules: AppModuleItemType[] = nodes.map((item) => ({ const modules: AppModuleItemType[] = nodes.map((item) => ({
moduleId: item.data.moduleId, moduleId: item.data.moduleId,
name: item.data.name,
flowType: item.data.flowType, flowType: item.data.flowType,
showStatus: item.data.showStatus,
position: item.position, position: item.position,
inputs: item.data.inputs.map((item) => ({ inputs: item.data.inputs.map((item) => ({
...item, ...item,

View File

@@ -34,6 +34,9 @@ export async function dispatchContentExtract({
history = [], history = [],
description description
}: Props): Promise<Response> { }: Props): Promise<Response> {
if (!content) {
return Promise.reject('Input is empty');
}
const messages: ChatItemType[] = [ const messages: ChatItemType[] = [
...history, ...history,
{ {

View File

@@ -69,9 +69,11 @@ export type VariableItemType = {
/* app module */ /* app module */
export type AppModuleItemType = { export type AppModuleItemType = {
name: string;
moduleId: string; moduleId: string;
position?: XYPosition; position?: XYPosition;
flowType: `${FlowModuleTypeEnum}`; flowType: `${FlowModuleTypeEnum}`;
showStatus?: boolean;
inputs: FlowInputItemType[]; inputs: FlowInputItemType[];
outputs: FlowOutputItemType[]; outputs: FlowOutputItemType[];
}; };
@@ -83,8 +85,11 @@ export type AppItemType = {
}; };
export type RunningModuleItemType = { export type RunningModuleItemType = {
moduleId: string; name: AppModuleItemType['name'];
flowType: `${FlowModuleTypeEnum}`; moduleId: AppModuleItemType['moduleId'];
flowType: AppModuleItemType['flowType'];
showStatus?: AppModuleItemType['showStatus'];
} & {
inputs: { inputs: {
key: string; key: string;
value?: any; value?: any;

View File

@@ -13,7 +13,8 @@ export type ChatItemType = {
}; };
export type ChatSiteItemType = { export type ChatSiteItemType = {
status: 'loading' | 'finish'; status: 'loading' | 'running' | 'finish';
moduleName?: string;
} & ChatItemType; } & ChatItemType;
export type HistoryItemType = { export type HistoryItemType = {

View File

@@ -55,6 +55,7 @@ export type FlowModuleTemplateType = {
flowType: `${FlowModuleTypeEnum}`; flowType: `${FlowModuleTypeEnum}`;
inputs: FlowInputItemType[]; inputs: FlowInputItemType[];
outputs: FlowOutputItemType[]; outputs: FlowOutputItemType[];
showStatus?: boolean;
}; };
export type FlowModuleItemType = FlowModuleTemplateType & { export type FlowModuleItemType = FlowModuleTemplateType & {
moduleId: string; moduleId: string;

View File

@@ -219,6 +219,7 @@ const welcomeTemplate = (formData: EditFormType): AppModuleItemType[] =>
formData.guide?.welcome?.text formData.guide?.welcome?.text
? [ ? [
{ {
name: '用户引导',
flowType: FlowModuleTypeEnum.userGuide, flowType: FlowModuleTypeEnum.userGuide,
inputs: [ inputs: [
{ {
@@ -242,6 +243,7 @@ const variableTemplate = (formData: EditFormType): AppModuleItemType[] =>
formData.variables.length > 0 formData.variables.length > 0
? [ ? [
{ {
name: '全局变量',
flowType: FlowModuleTypeEnum.variable, flowType: FlowModuleTypeEnum.variable,
inputs: [ inputs: [
{ {
@@ -263,6 +265,7 @@ const variableTemplate = (formData: EditFormType): AppModuleItemType[] =>
: []; : [];
const simpleChatTemplate = (formData: EditFormType): AppModuleItemType[] => [ const simpleChatTemplate = (formData: EditFormType): AppModuleItemType[] => [
{ {
name: '用户问题(对话入口)',
flowType: FlowModuleTypeEnum.questionInput, flowType: FlowModuleTypeEnum.questionInput,
inputs: [ inputs: [
{ {
@@ -290,6 +293,7 @@ const simpleChatTemplate = (formData: EditFormType): AppModuleItemType[] => [
moduleId: 'userChatInput' moduleId: 'userChatInput'
}, },
{ {
name: '聊天记录',
flowType: FlowModuleTypeEnum.historyNode, flowType: FlowModuleTypeEnum.historyNode,
inputs: [ inputs: [
{ {
@@ -324,6 +328,7 @@ const simpleChatTemplate = (formData: EditFormType): AppModuleItemType[] => [
moduleId: 'history' moduleId: 'history'
}, },
{ {
name: 'AI 对话',
flowType: FlowModuleTypeEnum.chatNode, flowType: FlowModuleTypeEnum.chatNode,
inputs: chatModelInput(formData), inputs: chatModelInput(formData),
outputs: [ outputs: [
@@ -352,6 +357,7 @@ const simpleChatTemplate = (formData: EditFormType): AppModuleItemType[] => [
]; ];
const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [ const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
{ {
name: '用户问题(对话入口)',
flowType: FlowModuleTypeEnum.questionInput, flowType: FlowModuleTypeEnum.questionInput,
inputs: [ inputs: [
{ {
@@ -383,6 +389,7 @@ const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
moduleId: 'userChatInput' moduleId: 'userChatInput'
}, },
{ {
name: '聊天记录',
flowType: FlowModuleTypeEnum.historyNode, flowType: FlowModuleTypeEnum.historyNode,
inputs: [ inputs: [
{ {
@@ -417,6 +424,7 @@ const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
moduleId: 'history' moduleId: 'history'
}, },
{ {
name: '知识库搜索',
flowType: FlowModuleTypeEnum.kbSearchNode, flowType: FlowModuleTypeEnum.kbSearchNode,
inputs: [ inputs: [
{ {
@@ -498,6 +506,7 @@ const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
...(formData.kb.searchEmptyText ...(formData.kb.searchEmptyText
? [ ? [
{ {
name: '指定回复',
flowType: FlowModuleTypeEnum.answerNode, flowType: FlowModuleTypeEnum.answerNode,
inputs: [ inputs: [
{ {
@@ -525,6 +534,7 @@ const kbTemplate = (formData: EditFormType): AppModuleItemType[] => [
] ]
: []), : []),
{ {
name: 'AI 对话',
flowType: FlowModuleTypeEnum.chatNode, flowType: FlowModuleTypeEnum.chatNode,
inputs: chatModelInput(formData), inputs: chatModelInput(formData),
outputs: [ outputs: [