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
f764a27e
authored
May 24, 2025
by
Apple\Apple
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/main' into ui/refactor
parents
334011d4
eea5967e
Hide whitespace changes
Inline
Side-by-side
Showing
15 changed files
with
155 additions
and
34 deletions
+155
-34
dto/dalle.go
+1
-0
main.go
+16
-3
model/ability.go
+36
-11
model/cache.go
+5
-2
model/channel.go
+11
-0
relay/channel/aws/constants.go
+12
-0
relay/channel/claude/adaptor.go
+3
-3
relay/channel/claude/constants.go
+4
-0
relay/channel/gemini/dto.go
+4
-3
relay/channel/gemini/relay-gemini.go
+15
-3
relay/channel/vertex/adaptor.go
+2
-0
relay/relay-mj.go
+17
-1
setting/operation_setting/cache_ratio.go
+8
-0
setting/operation_setting/model-ratio.go
+9
-5
web/src/components/ChannelsTable.js
+12
-3
No files found.
dto/dalle.go
View file @
f764a27e
...
...
@@ -14,6 +14,7 @@ type ImageRequest struct {
ExtraFields
json
.
RawMessage
`json:"extra_fields,omitempty"`
Background
string
`json:"background,omitempty"`
Moderation
string
`json:"moderation,omitempty"`
OutputFormat
string
`json:"output_format,omitempty"`
}
type
ImageResponse
struct
{
...
...
main.go
View file @
f764a27e
...
...
@@ -89,9 +89,22 @@ func main() {
if
common
.
MemoryCacheEnabled
{
common
.
SysLog
(
"memory cache enabled"
)
common
.
SysError
(
fmt
.
Sprintf
(
"sync frequency: %d seconds"
,
common
.
SyncFrequency
))
model
.
InitChannelCache
()
}
if
common
.
MemoryCacheEnabled
{
// Add panic recovery and retry for InitChannelCache
func
()
{
defer
func
()
{
if
r
:=
recover
();
r
!=
nil
{
common
.
SysError
(
fmt
.
Sprintf
(
"InitChannelCache panic: %v, retrying once"
,
r
))
// Retry once
_
,
fixErr
:=
model
.
FixAbility
()
if
fixErr
!=
nil
{
common
.
SysError
(
fmt
.
Sprintf
(
"InitChannelCache failed: %s"
,
fixErr
.
Error
()))
}
}
}()
model
.
InitChannelCache
()
}()
go
model
.
SyncOptions
(
common
.
SyncFrequency
)
go
model
.
SyncChannelCache
(
common
.
SyncFrequency
)
}
...
...
model/ability.go
View file @
f764a27e
...
...
@@ -50,7 +50,7 @@ func getPriority(group string, model string, retry int) (int, error) {
err
:=
DB
.
Model
(
&
Ability
{})
.
Select
(
"DISTINCT(priority)"
)
.
Where
(
groupCol
+
" = ? and model = ? and enabled = "
+
trueVal
,
group
,
model
)
.
Order
(
"priority DESC"
)
.
// 按优先级降序排序
Order
(
"priority DESC"
)
.
// 按优先级降序排序
Pluck
(
"priority"
,
&
priorities
)
.
Error
// Pluck用于将查询的结果直接扫描到一个切片中
if
err
!=
nil
{
...
...
@@ -261,12 +261,28 @@ func FixAbility() (int, error) {
common
.
SysError
(
fmt
.
Sprintf
(
"Get channel ids from channel table failed: %s"
,
err
.
Error
()))
return
0
,
err
}
// Delete abilities of channels that are not in channel table
err
=
DB
.
Where
(
"channel_id NOT IN (?)"
,
channelIds
)
.
Delete
(
&
Ability
{})
.
Error
if
err
!=
nil
{
common
.
SysError
(
fmt
.
Sprintf
(
"Delete abilities of channels that are not in channel table failed: %s"
,
err
.
Error
()))
return
0
,
err
// Delete abilities of channels that are not in channel table - in batches to avoid too many placeholders
if
len
(
channelIds
)
>
0
{
// Process deletion in chunks to avoid "too many placeholders" error
for
_
,
chunk
:=
range
lo
.
Chunk
(
channelIds
,
100
)
{
err
=
DB
.
Where
(
"channel_id NOT IN (?)"
,
chunk
)
.
Delete
(
&
Ability
{})
.
Error
if
err
!=
nil
{
common
.
SysError
(
fmt
.
Sprintf
(
"Delete abilities of channels (batch) that are not in channel table failed: %s"
,
err
.
Error
()))
return
0
,
err
}
}
}
else
{
// If no channels exist, delete all abilities
err
=
DB
.
Delete
(
&
Ability
{})
.
Error
if
err
!=
nil
{
common
.
SysError
(
fmt
.
Sprintf
(
"Delete all abilities failed: %s"
,
err
.
Error
()))
return
0
,
err
}
common
.
SysLog
(
"Delete all abilities successfully"
)
return
0
,
nil
}
common
.
SysLog
(
fmt
.
Sprintf
(
"Delete abilities of channels that are not in channel table successfully, ids: %v"
,
channelIds
))
count
+=
len
(
channelIds
)
...
...
@@ -275,17 +291,26 @@ func FixAbility() (int, error) {
err
=
DB
.
Table
(
"abilities"
)
.
Distinct
(
"channel_id"
)
.
Pluck
(
"channel_id"
,
&
abilityChannelIds
)
.
Error
if
err
!=
nil
{
common
.
SysError
(
fmt
.
Sprintf
(
"Get channel ids from abilities table failed: %s"
,
err
.
Error
()))
return
0
,
err
return
count
,
err
}
var
channels
[]
Channel
if
len
(
abilityChannelIds
)
==
0
{
err
=
DB
.
Find
(
&
channels
)
.
Error
}
else
{
err
=
DB
.
Where
(
"id NOT IN (?)"
,
abilityChannelIds
)
.
Find
(
&
channels
)
.
Error
}
if
err
!=
nil
{
return
0
,
err
// Process query in chunks to avoid "too many placeholders" error
err
=
nil
for
_
,
chunk
:=
range
lo
.
Chunk
(
abilityChannelIds
,
100
)
{
var
channelsChunk
[]
Channel
err
=
DB
.
Where
(
"id NOT IN (?)"
,
chunk
)
.
Find
(
&
channelsChunk
)
.
Error
if
err
!=
nil
{
common
.
SysError
(
fmt
.
Sprintf
(
"Find channels not in abilities table failed: %s"
,
err
.
Error
()))
return
count
,
err
}
channels
=
append
(
channels
,
channelsChunk
...
)
}
}
for
_
,
channel
:=
range
channels
{
err
:=
channel
.
UpdateAbilities
(
nil
)
if
err
!=
nil
{
...
...
model/cache.go
View file @
f764a27e
...
...
@@ -16,6 +16,9 @@ var channelsIDM map[int]*Channel
var
channelSyncLock
sync
.
RWMutex
func
InitChannelCache
()
{
if
!
common
.
MemoryCacheEnabled
{
return
}
newChannelId2channel
:=
make
(
map
[
int
]
*
Channel
)
var
channels
[]
*
Channel
DB
.
Where
(
"status = ?"
,
common
.
ChannelStatusEnabled
)
.
Find
(
&
channels
)
...
...
@@ -84,11 +87,11 @@ func CacheGetRandomSatisfiedChannel(group string, model string, retry int) (*Cha
if
!
common
.
MemoryCacheEnabled
{
return
GetRandomSatisfiedChannel
(
group
,
model
,
retry
)
}
channelSyncLock
.
RLock
()
channels
:=
group2model2channels
[
group
][
model
]
channelSyncLock
.
RUnlock
()
if
len
(
channels
)
==
0
{
return
nil
,
errors
.
New
(
"channel not found"
)
}
...
...
model/channel.go
View file @
f764a27e
...
...
@@ -46,6 +46,17 @@ func (channel *Channel) GetModels() []string {
return
strings
.
Split
(
strings
.
Trim
(
channel
.
Models
,
","
),
","
)
}
func
(
channel
*
Channel
)
GetGroups
()
[]
string
{
if
channel
.
Group
==
""
{
return
[]
string
{}
}
groups
:=
strings
.
Split
(
strings
.
Trim
(
channel
.
Group
,
","
),
","
)
for
i
,
group
:=
range
groups
{
groups
[
i
]
=
strings
.
TrimSpace
(
group
)
}
return
groups
}
func
(
channel
*
Channel
)
GetOtherInfo
()
map
[
string
]
interface
{}
{
otherInfo
:=
make
(
map
[
string
]
interface
{})
if
channel
.
OtherInfo
!=
""
{
...
...
relay/channel/aws/constants.go
View file @
f764a27e
...
...
@@ -11,6 +11,8 @@ var awsModelIDMap = map[string]string{
"claude-3-5-sonnet-20241022"
:
"anthropic.claude-3-5-sonnet-20241022-v2:0"
,
"claude-3-5-haiku-20241022"
:
"anthropic.claude-3-5-haiku-20241022-v1:0"
,
"claude-3-7-sonnet-20250219"
:
"anthropic.claude-3-7-sonnet-20250219-v1:0"
,
"claude-sonnet-4-20250514"
:
"anthropic.claude-sonnet-4-20250514-v1:0"
,
"claude-opus-4-20250514"
:
"anthropic.claude-opus-4-20250514-v1:0"
,
}
var
awsModelCanCrossRegionMap
=
map
[
string
]
map
[
string
]
bool
{
...
...
@@ -41,6 +43,16 @@ var awsModelCanCrossRegionMap = map[string]map[string]bool{
},
"anthropic.claude-3-7-sonnet-20250219-v1:0"
:
{
"us"
:
true
,
"ap"
:
true
,
"eu"
:
true
,
},
"apac.anthropic.claude-sonnet-4-20250514-v1:0"
:
{
"us"
:
true
,
"ap"
:
true
,
"eu"
:
true
,
},
"anthropic.claude-opus-4-20250514-v1:0"
:
{
"us"
:
true
,
},
}
...
...
relay/channel/claude/adaptor.go
View file @
f764a27e
...
...
@@ -38,10 +38,10 @@ func (a *Adaptor) ConvertImageRequest(c *gin.Context, info *relaycommon.RelayInf
}
func
(
a
*
Adaptor
)
Init
(
info
*
relaycommon
.
RelayInfo
)
{
if
strings
.
HasPrefix
(
info
.
UpstreamModelName
,
"claude-3"
)
{
a
.
RequestMode
=
RequestModeMessage
}
else
{
if
strings
.
HasPrefix
(
info
.
UpstreamModelName
,
"claude-2"
)
||
strings
.
HasPrefix
(
info
.
UpstreamModelName
,
"claude-instant"
)
{
a
.
RequestMode
=
RequestModeCompletion
}
else
{
a
.
RequestMode
=
RequestModeMessage
}
}
...
...
relay/channel/claude/constants.go
View file @
f764a27e
...
...
@@ -13,6 +13,10 @@ var ModelList = []string{
"claude-3-5-sonnet-20241022"
,
"claude-3-7-sonnet-20250219"
,
"claude-3-7-sonnet-20250219-thinking"
,
"claude-sonnet-4-20250514"
,
"claude-sonnet-4-20250514-thinking"
,
"claude-opus-4-20250514"
,
"claude-opus-4-20250514-thinking"
,
}
var
ChannelName
=
"claude"
relay/channel/gemini/dto.go
View file @
f764a27e
...
...
@@ -2,10 +2,10 @@ package gemini
type
GeminiChatRequest
struct
{
Contents
[]
GeminiChatContent
`json:"contents"`
SafetySettings
[]
GeminiChatSafetySettings
`json:"safety
_s
ettings,omitempty"`
GenerationConfig
GeminiChatGenerationConfig
`json:"generation
_c
onfig,omitempty"`
SafetySettings
[]
GeminiChatSafetySettings
`json:"safety
S
ettings,omitempty"`
GenerationConfig
GeminiChatGenerationConfig
`json:"generation
C
onfig,omitempty"`
Tools
[]
GeminiChatTool
`json:"tools,omitempty"`
SystemInstructions
*
GeminiChatContent
`json:"system
_i
nstruction,omitempty"`
SystemInstructions
*
GeminiChatContent
`json:"system
I
nstruction,omitempty"`
}
type
GeminiThinkingConfig
struct
{
...
...
@@ -54,6 +54,7 @@ type GeminiFileData struct {
type
GeminiPart
struct
{
Text
string
`json:"text,omitempty"`
Thought
bool
`json:"thought,omitempty"`
InlineData
*
GeminiInlineData
`json:"inlineData,omitempty"`
FunctionCall
*
FunctionCall
`json:"functionCall,omitempty"`
FunctionResponse
*
FunctionResponse
`json:"functionResponse,omitempty"`
...
...
relay/channel/gemini/relay-gemini.go
View file @
f764a27e
...
...
@@ -539,6 +539,8 @@ func responseGeminiChat2OpenAI(response *GeminiChatResponse) *dto.OpenAITextResp
if
call
:=
getResponseToolCall
(
&
part
);
call
!=
nil
{
toolCalls
=
append
(
toolCalls
,
*
call
)
}
}
else
if
part
.
Thought
{
choice
.
Message
.
ReasoningContent
=
part
.
Text
}
else
{
if
part
.
ExecutableCode
!=
nil
{
texts
=
append
(
texts
,
"```"
+
part
.
ExecutableCode
.
Language
+
"
\n
"
+
part
.
ExecutableCode
.
Code
+
"
\n
```"
)
...
...
@@ -556,7 +558,6 @@ func responseGeminiChat2OpenAI(response *GeminiChatResponse) *dto.OpenAITextResp
choice
.
Message
.
SetToolCalls
(
toolCalls
)
isToolCall
=
true
}
choice
.
Message
.
SetStringContent
(
strings
.
Join
(
texts
,
"
\n
"
))
}
...
...
@@ -596,6 +597,7 @@ func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) (*dto.C
}
var
texts
[]
string
isTools
:=
false
isThought
:=
false
if
candidate
.
FinishReason
!=
nil
{
// p := GeminiConvertFinishReason(*candidate.FinishReason)
switch
*
candidate
.
FinishReason
{
...
...
@@ -620,6 +622,9 @@ func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) (*dto.C
call
.
SetIndex
(
len
(
choice
.
Delta
.
ToolCalls
))
choice
.
Delta
.
ToolCalls
=
append
(
choice
.
Delta
.
ToolCalls
,
*
call
)
}
}
else
if
part
.
Thought
{
isThought
=
true
texts
=
append
(
texts
,
part
.
Text
)
}
else
{
if
part
.
ExecutableCode
!=
nil
{
texts
=
append
(
texts
,
"```"
+
part
.
ExecutableCode
.
Language
+
"
\n
"
+
part
.
ExecutableCode
.
Code
+
"
\n
```
\n
"
)
...
...
@@ -632,7 +637,11 @@ func streamResponseGeminiChat2OpenAI(geminiResponse *GeminiChatResponse) (*dto.C
}
}
}
choice
.
Delta
.
SetContentString
(
strings
.
Join
(
texts
,
"
\n
"
))
if
isThought
{
choice
.
Delta
.
SetReasoningContent
(
strings
.
Join
(
texts
,
"
\n
"
))
}
else
{
choice
.
Delta
.
SetContentString
(
strings
.
Join
(
texts
,
"
\n
"
))
}
if
isTools
{
choice
.
FinishReason
=
&
constant
.
FinishReasonToolCalls
}
...
...
@@ -716,8 +725,11 @@ func GeminiChatHandler(c *gin.Context, resp *http.Response, info *relaycommon.Re
if
err
!=
nil
{
return
service
.
OpenAIErrorWrapper
(
err
,
"close_response_body_failed"
,
http
.
StatusInternalServerError
),
nil
}
if
common
.
DebugEnabled
{
println
(
string
(
responseBody
))
}
var
geminiResponse
GeminiChatResponse
err
=
json
.
Unmarshal
(
responseBody
,
&
geminiResponse
)
err
=
common
.
DecodeJson
(
responseBody
,
&
geminiResponse
)
if
err
!=
nil
{
return
service
.
OpenAIErrorWrapper
(
err
,
"unmarshal_response_body_failed"
,
http
.
StatusInternalServerError
),
nil
}
...
...
relay/channel/vertex/adaptor.go
View file @
f764a27e
...
...
@@ -31,6 +31,8 @@ var claudeModelMap = map[string]string{
"claude-3-5-sonnet-20240620"
:
"claude-3-5-sonnet@20240620"
,
"claude-3-5-sonnet-20241022"
:
"claude-3-5-sonnet-v2@20241022"
,
"claude-3-7-sonnet-20250219"
:
"claude-3-7-sonnet@20250219"
,
"claude-sonnet-4-20250514"
:
"claude-sonnet-4@20250514"
,
"claude-opus-4-20250514"
:
"claude-opus-4@20250514"
,
}
const
anthropicVersion
=
"vertex-2023-10-16"
...
...
relay/relay-mj.go
View file @
f764a27e
...
...
@@ -32,7 +32,23 @@ func RelayMidjourneyImage(c *gin.Context) {
})
return
}
resp
,
err
:=
http
.
Get
(
midjourneyTask
.
ImageUrl
)
var
httpClient
*
http
.
Client
if
channel
,
err
:=
model
.
CacheGetChannel
(
midjourneyTask
.
ChannelId
);
err
==
nil
{
if
proxy
,
ok
:=
channel
.
GetSetting
()[
"proxy"
];
ok
{
if
proxyURL
,
ok
:=
proxy
.
(
string
);
ok
&&
proxyURL
!=
""
{
if
httpClient
,
err
=
service
.
NewProxyHttpClient
(
proxyURL
);
err
!=
nil
{
c
.
JSON
(
400
,
gin
.
H
{
"error"
:
"proxy_url_invalid"
,
})
return
}
}
}
}
if
httpClient
==
nil
{
httpClient
=
service
.
GetHttpClient
()
}
resp
,
err
:=
httpClient
.
Get
(
midjourneyTask
.
ImageUrl
)
if
err
!=
nil
{
c
.
JSON
(
http
.
StatusInternalServerError
,
gin
.
H
{
"error"
:
"http_get_image_failed"
,
...
...
setting/operation_setting/cache_ratio.go
View file @
f764a27e
...
...
@@ -36,6 +36,10 @@ var defaultCacheRatio = map[string]float64{
"claude-3-5-sonnet-20241022"
:
0.1
,
"claude-3-7-sonnet-20250219"
:
0.1
,
"claude-3-7-sonnet-20250219-thinking"
:
0.1
,
"claude-sonnet-4-20250514"
:
0.1
,
"claude-sonnet-4-20250514-thinking"
:
0.1
,
"claude-opus-4-20250514"
:
0.1
,
"claude-opus-4-20250514-thinking"
:
0.1
,
}
var
defaultCreateCacheRatio
=
map
[
string
]
float64
{
...
...
@@ -47,6 +51,10 @@ var defaultCreateCacheRatio = map[string]float64{
"claude-3-5-sonnet-20241022"
:
1.25
,
"claude-3-7-sonnet-20250219"
:
1.25
,
"claude-3-7-sonnet-20250219-thinking"
:
1.25
,
"claude-sonnet-4-20250514"
:
1.25
,
"claude-sonnet-4-20250514-thinking"
:
1.25
,
"claude-opus-4-20250514"
:
1.25
,
"claude-opus-4-20250514-thinking"
:
1.25
,
}
//var defaultCreateCacheRatio = map[string]float64{}
...
...
setting/operation_setting/model-ratio.go
View file @
f764a27e
...
...
@@ -114,7 +114,9 @@ var defaultModelRatio = map[string]float64{
"claude-3-5-sonnet-20241022"
:
1.5
,
"claude-3-7-sonnet-20250219"
:
1.5
,
"claude-3-7-sonnet-20250219-thinking"
:
1.5
,
"claude-sonnet-4-20250514"
:
1.5
,
"claude-3-opus-20240229"
:
7.5
,
// $15 / 1M tokens
"claude-opus-4-20250514"
:
7.5
,
"ERNIE-4.0-8K"
:
0.120
*
RMB
,
"ERNIE-3.5-8K"
:
0.012
*
RMB
,
"ERNIE-3.5-8K-0205"
:
0.024
*
RMB
,
...
...
@@ -440,13 +442,15 @@ func getHardcodedCompletionModelRatio(name string) (float64, bool) {
if
name
==
"chatgpt-4o-latest"
{
return
3
,
true
}
if
strings
.
Contains
(
name
,
"claude-instant-1"
)
{
return
3
,
true
}
else
if
strings
.
Contains
(
name
,
"claude-2"
)
{
return
3
,
true
}
else
if
strings
.
Contains
(
name
,
"claude-3"
)
{
if
strings
.
Contains
(
name
,
"claude-3"
)
{
return
5
,
true
}
else
if
strings
.
Contains
(
name
,
"claude-sonnet-4"
)
||
strings
.
Contains
(
name
,
"claude-opus-4"
)
{
return
5
,
true
}
else
if
strings
.
Contains
(
name
,
"claude-instant-1"
)
||
strings
.
Contains
(
name
,
"claude-2"
)
{
return
3
,
true
}
if
strings
.
HasPrefix
(
name
,
"gpt-3.5"
)
{
if
name
==
"gpt-3.5-turbo"
||
strings
.
HasSuffix
(
name
,
"0125"
)
{
// https://openai.com/blog/new-embedding-models-and-api-updates
...
...
web/src/components/ChannelsTable.js
View file @
f764a27e
...
...
@@ -871,7 +871,16 @@ const ChannelsTable = () => {
};
const
refresh
=
async
()
=>
{
await
loadChannels
(
activePage
-
1
,
pageSize
,
idSort
,
enableTagMode
);
if
(
searchKeyword
===
''
&&
searchGroup
===
''
&&
searchModel
===
''
)
{
await
loadChannels
(
activePage
-
1
,
pageSize
,
idSort
,
enableTagMode
);
}
else
{
await
searchChannels
(
searchKeyword
,
searchGroup
,
searchModel
,
enableTagMode
,
);
}
};
useEffect
(()
=>
{
...
...
@@ -979,8 +988,8 @@ const ChannelsTable = () => {
enableTagMode
,
)
=>
{
if
(
searchKeyword
===
''
&&
searchGroup
===
''
&&
searchModel
===
''
)
{
await
loadChannels
(
0
,
pageSize
,
idSort
,
enableTagMode
);
setActivePage
(
1
);
await
loadChannels
(
activePage
-
1
,
pageSize
,
idSort
,
enableTagMode
);
//
setActivePage(1);
return
;
}
setSearching
(
true
);
...
...
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