feat: openapi cors

This commit is contained in:
archer
2023-05-19 12:01:59 +08:00
parent bb312441c6
commit 2843178ede
12 changed files with 71 additions and 209 deletions

View File

@@ -39,6 +39,7 @@
"mongoose": "^6.10.0",
"nanoid": "^4.0.1",
"next": "13.1.6",
"nextjs-cors": "^2.1.2",
"nodemailer": "^6.9.1",
"nprogress": "^0.2.0",
"openai": "^3.2.1",

31
pnpm-lock.yaml generated
View File

@@ -46,6 +46,7 @@ specifiers:
mongoose: ^6.10.0
nanoid: ^4.0.1
next: 13.1.6
nextjs-cors: ^2.1.2
nodemailer: ^6.9.1
nprogress: ^0.2.0
openai: ^3.2.1
@@ -97,6 +98,7 @@ dependencies:
mongoose: registry.npmmirror.com/mongoose/6.10.0
nanoid: registry.npmmirror.com/nanoid/4.0.1
next: registry.npmmirror.com/next/13.1.6_wiv434v7erz4aedd5whhdwmpv4
nextjs-cors: 2.1.2_next@13.1.6
nodemailer: registry.npmmirror.com/nodemailer/6.9.1
nprogress: registry.npmmirror.com/nprogress/0.2.0
openai: registry.npmmirror.com/openai/3.2.1
@@ -309,6 +311,14 @@ packages:
engines: {node: '>= 0.6'}
dev: false
/cors/2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
dependencies:
object-assign: 4.1.1
vary: 1.1.2
dev: false
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -323,6 +333,20 @@ packages:
dev: false
optional: true
/nextjs-cors/2.1.2_next@13.1.6:
resolution: {integrity: sha512-2yOVivaaf2ILe4f/qY32hnj3oC77VCOsUQJQfhVMGsXE/YMEWUY2zy78sH9FKUCM7eG42/l3pDofIzMD781XGA==}
peerDependencies:
next: ^8.1.1-canary.54 || ^9.0.0 || ^10.0.0-0 || ^11.0.0 || ^12.0.0 || ^13.0.0
dependencies:
cors: 2.8.5
next: registry.npmmirror.com/next/13.1.6_wiv434v7erz4aedd5whhdwmpv4
dev: false
/object-assign/4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
dev: false
/saslprep/1.0.3:
resolution: {integrity: sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==}
engines: {node: '>=6'}
@@ -339,6 +363,11 @@ packages:
dev: false
optional: true
/vary/1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
dev: false
registry.npmmirror.com/@alicloud/credentials/2.2.6:
resolution: {integrity: sha512-jG+msY77dHmAF3x+8VTy7fEgORyXLHmDci8t92HeipBdCHsPptDegA++GEwKgR7f6G4wvafYt+aqMZ1iligdrQ==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/@alicloud/credentials/-/credentials-2.2.6.tgz}
name: '@alicloud/credentials'
@@ -9453,7 +9482,7 @@ packages:
version: 2.7.0
dependencies:
any-promise: registry.npmmirror.com/any-promise/1.3.0
object-assign: registry.npmmirror.com/object-assign/4.1.1
object-assign: 4.1.1
thenify-all: registry.npmmirror.com/thenify-all/1.6.0
dev: false

View File

@@ -8,9 +8,10 @@ import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
import { pushChatBill } from '@/service/events/pushBill';
import { searchKb } from '@/service/plugins/searchKb';
import { ChatRoleEnum } from '@/constants/chat';
import { withNextCors } from '@/service/utils/tools';
/* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
let step = 0; // step=1时表示开始了流响应
res.on('error', () => {
console.log('error: ', 'request error');
@@ -143,4 +144,4 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
});
}
}
}
});

View File

@@ -1,184 +0,0 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { connectToDatabase } from '@/service/mongo';
import { authOpenApiKey, authModel, getApiKey } from '@/service/utils/auth';
import { resStreamResponse, modelServiceToolMap } from '@/service/utils/chat';
import { ChatItemSimpleType } from '@/types/chat';
import { jsonRes } from '@/service/response';
import { ChatModelMap, ModelVectorSearchModeMap } from '@/constants/model';
import { pushChatBill } from '@/service/events/pushBill';
import { searchKb } from '@/service/plugins/searchKb';
import { ChatRoleEnum } from '@/constants/chat';
/* 发送提示词 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
let step = 0; // step=1时表示开始了流响应
res.on('error', () => {
console.log('error: ', 'request error');
res.end();
});
try {
const {
prompt,
modelId,
isStream = true
} = req.body as {
prompt: ChatItemSimpleType;
modelId: string;
isStream: boolean;
};
if (!prompt || !modelId) {
throw new Error('缺少参数');
}
await connectToDatabase();
let startTime = Date.now();
/* 凭证校验 */
const { userId } = await authOpenApiKey(req);
/* 查找数据库里的模型信息 */
const { model } = await authModel({
userId,
modelId
});
/* get api key */
const { systemAuthKey: apiKey } = await getApiKey({
model: model.chat.chatModel,
userId,
mustPay: true
});
const modelConstantsData = ChatModelMap[model.chat.chatModel];
console.log('laf gpt start');
// 请求一次 chatgpt 拆解需求
const { responseText: resolveText, totalTokens: resolveTokens } = await modelServiceToolMap[
model.chat.chatModel
].chatCompletion({
apiKey,
temperature: 0,
messages: [
{
obj: ChatRoleEnum.System,
value: `服务端逻辑生成器.根据用户输入的需求,拆解成 laf 云函数实现的步骤,只返回步骤,按格式返回步骤: 1.\n2.\n3.\n ......
下面是一些例子:
一个 hello world 例子
1. 返回字符串: "hello world"
计算圆的面积
1. 从 body 中获取半径 radius.
2. 校验 radius 是否为有效的数字.
3. 计算圆的面积.
4. 返回圆的面积: {area}
实现一个手机号发生注册验证码方法.
1. 从 query 中获取 phone.
2. 校验手机号格式是否正确,不正确则返回错误原因:手机号格式错误.
3. 给 phone 发送一个短信验证码,验证码长度为6位字符串,内容为:你正在注册laf,验证码为:code.
4. 数据库添加数据,表为"codes",内容为 {phone, code}.
实现一个云函数,使用手机号注册账号,需要验证手机验证码.
1. 从 body 中获取 phone 和 code.
2. 校验手机号格式是否正确,不正确则返回错误原因:手机号格式错误.
2. 获取数据库数据,表为"codes",查找是否有符合 phone, code 等于body参数的记录,没有的话返回错误原因:验证码不正确.
4. 添加数据库数据,表为"users" ,内容为{phone, code, createTime}.
5. 删除数据库数据,删除 code 记录.
6. 返回新建用户的Id: return {userId}
更新博客记录。传入blogId,blogText,tags,还需要记录更新的时间.
1. 从 body 中获取 blogId,blogText 和 tags.
2. 校验 blogId 是否为空,为空则返回错误原因:博客ID不能为空.
3. 校验 blogText 是否为空,为空则返回错误原因:博客内容不能为空.
4. 校验 tags 是否为数组,不是则返回错误原因:标签必须为数组.
5. 获取当前时间,记录为 updateTime.
6. 更新数据库数据,表为"blogs",更新符合 blogId 的记录的内容为{blogText, tags, updateTime}.
7. 返回结果 "更新博客记录成功"`
},
{
obj: ChatRoleEnum.Human,
value: prompt.value
}
],
stream: false
});
prompt.value += ` ${resolveText}`;
console.log('prompt resolve success, time:', `${(Date.now() - startTime) / 1000}s`);
// 读取对话内容
const prompts = [prompt];
// 获取向量匹配到的提示词
const { searchPrompts } = await searchKb({
similarity: ModelVectorSearchModeMap[model.chat.searchMode]?.similarity,
prompts,
model,
userId
});
prompts.splice(prompts.length - 1, 0, ...searchPrompts);
// 计算温度
const temperature = (modelConstantsData.maxTemperature * (model.chat.temperature / 10)).toFixed(
2
);
// 发出请求
const { streamResponse, responseMessages, responseText, totalTokens } =
await modelServiceToolMap[model.chat.chatModel].chatCompletion({
apiKey,
temperature: +temperature,
messages: prompts,
stream: isStream
});
console.log('api response time:', `${(Date.now() - startTime) / 1000}s`);
let textLen = resolveText.length;
let tokens = resolveTokens;
if (isStream) {
step = 1;
const { finishMessages, totalTokens } = await resStreamResponse({
model: model.chat.chatModel,
res,
chatResponse: streamResponse,
prompts
});
textLen += finishMessages.map((item) => item.value).join('').length;
tokens += totalTokens;
} else {
textLen += responseMessages.map((item) => item.value).join('').length;
tokens += totalTokens;
jsonRes(res, {
data: responseText
});
}
console.log('laf gpt done. time:', `${(Date.now() - startTime) / 1000}s`);
pushChatBill({
isPay: true,
chatModel: model.chat.chatModel,
userId,
textLen,
tokens
});
} catch (err: any) {
if (step === 1) {
// 直接结束流
console.log('error结束');
res.end();
} else {
res.status(500);
jsonRes(res, {
code: 500,
error: err
});
}
}
}

View File

@@ -2,8 +2,9 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authToken } from '@/service/utils/auth';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
let { dataId } = req.query as {
dataId: string;
@@ -28,4 +29,4 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
error: err
});
}
}
});

View File

@@ -6,8 +6,9 @@ import { authToken } from '@/service/utils/auth';
import { generateVector } from '@/service/events/generateVector';
import { PgClient } from '@/service/pg';
import { authKb } from '@/service/utils/auth';
import { withNextCors } from '@/service/utils/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const {
kbId,
@@ -89,4 +90,4 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
error: err
});
}
}
});

View File

@@ -4,8 +4,9 @@ import { authToken } from '@/service/utils/auth';
import { ModelDataStatusEnum } from '@/constants/model';
import { generateVector } from '@/service/events/generateVector';
import { PgClient } from '@/service/pg';
import { withNextCors } from '@/service/utils/tools';
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
const { dataId, a, q } = req.body as { dataId: string; a: string; q?: string };
@@ -39,4 +40,4 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
error: err
});
}
}
});

View File

@@ -6,9 +6,10 @@ import { generateVector } from '@/service/events/generateVector';
import { generateQA } from '@/service/events/generateQA';
import { PgClient } from '@/service/pg';
import { SplitTextTypEnum } from '@/constants/plugin';
import { withNextCors } from '@/service/utils/tools';
/* split text */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { chunks, kbId, prompt, mode } = req.body as {
kbId: string;
@@ -62,7 +63,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
error: err
});
}
}
});
export const config = {
api: {

View File

@@ -8,7 +8,7 @@ import { PgClient } from '@/service/pg';
import { SplitDataSchema } from '@/types/mongoSchema';
import { modelServiceToolMap } from '../utils/chat';
import { ChatRoleEnum } from '@/constants/chat';
import { getErrMessage } from '../utils/tools';
import { getErrText } from '@/utils/tools';
export async function generateQA(next = false): Promise<any> {
if (process.env.queueTask !== '1') {
@@ -56,7 +56,7 @@ export async function generateQA(next = false): Promise<any> {
// 余额不够了, 清空该记录
await SplitData.findByIdAndUpdate(dataItem._id, {
textList: [],
errorText: getErrMessage(err, '获取 OpenAi Key 失败')
errorText: getErrText(err, '获取 OpenAi Key 失败')
});
generateQA(true);
return;

View File

@@ -2,7 +2,7 @@ import { openaiCreateEmbedding } from '../utils/chat/openai';
import { getApiKey } from '../utils/auth';
import { openaiError2 } from '../errorCode';
import { PgClient } from '@/service/pg';
import { getErrMessage } from '../utils/tools';
import { getErrText } from '@/utils/tools';
export async function generateVector(next = false): Promise<any> {
if (process.env.queueTask !== '1') {
@@ -51,7 +51,7 @@ export async function generateVector(next = false): Promise<any> {
where: [['id', dataId]]
});
generateVector(true);
getErrMessage(err, '获取 OpenAi Key 失败');
getErrText(err, '获取 OpenAi Key 失败');
return;
}

View File

@@ -1,4 +1,5 @@
import type { NextApiResponse } from 'next';
import type { NextApiResponse, NextApiHandler, NextApiRequest } from 'next';
import NextCors from 'nextjs-cors';
import crypto from 'crypto';
import jwt from 'jsonwebtoken';
@@ -37,11 +38,19 @@ export const axiosConfig = () => ({
}
});
/**
* get error message
*/
export const getErrMessage = (err: any, defaultMsg = ''): string => {
const msg = typeof err === 'string' ? err : err?.message || defaultMsg || '';
msg && console.log('error =>', msg);
return msg;
};
export function withNextCors(handler: NextApiHandler): NextApiHandler {
return async function nextApiHandlerWrappedWithNextCors(
req: NextApiRequest,
res: NextApiResponse
) {
const methods = ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'];
const origin = req.headers.origin;
await NextCors(req, res, {
methods,
origin: origin,
optionsSuccessStatus: 200
});
return handler(req, res);
};
}

View File

@@ -122,5 +122,7 @@ export const formatLinkText = (text: string) => {
};
export const getErrText = (err: any, def = '') => {
return typeof err === 'string' ? err : err?.message || def;
const msg = typeof err === 'string' ? err : err?.message || def || '';
msg && console.log('error =>', msg);
return msg;
};