mirror of
https://github.com/labring/FastGPT.git
synced 2025-07-23 13:03:50 +00:00
user timezone
This commit is contained in:
@@ -62,6 +62,7 @@
|
||||
"remark-math": "^5.1.1",
|
||||
"request-ip": "^3.3.0",
|
||||
"sass": "^1.58.3",
|
||||
"timezones-list": "^3.0.2",
|
||||
"tunnel": "^0.0.6",
|
||||
"winston": "^3.10.0",
|
||||
"winston-mongodb": "^5.1.1",
|
||||
|
9
client/pnpm-lock.yaml
generated
9
client/pnpm-lock.yaml
generated
@@ -164,6 +164,9 @@ dependencies:
|
||||
sass:
|
||||
specifier: ^1.58.3
|
||||
version: registry.npmmirror.com/sass@1.58.3
|
||||
timezones-list:
|
||||
specifier: ^3.0.2
|
||||
version: registry.npmmirror.com/timezones-list@3.0.2
|
||||
tunnel:
|
||||
specifier: ^0.0.6
|
||||
version: registry.npmmirror.com/tunnel@0.0.6
|
||||
@@ -11732,6 +11735,12 @@ packages:
|
||||
version: 0.2.0
|
||||
dev: true
|
||||
|
||||
registry.npmmirror.com/timezones-list@3.0.2:
|
||||
resolution: {integrity: sha512-I698hm6Jp/xxkwyTSOr39pZkYKETL8LDJeSIhjxXBfPUAHM5oZNuQ4o9UK3PSkDBOkjATecSOBb3pR1IkIBUsg==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/timezones-list/-/timezones-list-3.0.2.tgz}
|
||||
name: timezones-list
|
||||
version: 3.0.2
|
||||
dev: false
|
||||
|
||||
registry.npmmirror.com/tiny-invariant@1.3.1:
|
||||
resolution: {integrity: sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw==, registry: https://registry.npm.taobao.org/, tarball: https://registry.npmmirror.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz}
|
||||
name: tiny-invariant
|
||||
|
@@ -184,6 +184,7 @@
|
||||
"Sign Out": "Sign Out",
|
||||
"Source": "Source",
|
||||
"Time": "Time",
|
||||
"Timezone": "Timezone",
|
||||
"Total Amount": "Total Amount",
|
||||
"Update Password": "Update Password",
|
||||
"Update password failed": "Update password failed",
|
||||
|
@@ -184,6 +184,7 @@
|
||||
"Sign Out": "登出",
|
||||
"Source": "来源",
|
||||
"Time": "时间",
|
||||
"Timezone": "时区",
|
||||
"Total Amount": "总金额",
|
||||
"Update Password": "修改密码",
|
||||
"Update password failed": "修改密码异常",
|
||||
|
@@ -37,7 +37,7 @@ import { fileDownload } from '@/utils/file';
|
||||
import { htmlTemplate } from '@/constants/common';
|
||||
import { useRouter } from 'next/router';
|
||||
import { useGlobalStore } from '@/store/global';
|
||||
import { TaskResponseKeyEnum, getDefaultChatVariables } from '@/constants/chat';
|
||||
import { TaskResponseKeyEnum } from '@/constants/chat';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { customAlphabet } from 'nanoid';
|
||||
import { userUpdateChatFeedback, adminUpdateChatFeedback } from '@/api/chat';
|
||||
@@ -350,10 +350,7 @@ const ChatBox = (
|
||||
messages,
|
||||
controller: abortSignal,
|
||||
generatingMessage,
|
||||
variables: {
|
||||
...getDefaultChatVariables(),
|
||||
...variables
|
||||
}
|
||||
variables
|
||||
});
|
||||
|
||||
// set finish status
|
||||
|
@@ -3,6 +3,7 @@ import { Menu, MenuButton, MenuItem, MenuList, MenuButtonProps } from '@chakra-u
|
||||
import { getLangStore, LangEnum, setLangStore } from '@/utils/i18n';
|
||||
import MyIcon from '@/components/Icon';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const langMap = {
|
||||
[LangEnum.en]: {
|
||||
@@ -16,6 +17,7 @@ const langMap = {
|
||||
};
|
||||
|
||||
const Language = (props: MenuButtonProps) => {
|
||||
const router = useRouter();
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
const [language, setLanguage] = useState<`${LangEnum}`>(getLangStore());
|
||||
@@ -43,6 +45,7 @@ const Language = (props: MenuButtonProps) => {
|
||||
setLangStore(lang);
|
||||
setLanguage(lang);
|
||||
i18n?.changeLanguage?.(lang);
|
||||
router.reload();
|
||||
}}
|
||||
>
|
||||
{lang.label}
|
||||
|
@@ -21,7 +21,7 @@ interface Props extends ButtonProps {
|
||||
}
|
||||
|
||||
const MySelect = (
|
||||
{ placeholder, value, width = 'auto', list, onchange, ...props }: Props,
|
||||
{ placeholder, value, width = '100%', list, onchange, ...props }: Props,
|
||||
selectRef: any
|
||||
) => {
|
||||
const ref = useRef<HTMLButtonElement>(null);
|
||||
@@ -94,6 +94,8 @@ const MySelect = (
|
||||
}
|
||||
zIndex={99}
|
||||
transform={'translateY(35px) !important'}
|
||||
maxH={'40vh'}
|
||||
overflowY={'auto'}
|
||||
>
|
||||
{list.map((item) => (
|
||||
<MenuItem
|
||||
|
@@ -67,7 +67,3 @@ export enum OutLinkTypeEnum {
|
||||
|
||||
export const HUMAN_ICON = `/icon/human.png`;
|
||||
export const LOGO_ICON = `/icon/logo.svg`;
|
||||
|
||||
export const getDefaultChatVariables = () => ({
|
||||
cTime: dayjs().format('YYYY/MM/DD HH:mm:ss')
|
||||
});
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { Box, Flex, Button, useDisclosure, useTheme, Divider } from '@chakra-ui/react';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import { Box, Flex, Button, useDisclosure, useTheme, Divider, Select } from '@chakra-ui/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { UserUpdateParams } from '@/types/user';
|
||||
import { useToast } from '@/hooks/useToast';
|
||||
@@ -11,6 +11,7 @@ import { useSelectFile } from '@/hooks/useSelectFile';
|
||||
import { compressImg } from '@/utils/file';
|
||||
import { feConfigs } from '@/store/static';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { timezoneList } from '@/utils/user';
|
||||
import Loading from '@/components/Loading';
|
||||
import Avatar from '@/components/Avatar';
|
||||
import MyIcon from '@/components/Icon';
|
||||
@@ -33,6 +34,7 @@ const UserInfo = () => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
const { userInfo, updateUserInfo, initUserInfo } = useUserStore();
|
||||
const timezones = useRef(timezoneList());
|
||||
const { reset } = useForm<UserUpdateParams>({
|
||||
defaultValues: userInfo as UserType
|
||||
});
|
||||
@@ -59,6 +61,7 @@ const UserInfo = () => {
|
||||
async (data: UserType) => {
|
||||
await updateUserInfo({
|
||||
avatar: data.avatar,
|
||||
timezone: data.timezone,
|
||||
openaiAccount: data.openaiAccount
|
||||
});
|
||||
reset(data);
|
||||
@@ -102,7 +105,13 @@ const UserInfo = () => {
|
||||
});
|
||||
|
||||
return (
|
||||
<Box display={['block', 'flex']} py={[2, 10]} justifyContent={'center'} fontSize={['lg', 'xl']}>
|
||||
<Box
|
||||
display={['block', 'flex']}
|
||||
py={[2, 10]}
|
||||
justifyContent={'center'}
|
||||
alignItems={'flex-start'}
|
||||
fontSize={['lg', 'xl']}
|
||||
>
|
||||
<Flex
|
||||
flexDirection={'column'}
|
||||
alignItems={'center'}
|
||||
@@ -135,11 +144,27 @@ const UserInfo = () => {
|
||||
mt={[6, 0]}
|
||||
>
|
||||
<Flex alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 50px'}>{t('user.Account')}: </Box>
|
||||
<Box flex={'0 0 80px'}>{t('user.Account')}: </Box>
|
||||
<Box flex={1}>{userInfo?.username}</Box>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 50px'}>{t('user.Password')}: </Box>
|
||||
<Box flex={'0 0 80px'}>{t('user.Timezone')}: </Box>
|
||||
<Select
|
||||
value={userInfo?.timezone}
|
||||
onChange={(e) => {
|
||||
if (!userInfo) return;
|
||||
onclickSave({ ...userInfo, timezone: e.target.value });
|
||||
}}
|
||||
>
|
||||
{timezones.current.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.name}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</Flex>
|
||||
<Flex mt={6} alignItems={'center'} w={['85%', '300px']}>
|
||||
<Box flex={'0 0 80px'}>{t('user.Password')}: </Box>
|
||||
<Box flex={1}>*****</Box>
|
||||
<Button size={['sm', 'md']} variant={'base'} ml={5} onClick={onOpenUpdatePsw}>
|
||||
{t('user.Change')}
|
||||
@@ -149,7 +174,7 @@ const UserInfo = () => {
|
||||
<>
|
||||
<Box mt={6} whiteSpace={'nowrap'} w={['85%', '300px']}>
|
||||
<Flex alignItems={'center'}>
|
||||
<Box flex={'0 0 50px'}>{t('user.Balance')}: </Box>
|
||||
<Box flex={'0 0 80px'}>{t('user.Balance')}: </Box>
|
||||
<Box flex={1}>
|
||||
<strong>{userInfo?.balance.toFixed(3)}</strong> 元
|
||||
</Box>
|
||||
|
@@ -42,6 +42,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
||||
/* user auth */
|
||||
const { userId, user } = await authUser({ req, authBalance: true });
|
||||
|
||||
if (!user) {
|
||||
throw new Error('user not found');
|
||||
}
|
||||
|
||||
/* start process */
|
||||
const { responseData } = await dispatchModules({
|
||||
res,
|
||||
|
@@ -28,6 +28,7 @@ import { BillSourceEnum } from '@/constants/user';
|
||||
import { ChatHistoryItemResType } from '@/types/chat';
|
||||
import { UserModelSchema } from '@/types/mongoSchema';
|
||||
import { SystemInputEnum } from '@/constants/app';
|
||||
import { getSystemTime } from '@/utils/user';
|
||||
|
||||
export type MessageItemType = ChatCompletionRequestMessage & { dataId?: string };
|
||||
type FastGptWebChatProps = {
|
||||
@@ -95,9 +96,6 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
if (!user) {
|
||||
throw new Error('Account is error');
|
||||
}
|
||||
// if (authType === AuthUserTypeEnum.apikey || shareId) {
|
||||
// user.openaiAccount = undefined;
|
||||
// }
|
||||
|
||||
appId = appId ? appId : authAppid;
|
||||
if (!appId) {
|
||||
@@ -249,6 +247,7 @@ export default withNextCors(async function handler(req: NextApiRequest, res: Nex
|
||||
}
|
||||
});
|
||||
|
||||
/* running */
|
||||
export async function dispatchModules({
|
||||
res,
|
||||
modules,
|
||||
@@ -260,12 +259,16 @@ export async function dispatchModules({
|
||||
}: {
|
||||
res: NextApiResponse;
|
||||
modules: AppModuleItemType[];
|
||||
user?: UserModelSchema;
|
||||
user: UserModelSchema;
|
||||
params?: Record<string, any>;
|
||||
variables?: Record<string, any>;
|
||||
stream?: boolean;
|
||||
detail?: boolean;
|
||||
}) {
|
||||
variables = {
|
||||
...getSystemVariable({ timezone: user.timezone }),
|
||||
...variables
|
||||
};
|
||||
const runningModules = loadModules(modules, variables);
|
||||
|
||||
// let storeData: Record<string, any> = {}; // after module used
|
||||
@@ -390,6 +393,7 @@ export async function dispatchModules({
|
||||
};
|
||||
}
|
||||
|
||||
/* init store modules to running modules */
|
||||
function loadModules(
|
||||
modules: AppModuleItemType[],
|
||||
variables: Record<string, any>
|
||||
@@ -431,6 +435,7 @@ function loadModules(
|
||||
});
|
||||
}
|
||||
|
||||
/* sse response modules staus */
|
||||
export function responseStatus({
|
||||
res,
|
||||
status,
|
||||
@@ -451,6 +456,13 @@ export function responseStatus({
|
||||
});
|
||||
}
|
||||
|
||||
/* get system variable */
|
||||
export function getSystemVariable({ timezone }: { timezone: string }) {
|
||||
return {
|
||||
cTime: getSystemTime(timezone)
|
||||
};
|
||||
}
|
||||
|
||||
export const config = {
|
||||
api: {
|
||||
bodyParser: {
|
||||
|
@@ -10,7 +10,7 @@ import { axiosConfig, getAIChatApi, openaiBaseUrl } from '@/service/lib/openai';
|
||||
/* update user info */
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse<any>) {
|
||||
try {
|
||||
const { avatar, openaiAccount } = req.body as UserUpdateParams;
|
||||
const { avatar, timezone, openaiAccount } = req.body as UserUpdateParams;
|
||||
|
||||
const { userId } = await authUser({ req, authToken: true });
|
||||
|
||||
@@ -46,6 +46,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
|
||||
},
|
||||
{
|
||||
...(avatar && { avatar }),
|
||||
...(timezone && { timezone }),
|
||||
openaiAccount: openaiAccount?.key ? openaiAccount : null
|
||||
}
|
||||
);
|
||||
|
@@ -57,48 +57,7 @@ const AppSchema = new Schema({
|
||||
default: []
|
||||
},
|
||||
// 弃
|
||||
chat: {
|
||||
relatedKbs: {
|
||||
type: [Schema.Types.ObjectId],
|
||||
ref: 'kb',
|
||||
default: []
|
||||
},
|
||||
searchSimilarity: {
|
||||
type: Number,
|
||||
default: 0.4
|
||||
},
|
||||
searchLimit: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
searchEmptyText: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
systemPrompt: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
limitPrompt: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
maxToken: {
|
||||
type: Number,
|
||||
default: 4000,
|
||||
min: 100
|
||||
},
|
||||
temperature: {
|
||||
type: Number,
|
||||
min: 0,
|
||||
max: 10,
|
||||
default: 0
|
||||
},
|
||||
chatModel: {
|
||||
// 聊天时使用的模型
|
||||
type: String
|
||||
}
|
||||
}
|
||||
chat: Object
|
||||
});
|
||||
|
||||
try {
|
||||
|
@@ -49,6 +49,10 @@ const UserSchema = new Schema({
|
||||
key: String,
|
||||
baseUrl: String
|
||||
}
|
||||
},
|
||||
timezone: {
|
||||
type: String,
|
||||
default: 'Asia/Shanghai'
|
||||
}
|
||||
});
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import type { NextApiRequest } from 'next';
|
||||
import Cookie from 'cookie';
|
||||
import { App, OpenApi, User, OutLink, KB } from '../mongo';
|
||||
import type { AppSchema } from '@/types/mongoSchema';
|
||||
import type { AppSchema, UserModelSchema } from '@/types/mongoSchema';
|
||||
import { ERROR_ENUM } from '../errorCode';
|
||||
import { authJWT } from './tools';
|
||||
|
||||
@@ -25,7 +25,10 @@ export const authCookieToken = async (cookie?: string, token?: string): Promise<
|
||||
|
||||
/* auth balance */
|
||||
export const authBalanceByUid = async (uid: string) => {
|
||||
const user = await User.findById(uid);
|
||||
const user = await User.findById<UserModelSchema>(
|
||||
uid,
|
||||
'_id username balance openaiAccount timezone'
|
||||
);
|
||||
if (!user) {
|
||||
return Promise.reject(ERROR_ENUM.unAuthorization);
|
||||
}
|
||||
|
1
client/src/types/mongoSchema.d.ts
vendored
1
client/src/types/mongoSchema.d.ts
vendored
@@ -17,6 +17,7 @@ export interface UserModelSchema {
|
||||
inviterId?: string;
|
||||
openaiKey: string;
|
||||
createTime: number;
|
||||
timezone: string;
|
||||
openaiAccount?: {
|
||||
key: string;
|
||||
baseUrl: string;
|
||||
|
2
client/src/types/user.d.ts
vendored
2
client/src/types/user.d.ts
vendored
@@ -5,6 +5,7 @@ export interface UserType {
|
||||
username: string;
|
||||
avatar: string;
|
||||
balance: number;
|
||||
timezone: string;
|
||||
promotionRate: UserModelSchema['promotionRate'];
|
||||
openaiAccount: UserModelSchema['openaiAccount'];
|
||||
}
|
||||
@@ -12,6 +13,7 @@ export interface UserType {
|
||||
export interface UserUpdateParams {
|
||||
balance?: number;
|
||||
avatar?: string;
|
||||
timezone?: string;
|
||||
openaiAccount?: UserModelSchema['openaiAccount'];
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,12 @@
|
||||
import { PRICE_SCALE } from '@/constants/common';
|
||||
import { loginOut } from '@/api/user';
|
||||
import timezones from 'timezones-list';
|
||||
import dayjs from 'dayjs';
|
||||
import utc from 'dayjs/plugin/utc';
|
||||
import timezone from 'dayjs/plugin/timezone';
|
||||
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
|
||||
const tokenKey = 'token';
|
||||
export const clearToken = () => {
|
||||
@@ -24,3 +31,76 @@ export const getToken = () => {
|
||||
export const formatPrice = (val = 0, multiple = 1) => {
|
||||
return Number(((val / PRICE_SCALE) * multiple).toFixed(10));
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the offset from UTC in hours for the current locale.
|
||||
* @param {string} timeZone Timezone to get offset for
|
||||
* @returns {number} The offset from UTC in hours.
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
export const getTimezoneOffset = (timeZone: string): number => {
|
||||
const now = new Date();
|
||||
const tzString = now.toLocaleString('en-US', {
|
||||
timeZone
|
||||
});
|
||||
const localString = now.toLocaleString('en-US');
|
||||
const diff = (Date.parse(localString) - Date.parse(tzString)) / 3600000;
|
||||
const offset = diff + now.getTimezoneOffset() / 60;
|
||||
return -offset;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a list of timezones sorted by their offset from UTC.
|
||||
* @returns {object[]} A list of the given timezones sorted by their offset from UTC.
|
||||
*
|
||||
* Generated by Trelent
|
||||
*/
|
||||
export const timezoneList = () => {
|
||||
const result = timezones
|
||||
.map((timezone) => {
|
||||
try {
|
||||
let display = dayjs().tz(timezone.tzCode).format('Z');
|
||||
|
||||
return {
|
||||
name: `(UTC${display}) ${timezone.tzCode}`,
|
||||
value: timezone.tzCode,
|
||||
time: getTimezoneOffset(timezone.tzCode)
|
||||
};
|
||||
} catch (e) {}
|
||||
})
|
||||
.filter((item) => item);
|
||||
|
||||
result.sort((a, b) => {
|
||||
if (!a || !b) return 0;
|
||||
if (a.time > b.time) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (b.time > a.time) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return [
|
||||
{
|
||||
name: 'UTC',
|
||||
time: 0,
|
||||
value: 'UTC'
|
||||
},
|
||||
...result
|
||||
] as {
|
||||
name: string;
|
||||
value: string;
|
||||
time: number;
|
||||
}[];
|
||||
};
|
||||
|
||||
export const getSystemTime = (timeZone: string) => {
|
||||
const timezoneDiff = getTimezoneOffset(timeZone);
|
||||
const now = Date.now();
|
||||
const targetTime = now + timezoneDiff * 60 * 60 * 1000;
|
||||
return dayjs(targetTime).format('YYYY-MM-DD HH:mm:ss');
|
||||
};
|
||||
|
Reference in New Issue
Block a user