feat: user openai account

This commit is contained in:
archer 2023-07-28 13:29:06 +08:00
parent dfda5285bd
commit fb8635a951
No known key found for this signature in database
GPG Key ID: 569A5660D2379E28
9 changed files with 75 additions and 39 deletions

View File

@ -32,7 +32,8 @@ const OpenAIAccountModal = ({
<MyModal isOpen onClose={onClose} title={t('user.OpenAI Account Setting')}> <MyModal isOpen onClose={onClose} title={t('user.OpenAI Account Setting')}>
<ModalBody> <ModalBody>
<Box fontSize={'sm'} color={'myGray.500'}> <Box fontSize={'sm'} color={'myGray.500'}>
API Openai Chat API
</Box> </Box>
<Flex alignItems={'center'} mt={5}> <Flex alignItems={'center'} mt={5}>
<Box flex={'0 0 65px'}>API Key:</Box> <Box flex={'0 0 65px'}>API Key:</Box>

View File

@ -42,13 +42,14 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
await connectToDatabase(); await connectToDatabase();
/* user auth */ /* user auth */
const { userId } = await authUser({ req }); const { userId, user } = await authUser({ req, authBalance: true });
/* start process */ /* start process */
const { responseData } = await dispatchModules({ const { responseData } = await dispatchModules({
res, res,
modules: modules, modules: modules,
variables, variables,
user,
params: { params: {
history: gptMessage2ChatType(history), history: gptMessage2ChatType(history),
userChatInput: prompt userChatInput: prompt

View File

@ -24,6 +24,8 @@ import { AppModuleItemType, RunningModuleItemType } from '@/types/app';
import { pushTaskBill } from '@/service/events/pushBill'; import { pushTaskBill } from '@/service/events/pushBill';
import { BillSourceEnum } from '@/constants/user'; import { BillSourceEnum } from '@/constants/user';
import { ChatHistoryItemResType } from '@/types/chat'; import { ChatHistoryItemResType } from '@/types/chat';
import { UserModelSchema } from '@/types/mongoSchema';
import { getAIChatApi } from '@/service/ai/openai';
export type MessageItemType = ChatCompletionRequestMessage & { _id?: string }; export type MessageItemType = ChatCompletionRequestMessage & { _id?: string };
type FastGptWebChatProps = { type FastGptWebChatProps = {
@ -69,6 +71,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
/* user auth */ /* user auth */
const { const {
user,
userId, userId,
appId: authAppid, appId: authAppid,
authType authType
@ -76,7 +79,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
? authShareChat({ ? authShareChat({
shareId shareId
}) })
: authUser({ req })); : authUser({ req, authBalance: true }));
if (!user) {
throw new Error('Account is error');
}
if (authType !== 'token') {
user.openaiAccount = undefined;
}
appId = appId ? appId : authAppid; appId = appId ? appId : authAppid;
if (!appId) { if (!appId) {
@ -108,6 +118,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
const { responseData, answerText } = await dispatchModules({ const { responseData, answerText } = await dispatchModules({
res, res,
modules: app.modules, modules: app.modules,
user,
variables, variables,
params: { params: {
history: prompts, history: prompts,
@ -182,7 +193,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
responseData, responseData,
id: chatId || '', id: chatId || '',
model: '', model: '',
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }, usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 1 },
choices: [ choices: [
{ {
message: [{ role: 'assistant', content: answerText }], message: [{ role: 'assistant', content: answerText }],
@ -217,12 +228,14 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
export async function dispatchModules({ export async function dispatchModules({
res, res,
modules, modules,
user,
params = {}, params = {},
variables = {}, variables = {},
stream = false stream = false
}: { }: {
res: NextApiResponse; res: NextApiResponse;
modules: AppModuleItemType[]; modules: AppModuleItemType[];
user?: UserModelSchema;
params?: Record<string, any>; params?: Record<string, any>;
variables?: Record<string, any>; variables?: Record<string, any>;
stream?: boolean; stream?: boolean;
@ -304,6 +317,7 @@ export async function dispatchModules({
const props: Record<string, any> = { const props: Record<string, any> = {
res, res,
stream, stream,
userOpenaiAccount: user?.openaiAccount,
...params ...params
}; };

View File

@ -5,12 +5,12 @@ import { User } from '@/service/models/user';
import { connectToDatabase } from '@/service/mongo'; import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth'; import { authUser } from '@/service/utils/auth';
import { UserUpdateParams } from '@/types/user'; import { UserUpdateParams } from '@/types/user';
import { getAIChatApi, openaiBaseUrl } from '@/service/ai/openai'; import { axiosConfig, getAIChatApi, openaiBaseUrl } from '@/service/ai/openai';
/* 更新一些基本信息 */ /* update user info */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) { export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try { try {
let { avatar, openaiAccount } = req.body as UserUpdateParams; const { avatar, openaiAccount } = req.body as UserUpdateParams;
const { userId } = await authUser({ req, authToken: true }); const { userId } = await authUser({ req, authToken: true });
@ -19,17 +19,21 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
// auth key // auth key
if (openaiAccount?.key) { if (openaiAccount?.key) {
console.log('auth user openai key', openaiAccount?.key); console.log('auth user openai key', openaiAccount?.key);
const baseUrl = openaiAccount?.baseUrl || openaiBaseUrl;
openaiAccount.baseUrl = baseUrl;
const chatAPI = getAIChatApi({ const chatAPI = getAIChatApi(openaiAccount);
base: openaiAccount?.baseUrl || openaiBaseUrl,
apikey: openaiAccount?.key
});
const response = await chatAPI.createChatCompletion({ const response = await chatAPI.createChatCompletion(
model: 'gpt-3.5-turbo', {
max_tokens: 1, model: 'gpt-3.5-turbo',
messages: [{ role: 'user', content: 'hi' }] max_tokens: 1,
}); messages: [{ role: 'user', content: 'hi' }]
},
{
...axiosConfig(openaiAccount)
}
);
if (!response?.data?.choices?.[0]?.message?.content) { if (!response?.data?.choices?.[0]?.message?.content) {
throw new Error(JSON.stringify(response?.data)); throw new Error(JSON.stringify(response?.data));
} }
@ -42,7 +46,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
}, },
{ {
...(avatar && { avatar }), ...(avatar && { avatar }),
...(openaiAccount && { openaiAccount }) openaiAccount: openaiAccount?.key ? openaiAccount : null
} }
); );

View File

@ -1,3 +1,4 @@
import { UserModelSchema } from '@/types/mongoSchema';
import { Configuration, OpenAIApi } from 'openai'; import { Configuration, OpenAIApi } from 'openai';
export const openaiBaseUrl = 'https://api.openai.com/v1'; export const openaiBaseUrl = 'https://api.openai.com/v1';
@ -5,22 +6,22 @@ export const baseUrl = process.env.ONEAPI_URL || process.env.OPENAI_BASE_URL ||
export const systemAIChatKey = process.env.ONEAPI_KEY || process.env.OPENAIKEY || ''; export const systemAIChatKey = process.env.ONEAPI_KEY || process.env.OPENAIKEY || '';
export const getAIChatApi = (props?: { base?: string; apikey?: string }) => { export const getAIChatApi = (props?: UserModelSchema['openaiAccount']) => {
return new OpenAIApi( return new OpenAIApi(
new Configuration({ new Configuration({
basePath: props?.base || baseUrl, basePath: props?.baseUrl || baseUrl,
apiKey: props?.apikey || systemAIChatKey apiKey: props?.key || systemAIChatKey
}) })
); );
}; };
/* openai axios config */ /* openai axios config */
export const axiosConfig = (props?: { base?: string; apikey?: string }) => { export const axiosConfig = (props?: UserModelSchema['openaiAccount']) => {
return { return {
baseURL: props?.base || baseUrl, // 此处仅对非 npm 模块有效 baseURL: props?.baseUrl || baseUrl, // 此处仅对非 npm 模块有效
httpsAgent: global.httpsAgent, httpsAgent: global.httpsAgent,
headers: { headers: {
Authorization: `Bearer ${props?.apikey || systemAIChatKey}`, Authorization: `Bearer ${props?.key || systemAIChatKey}`,
auth: process.env.OPENAI_BASE_URL_AUTH || '' auth: process.env.OPENAI_BASE_URL_AUTH || ''
} }
}; };

View File

@ -19,7 +19,7 @@ export const pushTaskBill = async ({
shareId?: string; shareId?: string;
response: ChatHistoryItemResType[]; response: ChatHistoryItemResType[];
}) => { }) => {
const total = response.reduce((sum, item) => sum + item.price, 0); const total = response.reduce((sum, item) => sum + item.price, 0) || 1;
await Promise.allSettled([ await Promise.allSettled([
Bill.create({ Bill.create({

View File

@ -5,12 +5,15 @@ import { ChatModuleEnum, ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/c
import { getAIChatApi, axiosConfig } from '@/service/ai/openai'; import { getAIChatApi, axiosConfig } from '@/service/ai/openai';
import type { ClassifyQuestionAgentItemType } from '@/types/app'; import type { ClassifyQuestionAgentItemType } from '@/types/app';
import { countModelPrice } from '@/service/events/pushBill'; import { countModelPrice } from '@/service/events/pushBill';
import { UserModelSchema } from '@/types/mongoSchema';
import { getModel } from '@/service/utils/data';
export type CQProps = { export type CQProps = {
systemPrompt?: string; systemPrompt?: string;
history?: ChatItemType[]; history?: ChatItemType[];
userChatInput: string; userChatInput: string;
agents: ClassifyQuestionAgentItemType[]; agents: ClassifyQuestionAgentItemType[];
userOpenaiAccount: UserModelSchema['openaiAccount'];
}; };
export type CQResponse = { export type CQResponse = {
[TaskResponseKeyEnum.responseData]: ChatHistoryItemResType; [TaskResponseKeyEnum.responseData]: ChatHistoryItemResType;
@ -23,7 +26,7 @@ const maxTokens = 2000;
/* request openai chat */ /* request openai chat */
export const dispatchClassifyQuestion = async (props: Record<string, any>): Promise<CQResponse> => { export const dispatchClassifyQuestion = async (props: Record<string, any>): Promise<CQResponse> => {
const { agents, systemPrompt, history = [], userChatInput } = props as CQProps; const { agents, systemPrompt, history = [], userChatInput, userOpenaiAccount } = props as CQProps;
const messages: ChatItemType[] = [ const messages: ChatItemType[] = [
...(systemPrompt ...(systemPrompt
@ -63,7 +66,7 @@ export const dispatchClassifyQuestion = async (props: Record<string, any>): Prom
required: ['type'] required: ['type']
} }
}; };
const chatAPI = getAIChatApi(); const chatAPI = getAIChatApi(userOpenaiAccount);
const response = await chatAPI.createChatCompletion( const response = await chatAPI.createChatCompletion(
{ {
@ -74,7 +77,7 @@ export const dispatchClassifyQuestion = async (props: Record<string, any>): Prom
functions: [agentFunction] functions: [agentFunction]
}, },
{ {
...axiosConfig() ...axiosConfig(userOpenaiAccount)
} }
); );
@ -88,8 +91,8 @@ export const dispatchClassifyQuestion = async (props: Record<string, any>): Prom
[result.key]: 1, [result.key]: 1,
[TaskResponseKeyEnum.responseData]: { [TaskResponseKeyEnum.responseData]: {
moduleName: ChatModuleEnum.CQ, moduleName: ChatModuleEnum.CQ,
price: countModelPrice({ model: agentModel, tokens }), price: userOpenaiAccount?.key ? 0 : countModelPrice({ model: agentModel, tokens }),
model: agentModel, model: getModel(agentModel)?.name || agentModel,
tokens, tokens,
cqList: agents, cqList: agents,
cqResult: result.value cqResult: result.value

View File

@ -14,6 +14,7 @@ import { TaskResponseKeyEnum } from '@/constants/chat';
import { getChatModel } from '@/service/utils/data'; import { getChatModel } from '@/service/utils/data';
import { countModelPrice } from '@/service/events/pushBill'; import { countModelPrice } from '@/service/events/pushBill';
import { ChatModelItemType } from '@/types/model'; import { ChatModelItemType } from '@/types/model';
import { UserModelSchema } from '@/types/mongoSchema';
export type ChatProps = { export type ChatProps = {
res: NextApiResponse; res: NextApiResponse;
@ -26,6 +27,7 @@ export type ChatProps = {
quoteQA?: QuoteItemType[]; quoteQA?: QuoteItemType[];
systemPrompt?: string; systemPrompt?: string;
limitPrompt?: string; limitPrompt?: string;
userOpenaiAccount: UserModelSchema['openaiAccount'];
}; };
export type ChatResponse = { export type ChatResponse = {
[TaskResponseKeyEnum.answerText]: string; [TaskResponseKeyEnum.answerText]: string;
@ -45,7 +47,8 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
quoteQA = [], quoteQA = [],
userChatInput, userChatInput,
systemPrompt = '', systemPrompt = '',
limitPrompt = '' limitPrompt = '',
userOpenaiAccount
} = props as ChatProps; } = props as ChatProps;
// temperature adapt // temperature adapt
@ -77,7 +80,7 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
// FastGpt temperature range: 1~10 // FastGpt temperature range: 1~10
temperature = +(modelConstantsData.maxTemperature * (temperature / 10)).toFixed(2); temperature = +(modelConstantsData.maxTemperature * (temperature / 10)).toFixed(2);
temperature = Math.max(temperature, 0.01); temperature = Math.max(temperature, 0.01);
const chatAPI = getAIChatApi(); const chatAPI = getAIChatApi(userOpenaiAccount);
const response = await chatAPI.createChatCompletion( const response = await chatAPI.createChatCompletion(
{ {
@ -92,7 +95,7 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
{ {
timeout: stream ? 60000 : 480000, timeout: stream ? 60000 : 480000,
responseType: stream ? 'stream' : 'json', responseType: stream ? 'stream' : 'json',
...axiosConfig() ...axiosConfig(userOpenaiAccount)
} }
); );
@ -136,7 +139,7 @@ export const dispatchChatCompletion = async (props: Record<string, any>): Promis
[TaskResponseKeyEnum.answerText]: answerText, [TaskResponseKeyEnum.answerText]: answerText,
[TaskResponseKeyEnum.responseData]: { [TaskResponseKeyEnum.responseData]: {
moduleName: ChatModuleEnum.AIChat, moduleName: ChatModuleEnum.AIChat,
price: countModelPrice({ model, tokens: totalTokens }), price: userOpenaiAccount?.key ? 0 : countModelPrice({ model, tokens: totalTokens }),
model: modelConstantsData.name, model: modelConstantsData.name,
tokens: totalTokens, tokens: totalTokens,
question: userChatInput, question: userChatInput,

View File

@ -2,7 +2,7 @@ import type { NextApiRequest } from 'next';
import jwt from 'jsonwebtoken'; import jwt from 'jsonwebtoken';
import Cookie from 'cookie'; import Cookie from 'cookie';
import { App, OpenApi, User, OutLink, KB } from '../mongo'; import { App, OpenApi, User, OutLink, KB } from '../mongo';
import type { AppSchema } from '@/types/mongoSchema'; import type { AppSchema, UserModelSchema } from '@/types/mongoSchema';
import { formatPrice } from '@/utils/user'; import { formatPrice } from '@/utils/user';
import { ERROR_ENUM } from '../errorCode'; import { ERROR_ENUM } from '../errorCode';
@ -37,7 +37,7 @@ export const authBalanceByUid = async (uid: string) => {
return Promise.reject(ERROR_ENUM.unAuthorization); return Promise.reject(ERROR_ENUM.unAuthorization);
} }
if (!user.openaiKey && formatPrice(user.balance) <= 0) { if (user.balance <= 0) {
return Promise.reject(ERROR_ENUM.insufficientQuota); return Promise.reject(ERROR_ENUM.insufficientQuota);
} }
return user; return user;
@ -151,14 +151,17 @@ export const authUser = async ({
} }
// balance check // balance check
if (authBalance) { const user = await (() => {
await authBalanceByUid(uid); if (authBalance) {
} return authBalanceByUid(uid);
}
})();
return { return {
userId: uid, userId: uid,
appId, appId,
authType authType,
user
}; };
}; };
@ -217,7 +220,13 @@ export const authShareChat = async ({ shareId }: { shareId: string }) => {
return Promise.reject('分享链接已失效'); return Promise.reject('分享链接已失效');
} }
const uid = String(shareChat.userId);
// authBalance
const user = await authBalanceByUid(uid);
return { return {
user,
userId: String(shareChat.userId), userId: String(shareChat.userId),
appId: String(shareChat.appId), appId: String(shareChat.appId),
authType: 'token' as AuthType authType: 'token' as AuthType