* perf: insert mongo dataset data session * perf: dataset data index * remove delay * rename bill schema * rename bill record * perf: bill table * perf: prompt * perf: sub plan * change the usage count * feat: usage bill * publish usages * doc * 新增团队聊天功能 (#20) * perf: doc * feat 添加标签部分 feat 信息团队标签配置 feat 新增团队同步管理 feat team分享页面 feat 完成team分享页面 feat 实现模糊搜索 style 格式化 fix 修复迷糊匹配 style 样式修改 fix 团队标签功能修复 * fix 修复鉴权功能 * merge 合并代码 * fix 修复引用错误 * fix 修复pr问题 * fix 修复ts格式问题 --------- Co-authored-by: archer <545436317@qq.com> Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com> * update extra plan * fix: ts * format * perf: bill field * feat: standard plan * fix: ts * feat 个人账号页面修改 (#22) * feat 添加标签部分 feat 信息团队标签配置 feat 新增团队同步管理 feat team分享页面 feat 完成team分享页面 feat 实现模糊搜索 style 格式化 fix 修复迷糊匹配 style 样式修改 fix 团队标签功能修复 * fix 修复鉴权功能 * merge 合并代码 * fix 修复引用错误 * fix 修复pr问题 * fix 修复ts格式问题 * feat 修改个人账号页 --------- Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com> * fix chunk index; error page text * feat: dataset process Integral prediction * feat: stand plan field * feat: sub plan limit * perf: index * query extension * perf: share link push app name * perf: plan point unit * perf: get sub plan * perf: account page --------- Co-authored-by: yst <77910600+yu-and-liu@users.noreply.github.com> Co-authored-by: liuxingwan <liuxingwan.lxw@alibaba-inc.com>
362 lines
9.5 KiB
TypeScript
362 lines
9.5 KiB
TypeScript
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
import { authApp } from '@fastgpt/service/support/permission/auth/app';
|
|
import { authCert } from '@fastgpt/service/support/permission/auth/common';
|
|
import { sseErrRes, jsonRes } from '@fastgpt/service/common/response';
|
|
import { addLog } from '@fastgpt/service/common/system/log';
|
|
import { withNextCors } from '@fastgpt/service/common/middle/cors';
|
|
import { ChatRoleEnum, ChatSourceEnum } from '@fastgpt/global/core/chat/constants';
|
|
import { sseResponseEventEnum } from '@fastgpt/service/common/response/constant';
|
|
import { dispatchModules } from '@/service/moduleDispatch';
|
|
import type { ChatCompletionCreateParams } from '@fastgpt/global/core/ai/type.d';
|
|
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
|
|
import { gptMessage2ChatType, textAdaptGptResponse } from '@/utils/adapt';
|
|
import { getChatItems } from '@fastgpt/service/core/chat/controller';
|
|
import { saveChat } from '@/service/utils/chat/saveChat';
|
|
import { responseWrite } from '@fastgpt/service/common/response';
|
|
import { pushChatUsage } from '@/service/support/wallet/usage/push';
|
|
import { authOutLinkChatStart } from '@/service/support/permission/auth/outLink';
|
|
import { pushResult2Remote, addOutLinkUsage } from '@fastgpt/service/support/outLink/tools';
|
|
import requestIp from 'request-ip';
|
|
import { getUsageSourceByAuthType } from '@fastgpt/global/support/wallet/usage/tools';
|
|
|
|
import { selectShareResponse } from '@/utils/service/core/chat';
|
|
import { updateApiKeyUsage } from '@fastgpt/service/support/openapi/tools';
|
|
import { connectToDatabase } from '@/service/mongo';
|
|
import { getUserChatInfoAndAuthTeamPoints } from '@/service/support/permission/auth/team';
|
|
import { AuthUserTypeEnum } from '@fastgpt/global/support/permission/constant';
|
|
import { MongoApp } from '@fastgpt/service/core/app/schema';
|
|
import { autChatCrud } from '@/service/support/permission/auth/chat';
|
|
|
|
type FastGptWebChatProps = {
|
|
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
|
|
appId?: string;
|
|
};
|
|
type FastGptShareChatProps = {
|
|
shareId?: string;
|
|
outLinkUid?: string;
|
|
};
|
|
type FastGptTeamShareChatProps = {
|
|
teamId?: string;
|
|
outLinkUid?: string;
|
|
};
|
|
export type Props = ChatCompletionCreateParams &
|
|
FastGptWebChatProps &
|
|
FastGptShareChatProps &
|
|
FastGptTeamShareChatProps & {
|
|
messages: ChatMessageItemType[];
|
|
stream?: boolean;
|
|
detail?: boolean;
|
|
variables: Record<string, any>;
|
|
};
|
|
export type ChatResponseType = {
|
|
newChatId: string;
|
|
quoteLen?: number;
|
|
};
|
|
|
|
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
res.on('close', () => {
|
|
res.end();
|
|
});
|
|
res.on('error', () => {
|
|
console.log('error: ', 'request error');
|
|
res.end();
|
|
});
|
|
|
|
const {
|
|
chatId,
|
|
appId,
|
|
teamId,
|
|
shareId,
|
|
outLinkUid,
|
|
stream = false,
|
|
detail = false,
|
|
messages = [],
|
|
variables = {}
|
|
} = req.body as Props;
|
|
|
|
try {
|
|
const originIp = requestIp.getClientIp(req);
|
|
|
|
await connectToDatabase();
|
|
// body data check
|
|
if (!messages) {
|
|
throw new Error('Prams Error');
|
|
}
|
|
if (!Array.isArray(messages)) {
|
|
throw new Error('messages is not array');
|
|
}
|
|
if (messages.length === 0) {
|
|
throw new Error('messages is empty');
|
|
}
|
|
|
|
let startTime = Date.now();
|
|
|
|
const chatMessages = gptMessage2ChatType(messages);
|
|
if (chatMessages[chatMessages.length - 1].obj !== ChatRoleEnum.Human) {
|
|
chatMessages.pop();
|
|
}
|
|
|
|
// user question
|
|
const question = chatMessages.pop();
|
|
if (!question) {
|
|
throw new Error('Question is empty');
|
|
}
|
|
|
|
/* auth app permission */
|
|
const { teamId, tmbId, user, app, responseDetail, authType, apikey, canWrite, outLinkUserId } =
|
|
await (async () => {
|
|
if (shareId && outLinkUid) {
|
|
const { teamId, tmbId, user, appId, authType, responseDetail, uid } =
|
|
await authOutLinkChatStart({
|
|
shareId,
|
|
ip: originIp,
|
|
outLinkUid,
|
|
question: question.value
|
|
});
|
|
const app = await MongoApp.findById(appId);
|
|
|
|
if (!app) {
|
|
return Promise.reject('app is empty');
|
|
}
|
|
|
|
return {
|
|
teamId,
|
|
tmbId,
|
|
user,
|
|
app,
|
|
responseDetail,
|
|
apikey: '',
|
|
authType,
|
|
canWrite: false,
|
|
outLinkUserId: uid
|
|
};
|
|
}
|
|
|
|
const {
|
|
appId: apiKeyAppId,
|
|
teamId,
|
|
tmbId,
|
|
authType,
|
|
apikey
|
|
} = await authCert({
|
|
req,
|
|
authToken: true,
|
|
authApiKey: true
|
|
});
|
|
|
|
const user = await getUserChatInfoAndAuthTeamPoints(tmbId);
|
|
|
|
// openapi key
|
|
if (authType === AuthUserTypeEnum.apikey) {
|
|
if (!apiKeyAppId) {
|
|
return Promise.reject(
|
|
'Key is error. You need to use the app key rather than the account key.'
|
|
);
|
|
}
|
|
const app = await MongoApp.findById(apiKeyAppId);
|
|
|
|
if (!app) {
|
|
return Promise.reject('app is empty');
|
|
}
|
|
|
|
return {
|
|
teamId,
|
|
tmbId,
|
|
user,
|
|
app,
|
|
responseDetail: detail,
|
|
apikey,
|
|
authType,
|
|
canWrite: true
|
|
};
|
|
}
|
|
|
|
// token auth
|
|
if (!appId) {
|
|
return Promise.reject('appId is empty');
|
|
}
|
|
const { app, canWrite } = await authApp({
|
|
req,
|
|
authToken: true,
|
|
appId,
|
|
per: 'r'
|
|
});
|
|
|
|
return {
|
|
teamId,
|
|
tmbId,
|
|
user,
|
|
app,
|
|
responseDetail: detail,
|
|
apikey,
|
|
authType,
|
|
canWrite: canWrite || false
|
|
};
|
|
})();
|
|
|
|
// auth chat permission
|
|
await autChatCrud({
|
|
req,
|
|
authToken: true,
|
|
authApiKey: true,
|
|
appId: app._id,
|
|
chatId,
|
|
shareId,
|
|
outLinkUid,
|
|
per: 'w'
|
|
});
|
|
|
|
// get and concat history
|
|
const { history } = await getChatItems({
|
|
appId: app._id,
|
|
chatId,
|
|
limit: 30,
|
|
field: `dataId obj value`
|
|
});
|
|
|
|
const concatHistories = history.concat(chatMessages);
|
|
const responseChatItemId: string | undefined = messages[messages.length - 1].dataId;
|
|
|
|
/* start flow controller */
|
|
const { responseData, moduleDispatchBills, answerText } = await dispatchModules({
|
|
res,
|
|
mode: 'chat',
|
|
user,
|
|
teamId: String(teamId),
|
|
tmbId: String(tmbId),
|
|
appId: String(app._id),
|
|
chatId,
|
|
responseChatItemId,
|
|
modules: app.modules,
|
|
variables,
|
|
histories: concatHistories,
|
|
startParams: {
|
|
userChatInput: question.value
|
|
},
|
|
stream,
|
|
detail
|
|
});
|
|
console.log('af');
|
|
|
|
// save chat
|
|
if (chatId) {
|
|
await saveChat({
|
|
chatId,
|
|
appId: app._id,
|
|
teamId: teamId,
|
|
tmbId: tmbId,
|
|
variables,
|
|
updateUseTime: !shareId && String(tmbId) === String(app.tmbId), // owner update use time
|
|
shareId,
|
|
outLinkUid: outLinkUserId,
|
|
source: (() => {
|
|
if (shareId) {
|
|
return ChatSourceEnum.share;
|
|
}
|
|
if (authType === 'apikey') {
|
|
return ChatSourceEnum.api;
|
|
}
|
|
return ChatSourceEnum.online;
|
|
})(),
|
|
content: [
|
|
question,
|
|
{
|
|
dataId: responseChatItemId,
|
|
obj: ChatRoleEnum.AI,
|
|
value: answerText,
|
|
responseData
|
|
}
|
|
],
|
|
metadata: {
|
|
originIp
|
|
}
|
|
});
|
|
}
|
|
|
|
addLog.info(`completions running time: ${(Date.now() - startTime) / 1000}s`);
|
|
|
|
/* select fe response field */
|
|
const feResponseData = canWrite ? responseData : selectShareResponse({ responseData });
|
|
|
|
if (stream) {
|
|
responseWrite({
|
|
res,
|
|
event: detail ? sseResponseEventEnum.answer : undefined,
|
|
data: textAdaptGptResponse({
|
|
text: null,
|
|
finish_reason: 'stop'
|
|
})
|
|
});
|
|
responseWrite({
|
|
res,
|
|
event: detail ? sseResponseEventEnum.answer : undefined,
|
|
data: '[DONE]'
|
|
});
|
|
|
|
if (responseDetail && detail) {
|
|
responseWrite({
|
|
res,
|
|
event: sseResponseEventEnum.appStreamResponse,
|
|
data: JSON.stringify(feResponseData)
|
|
});
|
|
}
|
|
|
|
res.end();
|
|
} else {
|
|
res.json({
|
|
...(detail ? { responseData: feResponseData } : {}),
|
|
id: chatId || '',
|
|
model: '',
|
|
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 1 },
|
|
choices: [
|
|
{
|
|
message: { role: 'assistant', content: answerText },
|
|
finish_reason: 'stop',
|
|
index: 0
|
|
}
|
|
]
|
|
});
|
|
}
|
|
|
|
// add record
|
|
const { totalPoints } = pushChatUsage({
|
|
appName: app.name,
|
|
appId: app._id,
|
|
teamId: teamId,
|
|
tmbId: tmbId,
|
|
source: getUsageSourceByAuthType({ shareId, authType }),
|
|
moduleDispatchBills
|
|
});
|
|
|
|
if (shareId) {
|
|
pushResult2Remote({ outLinkUid, shareId, appName: app.name, responseData });
|
|
addOutLinkUsage({
|
|
shareId,
|
|
totalPoints
|
|
});
|
|
}
|
|
if (apikey) {
|
|
updateApiKeyUsage({
|
|
apikey,
|
|
totalPoints
|
|
});
|
|
}
|
|
} catch (err) {
|
|
if (stream) {
|
|
sseErrRes(res, err);
|
|
res.end();
|
|
} else {
|
|
jsonRes(res, {
|
|
code: 500,
|
|
error: err
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
export const config = {
|
|
api: {
|
|
responseLimit: '20mb'
|
|
}
|
|
};
|