diff --git a/relay/channel/aws/constants.go b/relay/channel/aws/constants.go
index 72d0f9890..5ac7ce998 100644
--- a/relay/channel/aws/constants.go
+++ b/relay/channel/aws/constants.go
@@ -21,6 +21,10 @@ var awsModelIDMap = map[string]string{
"nova-lite-v1:0": "amazon.nova-lite-v1:0",
"nova-pro-v1:0": "amazon.nova-pro-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{
@@ -82,10 +86,27 @@ var awsModelCanCrossRegionMap = map[string]map[string]bool{
"apac": true,
},
"amazon.nova-premier-v1:0": {
+ "us": true,
+ },
+ "amazon.nova-canvas-v1:0": {
"us": true,
"eu": 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{
"us": "us",
diff --git a/relay/claude_handler.go b/relay/claude_handler.go
index dbdc6ee1c..59d12abe4 100644
--- a/relay/claude_handler.go
+++ b/relay/claude_handler.go
@@ -6,6 +6,7 @@ import (
"io"
"net/http"
"one-api/common"
+ "one-api/constant"
"one-api/dto"
relaycommon "one-api/relay/common"
"one-api/relay/helper"
@@ -69,6 +70,31 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
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
if model_setting.GetGlobalSettings().PassThroughRequestEnabled || info.ChannelSetting.PassThroughBodyEnabled {
body, err := common.GetRequestBody(c)
diff --git a/relay/gemini_handler.go b/relay/gemini_handler.go
index 0252d6578..1410da606 100644
--- a/relay/gemini_handler.go
+++ b/relay/gemini_handler.go
@@ -6,6 +6,7 @@ import (
"io"
"net/http"
"one-api/common"
+ "one-api/constant"
"one-api/dto"
"one-api/logger"
"one-api/relay/channel/gemini"
@@ -94,6 +95,32 @@ func GeminiHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
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
if request.SystemInstructions != nil {
hasContent := false
diff --git a/web/jsconfig.json b/web/jsconfig.json
new file mode 100644
index 000000000..ced4d0543
--- /dev/null
+++ b/web/jsconfig.json
@@ -0,0 +1,9 @@
+{
+ "compilerOptions": {
+ "baseUrl": "./",
+ "paths": {
+ "@/*": ["src/*"]
+ }
+ },
+ "include": ["src/**/*"]
+}
\ No newline at end of file
diff --git a/web/src/components/layout/headerbar/UserArea.jsx b/web/src/components/layout/headerbar/UserArea.jsx
index 8ea70f47f..9fc011da1 100644
--- a/web/src/components/layout/headerbar/UserArea.jsx
+++ b/web/src/components/layout/headerbar/UserArea.jsx
@@ -17,7 +17,7 @@ along with this program. If not, see .
For commercial licensing, please contact support@quantumnous.com
*/
-import React from 'react';
+import React, { useRef } from 'react';
import { Link } from 'react-router-dom';
import { Avatar, Button, Dropdown, Typography } from '@douyinfe/semi-ui';
import { ChevronDown } from 'lucide-react';
@@ -39,6 +39,7 @@ const UserArea = ({
navigate,
t,
}) => {
+ const dropdownRef = useRef(null);
if (isLoading) {
return (
- {
- navigate('/console/personal');
- }}
- 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'
- >
-
-
- {t('个人设置')}
-
-
- {
- navigate('/console/token');
- }}
- 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'
- >
-
-
- {t('令牌管理')}
-
-
- {
- navigate('/console/topup');
- }}
- 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'
- >
-
-
- {t('钱包管理')}
-
-
-
-
-
- {t('退出')}
-
-
-
- }
- >
-
-
+
+ {userState.user.username[0].toUpperCase()}
+
+
+
+ {userState.user.username}
+
+
+
+
+
+
);
} else {
const showRegisterButton = !isSelfUseMode;
diff --git a/web/src/components/settings/personal/cards/NotificationSettings.jsx b/web/src/components/settings/personal/cards/NotificationSettings.jsx
index 0b097eaff..aad612d2c 100644
--- a/web/src/components/settings/personal/cards/NotificationSettings.jsx
+++ b/web/src/components/settings/personal/cards/NotificationSettings.jsx
@@ -44,6 +44,7 @@ import CodeViewer from '../../../playground/CodeViewer';
import { StatusContext } from '../../../../context/Status';
import { UserContext } from '../../../../context/User';
import { useUserPermissions } from '../../../../hooks/common/useUserPermissions';
+import { useSidebar } from '../../../../hooks/common/useSidebar';
const NotificationSettings = ({
t,
@@ -97,6 +98,9 @@ const NotificationSettings = ({
isSidebarModuleAllowed,
} = useUserPermissions();
+ // 使用useSidebar钩子获取刷新方法
+ const { refreshUserConfig } = useSidebar();
+
// 左侧边栏设置处理函数
const handleSectionChange = (sectionKey) => {
return (checked) => {
@@ -132,6 +136,9 @@ const NotificationSettings = ({
});
if (res.data.success) {
showSuccess(t('侧边栏设置保存成功'));
+
+ // 刷新useSidebar钩子中的用户配置,实现实时更新
+ await refreshUserConfig();
} else {
showError(res.data.message);
}
@@ -334,7 +341,7 @@ const NotificationSettings = ({
loading={sidebarLoading}
className='!rounded-lg'
>
- {t('保存边栏设置')}
+ {t('保存设置')}
>
) : (
diff --git a/web/src/components/table/mj-logs/MjLogsFilters.jsx b/web/src/components/table/mj-logs/MjLogsFilters.jsx
index 44c6bcfcd..6db96e791 100644
--- a/web/src/components/table/mj-logs/MjLogsFilters.jsx
+++ b/web/src/components/table/mj-logs/MjLogsFilters.jsx
@@ -21,6 +21,8 @@ import React from 'react';
import { Button, Form } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
+import { DATE_RANGE_PRESETS } from '../../../constants/console.constants';
+
const MjLogsFilters = ({
formInitValues,
setFormApi,
@@ -54,6 +56,11 @@ const MjLogsFilters = ({
showClear
pure
size='small'
+ presets={DATE_RANGE_PRESETS.map(preset => ({
+ text: t(preset.text),
+ start: preset.start(),
+ end: preset.end()
+ }))}
/>
diff --git a/web/src/components/table/task-logs/TaskLogsFilters.jsx b/web/src/components/table/task-logs/TaskLogsFilters.jsx
index d5e081ab7..e27cea867 100644
--- a/web/src/components/table/task-logs/TaskLogsFilters.jsx
+++ b/web/src/components/table/task-logs/TaskLogsFilters.jsx
@@ -21,6 +21,8 @@ import React from 'react';
import { Button, Form } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
+import { DATE_RANGE_PRESETS } from '../../../constants/console.constants';
+
const TaskLogsFilters = ({
formInitValues,
setFormApi,
@@ -54,6 +56,11 @@ const TaskLogsFilters = ({
showClear
pure
size='small'
+ presets={DATE_RANGE_PRESETS.map(preset => ({
+ text: t(preset.text),
+ start: preset.start(),
+ end: preset.end()
+ }))}
/>
diff --git a/web/src/components/table/usage-logs/UsageLogsFilters.jsx b/web/src/components/table/usage-logs/UsageLogsFilters.jsx
index f76ec823e..58e5a4692 100644
--- a/web/src/components/table/usage-logs/UsageLogsFilters.jsx
+++ b/web/src/components/table/usage-logs/UsageLogsFilters.jsx
@@ -21,6 +21,8 @@ import React from 'react';
import { Button, Form } from '@douyinfe/semi-ui';
import { IconSearch } from '@douyinfe/semi-icons';
+import { DATE_RANGE_PRESETS } from '../../../constants/console.constants';
+
const LogsFilters = ({
formInitValues,
setFormApi,
@@ -55,6 +57,11 @@ const LogsFilters = ({
showClear
pure
size='small'
+ presets={DATE_RANGE_PRESETS.map(preset => ({
+ text: t(preset.text),
+ start: preset.start(),
+ end: preset.end()
+ }))}
/>
diff --git a/web/src/constants/console.constants.js b/web/src/constants/console.constants.js
new file mode 100644
index 000000000..23ee1e17f
--- /dev/null
+++ b/web/src/constants/console.constants.js
@@ -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 .
+
+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()
+ },
+];
diff --git a/web/src/hooks/common/useSidebar.js b/web/src/hooks/common/useSidebar.js
index 5dce44f9e..13d76fd86 100644
--- a/web/src/hooks/common/useSidebar.js
+++ b/web/src/hooks/common/useSidebar.js
@@ -21,6 +21,10 @@ import { useState, useEffect, useMemo, useContext } from 'react';
import { StatusContext } from '../../context/Status';
import { API } from '../../helpers';
+// 创建一个全局事件系统来同步所有useSidebar实例
+const sidebarEventTarget = new EventTarget();
+const SIDEBAR_REFRESH_EVENT = 'sidebar-refresh';
+
export const useSidebar = () => {
const [statusState] = useContext(StatusContext);
const [userConfig, setUserConfig] = useState(null);
@@ -124,9 +128,12 @@ export const useSidebar = () => {
// 刷新用户配置的方法(供外部调用)
const refreshUserConfig = async () => {
- if (Object.keys(adminConfig).length > 0) {
+ if (Object.keys(adminConfig).length > 0) {
await loadUserConfig();
}
+
+ // 触发全局刷新事件,通知所有useSidebar实例更新
+ sidebarEventTarget.dispatchEvent(new CustomEvent(SIDEBAR_REFRESH_EVENT));
};
// 加载用户配置
@@ -137,6 +144,21 @@ export const useSidebar = () => {
}
}, [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 result = {};
diff --git a/web/src/hooks/dashboard/useDashboardStats.jsx b/web/src/hooks/dashboard/useDashboardStats.jsx
index aa9677a50..dbf3b67e7 100644
--- a/web/src/hooks/dashboard/useDashboardStats.jsx
+++ b/web/src/hooks/dashboard/useDashboardStats.jsx
@@ -102,7 +102,7 @@ export const useDashboardStats = (
},
{
title: t('统计Tokens'),
- value: isNaN(consumeTokens) ? 0 : consumeTokens,
+ value: isNaN(consumeTokens) ? 0 : consumeTokens.toLocaleString(),
icon: ,
avatarColor: 'pink',
trendData: trendData.tokens,
diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json
index b04698950..ceb0f2d35 100644
--- a/web/src/i18n/locales/en.json
+++ b/web/src/i18n/locales/en.json
@@ -2099,6 +2099,11 @@
"优惠": "Discount",
"折": "% off",
"节省": "Save",
+ "今天": "Today",
+ "近 7 天": "Last 7 Days",
+ "本周": "This Week",
+ "本月": "This Month",
+ "近 30 天": "Last 30 Days",
"代理设置": "Proxy Settings",
"更新Worker设置": "Update Worker Settings",
"SSRF防护设置": "SSRF Protection Settings",
diff --git a/web/vite.config.js b/web/vite.config.js
index 3515dce7b..d57fd9d9b 100644
--- a/web/vite.config.js
+++ b/web/vite.config.js
@@ -20,10 +20,16 @@ For commercial licensing, please contact support@quantumnous.com
import react from '@vitejs/plugin-react';
import { defineConfig, transformWithEsbuild } from 'vite';
import pkg from '@douyinfe/vite-plugin-semi';
+import path from 'path';
const { vitePluginSemi } = pkg;
// https://vitejs.dev/config/
export default defineConfig({
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
plugins: [
{
name: 'treat-js-files-as-jsx',