Merge remote-tracking branch 'origin/main'
This commit is contained in:
@@ -21,6 +21,10 @@ var awsModelIDMap = map[string]string{
|
|||||||
"nova-lite-v1:0": "amazon.nova-lite-v1:0",
|
"nova-lite-v1:0": "amazon.nova-lite-v1:0",
|
||||||
"nova-pro-v1:0": "amazon.nova-pro-v1:0",
|
"nova-pro-v1:0": "amazon.nova-pro-v1:0",
|
||||||
"nova-premier-v1:0": "amazon.nova-premier-v1:0",
|
"nova-premier-v1:0": "amazon.nova-premier-v1:0",
|
||||||
|
"nova-canvas-v1:0": "amazon.nova-canvas-v1:0",
|
||||||
|
"nova-reel-v1:0": "amazon.nova-reel-v1:0",
|
||||||
|
"nova-reel-v1:1": "amazon.nova-reel-v1:1",
|
||||||
|
"nova-sonic-v1:0": "amazon.nova-sonic-v1:0",
|
||||||
}
|
}
|
||||||
|
|
||||||
var awsModelCanCrossRegionMap = map[string]map[string]bool{
|
var awsModelCanCrossRegionMap = map[string]map[string]bool{
|
||||||
@@ -82,10 +86,27 @@ var awsModelCanCrossRegionMap = map[string]map[string]bool{
|
|||||||
"apac": true,
|
"apac": true,
|
||||||
},
|
},
|
||||||
"amazon.nova-premier-v1:0": {
|
"amazon.nova-premier-v1:0": {
|
||||||
|
"us": true,
|
||||||
|
},
|
||||||
|
"amazon.nova-canvas-v1:0": {
|
||||||
"us": true,
|
"us": true,
|
||||||
"eu": true,
|
"eu": true,
|
||||||
"apac": true,
|
"apac": true,
|
||||||
}}
|
},
|
||||||
|
"amazon.nova-reel-v1:0": {
|
||||||
|
"us": true,
|
||||||
|
"eu": true,
|
||||||
|
"apac": true,
|
||||||
|
},
|
||||||
|
"amazon.nova-reel-v1:1": {
|
||||||
|
"us": true,
|
||||||
|
},
|
||||||
|
"amazon.nova-sonic-v1:0": {
|
||||||
|
"us": true,
|
||||||
|
"eu": true,
|
||||||
|
"apac": true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
var awsRegionCrossModelPrefixMap = map[string]string{
|
var awsRegionCrossModelPrefixMap = map[string]string{
|
||||||
"us": "us",
|
"us": "us",
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
|
"one-api/constant"
|
||||||
"one-api/dto"
|
"one-api/dto"
|
||||||
relaycommon "one-api/relay/common"
|
relaycommon "one-api/relay/common"
|
||||||
"one-api/relay/helper"
|
"one-api/relay/helper"
|
||||||
@@ -69,6 +70,31 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
|
|||||||
info.UpstreamModelName = request.Model
|
info.UpstreamModelName = request.Model
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if info.ChannelSetting.SystemPrompt != "" {
|
||||||
|
if request.System == nil {
|
||||||
|
request.SetStringSystem(info.ChannelSetting.SystemPrompt)
|
||||||
|
} else if info.ChannelSetting.SystemPromptOverride {
|
||||||
|
common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true)
|
||||||
|
if request.IsStringSystem() {
|
||||||
|
existing := strings.TrimSpace(request.GetStringSystem())
|
||||||
|
if existing == "" {
|
||||||
|
request.SetStringSystem(info.ChannelSetting.SystemPrompt)
|
||||||
|
} else {
|
||||||
|
request.SetStringSystem(info.ChannelSetting.SystemPrompt + "\n" + existing)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
systemContents := request.ParseSystem()
|
||||||
|
newSystem := dto.ClaudeMediaMessage{Type: dto.ContentTypeText}
|
||||||
|
newSystem.SetText(info.ChannelSetting.SystemPrompt)
|
||||||
|
if len(systemContents) == 0 {
|
||||||
|
request.System = []dto.ClaudeMediaMessage{newSystem}
|
||||||
|
} else {
|
||||||
|
request.System = append([]dto.ClaudeMediaMessage{newSystem}, systemContents...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var requestBody io.Reader
|
var requestBody io.Reader
|
||||||
if model_setting.GetGlobalSettings().PassThroughRequestEnabled || info.ChannelSetting.PassThroughBodyEnabled {
|
if model_setting.GetGlobalSettings().PassThroughRequestEnabled || info.ChannelSetting.PassThroughBodyEnabled {
|
||||||
body, err := common.GetRequestBody(c)
|
body, err := common.GetRequestBody(c)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"one-api/common"
|
"one-api/common"
|
||||||
|
"one-api/constant"
|
||||||
"one-api/dto"
|
"one-api/dto"
|
||||||
"one-api/logger"
|
"one-api/logger"
|
||||||
"one-api/relay/channel/gemini"
|
"one-api/relay/channel/gemini"
|
||||||
@@ -94,6 +95,32 @@ func GeminiHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
|
|||||||
|
|
||||||
adaptor.Init(info)
|
adaptor.Init(info)
|
||||||
|
|
||||||
|
if info.ChannelSetting.SystemPrompt != "" {
|
||||||
|
if request.SystemInstructions == nil {
|
||||||
|
request.SystemInstructions = &dto.GeminiChatContent{
|
||||||
|
Parts: []dto.GeminiPart{
|
||||||
|
{Text: info.ChannelSetting.SystemPrompt},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else if len(request.SystemInstructions.Parts) == 0 {
|
||||||
|
request.SystemInstructions.Parts = []dto.GeminiPart{{Text: info.ChannelSetting.SystemPrompt}}
|
||||||
|
} else if info.ChannelSetting.SystemPromptOverride {
|
||||||
|
common.SetContextKey(c, constant.ContextKeySystemPromptOverride, true)
|
||||||
|
merged := false
|
||||||
|
for i := range request.SystemInstructions.Parts {
|
||||||
|
if request.SystemInstructions.Parts[i].Text == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
request.SystemInstructions.Parts[i].Text = info.ChannelSetting.SystemPrompt + "\n" + request.SystemInstructions.Parts[i].Text
|
||||||
|
merged = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !merged {
|
||||||
|
request.SystemInstructions.Parts = append([]dto.GeminiPart{{Text: info.ChannelSetting.SystemPrompt}}, request.SystemInstructions.Parts...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up empty system instruction
|
// Clean up empty system instruction
|
||||||
if request.SystemInstructions != nil {
|
if request.SystemInstructions != nil {
|
||||||
hasContent := false
|
hasContent := false
|
||||||
|
|||||||
9
web/jsconfig.json
Normal file
9
web/jsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"]
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
For commercial licensing, please contact support@quantumnous.com
|
For commercial licensing, please contact support@quantumnous.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useRef } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { Avatar, Button, Dropdown, Typography } from '@douyinfe/semi-ui';
|
import { Avatar, Button, Dropdown, Typography } from '@douyinfe/semi-ui';
|
||||||
import { ChevronDown } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
@@ -39,6 +39,7 @@ const UserArea = ({
|
|||||||
navigate,
|
navigate,
|
||||||
t,
|
t,
|
||||||
}) => {
|
}) => {
|
||||||
|
const dropdownRef = useRef(null);
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
<SkeletonWrapper
|
<SkeletonWrapper
|
||||||
@@ -52,90 +53,93 @@ const UserArea = ({
|
|||||||
|
|
||||||
if (userState.user) {
|
if (userState.user) {
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<div className='relative' ref={dropdownRef}>
|
||||||
position='bottomRight'
|
<Dropdown
|
||||||
render={
|
position='bottomRight'
|
||||||
<Dropdown.Menu className='!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600'>
|
getPopupContainer={() => dropdownRef.current}
|
||||||
<Dropdown.Item
|
render={
|
||||||
onClick={() => {
|
<Dropdown.Menu className='!bg-semi-color-bg-overlay !border-semi-color-border !shadow-lg !rounded-lg dark:!bg-gray-700 dark:!border-gray-600'>
|
||||||
navigate('/console/personal');
|
<Dropdown.Item
|
||||||
}}
|
onClick={() => {
|
||||||
className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-blue-500 dark:hover:!text-white'
|
navigate('/console/personal');
|
||||||
>
|
}}
|
||||||
<div className='flex items-center gap-2'>
|
className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-blue-500 dark:hover:!text-white'
|
||||||
<IconUserSetting
|
>
|
||||||
size='small'
|
<div className='flex items-center gap-2'>
|
||||||
className='text-gray-500 dark:text-gray-400'
|
<IconUserSetting
|
||||||
/>
|
size='small'
|
||||||
<span>{t('个人设置')}</span>
|
className='text-gray-500 dark:text-gray-400'
|
||||||
</div>
|
/>
|
||||||
</Dropdown.Item>
|
<span>{t('个人设置')}</span>
|
||||||
<Dropdown.Item
|
</div>
|
||||||
onClick={() => {
|
</Dropdown.Item>
|
||||||
navigate('/console/token');
|
<Dropdown.Item
|
||||||
}}
|
onClick={() => {
|
||||||
className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-blue-500 dark:hover:!text-white'
|
navigate('/console/token');
|
||||||
>
|
}}
|
||||||
<div className='flex items-center gap-2'>
|
className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-blue-500 dark:hover:!text-white'
|
||||||
<IconKey
|
>
|
||||||
size='small'
|
<div className='flex items-center gap-2'>
|
||||||
className='text-gray-500 dark:text-gray-400'
|
<IconKey
|
||||||
/>
|
size='small'
|
||||||
<span>{t('令牌管理')}</span>
|
className='text-gray-500 dark:text-gray-400'
|
||||||
</div>
|
/>
|
||||||
</Dropdown.Item>
|
<span>{t('令牌管理')}</span>
|
||||||
<Dropdown.Item
|
</div>
|
||||||
onClick={() => {
|
</Dropdown.Item>
|
||||||
navigate('/console/topup');
|
<Dropdown.Item
|
||||||
}}
|
onClick={() => {
|
||||||
className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-blue-500 dark:hover:!text-white'
|
navigate('/console/topup');
|
||||||
>
|
}}
|
||||||
<div className='flex items-center gap-2'>
|
className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-blue-500 dark:hover:!text-white'
|
||||||
<IconCreditCard
|
>
|
||||||
size='small'
|
<div className='flex items-center gap-2'>
|
||||||
className='text-gray-500 dark:text-gray-400'
|
<IconCreditCard
|
||||||
/>
|
size='small'
|
||||||
<span>{t('钱包管理')}</span>
|
className='text-gray-500 dark:text-gray-400'
|
||||||
</div>
|
/>
|
||||||
</Dropdown.Item>
|
<span>{t('钱包管理')}</span>
|
||||||
<Dropdown.Item
|
</div>
|
||||||
onClick={logout}
|
</Dropdown.Item>
|
||||||
className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-red-500 dark:hover:!text-white'
|
<Dropdown.Item
|
||||||
>
|
onClick={logout}
|
||||||
<div className='flex items-center gap-2'>
|
className='!px-3 !py-1.5 !text-sm !text-semi-color-text-0 hover:!bg-semi-color-fill-1 dark:!text-gray-200 dark:hover:!bg-red-500 dark:hover:!text-white'
|
||||||
<IconExit
|
>
|
||||||
size='small'
|
<div className='flex items-center gap-2'>
|
||||||
className='text-gray-500 dark:text-gray-400'
|
<IconExit
|
||||||
/>
|
size='small'
|
||||||
<span>{t('退出')}</span>
|
className='text-gray-500 dark:text-gray-400'
|
||||||
</div>
|
/>
|
||||||
</Dropdown.Item>
|
<span>{t('退出')}</span>
|
||||||
</Dropdown.Menu>
|
</div>
|
||||||
}
|
</Dropdown.Item>
|
||||||
>
|
</Dropdown.Menu>
|
||||||
<Button
|
}
|
||||||
theme='borderless'
|
|
||||||
type='tertiary'
|
|
||||||
className='flex items-center gap-1.5 !p-1 !rounded-full hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700 !bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 dark:hover:!bg-semi-color-fill-2'
|
|
||||||
>
|
>
|
||||||
<Avatar
|
<Button
|
||||||
size='extra-small'
|
theme='borderless'
|
||||||
color={stringToColor(userState.user.username)}
|
type='tertiary'
|
||||||
className='mr-1'
|
className='flex items-center gap-1.5 !p-1 !rounded-full hover:!bg-semi-color-fill-1 dark:hover:!bg-gray-700 !bg-semi-color-fill-0 dark:!bg-semi-color-fill-1 dark:hover:!bg-semi-color-fill-2'
|
||||||
>
|
>
|
||||||
{userState.user.username[0].toUpperCase()}
|
<Avatar
|
||||||
</Avatar>
|
size='extra-small'
|
||||||
<span className='hidden md:inline'>
|
color={stringToColor(userState.user.username)}
|
||||||
<Typography.Text className='!text-xs !font-medium !text-semi-color-text-1 dark:!text-gray-300 mr-1'>
|
className='mr-1'
|
||||||
{userState.user.username}
|
>
|
||||||
</Typography.Text>
|
{userState.user.username[0].toUpperCase()}
|
||||||
</span>
|
</Avatar>
|
||||||
<ChevronDown
|
<span className='hidden md:inline'>
|
||||||
size={14}
|
<Typography.Text className='!text-xs !font-medium !text-semi-color-text-1 dark:!text-gray-300 mr-1'>
|
||||||
className='text-xs text-semi-color-text-2 dark:text-gray-400'
|
{userState.user.username}
|
||||||
/>
|
</Typography.Text>
|
||||||
</Button>
|
</span>
|
||||||
</Dropdown>
|
<ChevronDown
|
||||||
|
size={14}
|
||||||
|
className='text-xs text-semi-color-text-2 dark:text-gray-400'
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
</Dropdown>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const showRegisterButton = !isSelfUseMode;
|
const showRegisterButton = !isSelfUseMode;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import CodeViewer from '../../../playground/CodeViewer';
|
|||||||
import { StatusContext } from '../../../../context/Status';
|
import { StatusContext } from '../../../../context/Status';
|
||||||
import { UserContext } from '../../../../context/User';
|
import { UserContext } from '../../../../context/User';
|
||||||
import { useUserPermissions } from '../../../../hooks/common/useUserPermissions';
|
import { useUserPermissions } from '../../../../hooks/common/useUserPermissions';
|
||||||
|
import { useSidebar } from '../../../../hooks/common/useSidebar';
|
||||||
|
|
||||||
const NotificationSettings = ({
|
const NotificationSettings = ({
|
||||||
t,
|
t,
|
||||||
@@ -97,6 +98,9 @@ const NotificationSettings = ({
|
|||||||
isSidebarModuleAllowed,
|
isSidebarModuleAllowed,
|
||||||
} = useUserPermissions();
|
} = useUserPermissions();
|
||||||
|
|
||||||
|
// 使用useSidebar钩子获取刷新方法
|
||||||
|
const { refreshUserConfig } = useSidebar();
|
||||||
|
|
||||||
// 左侧边栏设置处理函数
|
// 左侧边栏设置处理函数
|
||||||
const handleSectionChange = (sectionKey) => {
|
const handleSectionChange = (sectionKey) => {
|
||||||
return (checked) => {
|
return (checked) => {
|
||||||
@@ -132,6 +136,9 @@ const NotificationSettings = ({
|
|||||||
});
|
});
|
||||||
if (res.data.success) {
|
if (res.data.success) {
|
||||||
showSuccess(t('侧边栏设置保存成功'));
|
showSuccess(t('侧边栏设置保存成功'));
|
||||||
|
|
||||||
|
// 刷新useSidebar钩子中的用户配置,实现实时更新
|
||||||
|
await refreshUserConfig();
|
||||||
} else {
|
} else {
|
||||||
showError(res.data.message);
|
showError(res.data.message);
|
||||||
}
|
}
|
||||||
@@ -334,7 +341,7 @@ const NotificationSettings = ({
|
|||||||
loading={sidebarLoading}
|
loading={sidebarLoading}
|
||||||
className='!rounded-lg'
|
className='!rounded-lg'
|
||||||
>
|
>
|
||||||
{t('保存边栏设置')}
|
{t('保存设置')}
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import React from 'react';
|
|||||||
import { Button, Form } from '@douyinfe/semi-ui';
|
import { Button, Form } from '@douyinfe/semi-ui';
|
||||||
import { IconSearch } from '@douyinfe/semi-icons';
|
import { IconSearch } from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
|
import { DATE_RANGE_PRESETS } from '../../../constants/console.constants';
|
||||||
|
|
||||||
const MjLogsFilters = ({
|
const MjLogsFilters = ({
|
||||||
formInitValues,
|
formInitValues,
|
||||||
setFormApi,
|
setFormApi,
|
||||||
@@ -54,6 +56,11 @@ const MjLogsFilters = ({
|
|||||||
showClear
|
showClear
|
||||||
pure
|
pure
|
||||||
size='small'
|
size='small'
|
||||||
|
presets={DATE_RANGE_PRESETS.map(preset => ({
|
||||||
|
text: t(preset.text),
|
||||||
|
start: preset.start(),
|
||||||
|
end: preset.end()
|
||||||
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import React from 'react';
|
|||||||
import { Button, Form } from '@douyinfe/semi-ui';
|
import { Button, Form } from '@douyinfe/semi-ui';
|
||||||
import { IconSearch } from '@douyinfe/semi-icons';
|
import { IconSearch } from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
|
import { DATE_RANGE_PRESETS } from '../../../constants/console.constants';
|
||||||
|
|
||||||
const TaskLogsFilters = ({
|
const TaskLogsFilters = ({
|
||||||
formInitValues,
|
formInitValues,
|
||||||
setFormApi,
|
setFormApi,
|
||||||
@@ -54,6 +56,11 @@ const TaskLogsFilters = ({
|
|||||||
showClear
|
showClear
|
||||||
pure
|
pure
|
||||||
size='small'
|
size='small'
|
||||||
|
presets={DATE_RANGE_PRESETS.map(preset => ({
|
||||||
|
text: t(preset.text),
|
||||||
|
start: preset.start(),
|
||||||
|
end: preset.end()
|
||||||
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import React from 'react';
|
|||||||
import { Button, Form } from '@douyinfe/semi-ui';
|
import { Button, Form } from '@douyinfe/semi-ui';
|
||||||
import { IconSearch } from '@douyinfe/semi-icons';
|
import { IconSearch } from '@douyinfe/semi-icons';
|
||||||
|
|
||||||
|
import { DATE_RANGE_PRESETS } from '../../../constants/console.constants';
|
||||||
|
|
||||||
const LogsFilters = ({
|
const LogsFilters = ({
|
||||||
formInitValues,
|
formInitValues,
|
||||||
setFormApi,
|
setFormApi,
|
||||||
@@ -55,6 +57,11 @@ const LogsFilters = ({
|
|||||||
showClear
|
showClear
|
||||||
pure
|
pure
|
||||||
size='small'
|
size='small'
|
||||||
|
presets={DATE_RANGE_PRESETS.map(preset => ({
|
||||||
|
text: t(preset.text),
|
||||||
|
start: preset.start(),
|
||||||
|
end: preset.end()
|
||||||
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
49
web/src/constants/console.constants.js
Normal file
49
web/src/constants/console.constants.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
Copyright (C) 2025 QuantumNous
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License as
|
||||||
|
published by the Free Software Foundation, either version 3 of the
|
||||||
|
License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
For commercial licensing, please contact support@quantumnous.com
|
||||||
|
*/
|
||||||
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
// ========== 日期预设常量 ==========
|
||||||
|
export const DATE_RANGE_PRESETS = [
|
||||||
|
{
|
||||||
|
text: '今天',
|
||||||
|
start: () => dayjs().startOf('day').toDate(),
|
||||||
|
end: () => dayjs().endOf('day').toDate()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '近 7 天',
|
||||||
|
start: () => dayjs().subtract(6, 'day').startOf('day').toDate(),
|
||||||
|
end: () => dayjs().endOf('day').toDate()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '本周',
|
||||||
|
start: () => dayjs().startOf('week').toDate(),
|
||||||
|
end: () => dayjs().endOf('week').toDate()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '近 30 天',
|
||||||
|
start: () => dayjs().subtract(29, 'day').startOf('day').toDate(),
|
||||||
|
end: () => dayjs().endOf('day').toDate()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '本月',
|
||||||
|
start: () => dayjs().startOf('month').toDate(),
|
||||||
|
end: () => dayjs().endOf('month').toDate()
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -21,6 +21,10 @@ import { useState, useEffect, useMemo, useContext } from 'react';
|
|||||||
import { StatusContext } from '../../context/Status';
|
import { StatusContext } from '../../context/Status';
|
||||||
import { API } from '../../helpers';
|
import { API } from '../../helpers';
|
||||||
|
|
||||||
|
// 创建一个全局事件系统来同步所有useSidebar实例
|
||||||
|
const sidebarEventTarget = new EventTarget();
|
||||||
|
const SIDEBAR_REFRESH_EVENT = 'sidebar-refresh';
|
||||||
|
|
||||||
export const useSidebar = () => {
|
export const useSidebar = () => {
|
||||||
const [statusState] = useContext(StatusContext);
|
const [statusState] = useContext(StatusContext);
|
||||||
const [userConfig, setUserConfig] = useState(null);
|
const [userConfig, setUserConfig] = useState(null);
|
||||||
@@ -124,9 +128,12 @@ export const useSidebar = () => {
|
|||||||
|
|
||||||
// 刷新用户配置的方法(供外部调用)
|
// 刷新用户配置的方法(供外部调用)
|
||||||
const refreshUserConfig = async () => {
|
const refreshUserConfig = async () => {
|
||||||
if (Object.keys(adminConfig).length > 0) {
|
if (Object.keys(adminConfig).length > 0) {
|
||||||
await loadUserConfig();
|
await loadUserConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 触发全局刷新事件,通知所有useSidebar实例更新
|
||||||
|
sidebarEventTarget.dispatchEvent(new CustomEvent(SIDEBAR_REFRESH_EVENT));
|
||||||
};
|
};
|
||||||
|
|
||||||
// 加载用户配置
|
// 加载用户配置
|
||||||
@@ -137,6 +144,21 @@ export const useSidebar = () => {
|
|||||||
}
|
}
|
||||||
}, [adminConfig]);
|
}, [adminConfig]);
|
||||||
|
|
||||||
|
// 监听全局刷新事件
|
||||||
|
useEffect(() => {
|
||||||
|
const handleRefresh = () => {
|
||||||
|
if (Object.keys(adminConfig).length > 0) {
|
||||||
|
loadUserConfig();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sidebarEventTarget.addEventListener(SIDEBAR_REFRESH_EVENT, handleRefresh);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
sidebarEventTarget.removeEventListener(SIDEBAR_REFRESH_EVENT, handleRefresh);
|
||||||
|
};
|
||||||
|
}, [adminConfig]);
|
||||||
|
|
||||||
// 计算最终的显示配置
|
// 计算最终的显示配置
|
||||||
const finalConfig = useMemo(() => {
|
const finalConfig = useMemo(() => {
|
||||||
const result = {};
|
const result = {};
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export const useDashboardStats = (
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('统计Tokens'),
|
title: t('统计Tokens'),
|
||||||
value: isNaN(consumeTokens) ? 0 : consumeTokens,
|
value: isNaN(consumeTokens) ? 0 : consumeTokens.toLocaleString(),
|
||||||
icon: <IconTextStroked />,
|
icon: <IconTextStroked />,
|
||||||
avatarColor: 'pink',
|
avatarColor: 'pink',
|
||||||
trendData: trendData.tokens,
|
trendData: trendData.tokens,
|
||||||
|
|||||||
@@ -2099,6 +2099,11 @@
|
|||||||
"优惠": "Discount",
|
"优惠": "Discount",
|
||||||
"折": "% off",
|
"折": "% off",
|
||||||
"节省": "Save",
|
"节省": "Save",
|
||||||
|
"今天": "Today",
|
||||||
|
"近 7 天": "Last 7 Days",
|
||||||
|
"本周": "This Week",
|
||||||
|
"本月": "This Month",
|
||||||
|
"近 30 天": "Last 30 Days",
|
||||||
"代理设置": "Proxy Settings",
|
"代理设置": "Proxy Settings",
|
||||||
"更新Worker设置": "Update Worker Settings",
|
"更新Worker设置": "Update Worker Settings",
|
||||||
"SSRF防护设置": "SSRF Protection Settings",
|
"SSRF防护设置": "SSRF Protection Settings",
|
||||||
|
|||||||
@@ -20,10 +20,16 @@ For commercial licensing, please contact support@quantumnous.com
|
|||||||
import react from '@vitejs/plugin-react';
|
import react from '@vitejs/plugin-react';
|
||||||
import { defineConfig, transformWithEsbuild } from 'vite';
|
import { defineConfig, transformWithEsbuild } from 'vite';
|
||||||
import pkg from '@douyinfe/vite-plugin-semi';
|
import pkg from '@douyinfe/vite-plugin-semi';
|
||||||
|
import path from 'path';
|
||||||
const { vitePluginSemi } = pkg;
|
const { vitePluginSemi } = pkg;
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
|
},
|
||||||
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
{
|
||||||
name: 'treat-js-files-as-jsx',
|
name: 'treat-js-files-as-jsx',
|
||||||
|
|||||||
Reference in New Issue
Block a user