mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-15 15:41:05 +00:00
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:
@@ -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) =>
|
||||
|
9
packages/global/common/type/utils.ts
Normal file
9
packages/global/common/type/utils.ts
Normal 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];
|
@@ -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'
|
||||
};
|
||||
|
@@ -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'
|
||||
}
|
||||
|
11
packages/global/support/user/inform/type.d.ts
vendored
11
packages/global/support/user/inform/type.d.ts
vendored
@@ -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;
|
||||
|
4
packages/global/support/user/team/type.d.ts
vendored
4
packages/global/support/user/team/type.d.ts
vendored
@@ -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;
|
||||
|
6
packages/global/support/user/type.d.ts
vendored
6
packages/global/support/user/type.d.ts
vendored
@@ -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;
|
||||
};
|
||||
|
@@ -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}`
|
||||
|
@@ -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,
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -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",
|
||||
|
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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": "密码",
|
||||
|
@@ -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": "添加管理员"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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')}: </Box>
|
||||
<Input
|
||||
@@ -249,7 +255,7 @@ const MyInfo = () => {
|
||||
<Box {...labelStyles}>{t('common:user.Account')}: </Box>
|
||||
<Box flex={1}>{userInfo?.username}</Box>
|
||||
</Flex>
|
||||
{feConfigs.isPlus && (
|
||||
{feConfigs?.isPlus && (
|
||||
<Flex mt={6} alignItems={'center'}>
|
||||
<Box {...labelStyles}>{t('common:user.Password')}: </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')}: </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')}: </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')}: </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}
|
||||
|
@@ -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;
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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,
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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,
|
||||
|
@@ -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,
|
||||
|
Reference in New Issue
Block a user