Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
2c71c8b9
Commit
2c71c8b9
authored
Jan 01, 2026
by
shaw
Browse files
Merge PR #119: 支持自定义模型和优化模型选择
parents
901b03b8
7331220e
Changes
14
Expand all
Hide whitespace changes
Inline
Side-by-side
backend/cmd/server/wire_gen.go
View file @
2c71c8b9
...
...
@@ -114,15 +114,15 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
gitHubReleaseClient
:=
repository
.
NewGitHubReleaseClient
()
serviceBuildInfo
:=
provideServiceBuildInfo
(
buildInfo
)
updateService
:=
service
.
ProvideUpdateService
(
updateCache
,
gitHubReleaseClient
,
serviceBuildInfo
)
systemHandler
:=
handler
.
ProvideSystemHandler
(
updateService
)
adminSubscriptionHandler
:=
admin
.
NewSubscriptionHandler
(
subscriptionService
)
adminUsageHandler
:=
admin
.
NewUsageHandler
(
usageService
,
apiKeyService
,
adminService
)
adminHandlers
:=
handler
.
ProvideAdminHandlers
(
dashboardHandler
,
adminUserHandler
,
groupHandler
,
accountHandler
,
oAuthHandler
,
openAIOAuthHandler
,
geminiOAuthHandler
,
antigravityOAuthHandler
,
proxyHandler
,
adminRedeemHandler
,
settingHandler
,
systemHandler
,
adminSubscriptionHandler
,
adminUsageHandler
)
pricingRemoteClient
:=
repository
.
NewPricingRemoteClient
()
pricingService
,
err
:=
service
.
ProvidePricingService
(
configConfig
,
pricingRemoteClient
)
if
err
!=
nil
{
return
nil
,
err
}
systemHandler
:=
handler
.
ProvideSystemHandler
(
updateService
)
adminSubscriptionHandler
:=
admin
.
NewSubscriptionHandler
(
subscriptionService
)
adminUsageHandler
:=
admin
.
NewUsageHandler
(
usageService
,
apiKeyService
,
adminService
)
adminHandlers
:=
handler
.
ProvideAdminHandlers
(
dashboardHandler
,
adminUserHandler
,
groupHandler
,
accountHandler
,
oAuthHandler
,
openAIOAuthHandler
,
geminiOAuthHandler
,
antigravityOAuthHandler
,
proxyHandler
,
adminRedeemHandler
,
settingHandler
,
systemHandler
,
adminSubscriptionHandler
,
adminUsageHandler
)
billingService
:=
service
.
NewBillingService
(
configConfig
,
pricingService
)
identityCache
:=
repository
.
NewIdentityCache
(
redisClient
)
identityService
:=
service
.
NewIdentityService
(
identityCache
)
...
...
backend/internal/handler/gateway_handler.go
View file @
2c71c8b9
...
...
@@ -396,12 +396,42 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
// Models handles listing available models
// GET /v1/models
// Returns different model lists based on the API key's group platform
// Returns models based on account configurations (model_mapping whitelist)
// Falls back to default models if no whitelist is configured
func
(
h
*
GatewayHandler
)
Models
(
c
*
gin
.
Context
)
{
apiKey
,
_
:=
middleware2
.
GetApiKeyFromContext
(
c
)
// Return OpenAI models for OpenAI platform groups
if
apiKey
!=
nil
&&
apiKey
.
Group
!=
nil
&&
apiKey
.
Group
.
Platform
==
"openai"
{
var
groupID
*
int64
var
platform
string
if
apiKey
!=
nil
&&
apiKey
.
Group
!=
nil
{
groupID
=
&
apiKey
.
Group
.
ID
platform
=
apiKey
.
Group
.
Platform
}
// Get available models from account configurations (without platform filter)
availableModels
:=
h
.
gatewayService
.
GetAvailableModels
(
c
.
Request
.
Context
(),
groupID
,
""
)
if
len
(
availableModels
)
>
0
{
// Build model list from whitelist
models
:=
make
([]
claude
.
Model
,
0
,
len
(
availableModels
))
for
_
,
modelID
:=
range
availableModels
{
models
=
append
(
models
,
claude
.
Model
{
ID
:
modelID
,
Type
:
"model"
,
DisplayName
:
modelID
,
CreatedAt
:
"2024-01-01T00:00:00Z"
,
})
}
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"object"
:
"list"
,
"data"
:
models
,
})
return
}
// Fallback to default models
if
platform
==
"openai"
{
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"object"
:
"list"
,
"data"
:
openai
.
DefaultModels
,
...
...
@@ -409,7 +439,6 @@ func (h *GatewayHandler) Models(c *gin.Context) {
return
}
// Default: Claude models
c
.
JSON
(
http
.
StatusOK
,
gin
.
H
{
"object"
:
"list"
,
"data"
:
claude
.
DefaultModels
,
...
...
backend/internal/service/gateway_service.go
View file @
2c71c8b9
...
...
@@ -1925,3 +1925,58 @@ func (s *GatewayService) countTokensError(c *gin.Context, status int, errType, m
},
})
}
// GetAvailableModels returns the list of models available for a group
// It aggregates model_mapping keys from all schedulable accounts in the group
func
(
s
*
GatewayService
)
GetAvailableModels
(
ctx
context
.
Context
,
groupID
*
int64
,
platform
string
)
[]
string
{
var
accounts
[]
Account
var
err
error
if
groupID
!=
nil
{
accounts
,
err
=
s
.
accountRepo
.
ListSchedulableByGroupID
(
ctx
,
*
groupID
)
}
else
{
accounts
,
err
=
s
.
accountRepo
.
ListSchedulable
(
ctx
)
}
if
err
!=
nil
||
len
(
accounts
)
==
0
{
return
nil
}
// Filter by platform if specified
if
platform
!=
""
{
filtered
:=
make
([]
Account
,
0
)
for
_
,
acc
:=
range
accounts
{
if
acc
.
Platform
==
platform
{
filtered
=
append
(
filtered
,
acc
)
}
}
accounts
=
filtered
}
// Collect unique models from all accounts
modelSet
:=
make
(
map
[
string
]
struct
{})
hasAnyMapping
:=
false
for
_
,
acc
:=
range
accounts
{
mapping
:=
acc
.
GetModelMapping
()
if
len
(
mapping
)
>
0
{
hasAnyMapping
=
true
for
model
:=
range
mapping
{
modelSet
[
model
]
=
struct
{}{}
}
}
}
// If no account has model_mapping, return nil (use default)
if
!
hasAnyMapping
{
return
nil
}
// Convert to slice
models
:=
make
([]
string
,
0
,
len
(
modelSet
))
for
model
:=
range
modelSet
{
models
=
append
(
models
,
model
)
}
return
models
}
frontend/package-lock.json
View file @
2c71c8b9
This diff is collapsed.
Click to expand it.
frontend/package.json
View file @
2c71c8b9
...
...
@@ -11,6 +11,7 @@
"typecheck"
:
"vue-tsc --noEmit"
},
"dependencies"
:
{
"@lobehub/icons"
:
"^4.0.2"
,
"@vueuse/core"
:
"^10.7.0"
,
"axios"
:
"^1.6.2"
,
"chart.js"
:
"^4.4.1"
,
...
...
@@ -25,6 +26,7 @@
},
"devDependencies"
:
{
"@types/file-saver"
:
"^2.0.7"
,
"@types/mdx"
:
"^2.0.13"
,
"@types/node"
:
"^20.10.5"
,
"@vitejs/plugin-vue"
:
"^5.2.3"
,
"autoprefixer"
:
"^10.4.16"
,
...
...
frontend/pnpm-lock.yaml
View file @
2c71c8b9
...
...
@@ -20,6 +20,9 @@ importers:
driver.js
:
specifier
:
^1.4.0
version
:
1.4.0
file-saver
:
specifier
:
^2.0.5
version
:
2.0.5
pinia
:
specifier
:
^2.1.7
version
:
2.3.1(typescript@5.6.3)(vue@3.5.26(typescript@5.6.3))
...
...
@@ -35,7 +38,13 @@ importers:
vue-router
:
specifier
:
^4.2.5
version
:
4.6.4(vue@3.5.26(typescript@5.6.3))
xlsx
:
specifier
:
^0.18.5
version
:
0.18.5
devDependencies
:
'
@types/file-saver'
:
specifier
:
^2.0.7
version
:
2.0.7
'
@types/node'
:
specifier
:
^20.10.5
version
:
20.19.27
...
...
@@ -303,67 +312,56 @@ packages:
resolution
:
{
integrity
:
sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==
}
cpu
:
[
arm
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-arm-musleabihf@4.54.0'
:
resolution
:
{
integrity
:
sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==
}
cpu
:
[
arm
]
os
:
[
linux
]
libc
:
[
musl
]
'
@rollup/rollup-linux-arm64-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==
}
cpu
:
[
arm64
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-arm64-musl@4.54.0'
:
resolution
:
{
integrity
:
sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==
}
cpu
:
[
arm64
]
os
:
[
linux
]
libc
:
[
musl
]
'
@rollup/rollup-linux-loong64-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==
}
cpu
:
[
loong64
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-ppc64-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==
}
cpu
:
[
ppc64
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-riscv64-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==
}
cpu
:
[
riscv64
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-riscv64-musl@4.54.0'
:
resolution
:
{
integrity
:
sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==
}
cpu
:
[
riscv64
]
os
:
[
linux
]
libc
:
[
musl
]
'
@rollup/rollup-linux-s390x-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==
}
cpu
:
[
s390x
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-x64-gnu@4.54.0'
:
resolution
:
{
integrity
:
sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==
}
cpu
:
[
x64
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rollup/rollup-linux-x64-musl@4.54.0'
:
resolution
:
{
integrity
:
sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==
}
cpu
:
[
x64
]
os
:
[
linux
]
libc
:
[
musl
]
'
@rollup/rollup-openharmony-arm64@4.54.0'
:
resolution
:
{
integrity
:
sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==
}
...
...
@@ -393,6 +391,9 @@ packages:
'
@types/estree@1.0.8'
:
resolution
:
{
integrity
:
sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==
}
'
@types/file-saver@2.0.7'
:
resolution
:
{
integrity
:
sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==
}
'
@types/node@20.19.27'
:
resolution
:
{
integrity
:
sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==
}
...
...
@@ -467,6 +468,10 @@ packages:
'
@vueuse/shared@10.11.1'
:
resolution
:
{
integrity
:
sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==
}
adler-32@1.3.1
:
resolution
:
{
integrity
:
sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==
}
engines
:
{
node
:
'
>=0.8'
}
alien-signals@1.0.13
:
resolution
:
{
integrity
:
sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==
}
...
...
@@ -531,6 +536,10 @@ packages:
caniuse-lite@1.0.30001761
:
resolution
:
{
integrity
:
sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==
}
cfb@1.2.2
:
resolution
:
{
integrity
:
sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==
}
engines
:
{
node
:
'
>=0.8'
}
chart.js@4.5.1
:
resolution
:
{
integrity
:
sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==
}
engines
:
{
pnpm
:
'
>=8'
}
...
...
@@ -543,6 +552,10 @@ packages:
resolution
:
{
integrity
:
sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==
}
engines
:
{
node
:
'
>=
14.16.0'
}
codepage@1.15.0
:
resolution
:
{
integrity
:
sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==
}
engines
:
{
node
:
'
>=0.8'
}
combined-stream@1.0.8
:
resolution
:
{
integrity
:
sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
}
engines
:
{
node
:
'
>=
0.8'
}
...
...
@@ -551,6 +564,11 @@ packages:
resolution
:
{
integrity
:
sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
}
engines
:
{
node
:
'
>=
6'
}
crc-32@1.2.2
:
resolution
:
{
integrity
:
sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==
}
engines
:
{
node
:
'
>=0.8'
}
hasBin
:
true
cssesc@3.0.0
:
resolution
:
{
integrity
:
sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
}
engines
:
{
node
:
'
>=4'
}
...
...
@@ -630,6 +648,9 @@ packages:
picomatch
:
optional
:
true
file-saver@2.0.5
:
resolution
:
{
integrity
:
sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==
}
fill-range@7.1.1
:
resolution
:
{
integrity
:
sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==
}
engines
:
{
node
:
'
>=8'
}
...
...
@@ -647,6 +668,10 @@ packages:
resolution
:
{
integrity
:
sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
}
engines
:
{
node
:
'
>=
6'
}
frac@1.1.2
:
resolution
:
{
integrity
:
sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==
}
engines
:
{
node
:
'
>=0.8'
}
fraction.js@5.3.4
:
resolution
:
{
integrity
:
sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==
}
...
...
@@ -908,6 +933,10 @@ packages:
resolution
:
{
integrity
:
sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==
}
engines
:
{
node
:
'
>=0.10.0'
}
ssf@0.11.2
:
resolution
:
{
integrity
:
sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==
}
engines
:
{
node
:
'
>=0.8'
}
strip-ansi@7.1.2
:
resolution
:
{
integrity
:
sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==
}
engines
:
{
node
:
'
>=12'
}
...
...
@@ -1078,6 +1107,19 @@ packages:
typescript
:
optional
:
true
wmf@1.0.2
:
resolution
:
{
integrity
:
sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==
}
engines
:
{
node
:
'
>=0.8'
}
word@0.3.0
:
resolution
:
{
integrity
:
sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==
}
engines
:
{
node
:
'
>=0.8'
}
xlsx@0.18.5
:
resolution
:
{
integrity
:
sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==
}
engines
:
{
node
:
'
>=0.8'
}
hasBin
:
true
snapshots
:
'
@alloc/quick-lru@5.2.0'
:
{}
...
...
@@ -1278,6 +1320,8 @@ snapshots:
'
@types/estree@1.0.8'
:
{}
'
@types/file-saver@2.0.7'
:
{}
'
@types/node@20.19.27'
:
dependencies
:
undici-types
:
6.21.0
...
...
@@ -1394,6 +1438,8 @@ snapshots:
-
'
@vue/composition-api'
-
vue
adler-32@1.3.1
:
{}
alien-signals@1.0.13
:
{}
ansi-regex@6.2.2
:
{}
...
...
@@ -1457,6 +1503,11 @@ snapshots:
caniuse-lite@1.0.30001761
:
{}
cfb@1.2.2
:
dependencies
:
adler-32
:
1.3.1
crc-32
:
1.2.2
chart.js@4.5.1
:
dependencies
:
'
@kurkle/color'
:
0.3.4
...
...
@@ -1477,12 +1528,16 @@ snapshots:
dependencies
:
readdirp
:
4.1.2
codepage@1.15.0
:
{}
combined-stream@1.0.8
:
dependencies
:
delayed-stream
:
1.0.0
commander@4.1.1
:
{}
crc-32@1.2.2
:
{}
cssesc@3.0.0
:
{}
csstype@3.2.3
:
{}
...
...
@@ -1568,6 +1623,8 @@ snapshots:
optionalDependencies
:
picomatch
:
4.0.3
file-saver@2.0.5
:
{}
fill-range@7.1.1
:
dependencies
:
to-regex-range
:
5.0.1
...
...
@@ -1582,6 +1639,8 @@ snapshots:
hasown
:
2.0.2
mime-types
:
2.1.35
frac@1.1.2
:
{}
fraction.js@5.3.4
:
{}
fsevents@2.3.3
:
...
...
@@ -1818,6 +1877,10 @@ snapshots:
source-map-js@1.2.1
:
{}
ssf@0.11.2
:
dependencies
:
frac
:
1.1.2
strip-ansi@7.1.2
:
dependencies
:
ansi-regex
:
6.2.2
...
...
@@ -1960,3 +2023,17 @@ snapshots:
'@vue/shared'
:
3.5.26
optionalDependencies
:
typescript
:
5.6.3
wmf@1.0.2
:
{}
word@0.3.0
:
{}
xlsx@0.18.5
:
dependencies
:
adler-32
:
1.3.1
cfb
:
1.2.2
codepage
:
1.15.0
crc-32
:
1.2.2
ssf
:
0.11.2
wmf
:
1.0.2
word
:
0.3.0
frontend/src/components/account/CreateAccountModal.vue
View file @
2c71c8b9
...
...
@@ -880,47 +880,7 @@
<!-- Whitelist Mode -->
<div
v-if=
"modelRestrictionMode === 'whitelist'"
>
<div
class=
"mb-3 rounded-lg bg-blue-50 p-3 dark:bg-blue-900/20"
>
<p
class=
"text-xs text-blue-700 dark:text-blue-400"
>
<svg
class=
"mr-1 inline h-4 w-4"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{{
t
(
'
admin.accounts.selectAllowedModels
'
)
}}
</p>
</div>
<!-- Model Checkbox List -->
<div
class=
"mb-3 grid grid-cols-2 gap-2"
>
<label
v-for=
"model in commonModels"
:key=
"model.value"
class=
"flex cursor-pointer items-center rounded-lg border p-3 transition-all hover:bg-gray-50 dark:border-dark-600 dark:hover:bg-dark-700"
:class=
"
allowedModels.includes(model.value)
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
: 'border-gray-200'
"
>
<input
type=
"checkbox"
:value=
"model.value"
v-model=
"allowedModels"
class=
"mr-2 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span
class=
"text-sm text-gray-700 dark:text-gray-300"
>
{{
model
.
label
}}
</span>
</label>
</div>
<ModelWhitelistSelector
v-model=
"allowedModels"
:platform=
"form.platform"
/>
<p
class=
"text-xs text-gray-500 dark:text-gray-400"
>
{{
t
(
'
admin.accounts.selectedModels
'
,
{
count
:
allowedModels
.
length
}
)
}}
<
span
v
-
if
=
"
allowedModels.length === 0
"
>
{{
...
...
@@ -1549,6 +1509,7 @@
import
{
ref
,
reactive
,
computed
,
watch
}
from
'
vue
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
useAppStore
}
from
'
@/stores/app
'
import
{
claudeModels
,
getPresetMappingsByPlatform
,
getModelsByPlatform
,
commonErrorCodes
,
buildModelMappingObject
}
from
'
@/composables/useModelWhitelist
'
import
{
useAuthStore
}
from
'
@/stores/auth
'
import
{
adminAPI
}
from
'
@/api/admin
'
import
{
...
...
@@ -1563,6 +1524,7 @@ import type { Proxy, Group, AccountPlatform, AccountType } from '@/types'
import
BaseDialog
from
'
@/components/common/BaseDialog.vue
'
import
ProxySelector
from
'
@/components/common/ProxySelector.vue
'
import
GroupSelector
from
'
@/components/common/GroupSelector.vue
'
import
ModelWhitelistSelector
from
'
@/components/account/ModelWhitelistSelector.vue
'
import
OAuthAuthorizationFlow
from
'
./OAuthAuthorizationFlow.vue
'
// Type for exposed OAuthAuthorizationFlow component
...
...
@@ -1676,37 +1638,6 @@ const geminiOAuthType = ref<'code_assist' | 'google_one' | 'ai_studio'>('google_
const
geminiAIStudioOAuthEnabled
=
ref
(
false
)
const
showAdvancedOAuth
=
ref
(
false
)
// Common models for whitelist - Anthropic
const
anthropicModels
=
[
{
value
:
'
claude-opus-4-5-20251101
'
,
label
:
'
Claude Opus 4.5
'
}
,
{
value
:
'
claude-sonnet-4-20250514
'
,
label
:
'
Claude Sonnet 4
'
}
,
{
value
:
'
claude-sonnet-4-5-20250929
'
,
label
:
'
Claude Sonnet 4.5
'
}
,
{
value
:
'
claude-3-5-haiku-20241022
'
,
label
:
'
Claude 3.5 Haiku
'
}
,
{
value
:
'
claude-haiku-4-5-20251001
'
,
label
:
'
Claude Haiku 4.5
'
}
,
{
value
:
'
claude-3-opus-20240229
'
,
label
:
'
Claude 3 Opus
'
}
,
{
value
:
'
claude-3-5-sonnet-20241022
'
,
label
:
'
Claude 3.5 Sonnet
'
}
,
{
value
:
'
claude-3-haiku-20240307
'
,
label
:
'
Claude 3 Haiku
'
}
]
// Common models for whitelist - OpenAI
const
openaiModels
=
[
{
value
:
'
gpt-5.2-2025-12-11
'
,
label
:
'
GPT-5.2
'
}
,
{
value
:
'
gpt-5.2-codex
'
,
label
:
'
GPT-5.2 Codex
'
}
,
{
value
:
'
gpt-5.1-codex-max
'
,
label
:
'
GPT-5.1 Codex Max
'
}
,
{
value
:
'
gpt-5.1-codex
'
,
label
:
'
GPT-5.1 Codex
'
}
,
{
value
:
'
gpt-5.1-2025-11-13
'
,
label
:
'
GPT-5.1
'
}
,
{
value
:
'
gpt-5.1-codex-mini
'
,
label
:
'
GPT-5.1 Codex Mini
'
}
,
{
value
:
'
gpt-5-2025-08-07
'
,
label
:
'
GPT-5
'
}
]
// Common models for whitelist - Gemini
const
geminiModels
=
[
{
value
:
'
gemini-2.0-flash
'
,
label
:
'
Gemini 2.0 Flash
'
}
,
{
value
:
'
gemini-2.0-flash-lite
'
,
label
:
'
Gemini 2.0 Flash Lite
'
}
,
{
value
:
'
gemini-1.5-pro
'
,
label
:
'
Gemini 1.5 Pro
'
}
,
{
value
:
'
gemini-1.5-flash
'
,
label
:
'
Gemini 1.5 Flash
'
}
]
const
geminiQuotaDocs
=
{
codeAssist
:
'
https://developers.google.com/gemini-code-assist/resources/quotas
'
,
aiStudio
:
'
https://ai.google.dev/pricing
'
,
...
...
@@ -1721,147 +1652,8 @@ const geminiHelpLinks = {
countryCheck
:
'
https://policies.google.com/country-association-form
'
}
// Computed: current models based on platform
const
commonModels
=
computed
(()
=>
{
if
(
form
.
platform
===
'
openai
'
)
return
openaiModels
if
(
form
.
platform
===
'
gemini
'
)
return
geminiModels
return
anthropicModels
}
)
// Preset mappings for quick add - Anthropic
const
anthropicPresetMappings
=
[
{
label
:
'
Sonnet 4
'
,
from
:
'
claude-sonnet-4-20250514
'
,
to
:
'
claude-sonnet-4-20250514
'
,
color
:
'
bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400
'
}
,
{
label
:
'
Sonnet 4.5
'
,
from
:
'
claude-sonnet-4-5-20250929
'
,
to
:
'
claude-sonnet-4-5-20250929
'
,
color
:
'
bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400
'
}
,
{
label
:
'
Opus 4.5
'
,
from
:
'
claude-opus-4-5-20251101
'
,
to
:
'
claude-opus-4-5-20251101
'
,
color
:
'
bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400
'
}
,
{
label
:
'
Haiku 3.5
'
,
from
:
'
claude-3-5-haiku-20241022
'
,
to
:
'
claude-3-5-haiku-20241022
'
,
color
:
'
bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400
'
}
,
{
label
:
'
Haiku 4.5
'
,
from
:
'
claude-haiku-4-5-20251001
'
,
to
:
'
claude-haiku-4-5-20251001
'
,
color
:
'
bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400
'
}
,
{
label
:
'
Opus->Sonnet
'
,
from
:
'
claude-opus-4-5-20251101
'
,
to
:
'
claude-sonnet-4-5-20250929
'
,
color
:
'
bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400
'
}
]
// Preset mappings for quick add - OpenAI
const
openaiPresetMappings
=
[
{
label
:
'
GPT-5.2
'
,
from
:
'
gpt-5.2-2025-12-11
'
,
to
:
'
gpt-5.2-2025-12-11
'
,
color
:
'
bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400
'
}
,
{
label
:
'
GPT-5.2 Codex
'
,
from
:
'
gpt-5.2-codex
'
,
to
:
'
gpt-5.2-codex
'
,
color
:
'
bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400
'
}
,
{
label
:
'
GPT-5.1 Codex
'
,
from
:
'
gpt-5.1-codex
'
,
to
:
'
gpt-5.1-codex
'
,
color
:
'
bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400
'
}
,
{
label
:
'
Codex Max
'
,
from
:
'
gpt-5.1-codex-max
'
,
to
:
'
gpt-5.1-codex-max
'
,
color
:
'
bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400
'
}
,
{
label
:
'
Codex Mini
'
,
from
:
'
gpt-5.1-codex-mini
'
,
to
:
'
gpt-5.1-codex-mini
'
,
color
:
'
bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400
'
}
,
{
label
:
'
Max->Codex
'
,
from
:
'
gpt-5.1-codex-max
'
,
to
:
'
gpt-5.1-codex
'
,
color
:
'
bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400
'
}
]
// Preset mappings for quick add - Gemini
const
geminiPresetMappings
=
[
{
label
:
'
Flash
'
,
from
:
'
gemini-2.0-flash
'
,
to
:
'
gemini-2.0-flash
'
,
color
:
'
bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400
'
}
,
{
label
:
'
Flash Lite
'
,
from
:
'
gemini-2.0-flash-lite
'
,
to
:
'
gemini-2.0-flash-lite
'
,
color
:
'
bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400
'
}
,
{
label
:
'
1.5 Pro
'
,
from
:
'
gemini-1.5-pro
'
,
to
:
'
gemini-1.5-pro
'
,
color
:
'
bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400
'
}
,
{
label
:
'
1.5 Flash
'
,
from
:
'
gemini-1.5-flash
'
,
to
:
'
gemini-1.5-flash
'
,
color
:
'
bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400
'
}
]
// Computed: current preset mappings based on platform
const
presetMappings
=
computed
(()
=>
{
if
(
form
.
platform
===
'
openai
'
)
return
openaiPresetMappings
if
(
form
.
platform
===
'
gemini
'
)
return
geminiPresetMappings
return
anthropicPresetMappings
}
)
// Common HTTP error codes for quick selection
const
commonErrorCodes
=
[
{
value
:
401
,
label
:
'
Unauthorized
'
}
,
{
value
:
403
,
label
:
'
Forbidden
'
}
,
{
value
:
429
,
label
:
'
Rate Limit
'
}
,
{
value
:
500
,
label
:
'
Server Error
'
}
,
{
value
:
502
,
label
:
'
Bad Gateway
'
}
,
{
value
:
503
,
label
:
'
Unavailable
'
}
,
{
value
:
529
,
label
:
'
Overloaded
'
}
]
const
presetMappings
=
computed
(()
=>
getPresetMappingsByPlatform
(
form
.
platform
))
const
form
=
reactive
({
name
:
''
,
...
...
@@ -1899,7 +1691,10 @@ const canExchangeCode = computed(() => {
watch
(
()
=>
props
.
show
,
(
newVal
)
=>
{
if
(
!
newVal
)
{
if
(
newVal
)
{
// Modal opened - fill related models
allowedModels
.
value
=
[...
getModelsByPlatform
(
form
.
platform
)]
}
else
{
resetForm
()
}
}
...
...
@@ -1973,6 +1768,16 @@ const handleSelectGeminiOAuthType = (oauthType: 'code_assist' | 'google_one' | '
geminiOAuthType
.
value
=
oauthType
}
// Auto-fill related models when switching to whitelist mode or changing platform
watch
(
[
modelRestrictionMode
,
()
=>
form
.
platform
],
([
newMode
])
=>
{
if
(
newMode
===
'
whitelist
'
)
{
allowedModels
.
value
=
[...
getModelsByPlatform
(
form
.
platform
)]
}
}
)
// Model mapping helpers
const
addModelMapping
=
()
=>
{
modelMappings
.
value
.
push
({
from
:
''
,
to
:
''
}
)
...
...
@@ -1983,9 +1788,7 @@ const removeModelMapping = (index: number) => {
}
const
addPresetMapping
=
(
from
:
string
,
to
:
string
)
=>
{
// Check if mapping already exists
const
exists
=
modelMappings
.
value
.
some
((
m
)
=>
m
.
from
===
from
)
if
(
exists
)
{
if
(
modelMappings
.
value
.
some
((
m
)
=>
m
.
from
===
from
))
{
appStore
.
showInfo
(
t
(
'
admin.accounts.mappingExists
'
,
{
model
:
from
}
))
return
}
...
...
@@ -2025,28 +1828,6 @@ const removeErrorCode = (code: number) => {
}
}
const
buildModelMappingObject
=
():
Record
<
string
,
string
>
|
null
=>
{
const
mapping
:
Record
<
string
,
string
>
=
{
}
if
(
modelRestrictionMode
.
value
===
'
whitelist
'
)
{
// Whitelist mode: map model to itself
for
(
const
model
of
allowedModels
.
value
)
{
mapping
[
model
]
=
model
}
}
else
{
// Mapping mode: use custom mappings
for
(
const
m
of
modelMappings
.
value
)
{
const
from
=
m
.
from
.
trim
()
const
to
=
m
.
to
.
trim
()
if
(
from
&&
to
)
{
mapping
[
from
]
=
to
}
}
}
return
Object
.
keys
(
mapping
).
length
>
0
?
mapping
:
null
}
// Methods
const
resetForm
=
()
=>
{
step
.
value
=
1
...
...
@@ -2064,7 +1845,7 @@ const resetForm = () => {
apiKeyValue
.
value
=
''
modelMappings
.
value
=
[]
modelRestrictionMode
.
value
=
'
whitelist
'
allowedModels
.
value
=
[
]
allowedModels
.
value
=
[
...
claudeModels
]
// Default fill related models
customErrorCodesEnabled
.
value
=
false
selectedErrorCodes
.
value
=
[]
customErrorCodeInput
.
value
=
null
...
...
@@ -2113,7 +1894,7 @@ const handleSubmit = async () => {
}
// Add model mapping if configured
const
modelMapping
=
buildModelMappingObject
()
const
modelMapping
=
buildModelMappingObject
(
modelRestrictionMode
.
value
,
allowedModels
.
value
,
modelMappings
.
value
)
if
(
modelMapping
)
{
credentials
.
model_mapping
=
modelMapping
}
...
...
frontend/src/components/account/EditAccountModal.vue
View file @
2c71c8b9
...
...
@@ -111,47 +111,7 @@
<!-- Whitelist Mode -->
<div
v-if=
"modelRestrictionMode === 'whitelist'"
>
<div
class=
"mb-3 rounded-lg bg-blue-50 p-3 dark:bg-blue-900/20"
>
<p
class=
"text-xs text-blue-700 dark:text-blue-400"
>
<svg
class=
"mr-1 inline h-4 w-4"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
/>
</svg>
{{
t
(
'
admin.accounts.selectAllowedModels
'
)
}}
</p>
</div>
<!-- Model Checkbox List -->
<div
class=
"mb-3 grid grid-cols-2 gap-2"
>
<label
v-for=
"model in commonModels"
:key=
"model.value"
class=
"flex cursor-pointer items-center rounded-lg border p-3 transition-all hover:bg-gray-50 dark:border-dark-600 dark:hover:bg-dark-700"
:class=
"
allowedModels.includes(model.value)
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
: 'border-gray-200'
"
>
<input
type=
"checkbox"
:value=
"model.value"
v-model=
"allowedModels"
class=
"mr-2 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
/>
<span
class=
"text-sm text-gray-700 dark:text-gray-300"
>
{{
model
.
label
}}
</span>
</label>
</div>
<ModelWhitelistSelector
v-model=
"allowedModels"
:platform=
"account?.platform || 'anthropic'"
/>
<p
class=
"text-xs text-gray-500 dark:text-gray-400"
>
{{
t
(
'
admin.accounts.selectedModels
'
,
{
count
:
allowedModels
.
length
}
)
}}
<
span
v
-
if
=
"
allowedModels.length === 0
"
>
{{
...
...
@@ -565,6 +525,12 @@ import BaseDialog from '@/components/common/BaseDialog.vue'
import
Select
from
'
@/components/common/Select.vue
'
import
ProxySelector
from
'
@/components/common/ProxySelector.vue
'
import
GroupSelector
from
'
@/components/common/GroupSelector.vue
'
import
ModelWhitelistSelector
from
'
@/components/account/ModelWhitelistSelector.vue
'
import
{
getPresetMappingsByPlatform
,
commonErrorCodes
,
buildModelMappingObject
}
from
'
@/composables/useModelWhitelist
'
interface
Props
{
show
:
boolean
...
...
@@ -610,167 +576,8 @@ const customErrorCodeInput = ref<number | null>(null)
const
interceptWarmupRequests
=
ref
(
false
)
const
mixedScheduling
=
ref
(
false
)
// For antigravity accounts: enable mixed scheduling
// Common models for whitelist - Anthropic
const
anthropicModels
=
[
{
value
:
'
claude-opus-4-5-20251101
'
,
label
:
'
Claude Opus 4.5
'
}
,
{
value
:
'
claude-sonnet-4-20250514
'
,
label
:
'
Claude Sonnet 4
'
}
,
{
value
:
'
claude-sonnet-4-5-20250929
'
,
label
:
'
Claude Sonnet 4.5
'
}
,
{
value
:
'
claude-3-5-haiku-20241022
'
,
label
:
'
Claude 3.5 Haiku
'
}
,
{
value
:
'
claude-haiku-4-5-20251001
'
,
label
:
'
Claude Haiku 4.5
'
}
,
{
value
:
'
claude-3-opus-20240229
'
,
label
:
'
Claude 3 Opus
'
}
,
{
value
:
'
claude-3-5-sonnet-20241022
'
,
label
:
'
Claude 3.5 Sonnet
'
}
,
{
value
:
'
claude-3-haiku-20240307
'
,
label
:
'
Claude 3 Haiku
'
}
]
// Common models for whitelist - OpenAI
const
openaiModels
=
[
{
value
:
'
gpt-5.2-2025-12-11
'
,
label
:
'
GPT-5.2
'
}
,
{
value
:
'
gpt-5.2-codex
'
,
label
:
'
GPT-5.2 Codex
'
}
,
{
value
:
'
gpt-5.1-codex-max
'
,
label
:
'
GPT-5.1 Codex Max
'
}
,
{
value
:
'
gpt-5.1-codex
'
,
label
:
'
GPT-5.1 Codex
'
}
,
{
value
:
'
gpt-5.1-2025-11-13
'
,
label
:
'
GPT-5.1
'
}
,
{
value
:
'
gpt-5.1-codex-mini
'
,
label
:
'
GPT-5.1 Codex Mini
'
}
,
{
value
:
'
gpt-5-2025-08-07
'
,
label
:
'
GPT-5
'
}
]
// Common models for whitelist - Gemini
const
geminiModels
=
[
{
value
:
'
gemini-2.0-flash
'
,
label
:
'
Gemini 2.0 Flash
'
}
,
{
value
:
'
gemini-2.0-flash-lite
'
,
label
:
'
Gemini 2.0 Flash Lite
'
}
,
{
value
:
'
gemini-1.5-pro
'
,
label
:
'
Gemini 1.5 Pro
'
}
,
{
value
:
'
gemini-1.5-flash
'
,
label
:
'
Gemini 1.5 Flash
'
}
]
// Computed: current models based on platform
const
commonModels
=
computed
(()
=>
{
if
(
props
.
account
?.
platform
===
'
openai
'
)
return
openaiModels
if
(
props
.
account
?.
platform
===
'
gemini
'
)
return
geminiModels
return
anthropicModels
}
)
// Preset mappings for quick add - Anthropic
const
anthropicPresetMappings
=
[
{
label
:
'
Sonnet 4
'
,
from
:
'
claude-sonnet-4-20250514
'
,
to
:
'
claude-sonnet-4-20250514
'
,
color
:
'
bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400
'
}
,
{
label
:
'
Sonnet 4.5
'
,
from
:
'
claude-sonnet-4-5-20250929
'
,
to
:
'
claude-sonnet-4-5-20250929
'
,
color
:
'
bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400
'
}
,
{
label
:
'
Opus 4.5
'
,
from
:
'
claude-opus-4-5-20251101
'
,
to
:
'
claude-opus-4-5-20251101
'
,
color
:
'
bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400
'
}
,
{
label
:
'
Haiku 3.5
'
,
from
:
'
claude-3-5-haiku-20241022
'
,
to
:
'
claude-3-5-haiku-20241022
'
,
color
:
'
bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400
'
}
,
{
label
:
'
Haiku 4.5
'
,
from
:
'
claude-haiku-4-5-20251001
'
,
to
:
'
claude-haiku-4-5-20251001
'
,
color
:
'
bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400
'
}
,
{
label
:
'
Opus->Sonnet
'
,
from
:
'
claude-opus-4-5-20251101
'
,
to
:
'
claude-sonnet-4-5-20250929
'
,
color
:
'
bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400
'
}
]
// Preset mappings for quick add - OpenAI
const
openaiPresetMappings
=
[
{
label
:
'
GPT-5.2
'
,
from
:
'
gpt-5.2-2025-12-11
'
,
to
:
'
gpt-5.2-2025-12-11
'
,
color
:
'
bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400
'
}
,
{
label
:
'
GPT-5.2 Codex
'
,
from
:
'
gpt-5.2-codex
'
,
to
:
'
gpt-5.2-codex
'
,
color
:
'
bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400
'
}
,
{
label
:
'
GPT-5.1 Codex
'
,
from
:
'
gpt-5.1-codex
'
,
to
:
'
gpt-5.1-codex
'
,
color
:
'
bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400
'
}
,
{
label
:
'
Codex Max
'
,
from
:
'
gpt-5.1-codex-max
'
,
to
:
'
gpt-5.1-codex-max
'
,
color
:
'
bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400
'
}
,
{
label
:
'
Codex Mini
'
,
from
:
'
gpt-5.1-codex-mini
'
,
to
:
'
gpt-5.1-codex-mini
'
,
color
:
'
bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400
'
}
,
{
label
:
'
Max->Codex
'
,
from
:
'
gpt-5.1-codex-max
'
,
to
:
'
gpt-5.1-codex
'
,
color
:
'
bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400
'
}
]
// Preset mappings for quick add - Gemini
const
geminiPresetMappings
=
[
{
label
:
'
Flash
'
,
from
:
'
gemini-2.0-flash
'
,
to
:
'
gemini-2.0-flash
'
,
color
:
'
bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400
'
}
,
{
label
:
'
Flash Lite
'
,
from
:
'
gemini-2.0-flash-lite
'
,
to
:
'
gemini-2.0-flash-lite
'
,
color
:
'
bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400
'
}
,
{
label
:
'
1.5 Pro
'
,
from
:
'
gemini-1.5-pro
'
,
to
:
'
gemini-1.5-pro
'
,
color
:
'
bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400
'
}
,
{
label
:
'
1.5 Flash
'
,
from
:
'
gemini-1.5-flash
'
,
to
:
'
gemini-1.5-flash
'
,
color
:
'
bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400
'
}
]
// Computed: current preset mappings based on platform
const
presetMappings
=
computed
(()
=>
{
if
(
props
.
account
?.
platform
===
'
openai
'
)
return
openaiPresetMappings
if
(
props
.
account
?.
platform
===
'
gemini
'
)
return
geminiPresetMappings
return
anthropicPresetMappings
}
)
const
presetMappings
=
computed
(()
=>
getPresetMappingsByPlatform
(
props
.
account
?.
platform
||
'
anthropic
'
))
// Computed: default base URL based on platform
const
defaultBaseUrl
=
computed
(()
=>
{
...
...
@@ -779,17 +586,6 @@ const defaultBaseUrl = computed(() => {
return
'
https://api.anthropic.com
'
}
)
// Common HTTP error codes for quick selection
const
commonErrorCodes
=
[
{
value
:
401
,
label
:
'
Unauthorized
'
}
,
{
value
:
403
,
label
:
'
Forbidden
'
}
,
{
value
:
429
,
label
:
'
Rate Limit
'
}
,
{
value
:
500
,
label
:
'
Server Error
'
}
,
{
value
:
502
,
label
:
'
Bad Gateway
'
}
,
{
value
:
503
,
label
:
'
Unavailable
'
}
,
{
value
:
529
,
label
:
'
Overloaded
'
}
]
const
form
=
reactive
({
name
:
''
,
proxy_id
:
null
as
number
|
null
,
...
...
@@ -940,28 +736,6 @@ const removeErrorCode = (code: number) => {
}
}
const
buildModelMappingObject
=
():
Record
<
string
,
string
>
|
null
=>
{
const
mapping
:
Record
<
string
,
string
>
=
{
}
if
(
modelRestrictionMode
.
value
===
'
whitelist
'
)
{
// Whitelist mode: model maps to itself
for
(
const
model
of
allowedModels
.
value
)
{
mapping
[
model
]
=
model
}
}
else
{
// Mapping mode: use the mapping entries
for
(
const
m
of
modelMappings
.
value
)
{
const
from
=
m
.
from
.
trim
()
const
to
=
m
.
to
.
trim
()
if
(
from
&&
to
)
{
mapping
[
from
]
=
to
}
}
}
return
Object
.
keys
(
mapping
).
length
>
0
?
mapping
:
null
}
// Methods
const
handleClose
=
()
=>
{
emit
(
'
close
'
)
...
...
@@ -978,7 +752,7 @@ const handleSubmit = async () => {
if
(
props
.
account
.
type
===
'
apikey
'
)
{
const
currentCredentials
=
(
props
.
account
.
credentials
as
Record
<
string
,
unknown
>
)
||
{
}
const
newBaseUrl
=
editBaseUrl
.
value
.
trim
()
||
defaultBaseUrl
.
value
const
modelMapping
=
buildModelMappingObject
()
const
modelMapping
=
buildModelMappingObject
(
modelRestrictionMode
.
value
,
allowedModels
.
value
,
modelMappings
.
value
)
// Always update credentials for apikey type to handle model mapping changes
const
newCredentials
:
Record
<
string
,
unknown
>
=
{
...
...
frontend/src/components/account/ModelWhitelistSelector.vue
0 → 100644
View file @
2c71c8b9
<
template
>
<div>
<!-- Multi-select Dropdown -->
<div
class=
"relative mb-3"
>
<div
@
click=
"toggleDropdown"
class=
"cursor-pointer rounded-lg border border-gray-300 bg-white px-3 py-2 dark:border-dark-500 dark:bg-dark-700"
>
<div
class=
"grid grid-cols-2 gap-1.5"
>
<span
v-for=
"model in modelValue"
:key=
"model"
class=
"inline-flex items-center justify-between gap-1 rounded bg-gray-100 px-2 py-1 text-xs text-gray-700 dark:bg-dark-600 dark:text-gray-300"
>
<span
class=
"flex items-center gap-1 truncate"
>
<ModelIcon
:model=
"model"
size=
"14px"
/>
<span
class=
"truncate"
>
{{
model
}}
</span>
</span>
<button
type=
"button"
@
click.stop=
"removeModel(model)"
class=
"shrink-0 rounded-full hover:bg-gray-200 dark:hover:bg-dark-500"
>
<svg
class=
"h-3.5 w-3.5"
fill=
"none"
viewBox=
"0 0 24 24"
stroke=
"currentColor"
>
<path
stroke-linecap=
"round"
stroke-linejoin=
"round"
stroke-width=
"2"
d=
"M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</span>
</div>
<div
class=
"mt-2 flex items-center justify-between border-t border-gray-200 pt-2 dark:border-dark-600"
>
<span
class=
"text-xs text-gray-400"
>
{{
t
(
'
admin.accounts.modelCount
'
,
{
count
:
modelValue
.
length
}
)
}}
<
/span
>
<
svg
class
=
"
h-5 w-5 text-gray-400
"
fill
=
"
none
"
viewBox
=
"
0 0 24 24
"
stroke
=
"
currentColor
"
>
<
path
stroke
-
linecap
=
"
round
"
stroke
-
linejoin
=
"
round
"
stroke
-
width
=
"
2
"
d
=
"
M19 9l-7 7-7-7
"
/>
<
/svg
>
<
/div
>
<
/div
>
<!--
Dropdown
List
-->
<
div
v
-
if
=
"
showDropdown
"
class
=
"
absolute left-0 right-0 top-full z-50 mt-1 rounded-lg border border-gray-200 bg-white shadow-lg dark:border-dark-600 dark:bg-dark-700
"
>
<
div
class
=
"
sticky top-0 border-b border-gray-200 bg-white p-2 dark:border-dark-600 dark:bg-dark-700
"
>
<
input
v
-
model
=
"
searchQuery
"
type
=
"
text
"
class
=
"
input w-full text-sm
"
:
placeholder
=
"
t('admin.accounts.searchModels')
"
@
click
.
stop
/>
<
/div
>
<
div
class
=
"
max-h-52 overflow-auto
"
>
<
button
v
-
for
=
"
model in filteredModels
"
:
key
=
"
model.value
"
type
=
"
button
"
@
click
=
"
toggleModel(model.value)
"
class
=
"
flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-gray-100 dark:hover:bg-dark-600
"
>
<
span
:
class
=
"
[
'flex h-4 w-4 shrink-0 items-center justify-center rounded border',
modelValue.includes(model.value)
? 'border-primary-500 bg-primary-500 text-white'
: 'border-gray-300 dark:border-dark-500'
]
"
>
<
svg
v
-
if
=
"
modelValue.includes(model.value)
"
class
=
"
h-3 w-3
"
fill
=
"
none
"
viewBox
=
"
0 0 24 24
"
stroke
=
"
currentColor
"
>
<
path
stroke
-
linecap
=
"
round
"
stroke
-
linejoin
=
"
round
"
stroke
-
width
=
"
3
"
d
=
"
M5 13l4 4L19 7
"
/>
<
/svg
>
<
/span
>
<
ModelIcon
:
model
=
"
model.value
"
size
=
"
18px
"
/>
<
span
class
=
"
truncate text-gray-900 dark:text-white
"
>
{{
model
.
value
}}
<
/span
>
<
/button
>
<
div
v
-
if
=
"
filteredModels.length === 0
"
class
=
"
px-3 py-4 text-center text-sm text-gray-500
"
>
{{
t
(
'
admin.accounts.noMatchingModels
'
)
}}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<!--
Quick
Actions
-->
<
div
class
=
"
mb-4 flex flex-wrap gap-2
"
>
<
button
type
=
"
button
"
@
click
=
"
fillRelated
"
class
=
"
rounded-lg border border-blue-200 px-3 py-1.5 text-sm text-blue-600 hover:bg-blue-50 dark:border-blue-800 dark:text-blue-400 dark:hover:bg-blue-900/30
"
>
{{
t
(
'
admin.accounts.fillRelatedModels
'
)
}}
<
/button
>
<
button
type
=
"
button
"
@
click
=
"
clearAll
"
class
=
"
rounded-lg border border-red-200 px-3 py-1.5 text-sm text-red-600 hover:bg-red-50 dark:border-red-800 dark:text-red-400 dark:hover:bg-red-900/30
"
>
{{
t
(
'
admin.accounts.clearAllModels
'
)
}}
<
/button
>
<
/div
>
<!--
Custom
Model
Input
-->
<
div
class
=
"
mb-3
"
>
<
label
class
=
"
mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300
"
>
{{
t
(
'
admin.accounts.customModelName
'
)
}}
<
/label
>
<
div
class
=
"
flex gap-2
"
>
<
input
v
-
model
=
"
customModel
"
type
=
"
text
"
class
=
"
input flex-1
"
:
placeholder
=
"
t('admin.accounts.enterCustomModelName')
"
@
keydown
.
enter
.
prevent
=
"
handleEnter
"
@
compositionstart
=
"
isComposing = true
"
@
compositionend
=
"
isComposing = false
"
/>
<
button
type
=
"
button
"
@
click
=
"
addCustom
"
class
=
"
rounded-lg bg-primary-50 px-4 py-2 text-sm font-medium text-primary-600 hover:bg-primary-100 dark:bg-primary-900/30 dark:text-primary-400 dark:hover:bg-primary-900/50
"
>
{{
t
(
'
admin.accounts.addModel
'
)
}}
<
/button
>
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
script
setup
lang
=
"
ts
"
>
import
{
ref
,
computed
}
from
'
vue
'
import
{
useI18n
}
from
'
vue-i18n
'
import
{
useAppStore
}
from
'
@/stores/app
'
import
ModelIcon
from
'
@/components/common/ModelIcon.vue
'
import
{
allModels
,
getModelsByPlatform
}
from
'
@/composables/useModelWhitelist
'
const
{
t
}
=
useI18n
()
const
props
=
defineProps
<
{
modelValue
:
string
[]
platform
:
string
}
>
()
const
emit
=
defineEmits
<
{
'
update:modelValue
'
:
[
value
:
string
[]]
}
>
()
const
appStore
=
useAppStore
()
const
showDropdown
=
ref
(
false
)
const
searchQuery
=
ref
(
''
)
const
customModel
=
ref
(
''
)
const
isComposing
=
ref
(
false
)
const
filteredModels
=
computed
(()
=>
{
const
query
=
searchQuery
.
value
.
toLowerCase
().
trim
()
if
(
!
query
)
return
allModels
return
allModels
.
filter
(
m
=>
m
.
value
.
toLowerCase
().
includes
(
query
)
||
m
.
label
.
toLowerCase
().
includes
(
query
)
)
}
)
const
toggleDropdown
=
()
=>
{
showDropdown
.
value
=
!
showDropdown
.
value
if
(
!
showDropdown
.
value
)
searchQuery
.
value
=
''
}
const
removeModel
=
(
model
:
string
)
=>
{
emit
(
'
update:modelValue
'
,
props
.
modelValue
.
filter
(
m
=>
m
!==
model
))
}
const
toggleModel
=
(
model
:
string
)
=>
{
if
(
props
.
modelValue
.
includes
(
model
))
{
removeModel
(
model
)
}
else
{
emit
(
'
update:modelValue
'
,
[...
props
.
modelValue
,
model
])
}
}
const
addCustom
=
()
=>
{
const
model
=
customModel
.
value
.
trim
()
if
(
!
model
)
return
if
(
props
.
modelValue
.
includes
(
model
))
{
appStore
.
showInfo
(
t
(
'
admin.accounts.modelExists
'
))
return
}
emit
(
'
update:modelValue
'
,
[...
props
.
modelValue
,
model
])
customModel
.
value
=
''
}
const
handleEnter
=
()
=>
{
if
(
!
isComposing
.
value
)
addCustom
()
}
const
fillRelated
=
()
=>
{
const
models
=
getModelsByPlatform
(
props
.
platform
)
const
newModels
=
[...
props
.
modelValue
]
for
(
const
model
of
models
)
{
if
(
!
newModels
.
includes
(
model
))
newModels
.
push
(
model
)
}
emit
(
'
update:modelValue
'
,
newModels
)
}
const
clearAll
=
()
=>
{
emit
(
'
update:modelValue
'
,
[])
}
<
/script
>
frontend/src/components/common/ModelIcon.vue
0 → 100644
View file @
2c71c8b9
This diff is collapsed.
Click to expand it.
frontend/src/composables/useModelWhitelist.ts
0 → 100644
View file @
2c71c8b9
// =====================
// 模型列表(硬编码,与 new-api 一致)
// =====================
// OpenAI
const
openaiModels
=
[
'
gpt-3.5-turbo
'
,
'
gpt-3.5-turbo-0125
'
,
'
gpt-3.5-turbo-1106
'
,
'
gpt-3.5-turbo-16k
'
,
'
gpt-4
'
,
'
gpt-4-turbo
'
,
'
gpt-4-turbo-preview
'
,
'
gpt-4o
'
,
'
gpt-4o-2024-08-06
'
,
'
gpt-4o-2024-11-20
'
,
'
gpt-4o-mini
'
,
'
gpt-4o-mini-2024-07-18
'
,
'
gpt-4.5-preview
'
,
'
gpt-4.1
'
,
'
gpt-4.1-mini
'
,
'
gpt-4.1-nano
'
,
'
o1
'
,
'
o1-preview
'
,
'
o1-mini
'
,
'
o1-pro
'
,
'
o3
'
,
'
o3-mini
'
,
'
o3-pro
'
,
'
o4-mini
'
,
'
gpt-5
'
,
'
gpt-5-mini
'
,
'
gpt-5-nano
'
,
'
chatgpt-4o-latest
'
,
'
gpt-4o-audio-preview
'
,
'
gpt-4o-realtime-preview
'
]
// Anthropic Claude
export
const
claudeModels
=
[
'
claude-3-5-sonnet-20241022
'
,
'
claude-3-5-sonnet-20240620
'
,
'
claude-3-5-haiku-20241022
'
,
'
claude-3-opus-20240229
'
,
'
claude-3-sonnet-20240229
'
,
'
claude-3-haiku-20240307
'
,
'
claude-3-7-sonnet-20250219
'
,
'
claude-sonnet-4-20250514
'
,
'
claude-opus-4-20250514
'
,
'
claude-opus-4-1-20250805
'
,
'
claude-sonnet-4-5-20250929
'
,
'
claude-haiku-4-5-20251001
'
,
'
claude-opus-4-5-20251101
'
,
'
claude-2.1
'
,
'
claude-2.0
'
,
'
claude-instant-1.2
'
]
// Google Gemini
const
geminiModels
=
[
'
gemini-2.0-flash
'
,
'
gemini-2.0-flash-lite-preview
'
,
'
gemini-2.0-flash-exp
'
,
'
gemini-2.0-pro-exp
'
,
'
gemini-2.0-flash-thinking-exp
'
,
'
gemini-2.5-pro-exp-03-25
'
,
'
gemini-2.5-pro-preview-03-25
'
,
'
gemini-3-pro-preview
'
,
'
gemini-1.5-pro
'
,
'
gemini-1.5-pro-latest
'
,
'
gemini-1.5-flash
'
,
'
gemini-1.5-flash-latest
'
,
'
gemini-1.5-flash-8b
'
,
'
gemini-exp-1206
'
]
// 智谱 GLM
const
zhipuModels
=
[
'
glm-4
'
,
'
glm-4v
'
,
'
glm-4-plus
'
,
'
glm-4-0520
'
,
'
glm-4-air
'
,
'
glm-4-airx
'
,
'
glm-4-long
'
,
'
glm-4-flash
'
,
'
glm-4v-plus
'
,
'
glm-4.5
'
,
'
glm-4.6
'
,
'
glm-3-turbo
'
,
'
glm-4-alltools
'
,
'
chatglm_turbo
'
,
'
chatglm_pro
'
,
'
chatglm_std
'
,
'
chatglm_lite
'
,
'
cogview-3
'
,
'
cogvideo
'
]
// 阿里 通义千问
const
qwenModels
=
[
'
qwen-turbo
'
,
'
qwen-plus
'
,
'
qwen-max
'
,
'
qwen-max-longcontext
'
,
'
qwen-long
'
,
'
qwen2-72b-instruct
'
,
'
qwen2-57b-a14b-instruct
'
,
'
qwen2-7b-instruct
'
,
'
qwen2.5-72b-instruct
'
,
'
qwen2.5-32b-instruct
'
,
'
qwen2.5-14b-instruct
'
,
'
qwen2.5-7b-instruct
'
,
'
qwen2.5-3b-instruct
'
,
'
qwen2.5-1.5b-instruct
'
,
'
qwen2.5-coder-32b-instruct
'
,
'
qwen2.5-coder-14b-instruct
'
,
'
qwen2.5-coder-7b-instruct
'
,
'
qwen3-235b-a22b
'
,
'
qwq-32b
'
,
'
qwq-32b-preview
'
]
// DeepSeek
const
deepseekModels
=
[
'
deepseek-chat
'
,
'
deepseek-coder
'
,
'
deepseek-reasoner
'
,
'
deepseek-v3
'
,
'
deepseek-v3-0324
'
,
'
deepseek-r1
'
,
'
deepseek-r1-0528
'
,
'
deepseek-r1-distill-qwen-32b
'
,
'
deepseek-r1-distill-qwen-14b
'
,
'
deepseek-r1-distill-qwen-7b
'
,
'
deepseek-r1-distill-llama-70b
'
,
'
deepseek-r1-distill-llama-8b
'
]
// Mistral
const
mistralModels
=
[
'
mistral-small-latest
'
,
'
mistral-medium-latest
'
,
'
mistral-large-latest
'
,
'
open-mistral-7b
'
,
'
open-mixtral-8x7b
'
,
'
open-mixtral-8x22b
'
,
'
codestral-latest
'
,
'
codestral-mamba
'
,
'
pixtral-12b-2409
'
,
'
pixtral-large-latest
'
]
// Meta Llama
const
metaModels
=
[
'
llama-3.3-70b-instruct
'
,
'
llama-3.2-90b-vision-instruct
'
,
'
llama-3.2-11b-vision-instruct
'
,
'
llama-3.2-3b-instruct
'
,
'
llama-3.2-1b-instruct
'
,
'
llama-3.1-405b-instruct
'
,
'
llama-3.1-70b-instruct
'
,
'
llama-3.1-8b-instruct
'
,
'
llama-3-70b-instruct
'
,
'
llama-3-8b-instruct
'
,
'
codellama-70b-instruct
'
,
'
codellama-34b-instruct
'
,
'
codellama-13b-instruct
'
]
// xAI Grok
const
xaiModels
=
[
'
grok-4
'
,
'
grok-4-0709
'
,
'
grok-3-beta
'
,
'
grok-3-mini-beta
'
,
'
grok-3-fast-beta
'
,
'
grok-2
'
,
'
grok-2-vision
'
,
'
grok-2-image
'
,
'
grok-beta
'
,
'
grok-vision-beta
'
]
// Cohere
const
cohereModels
=
[
'
command-a-03-2025
'
,
'
command-r
'
,
'
command-r-plus
'
,
'
command-r-08-2024
'
,
'
command-r-plus-08-2024
'
,
'
c4ai-aya-23-35b
'
,
'
c4ai-aya-23-8b
'
,
'
command
'
,
'
command-light
'
]
// Yi (01.AI)
const
yiModels
=
[
'
yi-large
'
,
'
yi-large-turbo
'
,
'
yi-large-rag
'
,
'
yi-medium
'
,
'
yi-medium-200k
'
,
'
yi-spark
'
,
'
yi-vision
'
,
'
yi-1.5-34b-chat
'
,
'
yi-1.5-9b-chat
'
,
'
yi-1.5-6b-chat
'
]
// Moonshot/Kimi
const
moonshotModels
=
[
'
moonshot-v1-8k
'
,
'
moonshot-v1-32k
'
,
'
moonshot-v1-128k
'
,
'
kimi-latest
'
]
// 字节跳动 豆包
const
doubaoModels
=
[
'
doubao-pro-256k
'
,
'
doubao-pro-128k
'
,
'
doubao-pro-32k
'
,
'
doubao-pro-4k
'
,
'
doubao-lite-128k
'
,
'
doubao-lite-32k
'
,
'
doubao-lite-4k
'
,
'
doubao-vision-pro-32k
'
,
'
doubao-vision-lite-32k
'
,
'
doubao-1.5-pro-256k
'
,
'
doubao-1.5-pro-32k
'
,
'
doubao-1.5-lite-32k
'
,
'
doubao-1.5-pro-vision-32k
'
,
'
doubao-1.5-thinking-pro
'
]
// MiniMax
const
minimaxModels
=
[
'
abab6.5-chat
'
,
'
abab6.5s-chat
'
,
'
abab6.5s-chat-pro
'
,
'
abab6-chat
'
,
'
abab5.5-chat
'
,
'
abab5.5s-chat
'
]
// 百度 文心
const
baiduModels
=
[
'
ernie-4.0-8k-latest
'
,
'
ernie-4.0-8k
'
,
'
ernie-4.0-turbo-8k
'
,
'
ernie-3.5-8k
'
,
'
ernie-3.5-128k
'
,
'
ernie-speed-8k
'
,
'
ernie-speed-128k
'
,
'
ernie-speed-pro-128k
'
,
'
ernie-lite-8k
'
,
'
ernie-lite-pro-128k
'
,
'
ernie-tiny-8k
'
]
// 讯飞 星火
const
sparkModels
=
[
'
spark-desk
'
,
'
spark-desk-v1.1
'
,
'
spark-desk-v2.1
'
,
'
spark-desk-v3.1
'
,
'
spark-desk-v3.5
'
,
'
spark-desk-v4.0
'
,
'
spark-lite
'
,
'
spark-pro
'
,
'
spark-max
'
,
'
spark-ultra
'
]
// 腾讯 混元
const
hunyuanModels
=
[
'
hunyuan-lite
'
,
'
hunyuan-standard
'
,
'
hunyuan-standard-256k
'
,
'
hunyuan-pro
'
,
'
hunyuan-turbo
'
,
'
hunyuan-large
'
,
'
hunyuan-vision
'
,
'
hunyuan-code
'
]
// Perplexity
const
perplexityModels
=
[
'
sonar
'
,
'
sonar-pro
'
,
'
sonar-reasoning
'
,
'
llama-3-sonar-small-32k-online
'
,
'
llama-3-sonar-large-32k-online
'
,
'
llama-3-sonar-small-32k-chat
'
,
'
llama-3-sonar-large-32k-chat
'
]
// 所有模型(去重)
const
allModelsList
:
string
[]
=
[
...
openaiModels
,
...
claudeModels
,
...
geminiModels
,
...
zhipuModels
,
...
qwenModels
,
...
deepseekModels
,
...
mistralModels
,
...
metaModels
,
...
xaiModels
,
...
cohereModels
,
...
yiModels
,
...
moonshotModels
,
...
doubaoModels
,
...
minimaxModels
,
...
baiduModels
,
...
sparkModels
,
...
hunyuanModels
,
...
perplexityModels
]
// 转换为下拉选项格式
export
const
allModels
=
allModelsList
.
map
(
m
=>
({
value
:
m
,
label
:
m
}))
// =====================
// 预设映射
// =====================
const
anthropicPresetMappings
=
[
{
label
:
'
Sonnet 4
'
,
from
:
'
claude-sonnet-4-20250514
'
,
to
:
'
claude-sonnet-4-20250514
'
,
color
:
'
bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400
'
},
{
label
:
'
Sonnet 4.5
'
,
from
:
'
claude-sonnet-4-5-20250929
'
,
to
:
'
claude-sonnet-4-5-20250929
'
,
color
:
'
bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400
'
},
{
label
:
'
Opus 4.5
'
,
from
:
'
claude-opus-4-5-20251101
'
,
to
:
'
claude-opus-4-5-20251101
'
,
color
:
'
bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400
'
},
{
label
:
'
Haiku 3.5
'
,
from
:
'
claude-3-5-haiku-20241022
'
,
to
:
'
claude-3-5-haiku-20241022
'
,
color
:
'
bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400
'
},
{
label
:
'
Haiku 4.5
'
,
from
:
'
claude-haiku-4-5-20251001
'
,
to
:
'
claude-haiku-4-5-20251001
'
,
color
:
'
bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400
'
},
{
label
:
'
Opus->Sonnet
'
,
from
:
'
claude-opus-4-5-20251101
'
,
to
:
'
claude-sonnet-4-5-20250929
'
,
color
:
'
bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400
'
}
]
const
openaiPresetMappings
=
[
{
label
:
'
GPT-4o
'
,
from
:
'
gpt-4o
'
,
to
:
'
gpt-4o
'
,
color
:
'
bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400
'
},
{
label
:
'
GPT-4o Mini
'
,
from
:
'
gpt-4o-mini
'
,
to
:
'
gpt-4o-mini
'
,
color
:
'
bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400
'
},
{
label
:
'
GPT-4.1
'
,
from
:
'
gpt-4.1
'
,
to
:
'
gpt-4.1
'
,
color
:
'
bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400
'
},
{
label
:
'
o1
'
,
from
:
'
o1
'
,
to
:
'
o1
'
,
color
:
'
bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400
'
},
{
label
:
'
o3
'
,
from
:
'
o3
'
,
to
:
'
o3
'
,
color
:
'
bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400
'
},
{
label
:
'
GPT-5
'
,
from
:
'
gpt-5
'
,
to
:
'
gpt-5
'
,
color
:
'
bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400
'
}
]
const
geminiPresetMappings
=
[
{
label
:
'
Flash 2.0
'
,
from
:
'
gemini-2.0-flash
'
,
to
:
'
gemini-2.0-flash
'
,
color
:
'
bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400
'
},
{
label
:
'
Flash Lite
'
,
from
:
'
gemini-2.0-flash-lite-preview
'
,
to
:
'
gemini-2.0-flash-lite-preview
'
,
color
:
'
bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400
'
},
{
label
:
'
1.5 Pro
'
,
from
:
'
gemini-1.5-pro
'
,
to
:
'
gemini-1.5-pro
'
,
color
:
'
bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400
'
},
{
label
:
'
1.5 Flash
'
,
from
:
'
gemini-1.5-flash
'
,
to
:
'
gemini-1.5-flash
'
,
color
:
'
bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400
'
}
]
// =====================
// 常用错误码
// =====================
export
const
commonErrorCodes
=
[
{
value
:
401
,
label
:
'
Unauthorized
'
},
{
value
:
403
,
label
:
'
Forbidden
'
},
{
value
:
429
,
label
:
'
Rate Limit
'
},
{
value
:
500
,
label
:
'
Server Error
'
},
{
value
:
502
,
label
:
'
Bad Gateway
'
},
{
value
:
503
,
label
:
'
Unavailable
'
},
{
value
:
529
,
label
:
'
Overloaded
'
}
]
// =====================
// 辅助函数
// =====================
// 按平台获取模型
export
function
getModelsByPlatform
(
platform
:
string
):
string
[]
{
switch
(
platform
)
{
case
'
openai
'
:
return
openaiModels
case
'
anthropic
'
:
case
'
claude
'
:
return
claudeModels
case
'
gemini
'
:
return
geminiModels
case
'
zhipu
'
:
return
zhipuModels
case
'
qwen
'
:
return
qwenModels
case
'
deepseek
'
:
return
deepseekModels
case
'
mistral
'
:
return
mistralModels
case
'
meta
'
:
return
metaModels
case
'
xai
'
:
return
xaiModels
case
'
cohere
'
:
return
cohereModels
case
'
yi
'
:
return
yiModels
case
'
moonshot
'
:
return
moonshotModels
case
'
doubao
'
:
return
doubaoModels
case
'
minimax
'
:
return
minimaxModels
case
'
baidu
'
:
return
baiduModels
case
'
spark
'
:
return
sparkModels
case
'
hunyuan
'
:
return
hunyuanModels
case
'
perplexity
'
:
return
perplexityModels
default
:
return
claudeModels
}
}
// 按平台获取预设映射
export
function
getPresetMappingsByPlatform
(
platform
:
string
)
{
if
(
platform
===
'
openai
'
)
return
openaiPresetMappings
if
(
platform
===
'
gemini
'
)
return
geminiPresetMappings
return
anthropicPresetMappings
}
// =====================
// 构建模型映射对象(用于 API)
// =====================
export
function
buildModelMappingObject
(
mode
:
'
whitelist
'
|
'
mapping
'
,
allowedModels
:
string
[],
modelMappings
:
{
from
:
string
;
to
:
string
}[]
):
Record
<
string
,
string
>
|
null
{
const
mapping
:
Record
<
string
,
string
>
=
{}
if
(
mode
===
'
whitelist
'
)
{
for
(
const
model
of
allowedModels
)
{
mapping
[
model
]
=
model
}
}
else
{
for
(
const
m
of
modelMappings
)
{
const
from
=
m
.
from
.
trim
()
const
to
=
m
.
to
.
trim
()
if
(
from
&&
to
)
mapping
[
from
]
=
to
}
}
return
Object
.
keys
(
mapping
).
length
>
0
?
mapping
:
null
}
frontend/src/i18n/locales/en.ts
View file @
2c71c8b9
...
...
@@ -945,6 +945,15 @@ export default {
actualModel
:
'
Actual model
'
,
addMapping
:
'
Add Mapping
'
,
mappingExists
:
'
Mapping for {model} already exists
'
,
searchModels
:
'
Search models...
'
,
noMatchingModels
:
'
No matching models
'
,
fillRelatedModels
:
'
Fill related models
'
,
clearAllModels
:
'
Clear all models
'
,
customModelName
:
'
Custom model name
'
,
enterCustomModelName
:
'
Enter custom model name
'
,
addModel
:
'
Add
'
,
modelExists
:
'
Model already exists
'
,
modelCount
:
'
{count} models
'
,
customErrorCodes
:
'
Custom Error Codes
'
,
customErrorCodesHint
:
'
Only stop scheduling for selected error codes
'
,
customErrorCodesWarning
:
...
...
frontend/src/i18n/locales/zh.ts
View file @
2c71c8b9
...
...
@@ -1102,6 +1102,15 @@ export default {
actualModel
:
'
实际模型
'
,
addMapping
:
'
添加映射
'
,
mappingExists
:
'
模型 {model} 的映射已存在
'
,
searchModels
:
'
搜索模型...
'
,
noMatchingModels
:
'
没有匹配的模型
'
,
fillRelatedModels
:
'
填入相关模型
'
,
clearAllModels
:
'
清除所有模型
'
,
customModelName
:
'
自定义模型名称
'
,
enterCustomModelName
:
'
输入自定义模型名称
'
,
addModel
:
'
填入
'
,
modelExists
:
'
该模型已存在
'
,
modelCount
:
'
{count} 个模型
'
,
customErrorCodes
:
'
自定义错误码
'
,
customErrorCodesHint
:
'
仅对选中的错误码停止调度
'
,
customErrorCodesWarning
:
'
仅选中的错误码会停止调度,其他错误将返回 500。
'
,
...
...
frontend/tsconfig.json
View file @
2c71c8b9
...
...
@@ -17,7 +17,8 @@
"noFallthroughCasesInSwitch"
:
true
,
"paths"
:
{
"@/*"
:
[
"./src/*"
]
}
},
"types"
:
[
"vite/client"
]
},
"include"
:
[
"src/**/*.ts"
,
"src/**/*.tsx"
,
"src/**/*.vue"
],
"references"
:
[{
"path"
:
"./tsconfig.node.json"
}]
...
...
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