This commit is contained in:
archer
2023-07-01 13:09:02 +08:00
parent 4c54e1821b
commit 9bdd5f522d
85 changed files with 4738 additions and 1236 deletions

View File

@@ -4,9 +4,9 @@ import { connectToDatabase, Chat, Model } from '@/service/mongo';
import type { InitChatResponse } from '@/api/response/chat';
import { authUser } from '@/service/utils/auth';
import { ChatItemType } from '@/types/chat';
import { authModel } from '@/service/utils/auth';
import { authApp } from '@/service/utils/auth';
import mongoose from 'mongoose';
import type { ModelSchema } from '@/types/mongoSchema';
import type { AppSchema } from '@/types/mongoSchema';
/* 初始化我的聊天框,需要身份验证 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
@@ -21,7 +21,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase();
// 没有 modelId 时直接获取用户的第一个id
const model = await (async () => {
const app = await (async () => {
if (!modelId) {
const myModel = await Model.findOne({ userId });
if (!myModel) {
@@ -29,23 +29,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
name: '应用1',
userId
});
return (await Model.findById(_id)) as ModelSchema;
return (await Model.findById(_id)) as AppSchema;
} else {
return myModel;
}
} else {
// 校验使用权限
const authRes = await authModel({
modelId,
const authRes = await authApp({
appId: modelId,
userId,
authUser: false,
authOwner: false
});
return authRes.model;
return authRes.app;
}
})();
modelId = modelId || model._id;
modelId = modelId || app._id;
// 历史记录
let history: ChatItemType[] = [];
@@ -87,21 +87,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
]);
}
const isOwner = String(model.userId) === userId;
const isOwner = String(app.userId) === userId;
jsonRes<InitChatResponse>(res, {
data: {
chatId: chatId || '',
modelId: modelId,
model: {
name: model.name,
avatar: model.avatar,
intro: model.intro,
canUse: model.share.isShare || isOwner
name: app.name,
avatar: app.avatar,
intro: app.intro,
canUse: app.share.isShare || isOwner
},
chatModel: model.chat.chatModel,
systemPrompt: isOwner ? model.chat.systemPrompt : '',
limitPrompt: isOwner ? model.chat.limitPrompt : '',
chatModel: app.chat.chatModel,
systemPrompt: isOwner ? app.chat.systemPrompt : '',
limitPrompt: isOwner ? app.chat.limitPrompt : '',
history
}
});

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { ChatItemType } from '@/types/chat';
import { connectToDatabase, Chat, Model } from '@/service/mongo';
import { authModel } from '@/service/utils/auth';
import { authApp } from '@/service/utils/auth';
import { authUser } from '@/service/utils/auth';
import { Types } from 'mongoose';
@@ -49,7 +49,7 @@ export async function saveChat({
userId
}: Props & { newChatId?: Types.ObjectId; userId: string }): Promise<{ newChatId: string }> {
await connectToDatabase();
const { model } = await authModel({ modelId, userId, authOwner: false });
const { app } = await authApp({ appId: modelId, userId, authOwner: false });
const content = prompts.map((item) => ({
_id: item._id,
@@ -59,7 +59,7 @@ export async function saveChat({
quote: item.quote || []
}));
if (String(model.userId) === userId) {
if (String(app.userId) === userId) {
await Model.findByIdAndUpdate(modelId, {
updateTime: new Date()
});
@@ -93,8 +93,8 @@ export async function saveChat({
newChatId: String(res._id)
}))
]),
// update model
...(String(model.userId) === userId
// update app
...(String(app.userId) === userId
? [
Model.findByIdAndUpdate(modelId, {
updateTime: new Date()

View File

@@ -1,7 +1,7 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat } from '@/service/mongo';
import { authModel, authUser } from '@/service/utils/auth';
import { authApp, authUser } from '@/service/utils/auth';
import type { ShareChatEditType } from '@/types/model';
/* create a shareChat */
@@ -14,8 +14,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase();
const { userId } = await authUser({ req, authToken: true });
await authModel({
modelId,
await authApp({
appId: modelId,
userId,
authOwner: false
});

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase, ShareChat, User } from '@/service/mongo';
import type { InitShareChatResponse } from '@/api/response/chat';
import { authModel } from '@/service/utils/auth';
import { authApp } from '@/service/utils/auth';
import { hashPassword } from '@/service/utils/tools';
import { HUMAN_ICON } from '@/constants/chat';
@@ -35,8 +35,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
// 校验使用权限
const { model } = await authModel({
modelId: shareChat.modelId,
const { app } = await authApp({
appId: shareChat.modelId,
userId: String(shareChat.userId),
authOwner: false
});
@@ -48,11 +48,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
maxContext: shareChat.maxContext,
userAvatar: user?.avatar || HUMAN_ICON,
model: {
name: model.name,
avatar: model.avatar,
intro: model.intro
name: app.name,
avatar: app.avatar,
intro: app.intro
},
chatModel: model.chat.chatModel
chatModel: app.chat.chatModel
}
});
} catch (err) {

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { Chat, Model, connectToDatabase, Collection, ShareChat } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { authModel } from '@/service/utils/auth';
import { authApp } from '@/service/utils/auth';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -19,8 +19,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await connectToDatabase();
// 验证是否是该用户的 model
await authModel({
modelId,
await authApp({
appId: modelId,
userId
});

View File

@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { authModel } from '@/service/utils/auth';
import { authApp } from '@/service/utils/auth';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -18,14 +18,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await connectToDatabase();
const { model } = await authModel({
modelId,
const { app } = await authApp({
appId: modelId,
userId,
authOwner: false
});
jsonRes(res, {
data: model
data: app
});
} catch (err) {
jsonRes(res, {

View File

@@ -13,7 +13,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await connectToDatabase();
// 根据 userId 获取模型信息
const [myModels, myCollections] = await Promise.all([
const [myApps, myCollections] = await Promise.all([
Model.find(
{
userId
@@ -33,20 +33,20 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
jsonRes<ModelListResponse>(res, {
data: {
myModels: myModels.map((item) => ({
myApps: myApps.map((item) => ({
_id: item._id,
name: item.name,
avatar: item.avatar,
intro: item.intro
})),
myCollectionModels: myCollections
myCollectionApps: myCollections
.map((item: any) => ({
_id: item.modelId?._id,
name: item.modelId?.name,
avatar: item.modelId?.avatar,
intro: item.modelId?.intro
}))
.filter((item) => !myModels.find((model) => String(model._id) === String(item._id))) // 去重
.filter((item) => !myApps.find((model) => String(model._id) === String(item._id))) // 去重
}
});
} catch (err) {

View File

@@ -4,16 +4,16 @@ import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { Model } from '@/service/models/model';
import type { ModelUpdateParams } from '@/types/model';
import { authModel } from '@/service/utils/auth';
import { authApp } from '@/service/utils/auth';
/* 获取我的模型 */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { name, avatar, chat, share, intro } = req.body as ModelUpdateParams;
const { modelId } = req.query as { modelId: string };
const { name, avatar, chat, share, intro, modules } = req.body as ModelUpdateParams;
const { appId } = req.query as { appId: string };
if (!modelId) {
throw new Error('参数错误');
if (!appId) {
throw new Error('appId is empty');
}
// 凭证校验
@@ -21,15 +21,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
await connectToDatabase();
await authModel({
modelId,
await authApp({
appId,
userId
});
// 更新模型
await Model.updateOne(
{
_id: modelId,
_id: appId,
userId
},
{
@@ -40,7 +40,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
...(share && {
'share.isShare': share.isShare,
'share.isShareDetail': share.isShareDetail
})
}),
...(modules && { modules })
}
);

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authUser, authModel, getApiKey } from '@/service/utils/auth';
import { authUser, authApp, getApiKey } from '@/service/utils/auth';
import { modelServiceToolMap, resStreamResponse } from '@/service/utils/chat';
import { ChatItemType } from '@/types/chat';
import { jsonRes } from '@/service/response';
@@ -50,19 +50,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
/* 凭证校验 */
const { userId } = await authUser({ req });
const { model } = await authModel({
const { app } = await authApp({
userId,
modelId
appId: modelId
});
/* get api key */
const { systemAuthKey: apiKey } = await getApiKey({
model: model.chat.chatModel,
model: app.chat.chatModel,
userId,
mustPay: true
});
const modelConstantsData = ChatModelMap[model.chat.chatModel];
const modelConstantsData = ChatModelMap[app.chat.chatModel];
const prompt = prompts[prompts.length - 1];
const {
@@ -71,14 +71,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
quotePrompt = []
} = await (async () => {
// 使用了知识库搜索
if (model.chat.relatedKbs?.length > 0) {
if (app.chat.relatedKbs?.length > 0) {
const { quotePrompt, userSystemPrompt, userLimitPrompt } = await appKbSearch({
model,
model: app,
userId,
fixedQuote: [],
prompt: prompt,
similarity: model.chat.searchSimilarity,
limit: model.chat.searchLimit
similarity: app.chat.searchSimilarity,
limit: app.chat.searchLimit
});
return {
@@ -88,19 +88,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
};
}
return {
userSystemPrompt: model.chat.systemPrompt
userSystemPrompt: app.chat.systemPrompt
? [
{
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
value: app.chat.systemPrompt
}
]
: [],
userLimitPrompt: model.chat.limitPrompt
userLimitPrompt: app.chat.limitPrompt
? [
{
obj: ChatRoleEnum.Human,
value: model.chat.limitPrompt
value: app.chat.limitPrompt
}
]
: []
@@ -108,8 +108,8 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
})();
// search result is empty
if (model.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && model.chat.searchEmptyText) {
const response = model.chat.searchEmptyText;
if (app.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && app.chat.searchEmptyText) {
const response = app.chat.searchEmptyText;
return res.end(response);
}
@@ -123,14 +123,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
];
// 计算温度
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
const temperature = (modelConstantsData.maxTemperature * (app.chat.temperature / 10)).toFixed(
2
);
// 发出请求
const { streamResponse, responseMessages, responseText, totalTokens } =
await modelServiceToolMap.chatCompletion({
model: model.chat.chatModel,
model: app.chat.chatModel,
apiKey,
temperature: +temperature,
messages: completePrompts,
@@ -146,7 +146,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
if (isStream) {
try {
const { finishMessages, totalTokens } = await resStreamResponse({
model: model.chat.chatModel,
model: app.chat.chatModel,
res,
chatResponse: streamResponse,
prompts: responseMessages
@@ -173,7 +173,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
pushChatBill({
isPay: true,
chatModel: model.chat.chatModel,
chatModel: app.chat.chatModel,
userId,
textLen,
tokens,

View File

@@ -4,8 +4,8 @@ import { authUser } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
import type { ChatItemType } from '@/types/chat';
import type { ModelSchema } from '@/types/mongoSchema';
import { authModel } from '@/service/utils/auth';
import type { AppSchema } from '@/types/mongoSchema';
import { authApp } from '@/service/utils/auth';
import { ChatModelMap } from '@/constants/model';
import { ChatRoleEnum } from '@/constants/chat';
import { openaiEmbedding } from '../plugin/openaiEmbedding';
@@ -54,13 +54,13 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}
// auth model
const { model } = await authModel({
modelId: appId,
const { app } = await authApp({
appId,
userId
});
const result = await appKbSearch({
model,
model: app,
userId,
fixedQuote: [],
prompt: prompts[prompts.length - 1],
@@ -88,7 +88,7 @@ export async function appKbSearch({
similarity = 0.8,
limit = 5
}: {
model: ModelSchema;
model: AppSchema;
userId: string;
fixedQuote?: QuoteItemType[];
prompt: ChatItemType;

View File

@@ -106,7 +106,7 @@ export async function classifyQuestion({
if (!arg.type) {
throw new Error('');
}
console.log(adaptMessages, arg.type);
console.log(arg.type);
return {
[arg.type]: 1

View File

@@ -5,9 +5,8 @@ import { sseResponse } from '@/service/utils/tools';
import { ChatModelMap, OpenAiChatEnum } from '@/constants/model';
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
import { modelToolMap } from '@/utils/plugin';
import { ChatCompletionType, ChatContextFilter } from '@/service/utils/chat/index';
import { ChatContextFilter } from '@/service/utils/chat/index';
import type { ChatItemType } from '@/types/chat';
import { getSystemOpenAiKey } from '@/service/utils/auth';
import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
import { parseStreamChunk, textAdaptGptResponse } from '@/utils/adapt';
import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
@@ -23,7 +22,7 @@ export type Props = {
systemPrompt?: string;
limitPrompt?: string;
};
export type Response = { history: ChatItemType[] };
export type Response = { answer: string };
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
@@ -89,7 +88,7 @@ export async function chatCompletion({
userChatInput,
systemPrompt,
limitPrompt
}: Props & { res: NextApiResponse }) {
}: Props & { res: NextApiResponse }): Promise<Response> {
const messages: ChatItemType[] = [
...(quotePrompt
? [
@@ -131,7 +130,6 @@ export async function chatCompletion({
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
const chatAPI = getOpenAIApi();
console.log(adaptMessages);
/* count response max token */
const promptsToken = modelToolMap[model].countTokens({
@@ -156,37 +154,35 @@ export async function chatCompletion({
}
);
const { answer, totalTokens } = await (async () => {
const { answer } = await (async () => {
if (stream) {
// sse response
const { answer } = await streamResponse({ res, response });
// count tokens
const finishMessages = filterMessages.concat({
obj: ChatRoleEnum.AI,
value: answer
});
// const finishMessages = filterMessages.concat({
// obj: ChatRoleEnum.AI,
// value: answer
// });
const totalTokens = modelToolMap[model].countTokens({
messages: finishMessages
});
// const totalTokens = modelToolMap[model].countTokens({
// messages: finishMessages
// });
return {
answer,
totalTokens
answer
// totalTokens
};
} else {
const answer = stream ? '' : response.data.choices?.[0].message?.content || '';
const totalTokens = stream ? 0 : response.data.usage?.total_tokens || 0;
// const totalTokens = stream ? 0 : response.data.usage?.total_tokens || 0;
return {
answer,
totalTokens
answer
// totalTokens
};
}
})();
// count price
const unitPrice = ChatModelMap[model]?.price || 3;
return {
answer
};

View File

@@ -0,0 +1,20 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { SystemInputEnum } from '@/constants/app';
import { ChatItemType } from '@/types/chat';
export type Props = {
maxContext: number;
[SystemInputEnum.history]: ChatItemType[];
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { maxContext = 5, history } = req.body as Props;
jsonRes(res, {
data: {
history: history.slice(-maxContext)
}
});
}

View File

@@ -0,0 +1,17 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { SystemInputEnum } from '@/constants/app';
export type Props = {
[SystemInputEnum.userChatInput]: string;
};
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { userChatInput } = req.body as Props;
jsonRes(res, {
data: {
userChatInput
}
});
}

View File

@@ -25,7 +25,7 @@ type Props = {
type Response = {
rawSearch: QuoteItemType[];
isEmpty?: boolean;
quotePrompt: string;
quotePrompt?: string;
};
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
@@ -43,7 +43,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
throw new Error('params is error');
}
const result = await appKbSearch({
const result = await kbSearch({
kb_ids,
history,
similarity,
@@ -64,7 +64,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}
});
export async function appKbSearch({
export async function kbSearch({
kb_ids = [],
history = [],
similarity = 0.8,
@@ -108,8 +108,8 @@ export async function appKbSearch({
const rawSearch = searchRes.slice(0, sliceResult.length);
return {
isEmpty: rawSearch.length === 0,
isEmpty: rawSearch.length === 0 ? true : undefined,
rawSearch,
quotePrompt: sliceResult ? `知识库:\n${sliceResult}` : ''
quotePrompt: sliceResult ? `知识库:\n${sliceResult}` : undefined
};
}

View File

@@ -1,4 +0,0 @@
export type Props = {
url: string;
body: Record<string, any>;
};

View File

@@ -96,7 +96,7 @@ export async function openaiEmbedding_system({ input }: Props) {
input
},
{
timeout: 60000,
timeout: 20000,
...axiosConfig(apiKey)
}
)

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authUser, authModel, getApiKey, authShareChat } from '@/service/utils/auth';
import { authUser, authApp, getApiKey, authShareChat } from '@/service/utils/auth';
import { modelServiceToolMap, V2_StreamResponse } from '@/service/utils/chat';
import { jsonRes } from '@/service/response';
import { ChatModelMap } from '@/constants/model';
@@ -79,9 +79,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
}
// auth app permission
const { model, showModelDetail } = await authModel({
const { app, showModelDetail } = await authApp({
userId,
modelId: appId,
appId,
authOwner: false,
reserveDetail: true
});
@@ -90,7 +90,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
/* get api key */
const { systemAuthKey: apiKey, userOpenAiKey } = await getApiKey({
model: model.chat.chatModel,
model: app.chat.chatModel,
userId,
mustPay: authType !== 'token'
});
@@ -112,14 +112,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
quotePrompt = []
} = await (async () => {
// 使用了知识库搜索
if (model.chat.relatedKbs?.length > 0) {
if (app.chat.relatedKbs?.length > 0) {
const { rawSearch, quotePrompt, userSystemPrompt, userLimitPrompt } = await appKbSearch({
model,
model: app,
userId,
fixedQuote: history[history.length - 1]?.quote,
prompt,
similarity: model.chat.searchSimilarity,
limit: model.chat.searchLimit
similarity: app.chat.searchSimilarity,
limit: app.chat.searchLimit
});
return {
@@ -130,19 +130,19 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
};
}
return {
userSystemPrompt: model.chat.systemPrompt
userSystemPrompt: app.chat.systemPrompt
? [
{
obj: ChatRoleEnum.System,
value: model.chat.systemPrompt
value: app.chat.systemPrompt
}
]
: [],
userLimitPrompt: model.chat.limitPrompt
userLimitPrompt: app.chat.limitPrompt
? [
{
obj: ChatRoleEnum.Human,
value: model.chat.limitPrompt
value: app.chat.limitPrompt
}
]
: []
@@ -150,15 +150,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
})();
// search result is empty
if (model.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && model.chat.searchEmptyText) {
const response = model.chat.searchEmptyText;
if (app.chat.relatedKbs?.length > 0 && !quotePrompt[0]?.value && app.chat.searchEmptyText) {
const response = app.chat.searchEmptyText;
if (stream) {
sseResponse({
res,
event: sseResponseEventEnum.answer,
data: textAdaptGptResponse({
text: response,
model: model.chat.chatModel,
model: app.chat.chatModel,
finish_reason: 'stop'
})
});
@@ -166,9 +166,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
} else {
return res.json({
id: chatId || '',
model: model.chat.chatModel,
object: 'chat.completion',
created: 1688608930,
model: app.chat.chatModel,
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
choices: [
{ message: { role: 'assistant', content: response }, finish_reason: 'stop', index: 0 }
@@ -186,9 +186,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
prompt
];
// chat temperature
const modelConstantsData = ChatModelMap[model.chat.chatModel];
const modelConstantsData = ChatModelMap[app.chat.chatModel];
// FastGpt temperature range: 1~10
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
const temperature = (modelConstantsData.maxTemperature * (app.chat.temperature / 10)).toFixed(
2
);
@@ -196,13 +196,13 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
input: `${userSystemPrompt[0]?.value}\n${userLimitPrompt[0]?.value}\n${prompt.value}`
});
// start model api. responseText and totalTokens: valid only if stream = false
// start app api. responseText and totalTokens: valid only if stream = false
const { streamResponse, responseMessages, responseText, totalTokens } =
await modelServiceToolMap.chatCompletion({
model: model.chat.chatModel,
model: app.chat.chatModel,
apiKey: userOpenAiKey || apiKey,
temperature: +temperature,
maxToken: model.chat.maxToken,
maxToken: app.chat.maxToken,
messages: completePrompts,
stream,
res
@@ -242,7 +242,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
});
// response answer
const { finishMessages, totalTokens, responseContent } = await V2_StreamResponse({
model: model.chat.chatModel,
model: app.chat.chatModel,
res,
chatResponse: streamResponse,
prompts: responseMessages
@@ -300,7 +300,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
id: chatId || '',
object: 'chat.completion',
created: 1688608930,
model: model.chat.chatModel,
model: app.chat.chatModel,
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: tokens },
choices: [
{ message: { role: 'assistant', content: answer }, finish_reason: 'stop', index: 0 }
@@ -310,7 +310,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
pushChatBill({
isPay: !userOpenAiKey,
chatModel: model.chat.chatModel,
chatModel: app.chat.chatModel,
userId,
textLen,
tokens,

View File

@@ -1,6 +1,6 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authUser, authModel, getApiKey, authShareChat } from '@/service/utils/auth';
import { authUser, authApp, getApiKey, authShareChat } from '@/service/utils/auth';
import { sseErrRes, jsonRes } from '@/service/response';
import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
import { withNextCors } from '@/service/utils/tools';
@@ -13,14 +13,14 @@ import { type ChatCompletionRequestMessage } from 'openai';
import {
kbChatAppDemo,
chatAppDemo,
lafClassifyQuestionDemo,
classifyQuestionDemo,
SpecificInputEnum,
AppModuleItemTypeEnum
} from '@/constants/app';
import { Types } from 'mongoose';
import { model, Types } from 'mongoose';
import { moduleFetch } from '@/service/api/request';
import { AppModuleItemType } from '@/types/app';
import { AppModuleItemType, RunningModuleItemType } from '@/types/app';
import { FlowInputItemTypeEnum, FlowOutputItemTypeEnum } from '@/constants/flow';
import { SystemInputEnum } from '@/constants/app';
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
type FastGptWebChatProps = {
@@ -82,8 +82,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
throw new Error('appId is empty');
}
// get history
const { history } = await getChatHistory({ chatId, userId });
// auth app, get history
const [{ app }, { history }] = await Promise.all([
authApp({
appId,
userId
}),
getChatHistory({ chatId, userId })
]);
const prompts = history.concat(gptMessage2ChatType(messages));
if (prompts[prompts.length - 1].obj === 'AI') {
prompts.pop();
@@ -95,12 +102,15 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
throw new Error('Question is empty');
}
/* start process */
const modules = JSON.parse(JSON.stringify(classifyQuestionDemo.modules));
const newChatId = chatId === '' ? new Types.ObjectId() : undefined;
if (stream && newChatId) {
res.setHeader('newChatId', String(newChatId));
}
/* start process */
const { responseData, answerText } = await dispatchModules({
res,
modules,
modules: app.modules,
params: {
history: prompts,
userChatInput: prompt.value
@@ -110,8 +120,9 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
// save chat
if (typeof chatId === 'string') {
const { newChatId } = await saveChat({
await saveChat({
chatId,
newChatId,
modelId: appId,
prompts: [
prompt,
@@ -124,19 +135,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
],
userId
});
if (newChatId) {
sseResponse({
res,
event: sseResponseEventEnum.chatResponse,
data: JSON.stringify({
newChatId
})
});
}
}
if (stream) {
sseResponse({
res,
event: sseResponseEventEnum.answer,
data: '[DONE]'
});
sseResponse({
res,
event: sseResponseEventEnum.appStreamResponse,
@@ -145,7 +151,10 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
res.end();
} else {
res.json({
data: responseData,
data: {
newChatId,
...responseData
},
id: chatId || '',
model: '',
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
@@ -183,6 +192,7 @@ async function dispatchModules({
params?: Record<string, any>;
stream?: boolean;
}) {
const runningModules = loadModules(modules);
let storeData: Record<string, any> = {};
let responseData: Record<string, any> = {};
let answerText = '';
@@ -212,7 +222,10 @@ async function dispatchModules({
...data
};
}
function moduleInput(module: AppModuleItemType, data: Record<string, any> = {}): Promise<any> {
function moduleInput(
module: RunningModuleItemType,
data: Record<string, any> = {}
): Promise<any> {
const checkInputFinish = () => {
return !module.inputs.find((item: any) => item.value === undefined);
};
@@ -222,50 +235,58 @@ async function dispatchModules({
module.inputs[index].value = value;
};
const set = new Set();
return Promise.all(
Object.entries(data).map(([key, val]: any) => {
updateInputValue(key, val);
if (checkInputFinish()) {
if (!set.has(module.moduleId) && checkInputFinish()) {
set.add(module.moduleId);
return moduleRun(module);
}
})
);
}
function moduleOutput(module: AppModuleItemType, result: Record<string, any> = {}): Promise<any> {
function moduleOutput(
module: RunningModuleItemType,
result: Record<string, any> = {}
): Promise<any> {
return Promise.all(
module.outputs.map((item) => {
if (result[item.key] === undefined) return;
module.outputs.map((outputItem) => {
if (result[outputItem.key] === undefined) return;
/* update output value */
item.value = result[item.key];
outputItem.value = result[outputItem.key];
pushStore({
isResponse: item.response,
answer: item.answer ? item.value : '',
isResponse: outputItem.response,
answer: outputItem.answer ? outputItem.value : '',
data: {
[item.key]: item.value
[outputItem.key]: outputItem.value
}
});
/* update target */
return Promise.all(
item.targets.map((target: any) => {
outputItem.targets.map((target: any) => {
// find module
const targetModule = modules.find((item) => item.moduleId === target.moduleId);
const targetModule = runningModules.find((item) => item.moduleId === target.moduleId);
if (!targetModule) return;
return moduleInput(targetModule, { [target.key]: item.value });
return moduleInput(targetModule, { [target.key]: outputItem.value });
})
);
})
);
}
async function moduleRun(module: AppModuleItemType): Promise<any> {
async function moduleRun(module: RunningModuleItemType): Promise<any> {
if (res.closed) return Promise.resolve();
console.log('run=========', module.type, module.url);
if (module.type === AppModuleItemTypeEnum.answer) {
pushStore({
answer: module.inputs[0].value
answer: module.inputs.find((item) => item.key === SpecificInputEnum.answerText)?.value || ''
});
return AnswerResponse({
return StreamAnswer({
res,
stream,
text: module.inputs.find((item) => item.key === SpecificInputEnum.answerText)?.value
@@ -276,16 +297,19 @@ async function dispatchModules({
return moduleOutput(module, switchResponse(module));
}
if (module.type === AppModuleItemTypeEnum.http && module.url) {
if (
(module.type === AppModuleItemTypeEnum.http ||
module.type === AppModuleItemTypeEnum.initInput) &&
module.url
) {
// get fetch params
const inputParams: Record<string, any> = {};
const params: Record<string, any> = {};
module.inputs.forEach((item: any) => {
inputParams[item.key] = item.value;
params[item.key] = item.value;
});
const data = {
stream,
...module.body,
...inputParams
...params
};
// response data
@@ -299,8 +323,12 @@ async function dispatchModules({
}
}
// 从填充 params 开始进入递归
await Promise.all(modules.map((module) => moduleInput(module, params)));
// start process width initInput
const initModules = runningModules.filter(
(item) => item.type === AppModuleItemTypeEnum.initInput
);
await Promise.all(initModules.map((module) => moduleInput(module, params)));
return {
responseData,
@@ -308,7 +336,29 @@ async function dispatchModules({
};
}
function AnswerResponse({
function loadModules(modules: AppModuleItemType[]): RunningModuleItemType[] {
return modules.map((module) => {
return {
moduleId: module.moduleId,
type: module.type,
url: module.url,
inputs: module.inputs
.filter((item) => item.type !== FlowInputItemTypeEnum.target || item.connected) // filter unconnected target input
.map((item) => ({
key: item.key,
value: item.value
})),
outputs: module.outputs.map((item) => ({
key: item.key,
answer: item.type === FlowOutputItemTypeEnum.answer,
response: item.response,
value: undefined,
targets: item.targets
}))
};
});
}
function StreamAnswer({
res,
stream = false,
text = ''
@@ -322,13 +372,13 @@ function AnswerResponse({
res,
event: sseResponseEventEnum.answer,
data: textAdaptGptResponse({
text
text: text.replace(/\\n/g, '\n')
})
});
}
return text;
}
function switchResponse(module: any) {
function switchResponse(module: RunningModuleItemType) {
const val = module?.inputs?.[0]?.value;
if (val) {