mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
fix: sse render
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { sseResponseEventEnum, TaskResponseKeyEnum } from '@/constants/chat';
|
||||
import { getErrText } from '@/utils/tools';
|
||||
import { parseStreamChunk } from '@/utils/adapt';
|
||||
import { parseStreamChunk, SSEParseData } from '@/utils/sse';
|
||||
import type { ChatHistoryItemResType } from '@/types/chat';
|
||||
|
||||
interface StreamFetchProps {
|
||||
@@ -43,6 +43,8 @@ export const streamFetch = ({
|
||||
let errMsg = '';
|
||||
let responseData: ChatHistoryItemResType[] = [];
|
||||
|
||||
const parseData = new SSEParseData();
|
||||
|
||||
const read = async () => {
|
||||
try {
|
||||
const { done, value } = await reader.read();
|
||||
@@ -63,21 +65,20 @@ export const streamFetch = ({
|
||||
|
||||
chunkResponse.forEach((item) => {
|
||||
// parse json data
|
||||
const data = (() => {
|
||||
try {
|
||||
return JSON.parse(item.data);
|
||||
} catch (error) {
|
||||
return item.data;
|
||||
}
|
||||
})();
|
||||
const { eventName, data } = parseData.parse(item);
|
||||
|
||||
if (item.event === sseResponseEventEnum.answer && data !== '[DONE]') {
|
||||
if (!eventName || !data) return;
|
||||
|
||||
if (eventName === sseResponseEventEnum.answer && data !== '[DONE]') {
|
||||
const answer: string = data?.choices?.[0].delta.content || '';
|
||||
onMessage(answer);
|
||||
responseText += answer;
|
||||
} else if (item.event === sseResponseEventEnum.appStreamResponse) {
|
||||
} else if (
|
||||
eventName === sseResponseEventEnum.appStreamResponse &&
|
||||
Array.isArray(data)
|
||||
) {
|
||||
responseData = data;
|
||||
} else if (item.event === sseResponseEventEnum.error) {
|
||||
} else if (eventName === sseResponseEventEnum.error) {
|
||||
errMsg = getErrText(data, '流响应错误');
|
||||
}
|
||||
});
|
||||
|
@@ -41,14 +41,14 @@ import { useGlobalStore } from '@/store/global';
|
||||
import { FlowModuleTypeEnum } from '@/constants/flow';
|
||||
import { TaskResponseKeyEnum } from '@/constants/chat';
|
||||
|
||||
import dynamic from 'next/dynamic';
|
||||
const ResponseDetailModal = dynamic(() => import('./ResponseDetailModal'));
|
||||
|
||||
import MyIcon from '@/components/Icon';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import Markdown from '@/components/Markdown';
|
||||
import MySelect from '@/components/Select';
|
||||
import MyTooltip from '../MyTooltip';
|
||||
import dynamic from 'next/dynamic';
|
||||
const ResponseDetailModal = dynamic(() => import('./ResponseDetailModal'));
|
||||
|
||||
import styles from './index.module.scss';
|
||||
|
||||
const textareaMinH = '22px';
|
||||
|
@@ -23,6 +23,7 @@ import { postCreateApp } from '@/api/app';
|
||||
import { useRouter } from 'next/router';
|
||||
import { appTemplates } from '@/constants/flow/ModuleTemplate';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { useRequest } from '@/hooks/useRequest';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyTooltip from '@/components/MyTooltip';
|
||||
|
||||
@@ -34,7 +35,6 @@ type FormType = {
|
||||
|
||||
const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: () => void }) => {
|
||||
const [refresh, setRefresh] = useState(false);
|
||||
const [creating, setCreating] = useState(false);
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const theme = useTheme();
|
||||
@@ -74,32 +74,22 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
|
||||
[setValue, toast]
|
||||
);
|
||||
|
||||
const onclickCreate = useCallback(
|
||||
async (data: FormType) => {
|
||||
setCreating(true);
|
||||
try {
|
||||
const id = await postCreateApp({
|
||||
avatar: data.avatar,
|
||||
name: data.name,
|
||||
modules: appTemplates.find((item) => item.id === data.templateId)?.modules || []
|
||||
});
|
||||
toast({
|
||||
title: '创建成功',
|
||||
status: 'success'
|
||||
});
|
||||
router.push(`/app/detail?appId=${id}`);
|
||||
onClose();
|
||||
onSuccess();
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: getErrText(error, '创建应用异常'),
|
||||
status: 'error'
|
||||
});
|
||||
}
|
||||
setCreating(false);
|
||||
const { mutate: onclickCreate, isLoading: creating } = useRequest({
|
||||
mutationFn: async (data: FormType) => {
|
||||
return postCreateApp({
|
||||
avatar: data.avatar,
|
||||
name: data.name,
|
||||
modules: appTemplates.find((item) => item.id === data.templateId)?.modules || []
|
||||
});
|
||||
},
|
||||
[onClose, onSuccess, router, toast]
|
||||
);
|
||||
onSuccess(id: string) {
|
||||
router.push(`/app/detail?appId=${id}`);
|
||||
onSuccess();
|
||||
onClose();
|
||||
},
|
||||
successToast: '创建成功',
|
||||
errorToast: '创建应用异常'
|
||||
});
|
||||
|
||||
return (
|
||||
<Modal isOpen onClose={onClose} isCentered={!isPc}>
|
||||
@@ -180,7 +170,7 @@ const CreateModal = ({ onClose, onSuccess }: { onClose: () => void; onSuccess: (
|
||||
<Button variant={'base'} mr={3} onClick={onClose}>
|
||||
取消
|
||||
</Button>
|
||||
<Button isLoading={creating} onClick={handleSubmit(onclickCreate)}>
|
||||
<Button isLoading={creating} onClick={handleSubmit((data) => onclickCreate(data))}>
|
||||
确认创建
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
|
@@ -7,7 +7,8 @@ import { ChatContextFilter } from '@/service/utils/chat/index';
|
||||
import type { ChatItemType, QuoteItemType } from '@/types/chat';
|
||||
import type { ChatHistoryItemResType } from '@/types/chat';
|
||||
import { ChatModuleEnum, ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
|
||||
import { parseStreamChunk, textAdaptGptResponse } from '@/utils/adapt';
|
||||
import { SSEParseData, parseStreamChunk } from '@/utils/sse';
|
||||
import { textAdaptGptResponse } from '@/utils/adapt';
|
||||
import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
|
||||
import { TaskResponseKeyEnum } from '@/constants/chat';
|
||||
import { getChatModel } from '@/service/utils/data';
|
||||
@@ -270,38 +271,28 @@ function getMaxTokens({
|
||||
async function streamResponse({ res, response }: { res: NextApiResponse; response: any }) {
|
||||
let answer = '';
|
||||
let error: any = null;
|
||||
|
||||
const clientRes = async (data: string) => {
|
||||
const { content = '' } = (() => {
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
const content: string = json?.choices?.[0].delta.content || '';
|
||||
error = json.error;
|
||||
answer += content;
|
||||
return { content };
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
})();
|
||||
|
||||
if (res.closed || error) return;
|
||||
|
||||
if (data !== '[DONE]') {
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text: content
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
const parseData = new SSEParseData();
|
||||
|
||||
try {
|
||||
for await (const chunk of response.data as any) {
|
||||
if (res.closed) break;
|
||||
const parse = parseStreamChunk(chunk);
|
||||
parse.forEach((item) => clientRes(item.data));
|
||||
parse.forEach((item) => {
|
||||
const { data } = parseData.parse(item);
|
||||
if (!data || data === '[DONE]') return;
|
||||
|
||||
const content: string = data?.choices?.[0].delta.content || '';
|
||||
error = data.error;
|
||||
answer += content;
|
||||
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text: content
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('pipe error', error);
|
||||
|
@@ -83,15 +83,7 @@ export const useChatStore = create<State>()(
|
||||
return [history, ...state.history];
|
||||
}
|
||||
})();
|
||||
// newHistory.sort(function (a, b) {
|
||||
// if (a.top === true && b.top === false) {
|
||||
// return -1;
|
||||
// } else if (a.top === false && b.top === true) {
|
||||
// return 1;
|
||||
// } else {
|
||||
// return 0;
|
||||
// }
|
||||
// });
|
||||
|
||||
state.history = newHistory;
|
||||
});
|
||||
},
|
||||
|
@@ -60,27 +60,6 @@ export const textAdaptGptResponse = ({
|
||||
});
|
||||
};
|
||||
|
||||
const decoder = new TextDecoder();
|
||||
export const parseStreamChunk = (value: BufferSource) => {
|
||||
const chunk = decoder.decode(value);
|
||||
const chunkLines = chunk.split('\n\n').filter((item) => item);
|
||||
const chunkResponse = chunkLines.map((item) => {
|
||||
const splitEvent = item.split('\n');
|
||||
if (splitEvent.length === 2) {
|
||||
return {
|
||||
event: splitEvent[0].replace('event: ', ''),
|
||||
data: splitEvent[1].replace('data: ', '')
|
||||
};
|
||||
}
|
||||
return {
|
||||
event: '',
|
||||
data: splitEvent[0].replace('data: ', '')
|
||||
};
|
||||
});
|
||||
|
||||
return chunkResponse;
|
||||
};
|
||||
|
||||
export const appModule2FlowNode = ({
|
||||
item,
|
||||
onChangeNode,
|
||||
|
55
client/src/utils/sse.ts
Normal file
55
client/src/utils/sse.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
export const parseStreamChunk = (value: BufferSource) => {
|
||||
const chunk = decoder.decode(value);
|
||||
const chunkLines = chunk.split('\n\n').filter((item) => item);
|
||||
const chunkResponse = chunkLines.map((item) => {
|
||||
const splitEvent = item.split('\n');
|
||||
if (splitEvent.length === 2) {
|
||||
return {
|
||||
event: splitEvent[0].replace('event: ', ''),
|
||||
data: splitEvent[1].replace('data: ', '')
|
||||
};
|
||||
}
|
||||
return {
|
||||
event: '',
|
||||
data: splitEvent[0].replace('data: ', '')
|
||||
};
|
||||
});
|
||||
|
||||
return chunkResponse;
|
||||
};
|
||||
|
||||
export class SSEParseData {
|
||||
storeReadData = '';
|
||||
storeEventName = '';
|
||||
|
||||
parse(item: { event: string; data: string }) {
|
||||
if (item.data === '[DONE]') return { eventName: item.event, data: item.data };
|
||||
|
||||
if (item.event) {
|
||||
this.storeEventName = item.event;
|
||||
}
|
||||
|
||||
try {
|
||||
const formatData = this.storeReadData + item.data;
|
||||
const parseData = JSON.parse(formatData);
|
||||
const eventName = this.storeEventName;
|
||||
|
||||
this.storeReadData = '';
|
||||
this.storeEventName = '';
|
||||
|
||||
return {
|
||||
eventName,
|
||||
data: parseData
|
||||
};
|
||||
} catch (error) {
|
||||
if (typeof item.data === 'string') {
|
||||
this.storeReadData += item.data;
|
||||
} else {
|
||||
this.storeReadData = '';
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user