System inform (#2263)

* feat: Bind Notification Pipe (#2229)

* chore: account page add bind notification modal

* feat: timerlock schema and type

* feat(fe): bind notification method modal

* chore: fe adjust

* feat: clean useless code

* fix: cron lock

* chore: adjust the code

* chore: rename api

* chore: remove unused code

* chore: fe adjust

* perf: bind inform ux

* fix: time ts

* chore: notification (#2251)

* perf: send message code

* perf: sub schema index

* fix: timezone plugin

* fix: format

---------

Co-authored-by: Finley Ge <32237950+FinleyGe@users.noreply.github.com>
This commit is contained in:
Archer
2024-08-05 00:29:14 +08:00
committed by GitHub
parent 998e7833e8
commit 56f6e69bc7
31 changed files with 344 additions and 171 deletions

View File

@@ -1,5 +1,10 @@
import dayjs from 'dayjs';
import cronParser from 'cron-parser';
import utc from 'dayjs/plugin/utc';
import timezone from 'dayjs/plugin/timezone';
dayjs.extend(utc);
dayjs.extend(timezone);
export const formatTime2YMDHMW = (time?: Date) => dayjs(time).format('YYYY-MM-DD HH:mm:ss dddd');
export const formatTime2YMDHM = (time?: Date) =>

View File

@@ -0,0 +1,9 @@
export type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Omit<T, Keys> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>;
}[Keys];
export type RequireOnlyOne<T, Keys extends keyof T = keyof T> = Omit<T, Keys> &
{
[K in Keys]-?: Required<Pick<T, K>> & Partial<Record<Exclude<Keys, K>, undefined>>;
}[Keys];

View File

@@ -1,11 +1,13 @@
export enum UserAuthTypeEnum {
register = 'register',
findPassword = 'findPassword',
wxLogin = 'wxLogin'
wxLogin = 'wxLogin',
bindNotification = 'bindNotification'
}
export const userAuthTypeMap = {
[UserAuthTypeEnum.register]: 'register',
[UserAuthTypeEnum.findPassword]: 'findPassword',
[UserAuthTypeEnum.wxLogin]: 'wxLogin'
[UserAuthTypeEnum.wxLogin]: 'wxLogin',
[UserAuthTypeEnum.bindNotification]: 'bindNotification'
};

View File

@@ -15,3 +15,14 @@ export const InformLevelMap = {
label: '紧急'
}
};
export enum SendInformTemplateCodeEnum {
EXPIRE_SOON = 'EXPIRE_SOON',
EXPIRED = 'EXPIRED',
FREE_CLEAN = 'FREE_CLEAN',
REGISTER = 'REGISTER',
RESET_PASSWORD = 'RESET_PASSWORD',
BIND_NOTIFICATION = 'BIND_NOTIFICATION',
LACK_OF_POINTS = 'LACK_OF_POINTS',
CUSTOM = 'CUSTOM'
}

View File

@@ -1,13 +1,16 @@
import { InformLevelEnum } from './constants';
import { InformLevelEnum, SendInformTemplateCodeEnum } from './constants';
export type SendInformProps = {
title: string;
content: string;
level: `${InformLevelEnum}`;
templateCode: `${SendInformTemplateCodeEnum}`;
templateParam: Record<string, any>;
customLockMinutes?: number; // custom lock minutes
};
export type SendInform2UserProps = SendInformProps & {
tmbId: string;
teamId: string;
};
export type SendInform2User = SendInformProps & {
type: `${InformTypeEnum}`;
tmbId: string;

View File

@@ -18,7 +18,9 @@ export type TeamSchema = {
};
lafAccount: LafAccountType;
defaultPermission: PermissionValueType;
notificationAccount?: string;
};
export type tagsType = {
label: string;
key: string;
@@ -63,6 +65,7 @@ export type TeamTmbItemType = {
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
lafAccount?: LafAccountType;
notificationAccount?: string;
permission: TeamPermission;
};
@@ -72,7 +75,6 @@ export type TeamMemberItemType = {
teamId: string;
memberName: string;
avatar: string;
// TODO: this should be deprecated.
role: `${TeamMemberRoleEnum}`;
status: `${TeamMemberStatusEnum}`;
permission: TeamPermission;

View File

@@ -1,12 +1,10 @@
import { TeamPermission } from '../permission/user/controller';
import { UserStatusEnum } from './constant';
import { TeamTmbItemType } from './team/type';
export type UserModelSchema = {
_id: string;
username: string;
email?: string;
phonePrefix?: number;
phone?: string;
password: string;
avatar: string;
promotionRate: number;
@@ -31,4 +29,6 @@ export type UserType = {
openaiAccount: UserModelSchema['openaiAccount'];
team: TeamTmbItemType;
standardInfo?: standardInfoType;
notificationAccount?: string;
permission: TeamPermission;
};

View File

@@ -4,14 +4,21 @@ export enum TimerIdEnum {
checkInvalidVector = 'checkInvalidVector',
clearExpiredSubPlan = 'clearExpiredSubPlan',
updateStandardPlan = 'updateStandardPlan',
scheduleTriggerApp = 'scheduleTriggerApp'
scheduleTriggerApp = 'scheduleTriggerApp',
notification = 'notification'
}
export const timerIdMap = {
[TimerIdEnum.checkInValidDatasetFiles]: 'checkInValidDatasetFiles',
[TimerIdEnum.checkInvalidDatasetData]: 'checkInvalidDatasetData',
[TimerIdEnum.checkInvalidVector]: 'checkInvalidVector',
[TimerIdEnum.clearExpiredSubPlan]: 'clearExpiredSubPlan',
[TimerIdEnum.updateStandardPlan]: 'updateStandardPlan',
[TimerIdEnum.scheduleTriggerApp]: 'scheduleTriggerApp'
};
export enum LockNotificationEnum {
NotificationExpire = 'notification_expire',
NotificationFreeClean = 'notification_free_clean',
NotificationLackOfPoints = 'notification_lack_of_points'
}
export type LockType = `${LockNotificationEnum}`;
// add a new type enum example:
// export enum ExampleLockEnum {
// ExampleLockType1 = 'example_lock_type1'
// }
//
// export type LockType = `${NotificationLockEnum}` | `${ExampleLockEnum}`

View File

@@ -1,6 +1,5 @@
import { connectionMongo, getMongoModel, type Model } from '../../mongo';
import { timerIdMap } from './constants';
const { Schema, model, models } = connectionMongo;
import { connectionMongo, getMongoModel } from '../../mongo';
const { Schema } = connectionMongo;
import { TimerLockSchemaType } from './type.d';
export const collectionName = 'systemtimerlocks';
@@ -9,8 +8,7 @@ const TimerLockSchema = new Schema({
timerId: {
type: String,
required: true,
unique: true,
enum: Object.keys(timerIdMap)
unique: true
},
expiredTime: {
type: Date,

View File

@@ -1,4 +1,3 @@
import { TimerIdEnum } from './constants';
import { MongoTimerLock } from './schema';
import { addMinutes } from 'date-fns';
@@ -9,7 +8,7 @@ export const checkTimerLock = async ({
timerId,
lockMinuted
}: {
timerId: TimerIdEnum;
timerId: string;
lockMinuted: number;
}) => {
try {

View File

@@ -45,6 +45,8 @@ export async function getUserDetail({
timezone: user.timezone,
promotionRate: user.promotionRate,
openaiAccount: user.openaiAccount,
team: tmb
team: tmb,
notificationAccount: tmb.notificationAccount,
permission: tmb.permission
};
}

View File

@@ -1,5 +1,5 @@
import { connectionMongo, getMongoModel, type Model } from '../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { connectionMongo, getMongoModel } from '../../common/mongo';
const { Schema } = connectionMongo;
import { hashStr } from '@fastgpt/global/common/string/tools';
import type { UserModelSchema } from '@fastgpt/global/support/user/type';
import { UserStatusEnum, userStatusMap } from '@fastgpt/global/support/user/constant';
@@ -18,15 +18,9 @@ const UserSchema = new Schema({
required: true,
unique: true // 唯一
},
email: {
type: String
},
phonePrefix: {
type: Number
},
phone: {
type: String
},
password: {
type: String,
required: true,

View File

@@ -40,7 +40,8 @@ async function getTeamMember(match: Record<string, any>): Promise<TeamTmbItemTyp
permission: new TeamPermission({
per: tmbPer?.permission ?? tmb.teamId.defaultPermission,
isOwner: tmb.role === TeamMemberRoleEnum.owner
})
}),
notificationAccount: tmb.teamId.notificationAccount
};
}

View File

@@ -1,5 +1,5 @@
import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { connectionMongo, getMongoModel } from '../../../common/mongo';
const { Schema } = connectionMongo;
import { TeamSchema as TeamType } from '@fastgpt/global/support/user/team/type.d';
import { userCollectionName } from '../../user/schema';
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
@@ -51,6 +51,10 @@ const TeamSchema = new Schema({
pat: {
type: String
}
},
notificationAccount: {
type: String,
required: false
}
});

View File

@@ -3,8 +3,8 @@
1. type=standard: There will only be 1, and each team will have one
2. type=extraDatasetSize/extraPoints: Can buy multiple
*/
import { connectionMongo, getMongoModel, type Model } from '../../../common/mongo';
const { Schema, model, models } = connectionMongo;
import { connectionMongo, getMongoModel } from '../../../common/mongo';
const { Schema } = connectionMongo;
import { TeamCollectionName } from '@fastgpt/global/support/user/team/constant';
import {
standardSubLevelMap,
@@ -84,11 +84,13 @@ const SubSchema = new Schema({
});
try {
// get team plan
SubSchema.index({ teamId: 1, type: 1, expiredTime: -1 });
// Get plan by expiredTime
SubSchema.index({ expiredTime: -1, currentSubLevel: 1 });
// timer task. check expired plan; update standard plan;
SubSchema.index({ type: 1, currentSubLevel: 1, expiredTime: -1 });
// Get team plan
SubSchema.index({ teamId: 1, type: 1, expiredTime: -1 });
// timer task. Get standard plan;Get free plan;Clear expired extract plan
SubSchema.index({ type: 1, expiredTime: -1, currentSubLevel: 1 });
} catch (error) {
console.log(error);
}

View File

@@ -63,7 +63,7 @@ const UsageSchema = new Schema({
try {
UsageSchema.index({ teamId: 1, tmbId: 1, source: 1, time: -1 }, { background: true });
// timer task. clear dead team
UsageSchema.index({ teamId: 1, time: -1 }, { background: true });
// UsageSchema.index({ teamId: 1, time: -1 }, { background: true });
UsageSchema.index({ time: 1 }, { expireAfterSeconds: 180 * 24 * 60 * 60 });
} catch (error) {

View File

@@ -1165,6 +1165,8 @@
"Language": "Language",
"Member Name": "Nickname",
"Notice": "Notice",
"Notification Receive": "Notification Receive",
"Notification Receive Bind": "Please bind notification receive method",
"Old password is error": "Old password is incorrect",
"OpenAI Account Setting": "OpenAI account settings",
"Password": "Password",

View File

@@ -1,4 +1,6 @@
{
"bind_inform_account_error": "Abnormal binding notification account",
"bind_inform_account_success": "Binding notification account successful",
"permission": {
"Manage": "administrator",
"Manage tip": "Team administrator, with full permissions",
@@ -10,4 +12,4 @@
"team": {
"Add manager": "Add manager"
}
}
}

View File

@@ -992,6 +992,8 @@
}
},
"user": {
"Email Or Phone": "邮箱/手机号",
"Verify Code": "验证码",
"Avatar": "头像",
"Go laf env": "点击前往 {{env}} 获取 PAT 凭证。",
"Laf account course": "查看绑定 laf 账号教程。",
@@ -1165,6 +1167,8 @@
"Language": "语言",
"Member Name": "昵称",
"Notice": "通知",
"Notification Receive": "通知接收",
"Notification Receive Bind": "请绑定通知接收途径",
"Old password is error": "旧密码错误",
"OpenAI Account Setting": "OpenAI 账号配置",
"Password": "密码",

View File

@@ -1,4 +1,9 @@
{
"bind_inform_account_error": "绑定通知账号异常",
"bind_inform_account_success": "绑定通知账号成功",
"notification": {
"Bind Notification Pipe Hint": "绑定接收通知的邮箱或手机号,以确保您能及时接收到重要的系统通知。"
},
"permission": {
"Manage": "管理员",
"Manage tip": "团队管理员,拥有全部权限",
@@ -10,4 +15,4 @@
"team": {
"Add manager": "添加管理员"
}
}
}

View File

@@ -50,6 +50,7 @@ const StandDetailModal = dynamic(() => import('./standardDetailModal'));
const TeamMenu = dynamic(() => import('@/components/support/user/team/TeamMenu'));
const PayModal = dynamic(() => import('./PayModal'));
const UpdatePswModal = dynamic(() => import('./UpdatePswModal'));
const UpdateNotification = dynamic(() => import('./UpdateNotificationModal'));
const OpenAIAccountModal = dynamic(() => import('./OpenAIAccountModal'));
const LafAccountModal = dynamic(() => import('@/components/support/laf/LafAccountModal'));
const CommunityModal = dynamic(() => import('@/components/CommunityModal'));
@@ -113,6 +114,11 @@ const MyInfo = () => {
onClose: onCloseUpdatePsw,
onOpen: onOpenUpdatePsw
} = useDisclosure();
const {
isOpen: isOpenUpdateNotification,
onClose: onCloseUpdateNotification,
onOpen: onOpenUpdateNotification
} = useDisclosure();
const { File, onOpen: onOpenSelectFile } = useSelectFile({
fileType: '.jpg,.png',
multiple: false
@@ -225,7 +231,7 @@ const MyInfo = () => {
</Flex>
</Flex>
)}
{feConfigs.isPlus && (
{feConfigs?.isPlus && (
<Flex mt={[0, 4]} alignItems={'center'}>
<Box {...labelStyles}>{t('common:user.Member Name')}:&nbsp;</Box>
<Input
@@ -249,7 +255,7 @@ const MyInfo = () => {
<Box {...labelStyles}>{t('common:user.Account')}:&nbsp;</Box>
<Box flex={1}>{userInfo?.username}</Box>
</Flex>
{feConfigs.isPlus && (
{feConfigs?.isPlus && (
<Flex mt={6} alignItems={'center'}>
<Box {...labelStyles}>{t('common:user.Password')}:&nbsp;</Box>
<Box flex={1}>*****</Box>
@@ -258,13 +264,27 @@ const MyInfo = () => {
</Button>
</Flex>
)}
{feConfigs?.isPlus && (
<Flex mt={6} alignItems={'center'}>
<Box {...labelStyles}>{t('common:user.Notification Receive')}:&nbsp;</Box>
<Box flex={1} {...(userInfo?.team.notificationAccount ? {} : { color: 'red.600' })}>
{userInfo?.team.notificationAccount || t('common:user.Notification Receive Bind')}
</Box>
{userInfo?.permission.isOwner && (
<Button size={'sm'} variant={'whitePrimary'} onClick={onOpenUpdateNotification}>
{t('common:user.Change')}
</Button>
)}
</Flex>
)}
<Flex mt={6} alignItems={'center'}>
<Box {...labelStyles}>{t('common:user.Team')}:&nbsp;</Box>
<Box flex={1}>
<TeamMenu />
</Box>
</Flex>
{feConfigs.isPlus && (
{feConfigs?.isPlus && (
<Box mt={6} whiteSpace={'nowrap'}>
<Flex alignItems={'center'}>
<Box {...labelStyles}>{t('common:user.team.Balance')}:&nbsp;</Box>
@@ -282,6 +302,7 @@ const MyInfo = () => {
</Box>
{isOpenPayModal && <PayModal onClose={onClosePayModal} />}
{isOpenUpdatePsw && <UpdatePswModal onClose={onCloseUpdatePsw} />}
{isOpenUpdateNotification && <UpdateNotification onClose={onCloseUpdateNotification} />}
<File onSelect={onSelectFile} />
</Box>
);
@@ -579,7 +600,7 @@ const Other = () => {
)}
{feConfigs?.chatbotUrl && (
<Link
href={feConfigs.chatbotUrl}
href={feConfigs?.chatbotUrl}
target="_blank"
display={'flex'}
py={3}

View File

@@ -0,0 +1,120 @@
import React, { useCallback } from 'react';
import { ModalBody, Box, Flex, Input, ModalFooter, Button, HStack } from '@chakra-ui/react';
import MyModal from '@fastgpt/web/components/common/MyModal';
import { useTranslation } from 'next-i18next';
import { useForm } from 'react-hook-form';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
import { updateNotificationAccount } from '@/web/support/user/api';
import Icon from '@fastgpt/web/components/common/Icon';
import { useSendCode } from '@/web/support/user/hooks/useSendCode';
import { useUserStore } from '@/web/support/user/useUserStore';
type FormType = {
account: string;
verifyCode: string;
};
const UpdateNotificationModal = ({ onClose }: { onClose: () => void }) => {
const { t } = useTranslation();
const { initUserInfo } = useUserStore();
const { register, handleSubmit, trigger, getValues, watch } = useForm<FormType>({
defaultValues: {
account: '',
verifyCode: ''
}
});
const account = watch('account');
const verifyCode = watch('verifyCode');
const { runAsync: onSubmit, loading: isLoading } = useRequest2(
(data: FormType) => {
return updateNotificationAccount(data);
},
{
onSuccess() {
initUserInfo();
onClose();
},
successToast: t('user:bind_inform_account_success'),
errorToast: t('user:bind_inform_account_error')
}
);
const { sendCodeText, sendCode, codeCountDown } = useSendCode();
const onclickSendCode = useCallback(async () => {
const check = await trigger('account');
if (!check) return;
sendCode({
username: getValues('account'),
type: 'bindNotification'
});
}, [getValues, sendCode, trigger]);
return (
<MyModal
isOpen
iconSrc="common/settingLight"
w={'32rem'}
title={t('common:user.Notification Receive')}
>
<ModalBody px={10}>
<Flex flexDirection="column">
<HStack px="6" py="3" color="primary.600" bgColor="primary.50" borderRadius="md">
<Icon name="common/info" w="1rem" />
<Box fontSize={'sm'}>{t('user:notification.Bind Notification Pipe Hint')}</Box>
</HStack>
<Flex mt="4" alignItems="center">
<Box flex={'0 0 70px'}>{t('common:user.Account')}</Box>
<Input
flex={1}
bg={'myGray.50'}
{...register('account', { required: true })}
placeholder={t('common:support.user.Email Or Phone')}
></Input>
</Flex>
<Flex mt="6" alignItems="center" position={'relative'}>
<Box flex={'0 0 70px'}>{t('common:support.user.Verify Code')}</Box>
<Input
flex={1}
bg={'myGray.50'}
{...register('verifyCode', { required: true })}
placeholder={t('common:support.user.Verify Code')}
></Input>
<Box
position={'absolute'}
right={2}
zIndex={1}
fontSize={'sm'}
{...(codeCountDown > 0
? {
color: 'myGray.500'
}
: {
color: 'primary.700',
cursor: 'pointer',
onClick: onclickSendCode
})}
>
{sendCodeText}
</Box>
</Flex>
</Flex>
</ModalBody>
<ModalFooter>
<Button mr={3} variant={'whiteBase'} onClick={onClose}>
{t('common:common.Cancel')}
</Button>
<Button
isLoading={isLoading}
isDisabled={!account || !verifyCode}
onClick={handleSubmit((data) => onSubmit(data))}
>
{t('common:common.Confirm')}
</Button>
</ModalFooter>
</MyModal>
);
};
export default UpdateNotificationModal;

View File

@@ -6,6 +6,7 @@ import { connectToDatabase } from '@/service/mongo';
import { getUserDetail } from '@fastgpt/service/support/user/controller';
import type { PostLoginProps } from '@fastgpt/global/support/user/api.d';
import { UserStatusEnum } from '@fastgpt/global/support/user/constant';
import { checkTeamAiPointsAndLock } from '@/service/events/utils';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {

View File

@@ -1,21 +1,15 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { connectToDatabase } from '@/service/mongo';
import { getUserDetail } from '@fastgpt/service/support/user/controller';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
await connectToDatabase();
const { tmbId } = await authCert({ req, authToken: true });
jsonRes(res, {
data: await getUserDetail({ tmbId })
});
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
}
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
export type TokenLoginQuery = {};
export type TokenLoginBody = {};
export type TokenLoginResponse = {};
async function handler(
req: ApiRequestProps<TokenLoginBody, TokenLoginQuery>,
_res: ApiResponseType<any>
): Promise<TokenLoginResponse> {
const { tmbId } = await authCert({ req, authToken: true });
return getUserDetail({ tmbId });
}
export default NextAPI(handler);

View File

@@ -1,62 +1,60 @@
import type { NextApiRequest, NextApiResponse } from 'next';
import { jsonRes } from '@fastgpt/service/common/response';
import { MongoUser } from '@fastgpt/service/support/user/schema';
import { authCert } from '@fastgpt/service/support/permission/auth/common';
import { UserUpdateParams } from '@/types/user';
import { getAIApi, openaiBaseUrl } from '@fastgpt/service/core/ai/config';
import { connectToDatabase } from '@/service/mongo';
import { MongoTeamMember } from '@fastgpt/service/support/user/team/teamMemberSchema';
/* update user info */
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
try {
await connectToDatabase();
const { avatar, timezone, openaiAccount, lafAccount } = req.body as UserUpdateParams;
import type { ApiRequestProps, ApiResponseType } from '@fastgpt/service/type/next';
import { NextAPI } from '@/service/middleware/entry';
export type UserAccountUpdateQuery = {};
export type UserAccountUpdateBody = UserUpdateParams;
export type UserAccountUpdateResponse = {};
async function handler(
req: ApiRequestProps<UserAccountUpdateBody, UserAccountUpdateQuery>,
_res: ApiResponseType<any>
): Promise<UserAccountUpdateResponse> {
const { avatar, timezone, openaiAccount, lafAccount } = req.body;
const { tmbId } = await authCert({ req, authToken: true });
const tmb = await MongoTeamMember.findById(tmbId);
if (!tmb) {
throw new Error('can not find it');
}
const userId = tmb.userId;
// auth key
if (openaiAccount?.key) {
console.log('auth user openai key', openaiAccount?.key);
const baseUrl = openaiAccount?.baseUrl || openaiBaseUrl;
openaiAccount.baseUrl = baseUrl;
const ai = getAIApi({
userKey: openaiAccount
});
const response = await ai.chat.completions.create({
model: 'gpt-4o-mini',
max_tokens: 1,
messages: [{ role: 'user', content: 'hi' }]
});
if (response?.choices?.[0]?.message?.content === undefined) {
throw new Error('Key response is empty');
}
}
// 更新对应的记录
await MongoUser.updateOne(
{
_id: userId
},
{
...(avatar && { avatar }),
...(timezone && { timezone }),
openaiAccount: openaiAccount?.key ? openaiAccount : null,
lafAccount: lafAccount?.token ? lafAccount : null
}
);
jsonRes(res);
} catch (err) {
jsonRes(res, {
code: 500,
error: err
});
const { tmbId } = await authCert({ req, authToken: true });
const tmb = await MongoTeamMember.findById(tmbId);
if (!tmb) {
throw new Error('can not find it');
}
const userId = tmb.userId;
// auth key
if (openaiAccount?.key) {
console.log('auth user openai key', openaiAccount?.key);
const baseUrl = openaiAccount?.baseUrl || openaiBaseUrl;
openaiAccount.baseUrl = baseUrl;
const ai = getAIApi({
userKey: openaiAccount
});
const response = await ai.chat.completions.create({
model: 'gpt-4o-mini',
max_tokens: 1,
messages: [{ role: 'user', content: 'hi' }]
});
if (response?.choices?.[0]?.message?.content === undefined) {
throw new Error('Key response is empty');
}
}
// 更新对应的记录
await MongoUser.updateOne(
{
_id: userId
},
{
...(avatar && { avatar }),
...(timezone && { timezone }),
openaiAccount: openaiAccount?.key ? openaiAccount : null,
lafAccount: lafAccount?.token ? lafAccount : null
}
);
return {};
}
export default NextAPI(handler);

View File

@@ -49,7 +49,6 @@ const ListItem = () => {
const { parentId = null } = router.query;
const { isPc } = useSystem();
const { lastChatAppId, setLastChatAppId } = useChatStore();
const { myApps, loadMyApps, onUpdateApp, setMoveAppId, folderDetail } = useContextSelector(
AppListContext,

View File

@@ -88,7 +88,7 @@ export async function generateQA(): Promise<any> {
}
// auth balance
if (!(await checkTeamAiPointsAndLock(data.teamId, data.tmbId))) {
if (!(await checkTeamAiPointsAndLock(data.teamId))) {
reduceQueue();
return generateQA();
}

View File

@@ -89,7 +89,7 @@ export async function generateVector(): Promise<any> {
}
// auth balance
if (!(await checkTeamAiPointsAndLock(data.teamId, data.tmbId))) {
if (!(await checkTeamAiPointsAndLock(data.teamId))) {
reduceQueue();
return generateVector();
}

View File

@@ -4,7 +4,7 @@ import { sendOneInform } from '../support/user/inform/api';
import { lockTrainingDataByTeamId } from '@fastgpt/service/core/dataset/training/controller';
import { InformLevelEnum } from '@fastgpt/global/support/user/inform/constants';
export const checkTeamAiPointsAndLock = async (teamId: string, tmbId: string) => {
export const checkTeamAiPointsAndLock = async (teamId: string) => {
try {
await checkTeamAIPoints(teamId);
return true;
@@ -13,11 +13,10 @@ export const checkTeamAiPointsAndLock = async (teamId: string, tmbId: string) =>
// send inform and lock data
try {
sendOneInform({
level: InformLevelEnum.important,
title: '文本训练任务中止',
content:
'该团队账号AI积分不足文本训练任务中止重新充值后将会继续。暂停的任务将在 7 天后被删除。',
tmbId: tmbId
level: InformLevelEnum.emergency,
templateCode: 'LACK_OF_POINTS',
templateParam: {},
teamId
});
console.log('余额不足,暂停【向量】生成任务');
lockTrainingDataByTeamId(teamId);

View File

@@ -63,6 +63,9 @@ export const updatePasswordByOld = ({ oldPsw, newPsw }: { oldPsw: string; newPsw
newPsw: hashStr(newPsw)
});
export const updateNotificationAccount = (data: { account: string; verifyCode: string }) =>
PUT('/proApi/support/user/team/updateNotificationAccount', data);
export const postLogin = ({ password, ...props }: PostLoginProps) =>
POST<ResLogin>('/support/user/account/loginByPassword', {
...props,

View File

@@ -1,19 +1,40 @@
import { useState, useMemo, useCallback } from 'react';
import { useState, useMemo } from 'react';
import { sendAuthCode } from '@/web/support/user/api';
import { UserAuthTypeEnum } from '@fastgpt/global/support/user/auth/constants';
import { useToast } from '@fastgpt/web/hooks/useToast';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useTranslation } from 'next-i18next';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useRequest2 } from '@fastgpt/web/hooks/useRequest';
let timer: any;
let timer: NodeJS.Timeout;
export const useSendCode = () => {
const { t } = useTranslation();
const { toast } = useToast();
const { feConfigs } = useSystemStore();
const [codeSending, setCodeSending] = useState(false);
const [codeCountDown, setCodeCountDown] = useState(0);
const { runAsync: sendCode, loading: codeSending } = useRequest2(
async ({ username, type }: { username: string; type: `${UserAuthTypeEnum}` }) => {
if (codeCountDown > 0) return;
const googleToken = await getClientToken(feConfigs.googleClientVerKey);
await sendAuthCode({ username, type, googleToken });
setCodeCountDown(60);
timer = setInterval(() => {
setCodeCountDown((val) => {
if (val <= 0) {
clearInterval(timer);
}
return val - 1;
});
}, 1000);
},
{
successToast: '验证码已发送',
errorToast: '验证码发送异常',
refreshDeps: [codeCountDown, feConfigs?.googleClientVerKey]
}
);
const sendCodeText = useMemo(() => {
if (codeSending) return t('common:support.user.auth.Sending Code');
if (codeCountDown >= 10) {
@@ -25,41 +46,6 @@ export const useSendCode = () => {
return '获取验证码';
}, [codeCountDown, codeSending, t]);
const sendCode = useCallback(
async ({ username, type }: { username: string; type: `${UserAuthTypeEnum}` }) => {
if (codeCountDown > 0) return;
setCodeSending(true);
try {
await sendAuthCode({
username,
type,
googleToken: await getClientToken(feConfigs.googleClientVerKey)
});
setCodeCountDown(60);
timer = setInterval(() => {
setCodeCountDown((val) => {
if (val <= 0) {
clearInterval(timer);
}
return val - 1;
});
}, 1000);
toast({
title: '验证码已发送',
status: 'success',
position: 'top'
});
} catch (error: any) {
toast({
title: getErrText(error, '验证码发送异常'),
status: 'error'
});
}
setCodeSending(false);
},
[codeCountDown, feConfigs?.googleClientVerKey, toast]
);
return {
codeSending,
sendCode,