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
8ed3e4f0
authored
Sep 28, 2025
by
JoeyLearnsToCode
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'remotes/up-origin/main' into feat-channel-block-edit
parents
a23955a0
4bbd59c7
Hide whitespace changes
Inline
Side-by-side
Showing
36 changed files
with
712 additions
and
176 deletions
+712
-176
.github/workflows/linux-release.yml
+4
-4
.github/workflows/macos-release.yml
+2
-2
.github/workflows/windows-release.yml
+2
-2
common/sys_log.go
+32
-1
common/utils.go
+72
-0
constant/task.go
+4
-2
controller/channel.go
+84
-2
dto/gemini.go
+31
-0
dto/openai_request.go
+3
-2
main.go
+7
-2
relay/channel/aws/constants.go
+22
-1
relay/channel/task/vidu/adaptor.go
+6
-8
relay/channel/volcengine/adaptor.go
+13
-6
relay/channel/volcengine/constants.go
+5
-0
relay/channel/xunfei/relay-xunfei.go
+3
-4
relay/claude_handler.go
+26
-0
relay/common/relay_utils.go
+10
-26
relay/gemini_handler.go
+27
-0
web/jsconfig.json
+10
-0
web/src/components/common/markdown/MarkdownRenderer.jsx
+2
-2
web/src/components/layout/headerbar/UserArea.jsx
+87
-83
web/src/components/settings/personal/cards/NotificationSettings.jsx
+8
-1
web/src/components/table/channels/modals/EditChannelModal.jsx
+62
-8
web/src/components/table/channels/modals/MultiKeyManageModal.jsx
+42
-1
web/src/components/table/mj-logs/MjLogsFilters.jsx
+7
-0
web/src/components/table/task-logs/TaskLogsColumnDefs.jsx
+18
-3
web/src/components/table/task-logs/TaskLogsFilters.jsx
+7
-0
web/src/components/table/usage-logs/UsageLogsFilters.jsx
+7
-0
web/src/constants/common.constant.js
+2
-0
web/src/constants/console.constants.js
+49
-0
web/src/helpers/api.js
+8
-7
web/src/helpers/utils.jsx
+11
-7
web/src/hooks/common/useSidebar.js
+23
-1
web/src/hooks/dashboard/useDashboardStats.jsx
+1
-1
web/src/i18n/locales/en.json
+9
-0
web/vite.config.js
+6
-0
No files found.
.github/workflows/linux-release.yml
View file @
8ed3e4f0
...
@@ -38,21 +38,21 @@ jobs:
...
@@ -38,21 +38,21 @@ jobs:
-
name
:
Build Backend (amd64)
-
name
:
Build Backend (amd64)
run
:
|
run
:
|
go mod download
go mod download
go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o
one
-api
go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o
new
-api
-
name
:
Build Backend (arm64)
-
name
:
Build Backend (arm64)
run
:
|
run
:
|
sudo apt-get update
sudo apt-get update
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y gcc-aarch64-linux-gnu
DEBIAN_FRONTEND=noninteractive sudo apt-get install -y gcc-aarch64-linux-gnu
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o
one
-api-arm64
CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o
new
-api-arm64
-
name
:
Release
-
name
:
Release
uses
:
softprops/action-gh-release@v1
uses
:
softprops/action-gh-release@v1
if
:
startsWith(github.ref, 'refs/tags/')
if
:
startsWith(github.ref, 'refs/tags/')
with
:
with
:
files
:
|
files
:
|
one
-api
new
-api
one
-api-arm64
new
-api-arm64
draft
:
true
draft
:
true
generate_release_notes
:
true
generate_release_notes
:
true
env
:
env
:
...
...
.github/workflows/macos-release.yml
View file @
8ed3e4f0
...
@@ -39,12 +39,12 @@ jobs:
...
@@ -39,12 +39,12 @@ jobs:
-
name
:
Build Backend
-
name
:
Build Backend
run
:
|
run
:
|
go mod download
go mod download
go build -ldflags "-X 'one-api/common.Version=$(git describe --tags)'" -o
one
-api-macos
go build -ldflags "-X 'one-api/common.Version=$(git describe --tags)'" -o
new
-api-macos
-
name
:
Release
-
name
:
Release
uses
:
softprops/action-gh-release@v1
uses
:
softprops/action-gh-release@v1
if
:
startsWith(github.ref, 'refs/tags/')
if
:
startsWith(github.ref, 'refs/tags/')
with
:
with
:
files
:
one
-api-macos
files
:
new
-api-macos
draft
:
true
draft
:
true
generate_release_notes
:
true
generate_release_notes
:
true
env
:
env
:
...
...
.github/workflows/windows-release.yml
View file @
8ed3e4f0
...
@@ -41,12 +41,12 @@ jobs:
...
@@ -41,12 +41,12 @@ jobs:
-
name
:
Build Backend
-
name
:
Build Backend
run
:
|
run
:
|
go mod download
go mod download
go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)'" -o
one
-api.exe
go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)'" -o
new
-api.exe
-
name
:
Release
-
name
:
Release
uses
:
softprops/action-gh-release@v1
uses
:
softprops/action-gh-release@v1
if
:
startsWith(github.ref, 'refs/tags/')
if
:
startsWith(github.ref, 'refs/tags/')
with
:
with
:
files
:
one
-api.exe
files
:
new
-api.exe
draft
:
true
draft
:
true
generate_release_notes
:
true
generate_release_notes
:
true
env
:
env
:
...
...
common/sys_log.go
View file @
8ed3e4f0
...
@@ -2,9 +2,10 @@ package common
...
@@ -2,9 +2,10 @@ package common
import
(
import
(
"fmt"
"fmt"
"github.com/gin-gonic/gin"
"os"
"os"
"time"
"time"
"github.com/gin-gonic/gin"
)
)
func
SysLog
(
s
string
)
{
func
SysLog
(
s
string
)
{
...
@@ -22,3 +23,33 @@ func FatalLog(v ...any) {
...
@@ -22,3 +23,33 @@ func FatalLog(v ...any) {
_
,
_
=
fmt
.
Fprintf
(
gin
.
DefaultErrorWriter
,
"[FATAL] %v | %v
\n
"
,
t
.
Format
(
"2006/01/02 - 15:04:05"
),
v
)
_
,
_
=
fmt
.
Fprintf
(
gin
.
DefaultErrorWriter
,
"[FATAL] %v | %v
\n
"
,
t
.
Format
(
"2006/01/02 - 15:04:05"
),
v
)
os
.
Exit
(
1
)
os
.
Exit
(
1
)
}
}
func
LogStartupSuccess
(
startTime
time
.
Time
,
port
string
)
{
duration
:=
time
.
Since
(
startTime
)
durationMs
:=
duration
.
Milliseconds
()
// Get network IPs
networkIps
:=
GetNetworkIps
()
// Print blank line for spacing
fmt
.
Fprintf
(
gin
.
DefaultWriter
,
"
\n
"
)
// Print the main success message
fmt
.
Fprintf
(
gin
.
DefaultWriter
,
"
\0
33[32m%s %s
\0
33[0m ready in %d ms
\n
"
,
SystemName
,
Version
,
durationMs
)
fmt
.
Fprintf
(
gin
.
DefaultWriter
,
"
\n
"
)
// Skip fancy startup message in container environments
if
!
IsRunningInContainer
()
{
// Print local URL
fmt
.
Fprintf
(
gin
.
DefaultWriter
,
" ➜
\0
33[1mLocal:
\0
33[0m http://localhost:%s/
\n
"
,
port
)
}
// Print network URLs
for
_
,
ip
:=
range
networkIps
{
fmt
.
Fprintf
(
gin
.
DefaultWriter
,
" ➜
\0
33[1mNetwork:
\0
33[0m http://%s:%s/
\n
"
,
ip
,
port
)
}
// Print blank line for spacing
fmt
.
Fprintf
(
gin
.
DefaultWriter
,
"
\n
"
)
}
common/utils.go
View file @
8ed3e4f0
...
@@ -68,6 +68,78 @@ func GetIp() (ip string) {
...
@@ -68,6 +68,78 @@ func GetIp() (ip string) {
return
return
}
}
func
GetNetworkIps
()
[]
string
{
var
networkIps
[]
string
ips
,
err
:=
net
.
InterfaceAddrs
()
if
err
!=
nil
{
log
.
Println
(
err
)
return
networkIps
}
for
_
,
a
:=
range
ips
{
if
ipNet
,
ok
:=
a
.
(
*
net
.
IPNet
);
ok
&&
!
ipNet
.
IP
.
IsLoopback
()
{
if
ipNet
.
IP
.
To4
()
!=
nil
{
ip
:=
ipNet
.
IP
.
String
()
// Include common private network ranges
if
strings
.
HasPrefix
(
ip
,
"10."
)
||
strings
.
HasPrefix
(
ip
,
"172."
)
||
strings
.
HasPrefix
(
ip
,
"192.168."
)
{
networkIps
=
append
(
networkIps
,
ip
)
}
}
}
}
return
networkIps
}
// IsRunningInContainer detects if the application is running inside a container
func
IsRunningInContainer
()
bool
{
// Method 1: Check for .dockerenv file (Docker containers)
if
_
,
err
:=
os
.
Stat
(
"/.dockerenv"
);
err
==
nil
{
return
true
}
// Method 2: Check cgroup for container indicators
if
data
,
err
:=
os
.
ReadFile
(
"/proc/1/cgroup"
);
err
==
nil
{
content
:=
string
(
data
)
if
strings
.
Contains
(
content
,
"docker"
)
||
strings
.
Contains
(
content
,
"containerd"
)
||
strings
.
Contains
(
content
,
"kubepods"
)
||
strings
.
Contains
(
content
,
"/lxc/"
)
{
return
true
}
}
// Method 3: Check environment variables commonly set by container runtimes
containerEnvVars
:=
[]
string
{
"KUBERNETES_SERVICE_HOST"
,
"DOCKER_CONTAINER"
,
"container"
,
}
for
_
,
envVar
:=
range
containerEnvVars
{
if
os
.
Getenv
(
envVar
)
!=
""
{
return
true
}
}
// Method 4: Check if init process is not the traditional init
if
data
,
err
:=
os
.
ReadFile
(
"/proc/1/comm"
);
err
==
nil
{
comm
:=
strings
.
TrimSpace
(
string
(
data
))
// In containers, process 1 is often not "init" or "systemd"
if
comm
!=
"init"
&&
comm
!=
"systemd"
{
// Additional check: if it's a common container entrypoint
if
strings
.
Contains
(
comm
,
"docker"
)
||
strings
.
Contains
(
comm
,
"containerd"
)
||
strings
.
Contains
(
comm
,
"runc"
)
{
return
true
}
}
}
return
false
}
var
sizeKB
=
1024
var
sizeKB
=
1024
var
sizeMB
=
sizeKB
*
1024
var
sizeMB
=
sizeKB
*
1024
var
sizeGB
=
sizeMB
*
1024
var
sizeGB
=
sizeMB
*
1024
...
...
constant/task.go
View file @
8ed3e4f0
...
@@ -11,8 +11,10 @@ const (
...
@@ -11,8 +11,10 @@ const (
SunoActionMusic
=
"MUSIC"
SunoActionMusic
=
"MUSIC"
SunoActionLyrics
=
"LYRICS"
SunoActionLyrics
=
"LYRICS"
TaskActionGenerate
=
"generate"
TaskActionGenerate
=
"generate"
TaskActionTextGenerate
=
"textGenerate"
TaskActionTextGenerate
=
"textGenerate"
TaskActionFirstTailGenerate
=
"firstTailGenerate"
TaskActionReferenceGenerate
=
"referenceGenerate"
)
)
var
SunoModel2Action
=
map
[
string
]
string
{
var
SunoModel2Action
=
map
[
string
]
string
{
...
...
controller/channel.go
View file @
8ed3e4f0
...
@@ -188,6 +188,8 @@ func FetchUpstreamModels(c *gin.Context) {
...
@@ -188,6 +188,8 @@ func FetchUpstreamModels(c *gin.Context) {
url
=
fmt
.
Sprintf
(
"%s/v1beta/openai/models"
,
baseURL
)
// Remove key in url since we need to use AuthHeader
url
=
fmt
.
Sprintf
(
"%s/v1beta/openai/models"
,
baseURL
)
// Remove key in url since we need to use AuthHeader
case
constant
.
ChannelTypeAli
:
case
constant
.
ChannelTypeAli
:
url
=
fmt
.
Sprintf
(
"%s/compatible-mode/v1/models"
,
baseURL
)
url
=
fmt
.
Sprintf
(
"%s/compatible-mode/v1/models"
,
baseURL
)
case
constant
.
ChannelTypeZhipu_v4
:
url
=
fmt
.
Sprintf
(
"%s/api/paas/v4/models"
,
baseURL
)
default
:
default
:
url
=
fmt
.
Sprintf
(
"%s/v1/models"
,
baseURL
)
url
=
fmt
.
Sprintf
(
"%s/v1/models"
,
baseURL
)
}
}
...
@@ -1101,8 +1103,8 @@ func CopyChannel(c *gin.Context) {
...
@@ -1101,8 +1103,8 @@ func CopyChannel(c *gin.Context) {
// MultiKeyManageRequest represents the request for multi-key management operations
// MultiKeyManageRequest represents the request for multi-key management operations
type
MultiKeyManageRequest
struct
{
type
MultiKeyManageRequest
struct
{
ChannelId
int
`json:"channel_id"`
ChannelId
int
`json:"channel_id"`
Action
string
`json:"action"`
// "disable_key", "enable_key", "delete_disabled_keys", "get_key_status"
Action
string
`json:"action"`
// "disable_key", "enable_key", "delete_
key", "delete_
disabled_keys", "get_key_status"
KeyIndex
*
int
`json:"key_index,omitempty"`
// for disable_key
and enabl
e_key actions
KeyIndex
*
int
`json:"key_index,omitempty"`
// for disable_key
, enable_key, and delet
e_key actions
Page
int
`json:"page,omitempty"`
// for get_key_status pagination
Page
int
`json:"page,omitempty"`
// for get_key_status pagination
PageSize
int
`json:"page_size,omitempty"`
// for get_key_status pagination
PageSize
int
`json:"page_size,omitempty"`
// for get_key_status pagination
Status
*
int
`json:"status,omitempty"`
// for get_key_status filtering: 1=enabled, 2=manual_disabled, 3=auto_disabled, nil=all
Status
*
int
`json:"status,omitempty"`
// for get_key_status filtering: 1=enabled, 2=manual_disabled, 3=auto_disabled, nil=all
...
@@ -1430,6 +1432,86 @@ func ManageMultiKeys(c *gin.Context) {
...
@@ -1430,6 +1432,86 @@ func ManageMultiKeys(c *gin.Context) {
})
})
return
return
case
"delete_key"
:
if
request
.
KeyIndex
==
nil
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"未指定要删除的密钥索引"
,
})
return
}
keyIndex
:=
*
request
.
KeyIndex
if
keyIndex
<
0
||
keyIndex
>=
channel
.
ChannelInfo
.
MultiKeySize
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"密钥索引超出范围"
,
})
return
}
keys
:=
channel
.
GetKeys
()
var
remainingKeys
[]
string
var
newStatusList
=
make
(
map
[
int
]
int
)
var
newDisabledTime
=
make
(
map
[
int
]
int64
)
var
newDisabledReason
=
make
(
map
[
int
]
string
)
newIndex
:=
0
for
i
,
key
:=
range
keys
{
// 跳过要删除的密钥
if
i
==
keyIndex
{
continue
}
remainingKeys
=
append
(
remainingKeys
,
key
)
// 保留其他密钥的状态信息,重新索引
if
channel
.
ChannelInfo
.
MultiKeyStatusList
!=
nil
{
if
status
,
exists
:=
channel
.
ChannelInfo
.
MultiKeyStatusList
[
i
];
exists
&&
status
!=
1
{
newStatusList
[
newIndex
]
=
status
}
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledTime
!=
nil
{
if
t
,
exists
:=
channel
.
ChannelInfo
.
MultiKeyDisabledTime
[
i
];
exists
{
newDisabledTime
[
newIndex
]
=
t
}
}
if
channel
.
ChannelInfo
.
MultiKeyDisabledReason
!=
nil
{
if
r
,
exists
:=
channel
.
ChannelInfo
.
MultiKeyDisabledReason
[
i
];
exists
{
newDisabledReason
[
newIndex
]
=
r
}
}
newIndex
++
}
if
len
(
remainingKeys
)
==
0
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
false
,
"message"
:
"不能删除最后一个密钥"
,
})
return
}
// Update channel with remaining keys
channel
.
Key
=
strings
.
Join
(
remainingKeys
,
"
\n
"
)
channel
.
ChannelInfo
.
MultiKeySize
=
len
(
remainingKeys
)
channel
.
ChannelInfo
.
MultiKeyStatusList
=
newStatusList
channel
.
ChannelInfo
.
MultiKeyDisabledTime
=
newDisabledTime
channel
.
ChannelInfo
.
MultiKeyDisabledReason
=
newDisabledReason
err
=
channel
.
Update
()
if
err
!=
nil
{
common
.
ApiError
(
c
,
err
)
return
}
model
.
InitChannelCache
()
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"success"
:
true
,
"message"
:
"密钥已删除"
,
})
return
case
"delete_disabled_keys"
:
case
"delete_disabled_keys"
:
keys
:=
channel
.
GetKeys
()
keys
:=
channel
.
GetKeys
()
var
remainingKeys
[]
string
var
remainingKeys
[]
string
...
...
dto/gemini.go
View file @
8ed3e4f0
...
@@ -14,7 +14,30 @@ type GeminiChatRequest struct {
...
@@ -14,7 +14,30 @@ type GeminiChatRequest struct {
SafetySettings
[]
GeminiChatSafetySettings
`json:"safetySettings,omitempty"`
SafetySettings
[]
GeminiChatSafetySettings
`json:"safetySettings,omitempty"`
GenerationConfig
GeminiChatGenerationConfig
`json:"generationConfig,omitempty"`
GenerationConfig
GeminiChatGenerationConfig
`json:"generationConfig,omitempty"`
Tools
json
.
RawMessage
`json:"tools,omitempty"`
Tools
json
.
RawMessage
`json:"tools,omitempty"`
ToolConfig
*
ToolConfig
`json:"toolConfig,omitempty"`
SystemInstructions
*
GeminiChatContent
`json:"systemInstruction,omitempty"`
SystemInstructions
*
GeminiChatContent
`json:"systemInstruction,omitempty"`
CachedContent
string
`json:"cachedContent,omitempty"`
}
type
ToolConfig
struct
{
FunctionCallingConfig
*
FunctionCallingConfig
`json:"functionCallingConfig,omitempty"`
RetrievalConfig
*
RetrievalConfig
`json:"retrievalConfig,omitempty"`
}
type
FunctionCallingConfig
struct
{
Mode
FunctionCallingConfigMode
`json:"mode,omitempty"`
AllowedFunctionNames
[]
string
`json:"allowedFunctionNames,omitempty"`
}
type
FunctionCallingConfigMode
string
type
RetrievalConfig
struct
{
LatLng
*
LatLng
`json:"latLng,omitempty"`
LanguageCode
string
`json:"languageCode,omitempty"`
}
type
LatLng
struct
{
Latitude
*
float64
`json:"latitude,omitempty"`
Longitude
*
float64
`json:"longitude,omitempty"`
}
}
func
(
r
*
GeminiChatRequest
)
GetTokenCountMeta
()
*
types
.
TokenCountMeta
{
func
(
r
*
GeminiChatRequest
)
GetTokenCountMeta
()
*
types
.
TokenCountMeta
{
...
@@ -239,12 +262,20 @@ type GeminiChatGenerationConfig struct {
...
@@ -239,12 +262,20 @@ type GeminiChatGenerationConfig struct {
StopSequences
[]
string
`json:"stopSequences,omitempty"`
StopSequences
[]
string
`json:"stopSequences,omitempty"`
ResponseMimeType
string
`json:"responseMimeType,omitempty"`
ResponseMimeType
string
`json:"responseMimeType,omitempty"`
ResponseSchema
any
`json:"responseSchema,omitempty"`
ResponseSchema
any
`json:"responseSchema,omitempty"`
ResponseJsonSchema
json
.
RawMessage
`json:"responseJsonSchema,omitempty"`
PresencePenalty
*
float32
`json:"presencePenalty,omitempty"`
FrequencyPenalty
*
float32
`json:"frequencyPenalty,omitempty"`
ResponseLogprobs
bool
`json:"responseLogprobs,omitempty"`
Logprobs
*
int32
`json:"logprobs,omitempty"`
MediaResolution
MediaResolution
`json:"mediaResolution,omitempty"`
Seed
int64
`json:"seed,omitempty"`
Seed
int64
`json:"seed,omitempty"`
ResponseModalities
[]
string
`json:"responseModalities,omitempty"`
ResponseModalities
[]
string
`json:"responseModalities,omitempty"`
ThinkingConfig
*
GeminiThinkingConfig
`json:"thinkingConfig,omitempty"`
ThinkingConfig
*
GeminiThinkingConfig
`json:"thinkingConfig,omitempty"`
SpeechConfig
json
.
RawMessage
`json:"speechConfig,omitempty"`
// RawMessage to allow flexible speech config
SpeechConfig
json
.
RawMessage
`json:"speechConfig,omitempty"`
// RawMessage to allow flexible speech config
}
}
type
MediaResolution
string
type
GeminiChatCandidate
struct
{
type
GeminiChatCandidate
struct
{
Content
GeminiChatContent
`json:"content"`
Content
GeminiChatContent
`json:"content"`
FinishReason
*
string
`json:"finishReason"`
FinishReason
*
string
`json:"finishReason"`
...
...
dto/openai_request.go
View file @
8ed3e4f0
...
@@ -772,11 +772,12 @@ type OpenAIResponsesRequest struct {
...
@@ -772,11 +772,12 @@ type OpenAIResponsesRequest struct {
Instructions
json
.
RawMessage
`json:"instructions,omitempty"`
Instructions
json
.
RawMessage
`json:"instructions,omitempty"`
MaxOutputTokens
uint
`json:"max_output_tokens,omitempty"`
MaxOutputTokens
uint
`json:"max_output_tokens,omitempty"`
Metadata
json
.
RawMessage
`json:"metadata,omitempty"`
Metadata
json
.
RawMessage
`json:"metadata,omitempty"`
ParallelToolCalls
bool
`json:"parallel_tool_calls,omitempty"`
ParallelToolCalls
json
.
RawMessage
`json:"parallel_tool_calls,omitempty"`
PreviousResponseID
string
`json:"previous_response_id,omitempty"`
PreviousResponseID
string
`json:"previous_response_id,omitempty"`
Reasoning
*
Reasoning
`json:"reasoning,omitempty"`
Reasoning
*
Reasoning
`json:"reasoning,omitempty"`
ServiceTier
string
`json:"service_tier,omitempty"`
ServiceTier
string
`json:"service_tier,omitempty"`
Store
bool
`json:"store,omitempty"`
Store
json
.
RawMessage
`json:"store,omitempty"`
PromptCacheKey
json
.
RawMessage
`json:"prompt_cache_key,omitempty"`
Stream
bool
`json:"stream,omitempty"`
Stream
bool
`json:"stream,omitempty"`
Temperature
float64
`json:"temperature,omitempty"`
Temperature
float64
`json:"temperature,omitempty"`
Text
json
.
RawMessage
`json:"text,omitempty"`
Text
json
.
RawMessage
`json:"text,omitempty"`
...
...
main.go
View file @
8ed3e4f0
...
@@ -16,6 +16,7 @@ import (
...
@@ -16,6 +16,7 @@ import (
"one-api/setting/ratio_setting"
"one-api/setting/ratio_setting"
"os"
"os"
"strconv"
"strconv"
"time"
"github.com/bytedance/gopkg/util/gopool"
"github.com/bytedance/gopkg/util/gopool"
"github.com/gin-contrib/sessions"
"github.com/gin-contrib/sessions"
...
@@ -33,6 +34,7 @@ var buildFS embed.FS
...
@@ -33,6 +34,7 @@ var buildFS embed.FS
var
indexPage
[]
byte
var
indexPage
[]
byte
func
main
()
{
func
main
()
{
startTime
:=
time
.
Now
()
err
:=
InitResources
()
err
:=
InitResources
()
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -150,6 +152,10 @@ func main() {
...
@@ -150,6 +152,10 @@ func main() {
if
port
==
""
{
if
port
==
""
{
port
=
strconv
.
Itoa
(
*
common
.
Port
)
port
=
strconv
.
Itoa
(
*
common
.
Port
)
}
}
// Log startup success message
common
.
LogStartupSuccess
(
startTime
,
port
)
err
=
server
.
Run
(
":"
+
port
)
err
=
server
.
Run
(
":"
+
port
)
if
err
!=
nil
{
if
err
!=
nil
{
common
.
FatalLog
(
"failed to start HTTP server: "
+
err
.
Error
())
common
.
FatalLog
(
"failed to start HTTP server: "
+
err
.
Error
())
...
@@ -204,4 +210,4 @@ func InitResources() error {
...
@@ -204,4 +210,4 @@ func InitResources() error {
return
err
return
err
}
}
return
nil
return
nil
}
}
\ No newline at end of file
relay/channel/aws/constants.go
View file @
8ed3e4f0
...
@@ -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/channel/task/vidu/adaptor.go
View file @
8ed3e4f0
...
@@ -80,8 +80,7 @@ func (a *TaskAdaptor) Init(info *relaycommon.RelayInfo) {
...
@@ -80,8 +80,7 @@ func (a *TaskAdaptor) Init(info *relaycommon.RelayInfo) {
}
}
func
(
a
*
TaskAdaptor
)
ValidateRequestAndSetAction
(
c
*
gin
.
Context
,
info
*
relaycommon
.
RelayInfo
)
*
dto
.
TaskError
{
func
(
a
*
TaskAdaptor
)
ValidateRequestAndSetAction
(
c
*
gin
.
Context
,
info
*
relaycommon
.
RelayInfo
)
*
dto
.
TaskError
{
// Use the unified validation method for TaskSubmitReq with image-based action determination
return
relaycommon
.
ValidateBasicTaskRequest
(
c
,
info
,
constant
.
TaskActionGenerate
)
return
relaycommon
.
ValidateTaskRequestWithImageBinding
(
c
,
info
)
}
}
func
(
a
*
TaskAdaptor
)
BuildRequestBody
(
c
*
gin
.
Context
,
_
*
relaycommon
.
RelayInfo
)
(
io
.
Reader
,
error
)
{
func
(
a
*
TaskAdaptor
)
BuildRequestBody
(
c
*
gin
.
Context
,
_
*
relaycommon
.
RelayInfo
)
(
io
.
Reader
,
error
)
{
...
@@ -112,6 +111,10 @@ func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, erro
...
@@ -112,6 +111,10 @@ func (a *TaskAdaptor) BuildRequestURL(info *relaycommon.RelayInfo) (string, erro
switch
info
.
Action
{
switch
info
.
Action
{
case
constant
.
TaskActionGenerate
:
case
constant
.
TaskActionGenerate
:
path
=
"/img2video"
path
=
"/img2video"
case
constant
.
TaskActionFirstTailGenerate
:
path
=
"/start-end2video"
case
constant
.
TaskActionReferenceGenerate
:
path
=
"/reference2video"
default
:
default
:
path
=
"/text2video"
path
=
"/text2video"
}
}
...
@@ -187,14 +190,9 @@ func (a *TaskAdaptor) GetChannelName() string {
...
@@ -187,14 +190,9 @@ func (a *TaskAdaptor) GetChannelName() string {
// ============================
// ============================
func
(
a
*
TaskAdaptor
)
convertToRequestPayload
(
req
*
relaycommon
.
TaskSubmitReq
)
(
*
requestPayload
,
error
)
{
func
(
a
*
TaskAdaptor
)
convertToRequestPayload
(
req
*
relaycommon
.
TaskSubmitReq
)
(
*
requestPayload
,
error
)
{
var
images
[]
string
if
req
.
Image
!=
""
{
images
=
[]
string
{
req
.
Image
}
}
r
:=
requestPayload
{
r
:=
requestPayload
{
Model
:
defaultString
(
req
.
Model
,
"viduq1"
),
Model
:
defaultString
(
req
.
Model
,
"viduq1"
),
Images
:
i
mages
,
Images
:
req
.
I
mages
,
Prompt
:
req
.
Prompt
,
Prompt
:
req
.
Prompt
,
Duration
:
defaultInt
(
req
.
Duration
,
5
),
Duration
:
defaultInt
(
req
.
Duration
,
5
),
Resolution
:
defaultString
(
req
.
Size
,
"1080p"
),
Resolution
:
defaultString
(
req
.
Size
,
"1080p"
),
...
...
relay/channel/volcengine/adaptor.go
View file @
8ed3e4f0
...
@@ -9,6 +9,7 @@ import (
...
@@ -9,6 +9,7 @@ import (
"mime/multipart"
"mime/multipart"
"net/http"
"net/http"
"net/textproto"
"net/textproto"
channelconstant
"one-api/constant"
"one-api/dto"
"one-api/dto"
"one-api/relay/channel"
"one-api/relay/channel"
"one-api/relay/channel/openai"
"one-api/relay/channel/openai"
...
@@ -188,20 +189,26 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
...
@@ -188,20 +189,26 @@ func (a *Adaptor) Init(info *relaycommon.RelayInfo) {
}
}
func
(
a
*
Adaptor
)
GetRequestURL
(
info
*
relaycommon
.
RelayInfo
)
(
string
,
error
)
{
func
(
a
*
Adaptor
)
GetRequestURL
(
info
*
relaycommon
.
RelayInfo
)
(
string
,
error
)
{
// 支持自定义域名,如果未设置则使用默认域名
baseUrl
:=
info
.
ChannelBaseUrl
if
baseUrl
==
""
{
baseUrl
=
channelconstant
.
ChannelBaseURLs
[
channelconstant
.
ChannelTypeVolcEngine
]
}
switch
info
.
RelayMode
{
switch
info
.
RelayMode
{
case
constant
.
RelayModeChatCompletions
:
case
constant
.
RelayModeChatCompletions
:
if
strings
.
HasPrefix
(
info
.
UpstreamModelName
,
"bot"
)
{
if
strings
.
HasPrefix
(
info
.
UpstreamModelName
,
"bot"
)
{
return
fmt
.
Sprintf
(
"%s/api/v3/bots/chat/completions"
,
info
.
ChannelB
aseUrl
),
nil
return
fmt
.
Sprintf
(
"%s/api/v3/bots/chat/completions"
,
b
aseUrl
),
nil
}
}
return
fmt
.
Sprintf
(
"%s/api/v3/chat/completions"
,
info
.
ChannelB
aseUrl
),
nil
return
fmt
.
Sprintf
(
"%s/api/v3/chat/completions"
,
b
aseUrl
),
nil
case
constant
.
RelayModeEmbeddings
:
case
constant
.
RelayModeEmbeddings
:
return
fmt
.
Sprintf
(
"%s/api/v3/embeddings"
,
info
.
ChannelB
aseUrl
),
nil
return
fmt
.
Sprintf
(
"%s/api/v3/embeddings"
,
b
aseUrl
),
nil
case
constant
.
RelayModeImagesGenerations
:
case
constant
.
RelayModeImagesGenerations
:
return
fmt
.
Sprintf
(
"%s/api/v3/images/generations"
,
info
.
ChannelB
aseUrl
),
nil
return
fmt
.
Sprintf
(
"%s/api/v3/images/generations"
,
b
aseUrl
),
nil
case
constant
.
RelayModeImagesEdits
:
case
constant
.
RelayModeImagesEdits
:
return
fmt
.
Sprintf
(
"%s/api/v3/images/edits"
,
info
.
ChannelB
aseUrl
),
nil
return
fmt
.
Sprintf
(
"%s/api/v3/images/edits"
,
b
aseUrl
),
nil
case
constant
.
RelayModeRerank
:
case
constant
.
RelayModeRerank
:
return
fmt
.
Sprintf
(
"%s/api/v3/rerank"
,
info
.
ChannelB
aseUrl
),
nil
return
fmt
.
Sprintf
(
"%s/api/v3/rerank"
,
b
aseUrl
),
nil
default
:
default
:
}
}
return
""
,
fmt
.
Errorf
(
"unsupported relay mode: %d"
,
info
.
RelayMode
)
return
""
,
fmt
.
Errorf
(
"unsupported relay mode: %d"
,
info
.
RelayMode
)
...
...
relay/channel/volcengine/constants.go
View file @
8ed3e4f0
...
@@ -9,6 +9,11 @@ var ModelList = []string{
...
@@ -9,6 +9,11 @@ var ModelList = []string{
"Doubao-lite-4k"
,
"Doubao-lite-4k"
,
"Doubao-embedding"
,
"Doubao-embedding"
,
"doubao-seedream-4-0-250828"
,
"doubao-seedream-4-0-250828"
,
"seedream-4-0-250828"
,
"doubao-seedance-1-0-pro-250528"
,
"seedance-1-0-pro-250528"
,
"doubao-seed-1-6-thinking-250715"
,
"seed-1-6-thinking-250715"
,
}
}
var
ChannelName
=
"volcengine"
var
ChannelName
=
"volcengine"
relay/channel/xunfei/relay-xunfei.go
View file @
8ed3e4f0
...
@@ -207,10 +207,6 @@ func xunfeiMakeRequest(textRequest dto.GeneralOpenAIRequest, domain, authUrl, ap
...
@@ -207,10 +207,6 @@ func xunfeiMakeRequest(textRequest dto.GeneralOpenAIRequest, domain, authUrl, ap
return
nil
,
nil
,
err
return
nil
,
nil
,
err
}
}
defer
func
()
{
conn
.
Close
()
}()
data
:=
requestOpenAI2Xunfei
(
textRequest
,
appId
,
domain
)
data
:=
requestOpenAI2Xunfei
(
textRequest
,
appId
,
domain
)
err
=
conn
.
WriteJSON
(
data
)
err
=
conn
.
WriteJSON
(
data
)
if
err
!=
nil
{
if
err
!=
nil
{
...
@@ -220,6 +216,9 @@ func xunfeiMakeRequest(textRequest dto.GeneralOpenAIRequest, domain, authUrl, ap
...
@@ -220,6 +216,9 @@ func xunfeiMakeRequest(textRequest dto.GeneralOpenAIRequest, domain, authUrl, ap
dataChan
:=
make
(
chan
XunfeiChatResponse
)
dataChan
:=
make
(
chan
XunfeiChatResponse
)
stopChan
:=
make
(
chan
bool
)
stopChan
:=
make
(
chan
bool
)
go
func
()
{
go
func
()
{
defer
func
()
{
conn
.
Close
()
}()
for
{
for
{
_
,
msg
,
err
:=
conn
.
ReadMessage
()
_
,
msg
,
err
:=
conn
.
ReadMessage
()
if
err
!=
nil
{
if
err
!=
nil
{
...
...
relay/claude_handler.go
View file @
8ed3e4f0
...
@@ -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/common/relay_utils.go
View file @
8ed3e4f0
...
@@ -79,34 +79,18 @@ func ValidateBasicTaskRequest(c *gin.Context, info *RelayInfo, action string) *d
...
@@ -79,34 +79,18 @@ func ValidateBasicTaskRequest(c *gin.Context, info *RelayInfo, action string) *d
req
.
Images
=
[]
string
{
req
.
Image
}
req
.
Images
=
[]
string
{
req
.
Image
}
}
}
storeTaskRequest
(
c
,
info
,
action
,
req
)
if
req
.
HasImage
()
{
return
nil
}
func
ValidateTaskRequestWithImage
(
c
*
gin
.
Context
,
info
*
RelayInfo
,
requestObj
interface
{})
*
dto
.
TaskError
{
hasPrompt
,
ok
:=
requestObj
.
(
HasPrompt
)
if
!
ok
{
return
createTaskError
(
fmt
.
Errorf
(
"request must have prompt"
),
"invalid_request"
,
http
.
StatusBadRequest
,
true
)
}
if
taskErr
:=
validatePrompt
(
hasPrompt
.
GetPrompt
());
taskErr
!=
nil
{
return
taskErr
}
action
:=
constant
.
TaskActionTextGenerate
if
hasImage
,
ok
:=
requestObj
.
(
HasImage
);
ok
&&
hasImage
.
HasImage
()
{
action
=
constant
.
TaskActionGenerate
action
=
constant
.
TaskActionGenerate
if
info
.
ChannelType
==
constant
.
ChannelTypeVidu
{
// vidu 增加 首尾帧生视频和参考图生视频
if
len
(
req
.
Images
)
==
2
{
action
=
constant
.
TaskActionFirstTailGenerate
}
else
if
len
(
req
.
Images
)
>
2
{
action
=
constant
.
TaskActionReferenceGenerate
}
}
}
}
storeTaskRequest
(
c
,
info
,
action
,
req
uestObj
)
storeTaskRequest
(
c
,
info
,
action
,
req
)
return
nil
return
nil
}
}
func
ValidateTaskRequestWithImageBinding
(
c
*
gin
.
Context
,
info
*
RelayInfo
)
*
dto
.
TaskError
{
var
req
TaskSubmitReq
if
err
:=
c
.
ShouldBindJSON
(
&
req
);
err
!=
nil
{
return
createTaskError
(
err
,
"invalid_request_body"
,
http
.
StatusBadRequest
,
false
)
}
return
ValidateTaskRequestWithImage
(
c
,
info
,
req
)
}
relay/gemini_handler.go
View file @
8ed3e4f0
...
@@ -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 @
8ed3e4f0
{
"compilerOptions"
:
{
"baseUrl"
:
"./"
,
"paths"
:
{
"@/*"
:
[
"src/*"
]
}
},
"include"
:
[
"src/**/*"
]
}
\ No newline at end of file
web/src/components/common/markdown/MarkdownRenderer.jsx
View file @
8ed3e4f0
...
@@ -181,8 +181,8 @@ export function PreCode(props) {
...
@@ -181,8 +181,8 @@ export function PreCode(props) {
e
.
preventDefault
();
e
.
preventDefault
();
e
.
stopPropagation
();
e
.
stopPropagation
();
if
(
ref
.
current
)
{
if
(
ref
.
current
)
{
const
code
=
const
code
Element
=
ref
.
current
.
querySelector
(
'code'
);
ref
.
current
.
querySelector
(
'code'
)?.
innerTex
t
??
''
;
const
code
=
codeElement
?.
textConten
t
??
''
;
copy
(
code
).
then
((
success
)
=>
{
copy
(
code
).
then
((
success
)
=>
{
if
(
success
)
{
if
(
success
)
{
Toast
.
success
(
t
(
'代码已复制到剪贴板'
));
Toast
.
success
(
t
(
'代码已复制到剪贴板'
));
...
...
web/src/components/layout/headerbar/UserArea.jsx
View file @
8ed3e4f0
...
@@ -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 @
8ed3e4f0
...
@@ -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/channels/modals/EditChannelModal.jsx
View file @
8ed3e4f0
...
@@ -87,6 +87,26 @@ const REGION_EXAMPLE = {
...
@@ -87,6 +87,26 @@ const REGION_EXAMPLE = {
'claude-3-5-sonnet-20240620'
:
'europe-west1'
,
'claude-3-5-sonnet-20240620'
:
'europe-west1'
,
};
};
// 支持并且已适配通过接口获取模型列表的渠道类型
const
MODEL_FETCHABLE_TYPES
=
new
Set
([
1
,
4
,
14
,
34
,
17
,
26
,
24
,
47
,
25
,
20
,
23
,
31
,
35
,
40
,
42
,
48
,
]);
function
type2secretPrompt
(
type
)
{
function
type2secretPrompt
(
type
)
{
// inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥')
// inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥')
switch
(
type
)
{
switch
(
type
)
{
...
@@ -260,7 +280,7 @@ const EditChannelModal = (props) => {
...
@@ -260,7 +280,7 @@ const EditChannelModal = (props) => {
pass_through_body_enabled
:
false
,
pass_through_body_enabled
:
false
,
system_prompt
:
''
,
system_prompt
:
''
,
});
});
const
showApiConfigCard
=
inputs
.
type
!==
45
;
// 控制是否显示 API 配置卡片(仅当渠道类型不是 豆包 时显示)
const
showApiConfigCard
=
true
;
// 控制是否显示 API 配置卡片
const
getInitValues
=
()
=>
({
...
originInputs
});
const
getInitValues
=
()
=>
({
...
originInputs
});
// 处理渠道额外设置的更新
// 处理渠道额外设置的更新
...
@@ -367,6 +387,10 @@ const EditChannelModal = (props) => {
...
@@ -367,6 +387,10 @@ const EditChannelModal = (props) => {
case
36
:
case
36
:
localModels
=
[
'suno_music'
,
'suno_lyrics'
];
localModels
=
[
'suno_music'
,
'suno_lyrics'
];
break
;
break
;
case
45
:
localModels
=
getChannelModels
(
value
);
setInputs
((
prevInputs
)
=>
({
...
prevInputs
,
base_url
:
'https://ark.cn-beijing.volces.com'
}));
break
;
default
:
default
:
localModels
=
getChannelModels
(
value
);
localModels
=
getChannelModels
(
value
);
break
;
break
;
...
@@ -869,6 +893,10 @@ const EditChannelModal = (props) => {
...
@@ -869,6 +893,10 @@ const EditChannelModal = (props) => {
showInfo
(
t
(
'请至少选择一个模型!'
));
showInfo
(
t
(
'请至少选择一个模型!'
));
return
;
return
;
}
}
if
(
localInputs
.
type
===
45
&&
(
!
localInputs
.
base_url
||
localInputs
.
base_url
.
trim
()
===
''
))
{
showInfo
(
t
(
'请输入API地址!'
));
return
;
}
if
(
if
(
localInputs
.
model_mapping
&&
localInputs
.
model_mapping
&&
localInputs
.
model_mapping
!==
''
&&
localInputs
.
model_mapping
!==
''
&&
...
@@ -1876,6 +1904,30 @@ const EditChannelModal = (props) => {
...
@@ -1876,6 +1904,30 @@ const EditChannelModal = (props) => {
/>
/>
</
div
>
</
div
>
)
}
)
}
{
inputs
.
type
===
45
&&
(
<
div
>
<
Form
.
Select
field=
'base_url'
label=
{
t
(
'API地址'
)
}
placeholder=
{
t
(
'请选择API地址'
)
}
onChange=
{
(
value
)
=>
handleInputChange
(
'base_url'
,
value
)
}
optionList=
{
[
{
value
:
'https://ark.cn-beijing.volces.com'
,
label
:
'https://ark.cn-beijing.volces.com'
},
{
value
:
'https://ark.ap-southeast.bytepluses.com'
,
label
:
'https://ark.ap-southeast.bytepluses.com'
}
]
}
defaultValue=
'https://ark.cn-beijing.volces.com'
/>
</
div
>
)
}
</
Card
>
</
Card
>
</
div
>
</
div
>
)
}
)
}
...
@@ -1961,13 +2013,15 @@ const EditChannelModal = (props) => {
...
@@ -1961,13 +2013,15 @@ const EditChannelModal = (props) => {
>
>
{
t
(
'填入所有模型'
)
}
{
t
(
'填入所有模型'
)
}
</
Button
>
</
Button
>
<
Button
{
MODEL_FETCHABLE_TYPES
.
has
(
inputs
.
type
)
&&
(
size=
'small'
<
Button
type=
'tertiary'
size=
'small'
onClick=
{
()
=>
fetchUpstreamModelList
(
'models'
)
}
type=
'tertiary'
>
onClick=
{
()
=>
fetchUpstreamModelList
(
'models'
)
}
{
t
(
'获取模型列表'
)
}
>
</
Button
>
{
t
(
'获取模型列表'
)
}
</
Button
>
)
}
<
Button
<
Button
size=
'small'
size=
'small'
type=
'warning'
type=
'warning'
...
...
web/src/components/table/channels/modals/MultiKeyManageModal.jsx
View file @
8ed3e4f0
...
@@ -247,6 +247,32 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => {
...
@@ -247,6 +247,32 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => {
}
}
};
};
// Delete a specific key
const
handleDeleteKey
=
async
(
keyIndex
)
=>
{
const
operationId
=
`delete_
${
keyIndex
}
`
;
setOperationLoading
((
prev
)
=>
({
...
prev
,
[
operationId
]:
true
}));
try
{
const
res
=
await
API
.
post
(
'/api/channel/multi_key/manage'
,
{
channel_id
:
channel
.
id
,
action
:
'delete_key'
,
key_index
:
keyIndex
,
});
if
(
res
.
data
.
success
)
{
showSuccess
(
t
(
'密钥已删除'
));
await
loadKeyStatus
(
currentPage
,
pageSize
);
// Reload current page
onRefresh
&&
onRefresh
();
// Refresh parent component
}
else
{
showError
(
res
.
data
.
message
);
}
}
catch
(
error
)
{
showError
(
t
(
'删除密钥失败'
));
}
finally
{
setOperationLoading
((
prev
)
=>
({
...
prev
,
[
operationId
]:
false
}));
}
};
// Handle page change
// Handle page change
const
handlePageChange
=
(
page
)
=>
{
const
handlePageChange
=
(
page
)
=>
{
setCurrentPage
(
page
);
setCurrentPage
(
page
);
...
@@ -384,7 +410,7 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => {
...
@@ -384,7 +410,7 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => {
title
:
t
(
'操作'
),
title
:
t
(
'操作'
),
key
:
'action'
,
key
:
'action'
,
fixed
:
'right'
,
fixed
:
'right'
,
width
:
1
0
0
,
width
:
1
5
0
,
render
:
(
_
,
record
)
=>
(
render
:
(
_
,
record
)
=>
(
<
Space
>
<
Space
>
{
record
.
status
===
1
?
(
{
record
.
status
===
1
?
(
...
@@ -406,6 +432,21 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => {
...
@@ -406,6 +432,21 @@ const MultiKeyManageModal = ({ visible, onCancel, channel, onRefresh }) => {
{
t
(
'启用'
)
}
{
t
(
'启用'
)
}
</
Button
>
</
Button
>
)
}
)
}
<
Popconfirm
title=
{
t
(
'确定要删除此密钥吗?'
)
}
content=
{
t
(
'此操作不可撤销,将永久删除该密钥'
)
}
onConfirm=
{
()
=>
handleDeleteKey
(
record
.
index
)
}
okType=
{
'danger'
}
position=
{
'topRight'
}
>
<
Button
type=
'danger'
size=
'small'
loading=
{
operationLoading
[
`delete_${record.index}`
]
}
>
{
t
(
'删除'
)
}
</
Button
>
</
Popconfirm
>
</
Space
>
</
Space
>
),
),
},
},
...
...
web/src/components/table/mj-logs/MjLogsFilters.jsx
View file @
8ed3e4f0
...
@@ -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/TaskLogsColumnDefs.jsx
View file @
8ed3e4f0
...
@@ -35,8 +35,9 @@ import {
...
@@ -35,8 +35,9 @@ import {
Sparkles
,
Sparkles
,
}
from
'lucide-react'
;
}
from
'lucide-react'
;
import
{
import
{
TASK_ACTION_GENERATE
,
TASK_ACTION_FIRST_TAIL_GENERATE
,
TASK_ACTION_TEXT_GENERATE
,
TASK_ACTION_GENERATE
,
TASK_ACTION_REFERENCE_GENERATE
,
TASK_ACTION_TEXT_GENERATE
}
from
'../../../constants/common.constant'
;
}
from
'../../../constants/common.constant'
;
import
{
CHANNEL_OPTIONS
}
from
'../../../constants/channel.constants'
;
import
{
CHANNEL_OPTIONS
}
from
'../../../constants/channel.constants'
;
...
@@ -111,6 +112,18 @@ const renderType = (type, t) => {
...
@@ -111,6 +112,18 @@ const renderType = (type, t) => {
{
t
(
'文生视频'
)
}
{
t
(
'文生视频'
)
}
</
Tag
>
</
Tag
>
);
);
case
TASK_ACTION_FIRST_TAIL_GENERATE
:
return
(
<
Tag
color=
'blue'
shape=
'circle'
prefixIcon=
{
<
Sparkles
size=
{
14
}
/>
}
>
{
t
(
'首尾生视频'
)
}
</
Tag
>
);
case
TASK_ACTION_REFERENCE_GENERATE
:
return
(
<
Tag
color=
'blue'
shape=
'circle'
prefixIcon=
{
<
Sparkles
size=
{
14
}
/>
}
>
{
t
(
'参照生视频'
)
}
</
Tag
>
);
default
:
default
:
return
(
return
(
<
Tag
color=
'white'
shape=
'circle'
prefixIcon=
{
<
HelpCircle
size=
{
14
}
/>
}
>
<
Tag
color=
'white'
shape=
'circle'
prefixIcon=
{
<
HelpCircle
size=
{
14
}
/>
}
>
...
@@ -343,7 +356,9 @@ export const getTaskLogsColumns = ({
...
@@ -343,7 +356,9 @@ export const getTaskLogsColumns = ({
// 仅当为视频生成任务且成功,且 fail_reason 是 URL 时显示可点击链接
// 仅当为视频生成任务且成功,且 fail_reason 是 URL 时显示可点击链接
const
isVideoTask
=
const
isVideoTask
=
record
.
action
===
TASK_ACTION_GENERATE
||
record
.
action
===
TASK_ACTION_GENERATE
||
record
.
action
===
TASK_ACTION_TEXT_GENERATE
;
record
.
action
===
TASK_ACTION_TEXT_GENERATE
||
record
.
action
===
TASK_ACTION_FIRST_TAIL_GENERATE
||
record
.
action
===
TASK_ACTION_REFERENCE_GENERATE
;
const
isSuccess
=
record
.
status
===
'SUCCESS'
;
const
isSuccess
=
record
.
status
===
'SUCCESS'
;
const
isUrl
=
typeof
text
===
'string'
&&
/^https
?
:
\/\/
/
.
test
(
text
);
const
isUrl
=
typeof
text
===
'string'
&&
/^https
?
:
\/\/
/
.
test
(
text
);
if
(
isSuccess
&&
isVideoTask
&&
isUrl
)
{
if
(
isSuccess
&&
isVideoTask
&&
isUrl
)
{
...
...
web/src/components/table/task-logs/TaskLogsFilters.jsx
View file @
8ed3e4f0
...
@@ -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 @
8ed3e4f0
...
@@ -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/common.constant.js
View file @
8ed3e4f0
...
@@ -40,3 +40,5 @@ export const API_ENDPOINTS = [
...
@@ -40,3 +40,5 @@ export const API_ENDPOINTS = [
export
const
TASK_ACTION_GENERATE
=
'generate'
;
export
const
TASK_ACTION_GENERATE
=
'generate'
;
export
const
TASK_ACTION_TEXT_GENERATE
=
'textGenerate'
;
export
const
TASK_ACTION_TEXT_GENERATE
=
'textGenerate'
;
export
const
TASK_ACTION_FIRST_TAIL_GENERATE
=
'firstTailGenerate'
;
export
const
TASK_ACTION_REFERENCE_GENERATE
=
'referenceGenerate'
;
web/src/constants/console.constants.js
0 → 100644
View file @
8ed3e4f0
/*
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/helpers/api.js
View file @
8ed3e4f0
...
@@ -118,7 +118,6 @@ export const buildApiPayload = (
...
@@ -118,7 +118,6 @@ export const buildApiPayload = (
model
:
inputs
.
model
,
model
:
inputs
.
model
,
group
:
inputs
.
group
,
group
:
inputs
.
group
,
messages
:
processedMessages
,
messages
:
processedMessages
,
group
:
inputs
.
group
,
stream
:
inputs
.
stream
,
stream
:
inputs
.
stream
,
};
};
...
@@ -132,13 +131,15 @@ export const buildApiPayload = (
...
@@ -132,13 +131,15 @@ export const buildApiPayload = (
seed
:
'seed'
,
seed
:
'seed'
,
};
};
Object
.
entries
(
parameterMappings
).
forEach
(([
key
,
param
])
=>
{
Object
.
entries
(
parameterMappings
).
forEach
(([
key
,
param
])
=>
{
if
(
const
enabled
=
parameterEnabled
[
key
];
parameterEnabled
[
key
]
&&
const
value
=
inputs
[
param
];
inputs
[
param
]
!==
undefined
&&
const
hasValue
=
value
!==
undefined
&&
value
!==
null
;
inputs
[
param
]
!==
null
)
{
payload
[
param
]
=
inputs
[
param
];
if
(
enabled
&&
hasValue
)
{
payload
[
param
]
=
value
;
}
}
});
});
...
...
web/src/helpers/utils.jsx
View file @
8ed3e4f0
...
@@ -75,13 +75,17 @@ export async function copy(text) {
...
@@ -75,13 +75,17 @@ export async function copy(text) {
await
navigator
.
clipboard
.
writeText
(
text
);
await
navigator
.
clipboard
.
writeText
(
text
);
}
catch
(
e
)
{
}
catch
(
e
)
{
try
{
try
{
// 构建input 执行 复制命令
// 构建 textarea 执行复制命令,保留多行文本格式
var
_input
=
window
.
document
.
createElement
(
'input'
);
const
textarea
=
window
.
document
.
createElement
(
'textarea'
);
_input
.
value
=
text
;
textarea
.
value
=
text
;
window
.
document
.
body
.
appendChild
(
_input
);
textarea
.
setAttribute
(
'readonly'
,
''
);
_input
.
select
();
textarea
.
style
.
position
=
'fixed'
;
window
.
document
.
execCommand
(
'Copy'
);
textarea
.
style
.
left
=
'-9999px'
;
window
.
document
.
body
.
removeChild
(
_input
);
textarea
.
style
.
top
=
'-9999px'
;
window
.
document
.
body
.
appendChild
(
textarea
);
textarea
.
select
();
window
.
document
.
execCommand
(
'copy'
);
window
.
document
.
body
.
removeChild
(
textarea
);
}
catch
(
e
)
{
}
catch
(
e
)
{
okay
=
false
;
okay
=
false
;
console
.
error
(
e
);
console
.
error
(
e
);
...
...
web/src/hooks/common/useSidebar.js
View file @
8ed3e4f0
...
@@ -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 @
8ed3e4f0
...
@@ -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 @
8ed3e4f0
...
@@ -1889,6 +1889,10 @@
...
@@ -1889,6 +1889,10 @@
"确定要删除所有已自动禁用的密钥吗?"
:
"Are you sure you want to delete all automatically disabled keys?"
,
"确定要删除所有已自动禁用的密钥吗?"
:
"Are you sure you want to delete all automatically disabled keys?"
,
"此操作不可撤销,将永久删除已自动禁用的密钥"
:
"This operation cannot be undone, and all automatically disabled keys will be permanently deleted."
,
"此操作不可撤销,将永久删除已自动禁用的密钥"
:
"This operation cannot be undone, and all automatically disabled keys will be permanently deleted."
,
"删除自动禁用密钥"
:
"Delete auto disabled keys"
,
"删除自动禁用密钥"
:
"Delete auto disabled keys"
,
"确定要删除此密钥吗?"
:
"Are you sure you want to delete this key?"
,
"此操作不可撤销,将永久删除该密钥"
:
"This operation cannot be undone, and the key will be permanently deleted."
,
"密钥已删除"
:
"Key has been deleted"
,
"删除密钥失败"
:
"Failed to delete key"
,
"图标"
:
"Icon"
,
"图标"
:
"Icon"
,
"模型图标"
:
"Model icon"
,
"模型图标"
:
"Model icon"
,
"请输入图标名称"
:
"Please enter the icon name"
,
"请输入图标名称"
:
"Please enter the icon name"
,
...
@@ -2095,6 +2099,11 @@
...
@@ -2095,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 @
8ed3e4f0
...
@@ -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