mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-29 01:40:51 +00:00
doc gpt V0.2
This commit is contained in:
5
src/service/constants/training.ts
Normal file
5
src/service/constants/training.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export enum OpenAiTuneStatusEnum {
|
||||
cancelled = 'cancelled',
|
||||
succeeded = 'succeeded',
|
||||
pending = 'pending'
|
||||
}
|
3
src/service/errorCode.ts
Normal file
3
src/service/errorCode.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export const openaiError: Record<string, string> = {
|
||||
context_length_exceeded: '内容超出长度'
|
||||
};
|
24
src/service/models/authCode.ts
Normal file
24
src/service/models/authCode.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { Schema, model, models } from 'mongoose';
|
||||
|
||||
const AuthCodeSchema = new Schema({
|
||||
email: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
code: {
|
||||
type: String,
|
||||
required: true,
|
||||
length: 6
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
enum: ['register', 'findPassword'],
|
||||
required: true
|
||||
},
|
||||
expiredTime: {
|
||||
type: Number,
|
||||
default: () => Date.now() + 5 * 60 * 1000
|
||||
}
|
||||
});
|
||||
|
||||
export const AuthCode = models['auth_code'] || model('auth_code', AuthCodeSchema);
|
26
src/service/models/chat.ts
Normal file
26
src/service/models/chat.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { Schema, model, models } from 'mongoose';
|
||||
|
||||
const ChatSchema = new Schema({
|
||||
userId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user',
|
||||
required: true
|
||||
},
|
||||
modelId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'model',
|
||||
required: true
|
||||
},
|
||||
expiredTime: {
|
||||
// 过期时间
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
loadAmount: {
|
||||
// 剩余加载次数
|
||||
type: Number,
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
export const Chat = models['chat'] || model('chat', ChatSchema);
|
28
src/service/models/chatWindow.ts
Normal file
28
src/service/models/chatWindow.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Schema, model, models } from 'mongoose';
|
||||
|
||||
const ChatWindowSchema = new Schema({
|
||||
chatId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'chat',
|
||||
required: true
|
||||
},
|
||||
updateTime: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
content: [
|
||||
{
|
||||
obj: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['Human', 'AI', 'SYSTEM']
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export const ChatWindow = models['chatWindow'] || model('chatWindow', ChatWindowSchema);
|
86
src/service/models/model.ts
Normal file
86
src/service/models/model.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Schema, model, models } from 'mongoose';
|
||||
|
||||
const ModelSchema = new Schema({
|
||||
name: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
avatar: {
|
||||
type: String,
|
||||
default: '/imgs/modelAvatar.png'
|
||||
},
|
||||
systemPrompt: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
userId: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user',
|
||||
required: true
|
||||
},
|
||||
status: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['waiting', 'running', 'training', 'closed']
|
||||
},
|
||||
updateTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
},
|
||||
trainingTimes: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
service: {
|
||||
company: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['openai']
|
||||
},
|
||||
trainId: {
|
||||
// 训练时需要的 ID
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
chatModel: {
|
||||
// 聊天时使用的模型
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
modelName: {
|
||||
// 底层模型的名称
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
security: {
|
||||
type: {
|
||||
domain: {
|
||||
type: [String],
|
||||
default: ['*']
|
||||
},
|
||||
contextMaxLen: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
contentMaxLen: {
|
||||
type: Number,
|
||||
default: 4000
|
||||
},
|
||||
expiredTime: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
set: (val: number) => val * (60 * 60 * 1000)
|
||||
},
|
||||
maxLoadAmount: {
|
||||
// 负数代表不限制
|
||||
type: Number,
|
||||
default: -1
|
||||
}
|
||||
},
|
||||
default: {},
|
||||
required: true
|
||||
}
|
||||
});
|
||||
|
||||
export const Model = models['model'] || model('model', ModelSchema);
|
28
src/service/models/training.ts
Normal file
28
src/service/models/training.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Schema, model, models } from 'mongoose';
|
||||
|
||||
const TrainingSChema = new Schema({
|
||||
serviceName: {
|
||||
// 模型厂商名
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
tuneId: {
|
||||
// 微调进程 ID
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
modelId: {
|
||||
// 关联模型的 ID
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'model',
|
||||
required: true
|
||||
},
|
||||
status: {
|
||||
// 状态值
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['pending', 'succeed', 'errored', 'canceled']
|
||||
}
|
||||
});
|
||||
|
||||
export const Training = models['training'] || model('training', TrainingSChema);
|
40
src/service/models/user.ts
Normal file
40
src/service/models/user.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Schema, model, models } from 'mongoose';
|
||||
import { hashPassword } from '@/service/utils/tools';
|
||||
|
||||
const UserSchema = new Schema({
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true // 唯一
|
||||
},
|
||||
password: {
|
||||
type: String,
|
||||
required: true,
|
||||
set: (val: string) => hashPassword(val),
|
||||
get: (val: string) => hashPassword(val),
|
||||
select: false
|
||||
},
|
||||
balance: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
accounts: [
|
||||
{
|
||||
type: {
|
||||
type: String,
|
||||
required: true,
|
||||
enum: ['openai'] // 定义允许的type
|
||||
},
|
||||
value: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
}
|
||||
],
|
||||
createTime: {
|
||||
type: Date,
|
||||
default: () => new Date()
|
||||
}
|
||||
});
|
||||
|
||||
export const User = models['user'] || model('user', UserSchema);
|
23
src/service/mongo.ts
Normal file
23
src/service/mongo.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import mongoose from 'mongoose';
|
||||
import type { Mongoose } from 'mongoose';
|
||||
|
||||
let cachedClient: Mongoose;
|
||||
|
||||
export async function connectToDatabase() {
|
||||
if (cachedClient && cachedClient.connection.readyState === 1) {
|
||||
return cachedClient;
|
||||
}
|
||||
|
||||
cachedClient = await mongoose.connect(process.env.MONGODB_UR as string, {
|
||||
dbName: 'doc_gpt'
|
||||
});
|
||||
|
||||
return cachedClient;
|
||||
}
|
||||
|
||||
export * from './models/authCode';
|
||||
export * from './models/chat';
|
||||
export * from './models/model';
|
||||
export * from './models/user';
|
||||
export * from './models/training';
|
||||
export * from './models/chatWindow';
|
21
src/service/preChatStore.ts
Normal file
21
src/service/preChatStore.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { ChatItemType } from '../types/chat';
|
||||
|
||||
export const chatWindows = new Map<string, ChatItemType[]>();
|
||||
|
||||
/**
|
||||
* 获取聊天窗口信息
|
||||
*/
|
||||
export const getWindowMessages = (id: string) => {
|
||||
return chatWindows.get(id) || [];
|
||||
};
|
||||
|
||||
export const pushWindowMessage = (id: string, prompt: ChatItemType) => {
|
||||
const messages = chatWindows.get(id) || [];
|
||||
messages.push(prompt);
|
||||
chatWindows.set(id, messages);
|
||||
return messages;
|
||||
};
|
||||
|
||||
export const deleteWindow = (id: string) => {
|
||||
chatWindows.delete(id);
|
||||
};
|
36
src/service/response.ts
Normal file
36
src/service/response.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { NextApiResponse } from 'next';
|
||||
import { openaiError } from './errorCode';
|
||||
|
||||
export interface ResponseType<T = any> {
|
||||
code: number;
|
||||
message: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export const jsonRes = (
|
||||
res: NextApiResponse,
|
||||
props?: {
|
||||
code?: number;
|
||||
message?: string;
|
||||
data?: any;
|
||||
error?: any;
|
||||
}
|
||||
) => {
|
||||
const { code = 200, message = '', data = null, error } = props || {};
|
||||
|
||||
let msg = message;
|
||||
if ((code < 200 || code >= 400) && !message) {
|
||||
msg =
|
||||
typeof error === 'string'
|
||||
? error
|
||||
: openaiError[error?.response?.data?.message] || error?.message || '请求错误';
|
||||
|
||||
console.log(msg);
|
||||
}
|
||||
|
||||
res.json({
|
||||
code,
|
||||
message: msg,
|
||||
data
|
||||
});
|
||||
};
|
45
src/service/utils/chat.ts
Normal file
45
src/service/utils/chat.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Configuration, OpenAIApi } from 'openai';
|
||||
import { Chat } from '../mongo';
|
||||
|
||||
export const getOpenAIApi = (apiKey: string) => {
|
||||
const configuration = new Configuration({
|
||||
apiKey
|
||||
});
|
||||
|
||||
return new OpenAIApi(configuration, undefined);
|
||||
};
|
||||
|
||||
export const authChat = async (chatId: string) => {
|
||||
// 获取 chat 数据
|
||||
const chat = await Chat.findById(chatId)
|
||||
.populate({
|
||||
path: 'modelId',
|
||||
options: {
|
||||
strictPopulate: false
|
||||
}
|
||||
})
|
||||
.populate({
|
||||
path: 'userId',
|
||||
options: {
|
||||
strictPopulate: false
|
||||
}
|
||||
});
|
||||
|
||||
if (!chat || !chat.modelId || !chat.userId) {
|
||||
return Promise.reject('聊天已过期');
|
||||
}
|
||||
|
||||
// 获取 user 的 apiKey
|
||||
const user = chat.userId;
|
||||
|
||||
const userApiKey = user.accounts?.find((item: any) => item.type === 'openai')?.value;
|
||||
|
||||
if (!userApiKey) {
|
||||
return Promise.reject('该用户缺少ApiKey, 无法请求');
|
||||
}
|
||||
|
||||
return {
|
||||
userApiKey,
|
||||
chat
|
||||
};
|
||||
};
|
63
src/service/utils/sendEmail.ts
Normal file
63
src/service/utils/sendEmail.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import * as nodemailer from 'nodemailer';
|
||||
import { EmailTypeEnum } from '@/constants/common';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
const myEmail = process.env.MY_MAIL;
|
||||
let mailTransport = nodemailer.createTransport({
|
||||
// host: 'smtp.qq.email',
|
||||
service: 'qq',
|
||||
secure: true, //安全方式发送,建议都加上
|
||||
auth: {
|
||||
user: myEmail,
|
||||
pass: process.env.MAILE_CODE
|
||||
}
|
||||
});
|
||||
|
||||
const emailMap: { [key: string]: any } = {
|
||||
[EmailTypeEnum.register]: {
|
||||
subject: '注册 DocGPT 账号',
|
||||
html: (code: string) => `<div>您正在注册 DocGPT 账号,验证码为:${code}</div>`
|
||||
},
|
||||
[EmailTypeEnum.findPassword]: {
|
||||
subject: '修改 DocGPT 密码',
|
||||
html: (code: string) => `<div>您正在修改 DocGPT 账号密码,验证码为:${code}</div>`
|
||||
}
|
||||
};
|
||||
|
||||
export const sendCode = (email: string, code: string, type: `${EmailTypeEnum}`) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
from: `"DocGPT" ${myEmail}`,
|
||||
to: email,
|
||||
subject: emailMap[type]?.subject,
|
||||
html: emailMap[type]?.html(code)
|
||||
};
|
||||
mailTransport.sendMail(options, function (err, msg) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
reject('邮箱异常');
|
||||
} else {
|
||||
resolve('');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const sendTrainSucceed = (email: string, modelName: string) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const options = {
|
||||
from: `"DocGPT" ${myEmail}`,
|
||||
to: email,
|
||||
subject: '模型训练完成通知',
|
||||
html: `你的模型 ${modelName} 已于 ${dayjs().format('YYYY-MM-DD HH:mm')} 训练完成!`
|
||||
};
|
||||
mailTransport.sendMail(options, function (err, msg) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
reject('邮箱异常');
|
||||
} else {
|
||||
resolve('');
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
63
src/service/utils/tools.ts
Normal file
63
src/service/utils/tools.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import crypto from 'crypto';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { User } from '../models/user';
|
||||
import tunnel from 'tunnel';
|
||||
|
||||
/* 密码加密 */
|
||||
export const hashPassword = (psw: string) => {
|
||||
return crypto.createHash('sha256').update(psw).digest('hex');
|
||||
};
|
||||
|
||||
/* 生成 token */
|
||||
export const generateToken = (userId: string) => {
|
||||
const key = process.env.TOKEN_KEY as string;
|
||||
const token = jwt.sign(
|
||||
{
|
||||
userId,
|
||||
exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24 * 7
|
||||
},
|
||||
key
|
||||
);
|
||||
return token;
|
||||
};
|
||||
|
||||
/* 校验 token */
|
||||
export const authToken = (token: string): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const key = process.env.TOKEN_KEY as string;
|
||||
|
||||
jwt.verify(token, key, function (err, decoded: any) {
|
||||
if (err || !decoded?.userId) {
|
||||
reject('凭证无效');
|
||||
return;
|
||||
}
|
||||
resolve(decoded.userId);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/* 获取用户的 openai APIkey */
|
||||
export const getUserOpenaiKey = async (userId: string) => {
|
||||
const user = await User.findById(userId);
|
||||
|
||||
const userApiKey = user?.accounts?.find((item: any) => item.type === 'openai')?.value;
|
||||
if (!userApiKey) {
|
||||
return Promise.reject('缺少ApiKey, 无法请求');
|
||||
}
|
||||
|
||||
return Promise.resolve(userApiKey);
|
||||
};
|
||||
|
||||
/* 代理 */
|
||||
export const openaiProxy: any =
|
||||
process.env.AXIOS_PROXY_PORT && process.env.AXIOS_PROXY_HOST
|
||||
? {
|
||||
httpsAgent: tunnel.httpsOverHttp({
|
||||
proxy: {
|
||||
host: process.env.AXIOS_PROXY_HOST,
|
||||
port: +process.env.AXIOS_PROXY_PORT
|
||||
}
|
||||
}),
|
||||
proxy: false
|
||||
}
|
||||
: undefined;
|
Reference in New Issue
Block a user