Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
phsl
/
new-api
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Members
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
e94d6f15
authored
Sep 27, 2025
by
CaIon
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/main'
parents
1235216c
84d00fb7
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
285 additions
and
87 deletions
+285
-87
relay/channel/aws/constants.go
+22
-1
relay/claude_handler.go
+26
-0
relay/gemini_handler.go
+27
-0
web/jsconfig.json
+10
-0
web/src/components/layout/headerbar/UserArea.jsx
+87
-83
web/src/components/settings/personal/cards/NotificationSettings.jsx
+8
-1
web/src/components/table/mj-logs/MjLogsFilters.jsx
+7
-0
web/src/components/table/task-logs/TaskLogsFilters.jsx
+7
-0
web/src/components/table/usage-logs/UsageLogsFilters.jsx
+7
-0
web/src/constants/console.constants.js
+49
-0
web/src/hooks/common/useSidebar.js
+23
-1
web/src/hooks/dashboard/useDashboardStats.jsx
+1
-1
web/src/i18n/locales/en.json
+5
-0
web/vite.config.js
+6
-0
No files found.
relay/channel/aws/constants.go
View file @
e94d6f15
...
@@ -21,6 +21,10 @@ var awsModelIDMap = map[string]string{
...
@@ -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{
...
@@ -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
,
"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
,
"us"
:
true
,
"eu"
:
true
,
"eu"
:
true
,
"apac"
:
true
,
"apac"
:
true
,
}}
},
}
var
awsRegionCrossModelPrefixMap
=
map
[
string
]
string
{
var
awsRegionCrossModelPrefixMap
=
map
[
string
]
string
{
"us"
:
"us"
,
"us"
:
"us"
,
...
...
relay/claude_handler.go
View file @
e94d6f15
...
@@ -6,6 +6,7 @@ import (
...
@@ -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
...
@@ -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
)
...
...
relay/gemini_handler.go
View file @
e94d6f15
...
@@ -6,6 +6,7 @@ import (
...
@@ -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
...
@@ -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
...
...
web/jsconfig.json
0 → 100644
View file @
e94d6f15
{
"compilerOptions"
:
{
"baseUrl"
:
"./"
,
"paths"
:
{
"@/*"
:
[
"src/*"
]
}
},
"include"
:
[
"src/**/*"
]
}
\ No newline at end of file
web/src/components/layout/headerbar/UserArea.jsx
View file @
e94d6f15
...
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
...
@@ -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 = ({
...
@@ -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 = ({
...
@@ -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
;
...
...
web/src/components/settings/personal/cards/NotificationSettings.jsx
View file @
e94d6f15
...
@@ -44,6 +44,7 @@ import CodeViewer from '../../../playground/CodeViewer';
...
@@ -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 = ({
...
@@ -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 = ({
...
@@ -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 = ({
...
@@ -334,7 +341,7 @@ const NotificationSettings = ({
loading=
{
sidebarLoading
}
loading=
{
sidebarLoading
}
className=
'!rounded-lg'
className=
'!rounded-lg'
>
>
{
t
(
'保存
边栏
设置'
)
}
{
t
(
'保存设置'
)
}
</
Button
>
</
Button
>
</>
</>
)
:
(
)
:
(
...
...
web/src/components/table/mj-logs/MjLogsFilters.jsx
View file @
e94d6f15
...
@@ -21,6 +21,8 @@ import React from 'react';
...
@@ -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 = ({
...
@@ -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
>
...
...
web/src/components/table/task-logs/TaskLogsFilters.jsx
View file @
e94d6f15
...
@@ -21,6 +21,8 @@ import React from 'react';
...
@@ -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 = ({
...
@@ -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
>
...
...
web/src/components/table/usage-logs/UsageLogsFilters.jsx
View file @
e94d6f15
...
@@ -21,6 +21,8 @@ import React from 'react';
...
@@ -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 = ({
...
@@ -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
>
...
...
web/src/constants/console.constants.js
0 → 100644
View file @
e94d6f15
/*
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
()
},
];
web/src/hooks/common/useSidebar.js
View file @
e94d6f15
...
@@ -21,6 +21,10 @@ import { useState, useEffect, useMemo, useContext } from 'react';
...
@@ -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 = () => {
...
@@ -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 = () => {
...
@@ -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
=
{};
...
...
web/src/hooks/dashboard/useDashboardStats.jsx
View file @
e94d6f15
...
@@ -102,7 +102,7 @@ export const useDashboardStats = (
...
@@ -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
,
...
...
web/src/i18n/locales/en.json
View file @
e94d6f15
...
@@ -2099,6 +2099,11 @@
...
@@ -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"
,
...
...
web/vite.config.js
View file @
e94d6f15
...
@@ -20,10 +20,16 @@ For commercial licensing, please contact support@quantumnous.com
...
@@ -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'
,
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment