perf: 流响应
This commit is contained in:
@@ -6,26 +6,19 @@ import { getOpenAIApi, authChat } from '@/service/utils/chat';
|
||||
import { httpsAgent } from '@/service/utils/tools';
|
||||
import { ChatCompletionRequestMessage, ChatCompletionRequestMessageRoleEnum } from 'openai';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { openaiError } from '@/service/errorCode';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { PassThrough } from 'stream';
|
||||
|
||||
/* 发送提示词 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
res.setHeader('Content-Type', 'text/event-stream;charset-utf-8');
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('X-Accel-Buffering', 'no');
|
||||
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
||||
|
||||
res.on('close', () => {
|
||||
res.end();
|
||||
});
|
||||
req.on('error', () => {
|
||||
res.end();
|
||||
});
|
||||
|
||||
const { chatId, windowId } = req.query as { chatId: string; windowId: string };
|
||||
const { chatId, windowId, prompt } = req.body as {
|
||||
prompt: ChatItemType;
|
||||
windowId: string;
|
||||
chatId: string;
|
||||
};
|
||||
|
||||
try {
|
||||
if (!windowId || !chatId) {
|
||||
if (!windowId || !chatId || !prompt) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
@@ -35,15 +28,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
|
||||
const model: ModelType = chat.modelId;
|
||||
|
||||
const map = {
|
||||
Human: ChatCompletionRequestMessageRoleEnum.User,
|
||||
AI: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
SYSTEM: ChatCompletionRequestMessageRoleEnum.System
|
||||
};
|
||||
// 读取对话内容
|
||||
const prompts: ChatItemType[] = (await ChatWindow.findById(windowId)).content;
|
||||
prompts.push(prompt);
|
||||
|
||||
// 长度过滤
|
||||
// 上下文长度过滤
|
||||
const maxContext = model.security.contextMaxLen;
|
||||
const filterPrompts =
|
||||
prompts.length > maxContext + 2
|
||||
@@ -51,6 +40,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
: prompts.slice(0, prompts.length);
|
||||
|
||||
// 格式化文本内容
|
||||
const map = {
|
||||
Human: ChatCompletionRequestMessageRoleEnum.User,
|
||||
AI: ChatCompletionRequestMessageRoleEnum.Assistant,
|
||||
SYSTEM: ChatCompletionRequestMessageRoleEnum.System
|
||||
};
|
||||
const formatPrompts: ChatCompletionRequestMessage[] = filterPrompts.map(
|
||||
(item: ChatItemType) => ({
|
||||
role: map[item.obj],
|
||||
@@ -62,9 +56,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
role: ChatCompletionRequestMessageRoleEnum.System,
|
||||
content: '如果你想返回代码,请务必声明代码的类型!并且在代码块前加一个换行符。'
|
||||
});
|
||||
|
||||
// 获取 chatAPI
|
||||
const chatAPI = getOpenAIApi(userApiKey);
|
||||
|
||||
// 发出请求
|
||||
const chatResponse = await chatAPI.createChatCompletion(
|
||||
{
|
||||
model: model.service.chatModel,
|
||||
@@ -84,58 +79,35 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
'response success'
|
||||
);
|
||||
|
||||
let AIResponse = '';
|
||||
// 创建响应流
|
||||
const pass = new PassThrough();
|
||||
pass.pipe(res);
|
||||
|
||||
// 解析数据
|
||||
const decoder = new TextDecoder();
|
||||
const onParse = async (event: ParsedEvent | ReconnectInterval) => {
|
||||
if (event.type === 'event') {
|
||||
const data = event.data;
|
||||
if (data === '[DONE]') {
|
||||
// 存入库
|
||||
await ChatWindow.findByIdAndUpdate(windowId, {
|
||||
$push: {
|
||||
content: {
|
||||
obj: 'AI',
|
||||
value: AIResponse
|
||||
}
|
||||
},
|
||||
updateTime: Date.now()
|
||||
});
|
||||
res.write('event: done\ndata: \n\n');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
const content: string = json?.choices?.[0].delta.content || '\n';
|
||||
// console.log('content:', content)
|
||||
res.write(`event: responseData\ndata: ${content.replace(/\n/g, '<br/>')}\n\n`);
|
||||
AIResponse += content;
|
||||
} catch (error) {
|
||||
error;
|
||||
}
|
||||
if (event.type !== 'event') return;
|
||||
const data = event.data;
|
||||
if (data === '[DONE]') return;
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
const content: string = json?.choices?.[0].delta.content || '';
|
||||
if (!content) return;
|
||||
// console.log('content:', content)
|
||||
pass.push(content.replace(/\n/g, '<br/>'));
|
||||
} catch (error) {
|
||||
error;
|
||||
}
|
||||
};
|
||||
|
||||
for await (const chunk of chatResponse.data as any) {
|
||||
const parser = createParser(onParse);
|
||||
parser.feed(decoder.decode(chunk));
|
||||
parser.feed(decodeURIComponent(chunk));
|
||||
}
|
||||
pass.push(null);
|
||||
} catch (err: any) {
|
||||
console.log('error->', err?.response, '===');
|
||||
let errorText = 'OpenAI 服务器访问超时';
|
||||
if (err.code === 'ECONNRESET' || err?.response?.status === 502) {
|
||||
errorText = '服务器代理出错';
|
||||
} else if (err?.response?.statusText && openaiError[err.response.statusText]) {
|
||||
errorText = openaiError[err.response.statusText];
|
||||
}
|
||||
console.log('error->', errorText);
|
||||
res.write(`event: serviceError\ndata: ${errorText}\n\n`);
|
||||
// 删除最一条数据库记录, 也就是预发送的那一条
|
||||
await ChatWindow.findByIdAndUpdate(windowId, {
|
||||
$pop: { content: 1 },
|
||||
updateTime: Date.now()
|
||||
res.status(500);
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,34 +2,31 @@ import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { ChatItemType } from '@/types/chat';
|
||||
import { connectToDatabase, ChatWindow } from '@/service/mongo';
|
||||
import type { ModelType } from '@/types/model';
|
||||
import { authChat } from '@/service/utils/chat';
|
||||
|
||||
/* 聊天预请求,存储聊天内容 */
|
||||
/* 聊天内容存存储 */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const { windowId, prompt, chatId } = req.body as {
|
||||
const { windowId, prompts } = req.body as {
|
||||
windowId: string;
|
||||
prompt: ChatItemType;
|
||||
chatId: string;
|
||||
prompts: ChatItemType[];
|
||||
};
|
||||
|
||||
if (!windowId || !prompt || !chatId) {
|
||||
if (!windowId || !prompts) {
|
||||
throw new Error('缺少参数');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
|
||||
const { chat } = await authChat(chatId);
|
||||
|
||||
// 长度校验
|
||||
const model: ModelType = chat.modelId;
|
||||
if (prompt.value.length > model.security.contentMaxLen) {
|
||||
throw new Error('输入内容超长');
|
||||
}
|
||||
|
||||
// 存入库
|
||||
await ChatWindow.findByIdAndUpdate(windowId, {
|
||||
$push: { content: prompt },
|
||||
$push: {
|
||||
content: {
|
||||
$each: prompts.map((item) => ({
|
||||
obj: item.obj,
|
||||
value: item.value
|
||||
}))
|
||||
}
|
||||
},
|
||||
updateTime: Date.now()
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user