mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 21:13:50 +00:00
feat: app module
This commit is contained in:
@@ -12,7 +12,7 @@ export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps)
|
||||
new Promise<ChatResponseType & { responseText: string; errMsg: string }>(
|
||||
async (resolve, reject) => {
|
||||
try {
|
||||
const response = await window.fetch('/api/openapi/v1/chat/completions', {
|
||||
const response = await window.fetch('/api/openapi/v1/chat/test', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
@@ -74,8 +74,9 @@ export const streamFetch = ({ data, onMessage, abortSignal }: StreamFetchProps)
|
||||
responseText += answer;
|
||||
} else if (item.event === sseResponseEventEnum.chatResponse) {
|
||||
const chatResponse = data as ChatResponseType;
|
||||
newChatId = chatResponse.newChatId;
|
||||
quoteLen = chatResponse.quoteLen || 0;
|
||||
newChatId =
|
||||
chatResponse.newChatId !== undefined ? chatResponse.newChatId : newChatId;
|
||||
quoteLen = chatResponse.quoteLen !== undefined ? chatResponse.quoteLen : quoteLen;
|
||||
} else if (item.event === sseResponseEventEnum.error) {
|
||||
errMsg = getErrText(data, '流响应错误');
|
||||
}
|
||||
|
886
client/src/constants/app.ts
Normal file
886
client/src/constants/app.ts
Normal file
@@ -0,0 +1,886 @@
|
||||
import type { ModuleItemCommonType, ModuleItemType, AppItemType } from '@/types/app';
|
||||
|
||||
/* flow module */
|
||||
export enum ModuleInputItemTypeEnum {
|
||||
system = 'system',
|
||||
numberInput = 'numberInput',
|
||||
select = 'select',
|
||||
slider = 'slider'
|
||||
}
|
||||
export enum ModulesInputItemTypeEnum {
|
||||
system = 'system'
|
||||
}
|
||||
|
||||
export const HistoryInputModule: ModuleItemCommonType = {
|
||||
key: 'history',
|
||||
label: '聊天记录',
|
||||
description: '',
|
||||
formType: ModuleInputItemTypeEnum.system
|
||||
};
|
||||
export const UserInputModule: ModuleItemCommonType = {
|
||||
key: 'userChatInput',
|
||||
label: '用户输入',
|
||||
description: '',
|
||||
formType: ModuleInputItemTypeEnum.system
|
||||
};
|
||||
|
||||
export const UserChatInputModule: ModuleItemType = {
|
||||
moduleId: '',
|
||||
avatar: '/imgs/logo.png',
|
||||
name: '用户问题输入',
|
||||
description: '',
|
||||
url: '',
|
||||
body: [],
|
||||
inputs: [UserInputModule],
|
||||
outputs: [
|
||||
{
|
||||
key: 'chatInput',
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const HistoryModule: ModuleItemType = {
|
||||
moduleId: '',
|
||||
avatar: '/imgs/logo.png',
|
||||
name: '聊天记录',
|
||||
description: '',
|
||||
url: '/openapi/chat/getHistory',
|
||||
body: [
|
||||
{
|
||||
key: 'historyLen',
|
||||
label: '最大记录数',
|
||||
formType: ModuleInputItemTypeEnum.numberInput,
|
||||
placeholder: '',
|
||||
max: 30,
|
||||
min: 0,
|
||||
default: 10
|
||||
}
|
||||
],
|
||||
inputs: [
|
||||
{
|
||||
key: 'chatId',
|
||||
label: '聊天框ID',
|
||||
formType: ModuleInputItemTypeEnum.system
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'history',
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const OpenAIChatModule: ModuleItemType = {
|
||||
moduleId: '',
|
||||
avatar: '/imgs/logo.png',
|
||||
name: 'GPT 对话',
|
||||
description: '',
|
||||
url: '/openapi/chat/completion',
|
||||
body: [
|
||||
{
|
||||
key: 'model',
|
||||
label: '模型',
|
||||
formType: ModuleInputItemTypeEnum.select,
|
||||
placeholder: '',
|
||||
enum: [
|
||||
{ label: 'Gpt35-4k', value: 'gpt-3.5-turbo' },
|
||||
{ label: 'Gpt35-16k', value: 'gpt-3.5-turbo-16k' },
|
||||
{ label: 'Gpt4', value: 'gpt-4' }
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
label: '温度',
|
||||
formType: ModuleInputItemTypeEnum.slider,
|
||||
enum: [
|
||||
{ label: '严谨', value: 0 },
|
||||
{ label: '发散', value: 10 }
|
||||
],
|
||||
max: 10,
|
||||
min: 0
|
||||
},
|
||||
{
|
||||
key: 'maxToken',
|
||||
label: '回复上限',
|
||||
formType: ModuleInputItemTypeEnum.slider,
|
||||
enum: [
|
||||
{ label: '严谨', value: 0 },
|
||||
{ label: '发散', value: 10 }
|
||||
],
|
||||
max: 10,
|
||||
min: 0
|
||||
}
|
||||
],
|
||||
inputs: [HistoryInputModule, UserInputModule],
|
||||
outputs: [
|
||||
{
|
||||
key: 'history',
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'jsonRes',
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
/* app */
|
||||
export enum AppModuleItemTypeEnum {
|
||||
'http' = 'http', // send a http request
|
||||
'switch' = 'switch', // one input and two outputs
|
||||
'answer' = 'answer' // redirect response
|
||||
}
|
||||
export enum SystemInputEnum {
|
||||
'start' = 'start', // a trigger switch
|
||||
'history' = 'history',
|
||||
'userChatInput' = 'userChatInput'
|
||||
}
|
||||
export enum SpecificInputEnum {
|
||||
'answerText' = 'answerText' // answer module text key
|
||||
}
|
||||
|
||||
export const answerModule = ({ id, defaultText }: { id: string; defaultText?: string }) => ({
|
||||
moduleId: id,
|
||||
type: AppModuleItemTypeEnum.answer,
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: SpecificInputEnum.answerText,
|
||||
value: defaultText
|
||||
},
|
||||
...(defaultText !== undefined
|
||||
? [
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
}
|
||||
]
|
||||
: [])
|
||||
],
|
||||
outputs: []
|
||||
});
|
||||
|
||||
export const chatAppDemo: AppItemType = {
|
||||
id: 'chat',
|
||||
// 标记字段
|
||||
modules: [
|
||||
{
|
||||
moduleId: '1',
|
||||
type: AppModuleItemTypeEnum.http,
|
||||
url: '/openapi/modules/chat/gpt',
|
||||
body: {
|
||||
model: 'gpt-3.5-turbo-16k',
|
||||
temperature: 5,
|
||||
maxToken: 4000
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'answer',
|
||||
answer: true,
|
||||
value: undefined,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const kbChatAppDemo: AppItemType = {
|
||||
id: 'kbchat',
|
||||
// 标记字段
|
||||
modules: [
|
||||
{
|
||||
moduleId: '1',
|
||||
type: 'http',
|
||||
url: '/openapi/modules/kb/search',
|
||||
body: {
|
||||
kb_ids: ['646627f4f7b896cfd8910e38'],
|
||||
similarity: 0.82,
|
||||
limit: 2,
|
||||
maxToken: 2500
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'rawSearch',
|
||||
value: undefined,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'isEmpty',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: '4',
|
||||
key: 'switch'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
response: true,
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: '2',
|
||||
key: 'quotePrompt'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: '4',
|
||||
type: 'switch',
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: 'switch',
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'true',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: '3',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'false',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: '2',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: '2',
|
||||
type: 'http',
|
||||
url: '/openapi/modules/chat/gpt',
|
||||
body: {
|
||||
model: 'gpt-3.5-turbo-16k',
|
||||
temperature: 5,
|
||||
maxToken: 4000,
|
||||
systemPrompt: '知识库是关于电影玲芽之旅的介绍。',
|
||||
limitPrompt: '你仅回答关于电影《玲芽之旅的问题》'
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'answer',
|
||||
value: undefined,
|
||||
answer: true,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
},
|
||||
answerModule({ id: '3', defaultText: '你好,我可以回答你关于电影《玲芽之旅》的问题。' })
|
||||
]
|
||||
};
|
||||
|
||||
export const classifyQuestionDemo: AppItemType = {
|
||||
id: 'classifyQuestionDemo',
|
||||
// 标记字段
|
||||
modules: [
|
||||
{
|
||||
moduleId: '1',
|
||||
type: AppModuleItemTypeEnum.http,
|
||||
url: '/openapi/modules/agent/classifyQuestion',
|
||||
body: {
|
||||
systemPrompt:
|
||||
'laf 一个云函数开发平台,提供了基于 Node 的 serveless 的快速开发和部署。是一个集「函数计算」、「数据库」、「对象存储」等于一身的一站式开发平台。支持云函数、云数据库、在线编程 IDE、触发器、云存储和静态网站托管等功能。',
|
||||
agents: [
|
||||
{
|
||||
desc: '打招呼、问候、身份询问等问题',
|
||||
key: 'a'
|
||||
},
|
||||
{
|
||||
desc: "询问 'laf 使用和介绍的问题'",
|
||||
key: 'b'
|
||||
},
|
||||
{
|
||||
desc: "询问 'laf 代码问题'",
|
||||
key: 'c'
|
||||
},
|
||||
{
|
||||
desc: '其他问题',
|
||||
key: 'd'
|
||||
}
|
||||
]
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'a',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'a',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'b',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'b',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'c',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'c',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'd',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'd',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'a',
|
||||
type: 'answer',
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: SpecificInputEnum.answerText,
|
||||
value: '你好,我是 Laf 助手,有什么可以帮助你的?'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
},
|
||||
// laf 知识库
|
||||
{
|
||||
moduleId: 'b',
|
||||
type: 'http',
|
||||
url: '/openapi/modules/kb/search',
|
||||
body: {
|
||||
kb_ids: ['646627f4f7b896cfd8910e24'],
|
||||
similarity: 0.82,
|
||||
limit: 4,
|
||||
maxToken: 2500
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'rawSearch',
|
||||
value: undefined,
|
||||
response: true,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'lafchat',
|
||||
key: 'quotePrompt'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
// laf 对话
|
||||
{
|
||||
moduleId: 'lafchat',
|
||||
type: 'http',
|
||||
url: '/openapi/modules/chat/gpt',
|
||||
body: {
|
||||
model: 'gpt-3.5-turbo-16k',
|
||||
temperature: 5,
|
||||
maxToken: 4000,
|
||||
systemPrompt: '知识库是关于 Laf 的内容。',
|
||||
limitPrompt: '你仅能参考知识库的内容回答问题,不能超出知识库范围。'
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'answer',
|
||||
answer: true,
|
||||
value: undefined,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
},
|
||||
// laf 代码知识库
|
||||
{
|
||||
moduleId: 'c',
|
||||
type: 'http',
|
||||
url: '/openapi/modules/kb/search',
|
||||
body: {
|
||||
kb_ids: ['646627f4f7b896cfd8910e26'],
|
||||
similarity: 0.8,
|
||||
limit: 4,
|
||||
maxToken: 2500
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'rawSearch',
|
||||
value: undefined,
|
||||
response: true,
|
||||
targets: []
|
||||
},
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'lafcodechat',
|
||||
key: 'quotePrompt'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
// laf代码对话
|
||||
{
|
||||
moduleId: 'lafcodechat',
|
||||
type: 'http',
|
||||
url: '/openapi/modules/chat/gpt',
|
||||
body: {
|
||||
model: 'gpt-3.5-turbo-16k',
|
||||
temperature: 5,
|
||||
maxToken: 4000,
|
||||
systemPrompt: `下例是laf结构\n~~~ts\nimport cloud from '@lafjs/cloud'\nexport default async function(ctx: FunctionContext){\nreturn \"success\"\n};\n~~~\n下例是@lafjs/cloud的api\n~~~\ncloud.fetch//完全等同axios\ncloud.database()// 获取操作数据库实例,和mongo语法相似.\ncloud.getToken(payload)//获取token\ncloud.parseToken(token)//解析token\n// 下面是持久化缓存Api\ncloud.shared.set(key,val); //设置缓存,仅能设置值,无法设置过期时间\ncloud.shared.get(key);\ncloud.shared.has(key); \ncloud.shared.delete(key); \ncloud.shared.clear(); \n~~~\n下例是ctx对象\n~~~\nctx.requestId\nctx.method\nctx.headers//请求的 headers, ctx.headers.get('Content-Type')获取Content-Type的值\nctx.user//Http Bearer Token 认证时,获取token值\nctx.query\nctx.body\nctx.request//同express的Request\nctx.response//同express的Response\nctx.socket/WebSocket 实例\nctx.files//上传的文件 (File对象数组)\nctx.env//自定义的环境变量\n~~~\n下例是数据库获取数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {minMemory} = ctx.query\nconst _ = db.command;\nconst {data: users,total} = collection(\"users\")\n .where({//条件查询\n category: \"computer\",\n type: {\n memory: _gt(minMemory), \n }\n }) \n .skip(10)//跳过10条-分页时使用\n .limit(10)//仅返回10条\n .orderBy(\"name\", \"asc\") \n .orderBy(\"age\", \"desc\")\n .field({age:true,name: false})//返回age不返回name\n}\nconst {data:user} = db.where({phone:req.body.phone}).getOne()//获取一个满足条件的用户\nreturn {users,total}\n~~~\n下例是数据库添加数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext) {\n const {username} = ctx.body\n const {id:userId, ok} = await collection(\"users\")\n .add({\n username, \n })\n if(ok) return {userId}\n return {code:500,message:\"失败\"}\n}\n~~~\n下例是数据库更新数据\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {id} = req.query\n//id直接修改\nawait collection(\"user\").doc(\"id\").update({\n name: \"Hey\",\n});\n//批量更新\nawait collection\n .where({name:\"1234\"})\n .update({\n age:18\n })\nconst _ = db.command;\nawait collection(\"user\")\n .doc(id)\n .set({\n count: _.inc(1)\n count: _.mul(2)\n count: _.remove()\n users: _.push([\"aaa\", \"bbb\"])\n users: _.push(\"aaa\")\n users: _.pop()\n users: _.unshift()\n users: _.shift()\n })\n}\n~~~\n下例是删除数据库记录\n~~~ts\nconst db = cloud.database();\nexport default async function(ctx: FunctionContext){\nconst {id} = req.query\ncollection(\"user\").doc(id).remove();\n//批量删除\ncollection\n .where({age:18}) \n .remove({multi: true})\nreturn \"success\"\n}\n~~~\n你只需返回 ts 代码块!不需要说明.\n用户的问题与 Laf 代码无关时,你直接回答: \"我不确定,我只会写 Laf 代码。\"`,
|
||||
limitPrompt:
|
||||
'你是由 Laf 团队开发的代码助手,把我的需求用 Laf 代码实现.参考知识库中 Laf 的例子.'
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: 'quotePrompt',
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'answer',
|
||||
answer: true,
|
||||
value: undefined,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'd',
|
||||
type: 'answer',
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: SpecificInputEnum.answerText,
|
||||
value: '你好,我没有理解你的意思,请问你有什么 Laf 相关的问题么?'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
export const lafClassifyQuestionDemo: AppItemType = {
|
||||
id: 'test',
|
||||
// 标记字段
|
||||
modules: [
|
||||
{
|
||||
moduleId: '1',
|
||||
type: AppModuleItemTypeEnum.http,
|
||||
url: '/openapi/modules/agent/classifyQuestion',
|
||||
body: {
|
||||
systemPrompt:
|
||||
'laf 一个云函数开发平台,提供了基于 Node 的 serveless 的快速开发和部署。是一个集「函数计算」、「数据库」、「对象存储」等于一身的一站式开发平台。支持云函数、云数据库、在线编程 IDE、触发器、云存储和静态网站托管等功能。\nsealos是一个 k8s 云平台,可以让用户快速部署云服务。',
|
||||
agents: [
|
||||
{
|
||||
desc: '打招呼、问候、身份询问等问题',
|
||||
key: 'a'
|
||||
},
|
||||
{
|
||||
desc: "询问 'laf 的使用和介绍'",
|
||||
key: 'b'
|
||||
},
|
||||
{
|
||||
desc: "询问 'laf 代码相关问题'",
|
||||
key: 'c'
|
||||
},
|
||||
{
|
||||
desc: "用户希望运行或知道 'laf 代码' 运行结果",
|
||||
key: 'g'
|
||||
},
|
||||
{
|
||||
desc: "询问 'sealos 相关问题'",
|
||||
key: 'd'
|
||||
},
|
||||
{
|
||||
desc: '其他问题',
|
||||
key: 'e'
|
||||
},
|
||||
{
|
||||
desc: '商务类问题',
|
||||
key: 'f'
|
||||
}
|
||||
]
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'a',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'a',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'b',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'b',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'c',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'c',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'd',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'd',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'e',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'e',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'f',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'f',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: 'g',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'g',
|
||||
key: SystemInputEnum.start
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'a',
|
||||
type: 'answer',
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: SpecificInputEnum.answerText,
|
||||
value: '你好,我是 环界云 助手,你有什么 Laf 或者 sealos 的 问题么?'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
},
|
||||
{
|
||||
moduleId: 'b',
|
||||
type: 'answer',
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: SpecificInputEnum.answerText,
|
||||
value: '查询 Laf 通用知识库:xxxxx'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
},
|
||||
{
|
||||
moduleId: 'c',
|
||||
type: 'answer',
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: SpecificInputEnum.answerText,
|
||||
value: '查询 Laf 代码知识库:xxxxx'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
},
|
||||
{
|
||||
moduleId: 'd',
|
||||
type: 'answer',
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: SpecificInputEnum.answerText,
|
||||
value: '查询 sealos 通用知识库: xxxx'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
},
|
||||
{
|
||||
moduleId: 'e',
|
||||
type: 'answer',
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: SpecificInputEnum.answerText,
|
||||
value: '其他问题。回复引导语:xxxx'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
},
|
||||
{
|
||||
moduleId: 'f',
|
||||
type: 'answer',
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: SpecificInputEnum.answerText,
|
||||
value: '商务类问题,联系方式:xxxxx'
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: []
|
||||
},
|
||||
{
|
||||
moduleId: 'g',
|
||||
type: 'http',
|
||||
url: '/openapi/modules/agent/extract',
|
||||
body: {
|
||||
description: '运行 laf 代码',
|
||||
agents: [
|
||||
{
|
||||
desc: '代码内容',
|
||||
key: 'code'
|
||||
}
|
||||
]
|
||||
},
|
||||
inputs: [
|
||||
{
|
||||
key: SystemInputEnum.start,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.history,
|
||||
value: undefined
|
||||
},
|
||||
{
|
||||
key: SystemInputEnum.userChatInput,
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'code',
|
||||
value: undefined,
|
||||
targets: [
|
||||
{
|
||||
moduleId: 'code_run',
|
||||
key: 'code'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
moduleId: 'code_run',
|
||||
type: AppModuleItemTypeEnum.http,
|
||||
url: 'https://v1cde7.laf.run/tess',
|
||||
body: {},
|
||||
inputs: [
|
||||
{
|
||||
key: 'code',
|
||||
value: undefined
|
||||
}
|
||||
],
|
||||
outputs: [
|
||||
{
|
||||
key: 'star',
|
||||
value: undefined,
|
||||
targets: []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
@@ -1,7 +1,9 @@
|
||||
export enum sseResponseEventEnum {
|
||||
error = 'error',
|
||||
answer = 'answer',
|
||||
chatResponse = 'chatResponse'
|
||||
chatResponse = 'chatResponse', //
|
||||
appStreamResponse = 'appStreamResponse', // sse response request
|
||||
moduleFetchResponse = 'moduleFetchResponse' // http module sse response
|
||||
}
|
||||
|
||||
export enum ChatRoleEnum {
|
||||
|
@@ -47,7 +47,7 @@ export async function saveChat({
|
||||
modelId,
|
||||
prompts,
|
||||
userId
|
||||
}: Props & { newChatId?: Types.ObjectId; userId: string }) {
|
||||
}: Props & { newChatId?: Types.ObjectId; userId: string }): Promise<{ newChatId: string }> {
|
||||
await connectToDatabase();
|
||||
const { model } = await authModel({ modelId, userId, authOwner: false });
|
||||
|
||||
@@ -104,6 +104,7 @@ export async function saveChat({
|
||||
]);
|
||||
|
||||
return {
|
||||
...response
|
||||
// @ts-ignore
|
||||
newChatId: response?.newChatId || ''
|
||||
};
|
||||
}
|
||||
|
114
client/src/pages/api/openapi/modules/agent/classifyQuestion.ts
Normal file
114
client/src/pages/api/openapi/modules/agent/classifyQuestion.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
|
||||
import { ChatContextFilter } from '@/service/utils/chat/index';
|
||||
import type { ChatItemType } from '@/types/chat';
|
||||
import { ChatRoleEnum } from '@/constants/chat';
|
||||
import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
|
||||
import type { ClassifyQuestionAgentItemType } from '@/types/app';
|
||||
|
||||
export type Props = {
|
||||
systemPrompt?: string;
|
||||
history?: ChatItemType[];
|
||||
userChatInput: string;
|
||||
agents: ClassifyQuestionAgentItemType[];
|
||||
};
|
||||
export type Response = { history: ChatItemType[] };
|
||||
|
||||
const agentModel = 'gpt-3.5-turbo-16k';
|
||||
const agentFunName = 'agent_user_question';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
let { systemPrompt, agents, history = [], userChatInput } = req.body as Props;
|
||||
|
||||
const response = await classifyQuestion({
|
||||
systemPrompt,
|
||||
history,
|
||||
userChatInput,
|
||||
agents
|
||||
});
|
||||
|
||||
jsonRes(res, {
|
||||
data: response
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* request openai chat */
|
||||
export async function classifyQuestion({
|
||||
agents,
|
||||
systemPrompt,
|
||||
history = [],
|
||||
userChatInput
|
||||
}: Props) {
|
||||
const messages: ChatItemType[] = [
|
||||
...(systemPrompt
|
||||
? [
|
||||
{
|
||||
obj: ChatRoleEnum.System,
|
||||
value: systemPrompt
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: userChatInput
|
||||
}
|
||||
];
|
||||
const filterMessages = ChatContextFilter({
|
||||
// @ts-ignore
|
||||
model: agentModel,
|
||||
prompts: messages,
|
||||
maxTokens: 1500
|
||||
});
|
||||
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
|
||||
|
||||
// function body
|
||||
const agentFunction = {
|
||||
name: agentFunName,
|
||||
description: '严格判断用户问题的类型',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
type: {
|
||||
type: 'string',
|
||||
description: agents.map((item) => `${item.desc},返回: '${item.key}'`).join('; '),
|
||||
enum: agents.map((item) => item.key)
|
||||
}
|
||||
},
|
||||
required: ['type']
|
||||
}
|
||||
};
|
||||
const chatAPI = getOpenAIApi();
|
||||
|
||||
const response = await chatAPI.createChatCompletion(
|
||||
{
|
||||
model: agentModel,
|
||||
temperature: 0,
|
||||
messages: [...adaptMessages],
|
||||
function_call: { name: agentFunName },
|
||||
functions: [agentFunction]
|
||||
},
|
||||
{
|
||||
...axiosConfig()
|
||||
}
|
||||
);
|
||||
|
||||
const arg = JSON.parse(response.data.choices?.[0]?.message?.function_call?.arguments || '');
|
||||
|
||||
if (!arg.type) {
|
||||
throw new Error('');
|
||||
}
|
||||
console.log(adaptMessages, arg.type);
|
||||
|
||||
return {
|
||||
[arg.type]: 1
|
||||
};
|
||||
}
|
97
client/src/pages/api/openapi/modules/agent/extract.ts
Normal file
97
client/src/pages/api/openapi/modules/agent/extract.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { adaptChatItem_openAI } from '@/utils/plugin/openai';
|
||||
import { ChatContextFilter } from '@/service/utils/chat/index';
|
||||
import type { ChatItemType } from '@/types/chat';
|
||||
import { ChatRoleEnum } from '@/constants/chat';
|
||||
import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
|
||||
import type { ClassifyQuestionAgentItemType } from '@/types/app';
|
||||
|
||||
export type Props = {
|
||||
history?: ChatItemType[];
|
||||
userChatInput: string;
|
||||
agents: ClassifyQuestionAgentItemType[];
|
||||
description: string;
|
||||
};
|
||||
export type Response = { history: ChatItemType[] };
|
||||
|
||||
const agentModel = 'gpt-3.5-turbo-16k';
|
||||
const agentFunName = 'agent_extract_data';
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
const response = await extract(req.body);
|
||||
|
||||
jsonRes(res, {
|
||||
data: response
|
||||
});
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* request openai chat */
|
||||
export async function extract({ agents, history = [], userChatInput, description }: Props) {
|
||||
const messages: ChatItemType[] = [
|
||||
...history.slice(-4),
|
||||
{
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: userChatInput
|
||||
}
|
||||
];
|
||||
const filterMessages = ChatContextFilter({
|
||||
// @ts-ignore
|
||||
model: agentModel,
|
||||
prompts: messages,
|
||||
maxTokens: 3000
|
||||
});
|
||||
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
|
||||
|
||||
const properties: Record<
|
||||
string,
|
||||
{
|
||||
type: string;
|
||||
description: string;
|
||||
}
|
||||
> = {};
|
||||
agents.forEach((item) => {
|
||||
properties[item.key] = {
|
||||
type: 'string',
|
||||
description: item.desc
|
||||
};
|
||||
});
|
||||
|
||||
// function body
|
||||
const agentFunction = {
|
||||
name: agentFunName,
|
||||
description,
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties,
|
||||
required: agents.map((item) => item.key)
|
||||
}
|
||||
};
|
||||
|
||||
const chatAPI = getOpenAIApi();
|
||||
|
||||
const response = await chatAPI.createChatCompletion(
|
||||
{
|
||||
model: agentModel,
|
||||
temperature: 0,
|
||||
messages: [...adaptMessages],
|
||||
function_call: { name: agentFunName },
|
||||
functions: [agentFunction]
|
||||
},
|
||||
{
|
||||
...axiosConfig()
|
||||
}
|
||||
);
|
||||
|
||||
const arg = JSON.parse(response.data.choices?.[0]?.message?.function_call?.arguments || '');
|
||||
|
||||
return arg;
|
||||
}
|
257
client/src/pages/api/openapi/modules/chat/gpt.ts
Normal file
257
client/src/pages/api/openapi/modules/chat/gpt.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
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 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';
|
||||
|
||||
export type Props = {
|
||||
model: `${OpenAiChatEnum}`;
|
||||
temperature?: number;
|
||||
maxToken?: number;
|
||||
history?: ChatItemType[];
|
||||
userChatInput: string;
|
||||
stream?: boolean;
|
||||
quotePrompt?: string;
|
||||
systemPrompt?: string;
|
||||
limitPrompt?: string;
|
||||
};
|
||||
export type Response = { history: ChatItemType[] };
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
try {
|
||||
let {
|
||||
model,
|
||||
stream = false,
|
||||
temperature = 0,
|
||||
maxToken = 4000,
|
||||
history = [],
|
||||
quotePrompt,
|
||||
userChatInput,
|
||||
systemPrompt,
|
||||
limitPrompt
|
||||
} = req.body as Props;
|
||||
|
||||
// temperature adapt
|
||||
const modelConstantsData = ChatModelMap[model];
|
||||
// FastGpt temperature range: 1~10
|
||||
temperature = +(modelConstantsData.maxTemperature * (temperature / 10)).toFixed(2);
|
||||
|
||||
const response = await chatCompletion({
|
||||
res,
|
||||
model,
|
||||
temperature,
|
||||
maxToken,
|
||||
stream,
|
||||
history,
|
||||
userChatInput,
|
||||
systemPrompt,
|
||||
limitPrompt,
|
||||
quotePrompt
|
||||
});
|
||||
|
||||
if (stream) {
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.moduleFetchResponse,
|
||||
data: JSON.stringify(response)
|
||||
});
|
||||
res.end();
|
||||
} else {
|
||||
jsonRes(res, {
|
||||
data: response
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* request openai chat */
|
||||
export async function chatCompletion({
|
||||
res,
|
||||
model = OpenAiChatEnum.GPT35,
|
||||
temperature,
|
||||
maxToken = 4000,
|
||||
stream,
|
||||
history = [],
|
||||
quotePrompt,
|
||||
userChatInput,
|
||||
systemPrompt,
|
||||
limitPrompt
|
||||
}: Props & { res: NextApiResponse }) {
|
||||
const messages: ChatItemType[] = [
|
||||
...(quotePrompt
|
||||
? [
|
||||
{
|
||||
obj: ChatRoleEnum.System,
|
||||
value: quotePrompt
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...(systemPrompt
|
||||
? [
|
||||
{
|
||||
obj: ChatRoleEnum.System,
|
||||
value: systemPrompt
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...history,
|
||||
...(limitPrompt
|
||||
? [
|
||||
{
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: limitPrompt
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
obj: ChatRoleEnum.Human,
|
||||
value: userChatInput
|
||||
}
|
||||
];
|
||||
const modelTokenLimit = ChatModelMap[model]?.contextMaxToken || 4000;
|
||||
|
||||
const filterMessages = ChatContextFilter({
|
||||
model,
|
||||
prompts: messages,
|
||||
maxTokens: Math.ceil(modelTokenLimit - 300) // filter token. not response maxToken
|
||||
});
|
||||
|
||||
const adaptMessages = adaptChatItem_openAI({ messages: filterMessages, reserveId: false });
|
||||
const chatAPI = getOpenAIApi();
|
||||
console.log(adaptMessages);
|
||||
|
||||
/* count response max token */
|
||||
const promptsToken = modelToolMap[model].countTokens({
|
||||
messages: filterMessages
|
||||
});
|
||||
maxToken = maxToken + promptsToken > modelTokenLimit ? modelTokenLimit - promptsToken : maxToken;
|
||||
|
||||
const response = await chatAPI.createChatCompletion(
|
||||
{
|
||||
model,
|
||||
temperature: Number(temperature || 0),
|
||||
max_tokens: maxToken,
|
||||
messages: adaptMessages,
|
||||
frequency_penalty: 0.5, // 越大,重复内容越少
|
||||
presence_penalty: -0.5, // 越大,越容易出现新内容
|
||||
stream
|
||||
},
|
||||
{
|
||||
timeout: stream ? 60000 : 480000,
|
||||
responseType: stream ? 'stream' : 'json',
|
||||
...axiosConfig()
|
||||
}
|
||||
);
|
||||
|
||||
const { answer, totalTokens } = await (async () => {
|
||||
if (stream) {
|
||||
// sse response
|
||||
const { answer } = await streamResponse({ res, response });
|
||||
// count tokens
|
||||
const finishMessages = filterMessages.concat({
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: answer
|
||||
});
|
||||
|
||||
const totalTokens = modelToolMap[model].countTokens({
|
||||
messages: finishMessages
|
||||
});
|
||||
|
||||
return {
|
||||
answer,
|
||||
totalTokens
|
||||
};
|
||||
} else {
|
||||
const answer = stream ? '' : response.data.choices?.[0].message?.content || '';
|
||||
const totalTokens = stream ? 0 : response.data.usage?.total_tokens || 0;
|
||||
|
||||
return {
|
||||
answer,
|
||||
totalTokens
|
||||
};
|
||||
}
|
||||
})();
|
||||
|
||||
// count price
|
||||
const unitPrice = ChatModelMap[model]?.price || 3;
|
||||
return {
|
||||
answer
|
||||
};
|
||||
}
|
||||
|
||||
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: null,
|
||||
finish_reason: 'stop'
|
||||
})
|
||||
});
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.answer,
|
||||
data: '[DONE]'
|
||||
});
|
||||
} else {
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text: content
|
||||
})
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
for await (const chunk of response.data as any) {
|
||||
if (res.closed) break;
|
||||
const parse = parseStreamChunk(chunk);
|
||||
parse.forEach((item) => clientRes(item.data));
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('pipe error', error);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
console.log(error);
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
||||
return {
|
||||
answer
|
||||
};
|
||||
}
|
115
client/src/pages/api/openapi/modules/kb/search.ts
Normal file
115
client/src/pages/api/openapi/modules/kb/search.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { jsonRes } from '@/service/response';
|
||||
import { PgClient } from '@/service/pg';
|
||||
import { withNextCors } from '@/service/utils/tools';
|
||||
import type { ChatItemType } from '@/types/chat';
|
||||
import { ChatRoleEnum } from '@/constants/chat';
|
||||
import { openaiEmbedding_system } from '../../plugin/openaiEmbedding';
|
||||
import { modelToolMap } from '@/utils/plugin';
|
||||
|
||||
export type QuoteItemType = {
|
||||
id: string;
|
||||
q: string;
|
||||
a: string;
|
||||
source?: string;
|
||||
};
|
||||
type Props = {
|
||||
kb_ids: string[];
|
||||
history: ChatItemType[];
|
||||
similarity: number;
|
||||
limit: number;
|
||||
maxToken: number;
|
||||
userChatInput: string;
|
||||
stream?: boolean;
|
||||
};
|
||||
type Response = {
|
||||
rawSearch: QuoteItemType[];
|
||||
isEmpty?: boolean;
|
||||
quotePrompt: string;
|
||||
};
|
||||
|
||||
export default withNextCors(async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const {
|
||||
kb_ids = [],
|
||||
history = [],
|
||||
similarity,
|
||||
limit,
|
||||
maxToken,
|
||||
userChatInput
|
||||
} = req.body as Props;
|
||||
|
||||
if (!similarity || !Array.isArray(kb_ids)) {
|
||||
throw new Error('params is error');
|
||||
}
|
||||
|
||||
const result = await appKbSearch({
|
||||
kb_ids,
|
||||
history,
|
||||
similarity,
|
||||
limit,
|
||||
maxToken,
|
||||
userChatInput
|
||||
});
|
||||
|
||||
jsonRes<Response>(res, {
|
||||
data: result
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export async function appKbSearch({
|
||||
kb_ids = [],
|
||||
history = [],
|
||||
similarity = 0.8,
|
||||
limit = 5,
|
||||
maxToken = 2500,
|
||||
userChatInput
|
||||
}: Props): Promise<Response> {
|
||||
// get vector
|
||||
const promptVector = await openaiEmbedding_system({
|
||||
input: [userChatInput]
|
||||
});
|
||||
|
||||
// search kb
|
||||
const res: any = await PgClient.query(
|
||||
`BEGIN;
|
||||
SET LOCAL ivfflat.probes = ${global.systemEnv.pgIvfflatProbe || 10};
|
||||
select id,q,a,source from modelData where kb_id IN (${kb_ids
|
||||
.map((item) => `'${item}'`)
|
||||
.join(',')}) AND vector <#> '[${promptVector[0]}]' < -${similarity} order by vector <#> '[${
|
||||
promptVector[0]
|
||||
}]' limit ${limit};
|
||||
COMMIT;`
|
||||
);
|
||||
|
||||
const searchRes: QuoteItemType[] = res?.[2]?.rows || [];
|
||||
|
||||
// filter part quote by maxToken
|
||||
const sliceResult = modelToolMap['gpt-3.5-turbo']
|
||||
.tokenSlice({
|
||||
maxToken,
|
||||
messages: searchRes.map((item, i) => ({
|
||||
obj: ChatRoleEnum.System,
|
||||
value: `${i + 1}: [${item.q}\n${item.a}]`
|
||||
}))
|
||||
})
|
||||
.map((item) => item.value)
|
||||
.join('\n')
|
||||
.trim();
|
||||
|
||||
// slice filterSearch
|
||||
const rawSearch = searchRes.slice(0, sliceResult.length);
|
||||
|
||||
return {
|
||||
isEmpty: rawSearch.length === 0,
|
||||
rawSearch,
|
||||
quotePrompt: sliceResult ? `知识库:\n${sliceResult}` : ''
|
||||
};
|
||||
}
|
@@ -0,0 +1,4 @@
|
||||
export type Props = {
|
||||
url: string;
|
||||
body: Record<string, any>;
|
||||
};
|
@@ -81,3 +81,35 @@ export async function openaiEmbedding({
|
||||
|
||||
return result.vectors;
|
||||
}
|
||||
|
||||
export async function openaiEmbedding_system({ input }: Props) {
|
||||
const apiKey = getSystemOpenAiKey();
|
||||
|
||||
// 获取 chatAPI
|
||||
const chatAPI = getOpenAIApi(apiKey);
|
||||
|
||||
// 把输入的内容转成向量
|
||||
const result = await chatAPI
|
||||
.createEmbedding(
|
||||
{
|
||||
model: embeddingModel,
|
||||
input
|
||||
},
|
||||
{
|
||||
timeout: 60000,
|
||||
...axiosConfig(apiKey)
|
||||
}
|
||||
)
|
||||
.then((res) => {
|
||||
if (!res.data?.usage?.total_tokens) {
|
||||
// @ts-ignore
|
||||
return Promise.reject(res.data?.error?.message || 'Embedding Error');
|
||||
}
|
||||
return {
|
||||
tokenLen: res.data.usage.total_tokens || 0,
|
||||
vectors: res.data.data.map((item) => item.embedding)
|
||||
};
|
||||
});
|
||||
|
||||
return result.vectors;
|
||||
}
|
||||
|
338
client/src/pages/api/openapi/v1/chat/test.ts
Normal file
338
client/src/pages/api/openapi/v1/chat/test.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
import type { NextApiRequest, NextApiResponse } from 'next';
|
||||
import { connectToDatabase } from '@/service/mongo';
|
||||
import { authUser, authModel, getApiKey, authShareChat } from '@/service/utils/auth';
|
||||
import { sseErrRes, jsonRes } from '@/service/response';
|
||||
import { ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
|
||||
import { withNextCors } from '@/service/utils/tools';
|
||||
import type { CreateChatCompletionRequest } from 'openai';
|
||||
import { gptMessage2ChatType, textAdaptGptResponse } from '@/utils/adapt';
|
||||
import { getChatHistory } from './getHistory';
|
||||
import { saveChat } from '@/pages/api/chat/saveChat';
|
||||
import { sseResponse } from '@/service/utils/tools';
|
||||
import { type ChatCompletionRequestMessage } from 'openai';
|
||||
import {
|
||||
kbChatAppDemo,
|
||||
chatAppDemo,
|
||||
lafClassifyQuestionDemo,
|
||||
classifyQuestionDemo,
|
||||
SpecificInputEnum,
|
||||
AppModuleItemTypeEnum
|
||||
} from '@/constants/app';
|
||||
import { Types } from 'mongoose';
|
||||
import { moduleFetch } from '@/service/api/request';
|
||||
import { AppModuleItemType } from '@/types/app';
|
||||
|
||||
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
|
||||
type FastGptWebChatProps = {
|
||||
chatId?: string; // undefined: nonuse history, '': new chat, 'xxxxx': use history
|
||||
appId?: string;
|
||||
};
|
||||
type FastGptShareChatProps = {
|
||||
password?: string;
|
||||
shareId?: string;
|
||||
};
|
||||
export type Props = CreateChatCompletionRequest &
|
||||
FastGptWebChatProps &
|
||||
FastGptShareChatProps & {
|
||||
messages: MessageItemType[];
|
||||
stream?: boolean;
|
||||
};
|
||||
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();
|
||||
});
|
||||
|
||||
let { chatId, appId, shareId, password = '', stream = false, messages = [] } = req.body as Props;
|
||||
|
||||
try {
|
||||
if (!messages) {
|
||||
throw new Error('Prams Error');
|
||||
}
|
||||
if (!Array.isArray(messages)) {
|
||||
throw new Error('messages is not array');
|
||||
}
|
||||
|
||||
await connectToDatabase();
|
||||
let startTime = Date.now();
|
||||
|
||||
/* user auth */
|
||||
const {
|
||||
userId,
|
||||
appId: authAppid,
|
||||
authType
|
||||
} = await (shareId
|
||||
? authShareChat({
|
||||
shareId,
|
||||
password
|
||||
})
|
||||
: authUser({ req }));
|
||||
|
||||
appId = appId ? appId : authAppid;
|
||||
if (!appId) {
|
||||
throw new Error('appId is empty');
|
||||
}
|
||||
|
||||
// get history
|
||||
const { history } = await getChatHistory({ chatId, userId });
|
||||
const prompts = history.concat(gptMessage2ChatType(messages));
|
||||
if (prompts[prompts.length - 1].obj === 'AI') {
|
||||
prompts.pop();
|
||||
}
|
||||
// user question
|
||||
const prompt = prompts.pop();
|
||||
|
||||
if (!prompt) {
|
||||
throw new Error('Question is empty');
|
||||
}
|
||||
|
||||
/* start process */
|
||||
const modules = JSON.parse(JSON.stringify(classifyQuestionDemo.modules));
|
||||
|
||||
const { responseData, answerText } = await dispatchModules({
|
||||
res,
|
||||
modules,
|
||||
params: {
|
||||
history: prompts,
|
||||
userChatInput: prompt.value
|
||||
},
|
||||
stream
|
||||
});
|
||||
|
||||
// save chat
|
||||
if (typeof chatId === 'string') {
|
||||
const { newChatId } = await saveChat({
|
||||
chatId,
|
||||
modelId: appId,
|
||||
prompts: [
|
||||
prompt,
|
||||
{
|
||||
_id: messages[messages.length - 1]._id,
|
||||
obj: ChatRoleEnum.AI,
|
||||
value: answerText,
|
||||
responseData
|
||||
}
|
||||
],
|
||||
userId
|
||||
});
|
||||
|
||||
if (newChatId) {
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.chatResponse,
|
||||
data: JSON.stringify({
|
||||
newChatId
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (stream) {
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.appStreamResponse,
|
||||
data: JSON.stringify(responseData)
|
||||
});
|
||||
res.end();
|
||||
} else {
|
||||
res.json({
|
||||
data: responseData,
|
||||
id: chatId || '',
|
||||
model: '',
|
||||
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
||||
choices: [
|
||||
{
|
||||
message: [{ role: 'assistant', content: answerText }],
|
||||
finish_reason: 'stop',
|
||||
index: 0
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
} catch (err: any) {
|
||||
if (stream) {
|
||||
res.status(500);
|
||||
sseErrRes(res, err);
|
||||
res.end();
|
||||
} else {
|
||||
jsonRes(res, {
|
||||
code: 500,
|
||||
error: err
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function dispatchModules({
|
||||
res,
|
||||
modules,
|
||||
params = {},
|
||||
stream = false
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
modules: AppModuleItemType[];
|
||||
params?: Record<string, any>;
|
||||
stream?: boolean;
|
||||
}) {
|
||||
let storeData: Record<string, any> = {};
|
||||
let responseData: Record<string, any> = {};
|
||||
let answerText = '';
|
||||
|
||||
function pushStore({
|
||||
isResponse = false,
|
||||
answer,
|
||||
data = {}
|
||||
}: {
|
||||
isResponse?: boolean;
|
||||
answer?: string;
|
||||
data?: Record<string, any>;
|
||||
}) {
|
||||
if (isResponse) {
|
||||
responseData = {
|
||||
...responseData,
|
||||
...data
|
||||
};
|
||||
}
|
||||
|
||||
if (answer) {
|
||||
answerText += answer;
|
||||
}
|
||||
|
||||
storeData = {
|
||||
...storeData,
|
||||
...data
|
||||
};
|
||||
}
|
||||
function moduleInput(module: AppModuleItemType, data: Record<string, any> = {}): Promise<any> {
|
||||
const checkInputFinish = () => {
|
||||
return !module.inputs.find((item: any) => item.value === undefined);
|
||||
};
|
||||
const updateInputValue = (key: string, value: any) => {
|
||||
const index = module.inputs.findIndex((item: any) => item.key === key);
|
||||
if (index === -1) return;
|
||||
module.inputs[index].value = value;
|
||||
};
|
||||
|
||||
return Promise.all(
|
||||
Object.entries(data).map(([key, val]: any) => {
|
||||
updateInputValue(key, val);
|
||||
if (checkInputFinish()) {
|
||||
return moduleRun(module);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
function moduleOutput(module: AppModuleItemType, result: Record<string, any> = {}): Promise<any> {
|
||||
return Promise.all(
|
||||
module.outputs.map((item) => {
|
||||
if (result[item.key] === undefined) return;
|
||||
/* update output value */
|
||||
item.value = result[item.key];
|
||||
|
||||
pushStore({
|
||||
isResponse: item.response,
|
||||
answer: item.answer ? item.value : '',
|
||||
data: {
|
||||
[item.key]: item.value
|
||||
}
|
||||
});
|
||||
|
||||
/* update target */
|
||||
return Promise.all(
|
||||
item.targets.map((target: any) => {
|
||||
// find module
|
||||
const targetModule = modules.find((item) => item.moduleId === target.moduleId);
|
||||
if (!targetModule) return;
|
||||
return moduleInput(targetModule, { [target.key]: item.value });
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
async function moduleRun(module: AppModuleItemType): Promise<any> {
|
||||
console.log('run=========', module.type, module.url);
|
||||
|
||||
if (module.type === AppModuleItemTypeEnum.answer) {
|
||||
pushStore({
|
||||
answer: module.inputs[0].value
|
||||
});
|
||||
return AnswerResponse({
|
||||
res,
|
||||
stream,
|
||||
text: module.inputs.find((item) => item.key === SpecificInputEnum.answerText)?.value
|
||||
});
|
||||
}
|
||||
|
||||
if (module.type === AppModuleItemTypeEnum.switch) {
|
||||
return moduleOutput(module, switchResponse(module));
|
||||
}
|
||||
|
||||
if (module.type === AppModuleItemTypeEnum.http && module.url) {
|
||||
// get fetch params
|
||||
const inputParams: Record<string, any> = {};
|
||||
module.inputs.forEach((item: any) => {
|
||||
inputParams[item.key] = item.value;
|
||||
});
|
||||
const data = {
|
||||
stream,
|
||||
...module.body,
|
||||
...inputParams
|
||||
};
|
||||
|
||||
// response data
|
||||
const fetchRes = await moduleFetch({
|
||||
res,
|
||||
url: module.url,
|
||||
data
|
||||
});
|
||||
|
||||
return moduleOutput(module, fetchRes);
|
||||
}
|
||||
}
|
||||
|
||||
// 从填充 params 开始进入递归
|
||||
await Promise.all(modules.map((module) => moduleInput(module, params)));
|
||||
|
||||
return {
|
||||
responseData,
|
||||
answerText
|
||||
};
|
||||
}
|
||||
|
||||
function AnswerResponse({
|
||||
res,
|
||||
stream = false,
|
||||
text = ''
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
stream?: boolean;
|
||||
text?: '';
|
||||
}) {
|
||||
if (stream) {
|
||||
return sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.answer,
|
||||
data: textAdaptGptResponse({
|
||||
text
|
||||
})
|
||||
});
|
||||
}
|
||||
return text;
|
||||
}
|
||||
function switchResponse(module: any) {
|
||||
const val = module?.inputs?.[0]?.value;
|
||||
|
||||
if (val) {
|
||||
return { true: 1 };
|
||||
}
|
||||
return { false: 1 };
|
||||
}
|
25
client/src/service/ai/openai.ts
Normal file
25
client/src/service/ai/openai.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { Configuration, OpenAIApi } from 'openai';
|
||||
|
||||
export const getSystemOpenAiKey = () => {
|
||||
return process.env.ONEAPI_KEY || '';
|
||||
};
|
||||
|
||||
export const getOpenAIApi = () => {
|
||||
return new OpenAIApi(
|
||||
new Configuration({
|
||||
basePath: process.env.ONEAPI_URL
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
/* openai axios config */
|
||||
export const axiosConfig = () => {
|
||||
return {
|
||||
baseURL: process.env.ONEAPI_URL, // 此处仅对非 npm 模块有效
|
||||
httpsAgent: global.httpsAgent,
|
||||
headers: {
|
||||
Authorization: `Bearer ${getSystemOpenAiKey()}`,
|
||||
auth: process.env.OPENAI_BASE_URL_AUTH || ''
|
||||
}
|
||||
};
|
||||
};
|
@@ -1,122 +1,79 @@
|
||||
import axios, { Method, InternalAxiosRequestConfig, AxiosResponse } from 'axios';
|
||||
import { sseResponseEventEnum } from '@/constants/chat';
|
||||
import { getErrText } from '@/utils/tools';
|
||||
import { parseStreamChunk } from '@/utils/adapt';
|
||||
import { NextApiResponse } from 'next';
|
||||
import { sseResponse } from '../utils/tools';
|
||||
|
||||
interface ConfigType {
|
||||
headers?: { [key: string]: string };
|
||||
hold?: boolean;
|
||||
}
|
||||
interface ResponseDataType {
|
||||
code: number;
|
||||
message: string;
|
||||
data: any;
|
||||
interface Props {
|
||||
res: NextApiResponse; // 用于流转发
|
||||
url: string;
|
||||
data: Record<string, any>;
|
||||
}
|
||||
export const moduleFetch = ({ url, data, res }: Props) =>
|
||||
new Promise<Record<string, any>>(async (resolve, reject) => {
|
||||
try {
|
||||
const baseUrl = `http://localhost:3000/api`;
|
||||
const requestUrl = url.startsWith('/') ? `${baseUrl}${url}` : url;
|
||||
const response = await fetch(requestUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
/**
|
||||
* 请求开始
|
||||
*/
|
||||
function requestStart(config: InternalAxiosRequestConfig): InternalAxiosRequestConfig {
|
||||
if (config.headers) {
|
||||
config.headers.rootkey = process.env.ROOT_KEY;
|
||||
}
|
||||
if (!response?.body) {
|
||||
throw new Error('Request Error');
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
const responseType = response.headers.get('content-type');
|
||||
if (responseType && responseType.includes('application/json')) {
|
||||
const jsonResponse = await response.json();
|
||||
return resolve(jsonResponse?.data || {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求成功,检查请求头
|
||||
*/
|
||||
function responseSuccess(response: AxiosResponse<ResponseDataType>) {
|
||||
return response;
|
||||
}
|
||||
/**
|
||||
* 响应数据检查
|
||||
*/
|
||||
function checkRes(data: ResponseDataType) {
|
||||
if (data === undefined) {
|
||||
return Promise.reject('服务器异常');
|
||||
} else if (data.code < 200 || data.code >= 400) {
|
||||
return Promise.reject(data);
|
||||
}
|
||||
return data.data;
|
||||
}
|
||||
const reader = response.body?.getReader();
|
||||
|
||||
/**
|
||||
* 响应错误
|
||||
*/
|
||||
function responseError(err: any) {
|
||||
if (!err) {
|
||||
return Promise.reject({ message: '未知错误' });
|
||||
}
|
||||
if (typeof err === 'string') {
|
||||
return Promise.reject({ message: err });
|
||||
}
|
||||
return Promise.reject(err);
|
||||
}
|
||||
let chatResponse = {};
|
||||
|
||||
/* 创建请求实例 */
|
||||
export const instance = axios.create({
|
||||
timeout: 60000, // 超时时间
|
||||
baseURL: `http://localhost:${process.env.PORT || 3000}/api`,
|
||||
headers: {
|
||||
rootkey: process.env.ROOT_KEY
|
||||
}
|
||||
});
|
||||
const read = async () => {
|
||||
try {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
return resolve(chatResponse);
|
||||
}
|
||||
const chunkResponse = parseStreamChunk(value);
|
||||
|
||||
/* 请求拦截 */
|
||||
instance.interceptors.request.use(requestStart, (err) => Promise.reject(err));
|
||||
/* 响应拦截 */
|
||||
instance.interceptors.response.use(responseSuccess, (err) => Promise.reject(err));
|
||||
|
||||
function request(url: string, data: any, config: ConfigType, method: Method): any {
|
||||
/* 去空 */
|
||||
for (const key in data) {
|
||||
if (data[key] === null || data[key] === undefined) {
|
||||
delete data[key];
|
||||
chunkResponse.forEach((item) => {
|
||||
// parse json data
|
||||
const data = (() => {
|
||||
try {
|
||||
return JSON.parse(item.data);
|
||||
} catch (error) {
|
||||
return {};
|
||||
}
|
||||
})();
|
||||
if (item.event === sseResponseEventEnum.moduleFetchResponse) {
|
||||
chatResponse = {
|
||||
...chatResponse,
|
||||
...data
|
||||
};
|
||||
} else if (item.event === sseResponseEventEnum.answer && data?.choices?.[0]?.delta) {
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.answer,
|
||||
data: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
});
|
||||
read();
|
||||
} catch (err: any) {
|
||||
reject(getErrText(err, '请求异常'));
|
||||
}
|
||||
};
|
||||
read();
|
||||
} catch (err: any) {
|
||||
console.log(err);
|
||||
reject(getErrText(err, '请求异常'));
|
||||
}
|
||||
}
|
||||
|
||||
return instance
|
||||
.request({
|
||||
url,
|
||||
method,
|
||||
data: method === 'GET' ? null : data,
|
||||
params: method === 'GET' ? data : null, // get请求不携带data,params放在url上
|
||||
...config // 用户自定义配置,可以覆盖前面的配置
|
||||
})
|
||||
.then((res) => checkRes(res.data))
|
||||
.catch((err) => responseError(err));
|
||||
}
|
||||
|
||||
/**
|
||||
* api请求方式
|
||||
* @param {String} url
|
||||
* @param {Any} params
|
||||
* @param {Object} config
|
||||
* @returns
|
||||
*/
|
||||
export function GET<T = { data: any }>(
|
||||
url: string,
|
||||
params = {},
|
||||
config: ConfigType = {}
|
||||
): Promise<T> {
|
||||
return request(url, params, config, 'GET');
|
||||
}
|
||||
|
||||
export function POST<T = { data: any }>(
|
||||
url: string,
|
||||
data = {},
|
||||
config: ConfigType = {}
|
||||
): Promise<T> {
|
||||
return request(url, data, config, 'POST');
|
||||
}
|
||||
|
||||
export function PUT<T = { data: any }>(
|
||||
url: string,
|
||||
data = {},
|
||||
config: ConfigType = {}
|
||||
): Promise<T> {
|
||||
return request(url, data, config, 'PUT');
|
||||
}
|
||||
|
||||
export function DELETE<T = { data: any }>(url: string, config: ConfigType = {}): Promise<T> {
|
||||
return request(url, {}, config, 'DELETE');
|
||||
}
|
||||
});
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import { sseResponseEventEnum } from '@/constants/chat';
|
||||
import { NextApiResponse } from 'next';
|
||||
import {
|
||||
openaiError,
|
||||
@@ -6,7 +7,7 @@ import {
|
||||
ERROR_RESPONSE,
|
||||
ERROR_ENUM
|
||||
} from './errorCode';
|
||||
import { clearCookie } from './utils/tools';
|
||||
import { clearCookie, sseResponse } from './utils/tools';
|
||||
|
||||
export interface ResponseType<T = any> {
|
||||
code: number;
|
||||
@@ -61,3 +62,41 @@ export const jsonRes = <T = any>(
|
||||
data: data !== undefined ? data : null
|
||||
});
|
||||
};
|
||||
|
||||
export const sseErrRes = (res: NextApiResponse, error: any) => {
|
||||
const errResponseKey = typeof error === 'string' ? error : error?.message;
|
||||
|
||||
// Specified error
|
||||
if (ERROR_RESPONSE[errResponseKey]) {
|
||||
// login is expired
|
||||
if (errResponseKey === ERROR_ENUM.unAuthorization) {
|
||||
clearCookie(res);
|
||||
}
|
||||
|
||||
return sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.error,
|
||||
data: JSON.stringify(ERROR_RESPONSE[errResponseKey])
|
||||
});
|
||||
}
|
||||
|
||||
let msg = error?.message || '请求错误';
|
||||
if (typeof error === 'string') {
|
||||
msg = error;
|
||||
} else if (proxyError[error?.code]) {
|
||||
msg = '接口连接异常';
|
||||
} else if (error?.response?.data?.error?.message) {
|
||||
msg = error?.response?.data?.error?.message;
|
||||
} else if (openaiAccountError[error?.response?.data?.error?.code]) {
|
||||
msg = openaiAccountError[error?.response?.data?.error?.code];
|
||||
} else if (openaiError[error?.response?.statusText]) {
|
||||
msg = openaiError[error.response.statusText];
|
||||
}
|
||||
console.log(error);
|
||||
|
||||
sseResponse({
|
||||
res,
|
||||
event: sseResponseEventEnum.error,
|
||||
data: JSON.stringify({ message: msg })
|
||||
});
|
||||
};
|
||||
|
@@ -79,7 +79,7 @@ export const sseResponse = ({
|
||||
data
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
event?: `${sseResponseEventEnum}`;
|
||||
event?: string;
|
||||
data: string;
|
||||
}) => {
|
||||
event && res.write(`event: ${event}\n`);
|
||||
|
69
client/src/types/app.d.ts
vendored
Normal file
69
client/src/types/app.d.ts
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
import { AppModuleItemTypeEnum, ModulesInputItemTypeEnum } from '../constants/app';
|
||||
|
||||
/* input item */
|
||||
export type ModuleItemCommonType = {
|
||||
key: string; // 字段名
|
||||
formType: `${ModuleInputItemTypeEnum}`;
|
||||
label: string;
|
||||
description?: string;
|
||||
placeholder?: string;
|
||||
max?: number;
|
||||
min?: number;
|
||||
default?: any;
|
||||
enum?: { label: string; value: any }[];
|
||||
};
|
||||
|
||||
export type ModuleItemOutputItemType = {
|
||||
key: string;
|
||||
targets: { moduleId: string; key: string }[];
|
||||
};
|
||||
|
||||
export type ModuleItemType = {
|
||||
moduleId: string;
|
||||
avatar: string;
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
body: ModuleItemCommonType[];
|
||||
inputs: ModuleItemCommonType[];
|
||||
outputs: ModuleItemOutputItemType[];
|
||||
};
|
||||
|
||||
/* input item */
|
||||
type FormItemCommonType = {
|
||||
key: string; // 字段名
|
||||
label: string;
|
||||
description: string;
|
||||
formType: `${ModulesInputItemTypeEnum}`;
|
||||
};
|
||||
|
||||
/* agent */
|
||||
/* question classify */
|
||||
export type ClassifyQuestionAgentItemType = {
|
||||
desc: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
/* app module */
|
||||
export type AppModuleItemType = {
|
||||
moduleId: string;
|
||||
type: `${AppModuleItemTypeEnum}`;
|
||||
url?: string;
|
||||
body: Record<string, any>;
|
||||
inputs: { key: string; value: any }[];
|
||||
outputs: {
|
||||
key: string;
|
||||
value?: any;
|
||||
response?: boolean;
|
||||
answer?: boolean; // json response
|
||||
targets: {
|
||||
moduleId: string;
|
||||
key: string;
|
||||
}[];
|
||||
}[];
|
||||
};
|
||||
|
||||
export type AppItemType = {
|
||||
id: string;
|
||||
modules: AppModuleItemType[];
|
||||
};
|
1
client/src/types/chat.d.ts
vendored
1
client/src/types/chat.d.ts
vendored
@@ -11,6 +11,7 @@ export type ChatItemType = {
|
||||
quoteLen?: number;
|
||||
quote?: QuoteItemType[];
|
||||
systemPrompt?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
|
||||
export type ChatSiteItemType = {
|
||||
|
Reference in New Issue
Block a user