diff --git a/client/public/imgs/openai.png b/client/public/imgs/openai.png
new file mode 100644
index 000000000..0f237a226
Binary files /dev/null and b/client/public/imgs/openai.png differ
diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json
index 2aa1112cb..d6c63f0a6 100644
--- a/client/public/locales/en/common.json
+++ b/client/public/locales/en/common.json
@@ -33,10 +33,12 @@
},
"user": {
"Bill Detail": "Bill Detail",
+ "Old password is error": "Old password is error",
+ "OpenAI Account Setting": "OpenAI Account Setting",
"Pay": "Pay",
+ "Set OpenAI Account Failed": "Set OpenAI account failed",
"Update Password": "Update Password",
"Update password failed": "Update password failed",
- "Update password succseful": "Update password succseful",
- "Old password is error": "Old password is error"
+ "Update password succseful": "Update password succseful"
}
}
diff --git a/client/public/locales/zh/common.json b/client/public/locales/zh/common.json
index 14b4636c9..e2b7b0a77 100644
--- a/client/public/locales/zh/common.json
+++ b/client/public/locales/zh/common.json
@@ -33,10 +33,12 @@
},
"user": {
"Bill Detail": "账单详情",
+ "Old password is error": "旧密码错误",
+ "OpenAI Account Setting": "OpenAI 账号配置",
"Pay": "充值",
+ "Set OpenAI Account Failed": "设置 OpenAI 账号异常",
"Update Password": "修改密码",
"Update password failed": "修改密码异常",
- "Update password succseful": "修改密码成功",
- "Old password is error": "旧密码错误"
+ "Update password succseful": "修改密码成功"
}
}
diff --git a/client/src/api/common.ts b/client/src/api/common.ts
deleted file mode 100644
index 4157b1029..000000000
--- a/client/src/api/common.ts
+++ /dev/null
@@ -1 +0,0 @@
-import { GET, POST, DELETE } from './request';
diff --git a/client/src/pages/account/components/Info.tsx b/client/src/pages/account/components/Info.tsx
index bb45dcea7..e8d071be4 100644
--- a/client/src/pages/account/components/Info.tsx
+++ b/client/src/pages/account/components/Info.tsx
@@ -1,10 +1,8 @@
import React, { useCallback } from 'react';
-import { Box, Flex, Button, useDisclosure, useTheme, ModalBody } from '@chakra-ui/react';
+import { Box, Flex, Button, useDisclosure, useTheme, Divider } from '@chakra-ui/react';
import { useForm } from 'react-hook-form';
import { UserUpdateParams } from '@/types/user';
-import { putUserInfo } from '@/api/user';
import { useToast } from '@/hooks/useToast';
-import { useGlobalStore } from '@/store/global';
import { useUserStore } from '@/store/user';
import { UserType } from '@/types/user';
import { useQuery } from '@tanstack/react-query';
@@ -28,13 +26,16 @@ const UpdatePswModal = dynamic(() => import('./UpdatePswModal'), {
loading: () => ,
ssr: false
});
+const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'), {
+ loading: () => ,
+ ssr: false
+});
const UserInfo = () => {
const theme = useTheme();
const { t } = useTranslation();
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
- const { setLoading } = useGlobalStore();
- const { reset, register } = useForm({
+ const { reset } = useForm({
defaultValues: userInfo as UserType
});
@@ -49,41 +50,32 @@ const UserInfo = () => {
onClose: onCloseUpdatePsw,
onOpen: onOpenUpdatePsw
} = useDisclosure();
+ const { isOpen: isOpenOpenai, onClose: onCloseOpenai, onOpen: onOpenOpenai } = useDisclosure();
+
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png',
multiple: false
});
const onclickSave = useCallback(
- async (data: UserUpdateParams) => {
- setLoading(true);
- try {
- await putUserInfo({
- avatar: data.avatar
- });
- updateUserInfo({
- avatar: data.avatar
- });
- reset(data);
- toast({
- title: '更新数据成功',
- status: 'success'
- });
- } catch (error) {
- toast({
- title: getErrText(error),
- status: 'error'
- });
- }
- setLoading(false);
+ async (data: UserType) => {
+ await updateUserInfo({
+ avatar: data.avatar,
+ openaiAccount: data.openaiAccount
+ });
+ reset(data);
+ toast({
+ title: '更新数据成功',
+ status: 'success'
+ });
},
- [reset, setLoading, toast, updateUserInfo]
+ [reset, toast, updateUserInfo]
);
const onSelectFile = useCallback(
async (e: File[]) => {
const file = e[0];
- if (!file) return;
+ if (!file || !userInfo) return;
try {
const src = await compressImg({
file,
@@ -110,6 +102,7 @@ const UserInfo = () => {
reset(res);
}
});
+
return (
{
{feConfigs?.show_userDetail && (
-
-
- 余额:
-
- {userInfo?.balance.toFixed(3)} 元
-
-
-
-
+ <>
+
+
+ 余额:
+
+ {userInfo?.balance.toFixed(3)} 元
+
+
+
+
+
+
+
+
+
+
+
+ OpenAI 账号
+
+
+
+
+ >
)}
{isOpenPayModal && }
{isOpenUpdatePsw && }
+ {isOpenOpenai && userInfo && (
+
+ onclickSave({
+ ...userInfo,
+ openaiAccount: data
+ })
+ }
+ onClose={onCloseOpenai}
+ />
+ )}
);
diff --git a/client/src/pages/account/components/OpenAIAccountModal.tsx b/client/src/pages/account/components/OpenAIAccountModal.tsx
new file mode 100644
index 000000000..174e6ca47
--- /dev/null
+++ b/client/src/pages/account/components/OpenAIAccountModal.tsx
@@ -0,0 +1,62 @@
+import React from 'react';
+import { ModalBody, Box, Flex, Input, ModalFooter, Button } from '@chakra-ui/react';
+import MyModal from '@/components/MyModal';
+import { useTranslation } from 'react-i18next';
+import { useForm } from 'react-hook-form';
+import { useRequest } from '@/hooks/useRequest';
+import { UserType } from '@/types/user';
+
+const OpenAIAccountModal = ({
+ defaultData,
+ onSuccess,
+ onClose
+}: {
+ defaultData: UserType['openaiAccount'];
+ onSuccess: (e: UserType['openaiAccount']) => Promise;
+ onClose: () => void;
+}) => {
+ const { t } = useTranslation();
+ const { register, handleSubmit } = useForm({
+ defaultValues: defaultData
+ });
+
+ const { mutate: onSubmit, isLoading } = useRequest({
+ mutationFn: async (data: UserType['openaiAccount']) => onSuccess(data),
+ onSuccess(res) {
+ onClose();
+ },
+ errorToast: t('user.Set OpenAI Account Failed')
+ });
+
+ return (
+
+
+
+ 如果你填写了该内容,平台上的聊天不会计费(不包含知识库训练和 API 调用)
+
+
+ API Key:
+
+
+
+ BaseUrl:
+
+
+
+
+
+
+
+
+ );
+};
+
+export default OpenAIAccountModal;
diff --git a/client/src/pages/api/admin/withdraw.ts b/client/src/pages/api/admin/withdraw.ts
deleted file mode 100644
index a674dc250..000000000
--- a/client/src/pages/api/admin/withdraw.ts
+++ /dev/null
@@ -1,62 +0,0 @@
-// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
-import type { NextApiRequest, NextApiResponse } from 'next';
-import { jsonRes } from '@/service/response';
-import { authUser } from '@/service/utils/auth';
-import { connectToDatabase, TrainingData, User, promotionRecord } from '@/service/mongo';
-import { TrainingModeEnum } from '@/constants/plugin';
-import mongoose from 'mongoose';
-
-export default async function handler(req: NextApiRequest, res: NextApiResponse) {
- try {
- await authUser({ req, authRoot: true });
- const { amount, userId, type } = req.body as {
- amount: number;
- userId: number;
- type: 'withdraw';
- };
- await connectToDatabase();
-
- if (!userId || !amount || type !== 'withdraw' || amount <= 0) {
- throw new Error('params is error');
- }
-
- // check promotion balance
- const countResidue: { totalAmount: number }[] = await promotionRecord.aggregate([
- { $match: { userId: new mongoose.Types.ObjectId(userId) } },
- {
- $group: {
- _id: null, // 分组条件,这里使用 null 表示不分组
- totalAmount: { $sum: '$amount' } // 计算 amount 字段的总和
- }
- },
- {
- $project: {
- _id: false, // 排除 _id 字段
- totalAmount: true // 只返回 totalAmount 字段
- }
- }
- ]);
-
- const balance = countResidue[0].totalAmount;
-
- if (balance < amount) {
- throw new Error('可提现余额不足');
- }
-
- // add record
- await promotionRecord.create({
- userId,
- type,
- amount: -amount
- });
-
- jsonRes(res, {
- data: balance
- });
- } catch (error) {
- jsonRes(res, {
- code: 500,
- error
- });
- }
-}
diff --git a/client/src/pages/api/openapi/plugin/vector.ts b/client/src/pages/api/openapi/plugin/vector.ts
index bdcc7eee2..0445b4299 100644
--- a/client/src/pages/api/openapi/plugin/vector.ts
+++ b/client/src/pages/api/openapi/plugin/vector.ts
@@ -2,7 +2,7 @@ import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
import { authBalanceByUid, authUser } from '@/service/utils/auth';
import { withNextCors } from '@/service/utils/tools';
-import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
+import { getAIChatApi, axiosConfig } from '@/service/ai/openai';
import { pushGenerateVectorBill } from '@/service/events/pushBill';
type Props = {
@@ -49,7 +49,7 @@ export async function getVector({
}
// 获取 chatAPI
- const chatAPI = getOpenAIApi();
+ const chatAPI = getAIChatApi();
// 把输入的内容转成向量
const result = await chatAPI
diff --git a/client/src/pages/api/openapi/text/sensitiveCheck.ts b/client/src/pages/api/openapi/text/sensitiveCheck.ts
index 12842f3c3..7966aa0b4 100644
--- a/client/src/pages/api/openapi/text/sensitiveCheck.ts
+++ b/client/src/pages/api/openapi/text/sensitiveCheck.ts
@@ -1,7 +1,7 @@
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@/service/response';
-import { authUser, getSystemOpenAiKey } from '@/service/utils/auth';
+import { authUser } from '@/service/utils/auth';
import axios from 'axios';
import { axiosConfig } from '@/service/ai/openai';
diff --git a/client/src/pages/api/user/account/update.ts b/client/src/pages/api/user/account/update.ts
index 397ee56ff..4189c9bc3 100644
--- a/client/src/pages/api/user/account/update.ts
+++ b/client/src/pages/api/user/account/update.ts
@@ -5,22 +5,44 @@ import { User } from '@/service/models/user';
import { connectToDatabase } from '@/service/mongo';
import { authUser } from '@/service/utils/auth';
import { UserUpdateParams } from '@/types/user';
+import { getAIChatApi, openaiBaseUrl } from '@/service/ai/openai';
/* 更新一些基本信息 */
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
- const { avatar } = req.body as UserUpdateParams;
+ let { avatar, openaiAccount } = req.body as UserUpdateParams;
const { userId } = await authUser({ req, authToken: true });
await connectToDatabase();
+
+ // auth key
+ if (openaiAccount?.key) {
+ console.log('auth user openai key', openaiAccount?.key);
+
+ const chatAPI = getAIChatApi({
+ base: openaiAccount?.baseUrl || openaiBaseUrl,
+ apikey: openaiAccount?.key
+ });
+
+ const response = await chatAPI.createChatCompletion({
+ model: 'gpt-3.5-turbo',
+ max_tokens: 1,
+ messages: [{ role: 'user', content: 'hi' }]
+ });
+ if (!response?.data?.choices?.[0]?.message?.content) {
+ throw new Error(JSON.stringify(response?.data));
+ }
+ }
+
// 更新对应的记录
await User.updateOne(
{
_id: userId
},
{
- ...(avatar && { avatar })
+ ...(avatar && { avatar }),
+ ...(openaiAccount && { openaiAccount })
}
);
diff --git a/client/src/pages/app/detail/components/OutLink.tsx b/client/src/pages/app/detail/components/OutLink.tsx
index 26b3393ee..731f9bb4f 100644
--- a/client/src/pages/app/detail/components/OutLink.tsx
+++ b/client/src/pages/app/detail/components/OutLink.tsx
@@ -230,13 +230,13 @@ const OutLink = ({ appId }: { appId: string }) => {
title: '免登录窗口',
desc: '分享链接给其他用户,无需登录即可直接进行使用',
value: LinkTypeEnum.share
- },
- {
- icon: 'outlink_iframe',
- title: '网页嵌入',
- desc: '嵌入到已有网页中,右下角会生成对话按键',
- value: LinkTypeEnum.iframe
}
+ // {
+ // icon: 'outlink_iframe',
+ // title: '网页嵌入',
+ // desc: '嵌入到已有网页中,右下角会生成对话按键',
+ // value: LinkTypeEnum.iframe
+ // }
]}
value={linkType}
onChange={(e) => setLinkType(e as `${LinkTypeEnum}`)}
diff --git a/client/src/service/ai/openai.ts b/client/src/service/ai/openai.ts
index 92481234e..c5beb46e3 100644
--- a/client/src/service/ai/openai.ts
+++ b/client/src/service/ai/openai.ts
@@ -1,27 +1,26 @@
import { Configuration, OpenAIApi } from 'openai';
-const baseUrl =
- process.env.ONEAPI_URL || process.env.OPENAI_BASE_URL || 'https://api.openai.com/v1';
+export const openaiBaseUrl = 'https://api.openai.com/v1';
+export const baseUrl = process.env.ONEAPI_URL || process.env.OPENAI_BASE_URL || openaiBaseUrl;
-export const getSystemOpenAiKey = () => {
- return process.env.ONEAPI_KEY || process.env.OPENAIKEY || '';
-};
+export const systemAIChatKey = process.env.ONEAPI_KEY || process.env.OPENAIKEY || '';
-export const getOpenAIApi = () => {
+export const getAIChatApi = (props?: { base?: string; apikey?: string }) => {
return new OpenAIApi(
new Configuration({
- basePath: baseUrl
+ basePath: props?.base || baseUrl,
+ apiKey: props?.apikey || systemAIChatKey
})
);
};
/* openai axios config */
-export const axiosConfig = () => {
+export const axiosConfig = (props?: { base?: string; apikey?: string }) => {
return {
- baseURL: baseUrl, // 此处仅对非 npm 模块有效
+ baseURL: props?.base || baseUrl, // 此处仅对非 npm 模块有效
httpsAgent: global.httpsAgent,
headers: {
- Authorization: `Bearer ${getSystemOpenAiKey()}`,
+ Authorization: `Bearer ${props?.apikey || systemAIChatKey}`,
auth: process.env.OPENAI_BASE_URL_AUTH || ''
}
};
diff --git a/client/src/service/events/generateQA.ts b/client/src/service/events/generateQA.ts
index d3527b3a0..d9e9413cd 100644
--- a/client/src/service/events/generateQA.ts
+++ b/client/src/service/events/generateQA.ts
@@ -5,7 +5,7 @@ import { TrainingModeEnum } from '@/constants/plugin';
import { ERROR_ENUM } from '../errorCode';
import { sendInform } from '@/pages/api/user/inform/send';
import { authBalanceByUid } from '../utils/auth';
-import { axiosConfig, getOpenAIApi } from '../ai/openai';
+import { axiosConfig, getAIChatApi } from '../ai/openai';
import { ChatCompletionRequestMessage } from 'openai';
import { modelToolMap } from '@/utils/plugin';
import { gptMessage2ChatType } from '@/utils/adapt';
@@ -55,7 +55,7 @@ export async function generateQA(): Promise {
const startTime = Date.now();
- const chatAPI = getOpenAIApi();
+ const chatAPI = getAIChatApi();
// 请求 chatgpt 获取回答
const response = await Promise.all(
diff --git a/client/src/service/models/user.ts b/client/src/service/models/user.ts
index 61db5f1e7..baec31964 100644
--- a/client/src/service/models/user.ts
+++ b/client/src/service/models/user.ts
@@ -35,18 +35,17 @@ const UserSchema = new Schema({
type: Schema.Types.ObjectId,
ref: 'user'
},
- promotion: {
- rate: {
- // 返现比例
- type: Number,
- default: 15
- }
- },
limit: {
exportKbTime: {
// Every half hour
type: Date
}
+ },
+ openaiAccount: {
+ type: {
+ key: String,
+ baseUrl: String
+ }
}
});
diff --git a/client/src/service/moduleDispatch/agent/classifyQuestion.ts b/client/src/service/moduleDispatch/agent/classifyQuestion.ts
index 1d0298d59..5361ea90a 100644
--- a/client/src/service/moduleDispatch/agent/classifyQuestion.ts
+++ b/client/src/service/moduleDispatch/agent/classifyQuestion.ts
@@ -2,7 +2,7 @@ import { adaptChatItem_openAI } from '@/utils/plugin/openai';
import { ChatContextFilter } from '@/service/utils/chat/index';
import type { ChatHistoryItemResType, ChatItemType } from '@/types/chat';
import { ChatModuleEnum, ChatRoleEnum, TaskResponseKeyEnum } from '@/constants/chat';
-import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
+import { getAIChatApi, axiosConfig } from '@/service/ai/openai';
import type { ClassifyQuestionAgentItemType } from '@/types/app';
import { countModelPrice } from '@/service/events/pushBill';
@@ -63,7 +63,7 @@ export const dispatchClassifyQuestion = async (props: Record): Prom
required: ['type']
}
};
- const chatAPI = getOpenAIApi();
+ const chatAPI = getAIChatApi();
const response = await chatAPI.createChatCompletion(
{
diff --git a/client/src/service/moduleDispatch/agent/extract.ts b/client/src/service/moduleDispatch/agent/extract.ts
index 16b52f5ed..c1bfa2c83 100644
--- a/client/src/service/moduleDispatch/agent/extract.ts
+++ b/client/src/service/moduleDispatch/agent/extract.ts
@@ -5,7 +5,7 @@ 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 { getAIChatApi, axiosConfig } from '@/service/ai/openai';
import type { ClassifyQuestionAgentItemType } from '@/types/app';
import { authUser } from '@/service/utils/auth';
@@ -79,7 +79,7 @@ export async function extract({ agents, history = [], userChatInput, description
}
};
- const chatAPI = getOpenAIApi();
+ const chatAPI = getAIChatApi();
const response = await chatAPI.createChatCompletion(
{
diff --git a/client/src/service/moduleDispatch/chat/oneapi.ts b/client/src/service/moduleDispatch/chat/oneapi.ts
index e33986904..5a6160c34 100644
--- a/client/src/service/moduleDispatch/chat/oneapi.ts
+++ b/client/src/service/moduleDispatch/chat/oneapi.ts
@@ -9,7 +9,7 @@ import type { ChatHistoryItemResType } from '@/types/chat';
import { ChatModuleEnum, ChatRoleEnum, sseResponseEventEnum } from '@/constants/chat';
import { SSEParseData, parseStreamChunk } from '@/utils/sse';
import { textAdaptGptResponse } from '@/utils/adapt';
-import { getOpenAIApi, axiosConfig } from '@/service/ai/openai';
+import { getAIChatApi, axiosConfig } from '@/service/ai/openai';
import { TaskResponseKeyEnum } from '@/constants/chat';
import { getChatModel } from '@/service/utils/data';
import { countModelPrice } from '@/service/events/pushBill';
@@ -77,7 +77,7 @@ export const dispatchChatCompletion = async (props: Record): Promis
// FastGpt temperature range: 1~10
temperature = +(modelConstantsData.maxTemperature * (temperature / 10)).toFixed(2);
temperature = Math.max(temperature, 0.01);
- const chatAPI = getOpenAIApi();
+ const chatAPI = getAIChatApi();
const response = await chatAPI.createChatCompletion(
{
diff --git a/client/src/service/utils/auth.ts b/client/src/service/utils/auth.ts
index 73123d1e0..4457865ff 100644
--- a/client/src/service/utils/auth.ts
+++ b/client/src/service/utils/auth.ts
@@ -162,11 +162,6 @@ export const authUser = async ({
};
};
-/* random get openai api key */
-export const getSystemOpenAiKey = () => {
- return process.env.ONEAPI_KEY || process.env.OPENAIKEY || '';
-};
-
// 模型使用权校验
export const authApp = async ({
appId,
diff --git a/client/src/store/user.ts b/client/src/store/user.ts
index 7d8e849f7..a537ebac0 100644
--- a/client/src/store/user.ts
+++ b/client/src/store/user.ts
@@ -4,7 +4,7 @@ import { immer } from 'zustand/middleware/immer';
import type { UserType, UserUpdateParams } from '@/types/user';
import { getMyModels, getModelById, putAppById } from '@/api/app';
import { formatPrice } from '@/utils/user';
-import { getTokenLogin } from '@/api/user';
+import { getTokenLogin, putUserInfo } from '@/api/user';
import { defaultApp } from '@/constants/model';
import { AppListItemType, AppUpdateParams } from '@/types/app';
import type { KbItemType, KbListItemType } from '@/types/plugin';
@@ -16,7 +16,7 @@ type State = {
userInfo: UserType | null;
initUserInfo: () => Promise;
setUserInfo: (user: UserType | null) => void;
- updateUserInfo: (user: UserUpdateParams) => void;
+ updateUserInfo: (user: UserUpdateParams) => Promise;
myApps: AppListItemType[];
myCollectionApps: AppListItemType[];
loadMyApps: (init?: boolean) => Promise;
@@ -52,7 +52,7 @@ export const useUserStore = create()(
: null;
});
},
- updateUserInfo(user: UserUpdateParams) {
+ async updateUserInfo(user: UserUpdateParams) {
set((state) => {
if (!state.userInfo) return;
state.userInfo = {
@@ -60,6 +60,7 @@ export const useUserStore = create()(
...user
};
});
+ await putUserInfo(user);
},
myApps: [],
myCollectionApps: [],
diff --git a/client/src/types/mongoSchema.d.ts b/client/src/types/mongoSchema.d.ts
index 45cda9630..0b9a05e52 100644
--- a/client/src/types/mongoSchema.d.ts
+++ b/client/src/types/mongoSchema.d.ts
@@ -16,8 +16,9 @@ export interface UserModelSchema {
promotionAmount: number;
openaiKey: string;
createTime: number;
- promotion: {
- rate: number;
+ openaiAccount?: {
+ key: string;
+ baseUrl: string;
};
limit: {
exportKbTime?: Date;
diff --git a/client/src/types/user.d.ts b/client/src/types/user.d.ts
index db0670296..f8e8d6f60 100644
--- a/client/src/types/user.d.ts
+++ b/client/src/types/user.d.ts
@@ -1,18 +1,17 @@
import { BillSourceEnum } from '@/constants/user';
-import type { BillSchema } from './mongoSchema';
+import type { BillSchema, UserModelSchema } from './mongoSchema';
export interface UserType {
_id: string;
username: string;
avatar: string;
balance: number;
- promotion: {
- rate: number;
- };
+ openaiAccount: UserModelSchema['openaiAccount'];
}
export interface UserUpdateParams {
balance?: number;
avatar?: string;
+ openaiAccount?: UserModelSchema['openaiAccount'];
}
export interface UserBillType {