Commit 55b00fcf by CaIon

feat(advanced-custom): remove fallback option and enhance path matching for advanced custom routes

parent 3f2c0aed
......@@ -179,10 +179,11 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) {
}()
retryParam := &service.RetryParam{
Ctx: c,
TokenGroup: relayInfo.TokenGroup,
ModelName: relayInfo.OriginModelName,
Retry: common.GetPointer(0),
Ctx: c,
TokenGroup: relayInfo.TokenGroup,
ModelName: relayInfo.OriginModelName,
RequestPath: c.Request.URL.Path,
Retry: common.GetPointer(0),
}
relayInfo.RetryIndex = 0
relayInfo.LastError = nil
......@@ -507,10 +508,11 @@ func RelayTask(c *gin.Context) {
}()
retryParam := &service.RetryParam{
Ctx: c,
TokenGroup: relayInfo.TokenGroup,
ModelName: relayInfo.OriginModelName,
Retry: common.GetPointer(0),
Ctx: c,
TokenGroup: relayInfo.TokenGroup,
ModelName: relayInfo.OriginModelName,
RequestPath: c.Request.URL.Path,
Retry: common.GetPointer(0),
}
for ; retryParam.GetRetry() <= common.RetryTimes; retryParam.IncreaseRetry() {
......
......@@ -73,8 +73,7 @@ const (
)
type AdvancedCustomConfig struct {
Routes []AdvancedCustomRoute `json:"advanced_routes,omitempty"`
Fallback AdvancedCustomFallback `json:"advanced_fallback,omitempty"`
Routes []AdvancedCustomRoute `json:"advanced_routes,omitempty"`
}
type AdvancedCustomRoute struct {
......@@ -84,16 +83,63 @@ type AdvancedCustomRoute struct {
Auth *AdvancedCustomRouteAuth `json:"auth,omitempty"`
}
type AdvancedCustomFallback struct {
Enabled bool `json:"enabled,omitempty"`
}
type AdvancedCustomRouteAuth struct {
Type string `json:"type,omitempty"`
Name string `json:"name,omitempty"`
Value string `json:"value,omitempty"`
}
const advancedCustomModelPlaceholder = "{model}"
// MatchPath returns the first route whose IncomingPath matches requestPath.
// Matching mirrors the relay adaptor: exact match, {model} placeholder, and
// :generateContent <-> :streamGenerateContent equivalence.
func (c *AdvancedCustomConfig) MatchPath(requestPath string) (AdvancedCustomRoute, bool) {
if c == nil {
return AdvancedCustomRoute{}, false
}
for _, route := range c.Routes {
if matchAdvancedCustomIncomingPath(strings.TrimSpace(route.IncomingPath), requestPath) {
return route, true
}
}
return AdvancedCustomRoute{}, false
}
// SupportsPath reports whether any route matches requestPath.
func (c *AdvancedCustomConfig) SupportsPath(requestPath string) bool {
_, ok := c.MatchPath(requestPath)
return ok
}
func matchAdvancedCustomIncomingPath(configuredPath string, requestPath string) bool {
if matchAdvancedCustomIncomingPathTemplate(configuredPath, requestPath) {
return true
}
if strings.Contains(configuredPath, ":generateContent") {
streamPath := strings.Replace(configuredPath, ":generateContent", ":streamGenerateContent", 1)
return matchAdvancedCustomIncomingPathTemplate(streamPath, requestPath)
}
return false
}
func matchAdvancedCustomIncomingPathTemplate(configuredPath string, requestPath string) bool {
if !strings.Contains(configuredPath, advancedCustomModelPlaceholder) {
return configuredPath == requestPath
}
parts := strings.Split(configuredPath, advancedCustomModelPlaceholder)
if len(parts) != 2 {
return false
}
if !strings.HasPrefix(requestPath, parts[0]) || !strings.HasSuffix(requestPath, parts[1]) {
return false
}
model := strings.TrimSuffix(strings.TrimPrefix(requestPath, parts[0]), parts[1])
return model != "" && !strings.Contains(model, "/")
}
func IsAdvancedCustomConverterAllowed(converter string) bool {
switch converter {
case AdvancedCustomConverterNone,
......@@ -112,8 +158,8 @@ func (c *AdvancedCustomConfig) Validate() error {
if c == nil {
return fmt.Errorf("advanced_custom is required")
}
if len(c.Routes) == 0 && !c.Fallback.Enabled {
return fmt.Errorf("advanced_custom requires at least one route or enabled fallback")
if len(c.Routes) == 0 {
return fmt.Errorf("advanced_custom requires at least one route")
}
seenPaths := make(map[string]struct{}, len(c.Routes))
......
......@@ -104,7 +104,8 @@ func Distribute() func(c *gin.Context) {
if preferredChannelID, found := service.GetPreferredChannelByAffinity(c, modelRequest.Model, usingGroup); found {
affinityUsable := false
preferred, err := model.CacheGetChannel(preferredChannelID)
if err == nil && preferred != nil && preferred.Status == common.ChannelStatusEnabled {
if err == nil && preferred != nil && preferred.Status == common.ChannelStatusEnabled &&
channelSupportsRequestPath(preferred, c.Request.URL.Path) {
if usingGroup == "auto" {
userGroup := common.GetContextKeyString(c, constant.ContextKeyUserGroup)
autoGroups := service.GetUserAutoGroup(userGroup)
......@@ -132,10 +133,11 @@ func Distribute() func(c *gin.Context) {
if channel == nil {
channel, selectGroup, err = service.CacheGetRandomSatisfiedChannel(&service.RetryParam{
Ctx: c,
ModelName: modelRequest.Model,
TokenGroup: usingGroup,
Retry: common.GetPointer(0),
Ctx: c,
ModelName: modelRequest.Model,
TokenGroup: usingGroup,
RequestPath: c.Request.URL.Path,
Retry: common.GetPointer(0),
})
if err != nil {
showGroup := usingGroup
......@@ -167,6 +169,20 @@ func Distribute() func(c *gin.Context) {
}
}
// channelSupportsRequestPath reports whether a channel can serve the request path.
// Only Advanced Custom (type 58) channels are path-checked; all other channel types
// always pass. A type-58 channel is usable only when one of its routes matches.
func channelSupportsRequestPath(channel *model.Channel, requestPath string) bool {
if channel == nil {
return false
}
if channel.Type != constant.ChannelTypeAdvancedCustom {
return true
}
config := channel.GetOtherSettings().AdvancedCustom
return config != nil && config.SupportsPath(requestPath)
}
// getModelFromRequest 从请求中读取模型信息
// 根据 Content-Type 自动处理:
// - application/json
......
......@@ -7,6 +7,8 @@ import (
"sync"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/dto"
"github.com/samber/lo"
"gorm.io/gorm"
......@@ -103,7 +105,7 @@ func getChannelQuery(group string, model string, retry int) (*gorm.DB, error) {
return channelQuery, nil
}
func GetChannel(group string, model string, retry int) (*Channel, error) {
func GetChannel(group string, model string, retry int, requestPath string) (*Channel, error) {
var abilities []Ability
var err error = nil
......@@ -119,6 +121,7 @@ func GetChannel(group string, model string, retry int) (*Channel, error) {
if err != nil {
return nil, err
}
abilities = filterAbilitiesByRequestPath(abilities, requestPath)
channel := Channel{}
if len(abilities) > 0 {
// Randomly choose one
......@@ -143,6 +146,52 @@ func GetChannel(group string, model string, retry int) (*Channel, error) {
return &channel, err
}
// filterAbilitiesByRequestPath restricts candidates by request path for the DB
// (non-memory-cache) selection path. Only Advanced Custom (type 58) channels are
// path-checked: kept only when one of their routes matches requestPath; all other
// channel types always pass. When requestPath is empty, filtering is skipped.
func filterAbilitiesByRequestPath(abilities []Ability, requestPath string) []Ability {
if requestPath == "" || len(abilities) == 0 {
return abilities
}
channelIds := make([]int, 0, len(abilities))
seen := make(map[int]struct{}, len(abilities))
for _, ability := range abilities {
if _, ok := seen[ability.ChannelId]; ok {
continue
}
seen[ability.ChannelId] = struct{}{}
channelIds = append(channelIds, ability.ChannelId)
}
var channels []*Channel
if err := DB.Where("id IN ?", channelIds).Find(&channels).Error; err != nil {
// On error, fall back to unfiltered candidates to avoid blocking selection
return abilities
}
advancedConfigs := make(map[int]*dto.AdvancedCustomConfig)
for _, channel := range channels {
if channel.Type == constant.ChannelTypeAdvancedCustom {
advancedConfigs[channel.Id] = channel.GetOtherSettings().AdvancedCustom
}
}
filtered := make([]Ability, 0, len(abilities))
for _, ability := range abilities {
config, isAdvancedCustom := advancedConfigs[ability.ChannelId]
if !isAdvancedCustom {
filtered = append(filtered, ability)
continue
}
if config != nil && config.SupportsPath(requestPath) {
filtered = append(filtered, ability)
}
}
return filtered
}
func (channel *Channel) AddAbilities(tx *gorm.DB) error {
models_ := strings.Split(channel.Models, ",")
groups_ := strings.Split(channel.Group, ",")
......
......@@ -956,9 +956,6 @@ func (channel *Channel) ValidateSettings() error {
if channelOtherSettings.AdvancedCustom == nil {
return fmt.Errorf("advanced_custom is required")
}
if channelOtherSettings.AdvancedCustom.Fallback.Enabled && (channel.BaseURL == nil || strings.TrimSpace(*channel.BaseURL) == "") {
return fmt.Errorf("base_url is required when advanced_custom advanced_fallback is enabled")
}
}
if channelOtherSettings.AdvancedCustom != nil {
if err := channelOtherSettings.AdvancedCustom.Validate(); err != nil {
......
......@@ -11,12 +11,16 @@ import (
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/logger"
"github.com/QuantumNous/new-api/setting/ratio_setting"
)
var group2model2channels map[string]map[string][]int // enabled channel
var channelsIDM map[int]*Channel // all channels include disabled
// channel2advancedCustomConfig caches parsed Advanced Custom (type 58) configs so
// path-aware selection avoids re-parsing JSON per request. Refreshed on full sync.
var channel2advancedCustomConfig map[int]*dto.AdvancedCustomConfig
var channelSyncLock sync.RWMutex
func InitChannelCache() {
......@@ -24,10 +28,16 @@ func InitChannelCache() {
return
}
newChannelId2channel := make(map[int]*Channel)
newChannel2advancedCustomConfig := make(map[int]*dto.AdvancedCustomConfig)
var channels []*Channel
DB.Find(&channels)
for _, channel := range channels {
newChannelId2channel[channel.Id] = channel
if channel.Type == constant.ChannelTypeAdvancedCustom {
if config := channel.GetOtherSettings().AdvancedCustom; config != nil {
newChannel2advancedCustomConfig[channel.Id] = config
}
}
}
var abilities []*Ability
DB.Find(&abilities)
......@@ -82,6 +92,7 @@ func InitChannelCache() {
}
}
channelsIDM = newChannelId2channel
channel2advancedCustomConfig = newChannel2advancedCustomConfig
channelSyncLock.Unlock()
common.SysLog("channels synced from database")
}
......@@ -94,22 +105,22 @@ func SyncChannelCache(frequency int) {
}
}
func GetRandomSatisfiedChannel(group string, model string, retry int) (*Channel, error) {
func GetRandomSatisfiedChannel(group string, model string, retry int, requestPath string) (*Channel, error) {
// if memory cache is disabled, get channel directly from database
if !common.MemoryCacheEnabled {
return GetChannel(group, model, retry)
return GetChannel(group, model, retry, requestPath)
}
channelSyncLock.RLock()
defer channelSyncLock.RUnlock()
// First, try to find channels with the exact model name.
channels := group2model2channels[group][model]
channels := filterChannelsByRequestPath(group2model2channels[group][model], requestPath)
// If no channels found, try to find channels with the normalized model name.
if len(channels) == 0 {
normalizedModel := ratio_setting.FormatMatchingModelName(model)
channels = group2model2channels[group][normalizedModel]
channels = filterChannelsByRequestPath(group2model2channels[group][normalizedModel], requestPath)
}
if len(channels) == 0 {
......@@ -191,6 +202,34 @@ func GetRandomSatisfiedChannel(group string, model string, retry int) (*Channel,
return nil, errors.New("channel not found")
}
// filterChannelsByRequestPath restricts candidates by request path. Only Advanced
// Custom (type 58) channels are path-checked: they are kept only when one of their
// configured routes matches requestPath. All other channel types always pass.
// When requestPath is empty (non-relay callers) filtering is skipped.
// Caller must hold channelSyncLock (read lock). The cached slice is never mutated.
func filterChannelsByRequestPath(channels []int, requestPath string) []int {
if requestPath == "" || len(channels) == 0 {
return channels
}
filtered := make([]int, 0, len(channels))
for _, channelId := range channels {
channel, ok := channelsIDM[channelId]
if !ok {
// keep it so the downstream consistency error is raised as before
filtered = append(filtered, channelId)
continue
}
if channel.Type != constant.ChannelTypeAdvancedCustom {
filtered = append(filtered, channelId)
continue
}
if config := channel2advancedCustomConfig[channelId]; config != nil && config.SupportsPath(requestPath) {
filtered = append(filtered, channelId)
}
}
return filtered
}
func CacheGetChannel(id int) (*Channel, error) {
if !common.MemoryCacheEnabled {
return GetChannelById(id, true)
......
......@@ -32,7 +32,6 @@ type Adaptor struct {
geminiAdaptor gemini.Adaptor
resolved bool
fallback bool
converted bool
route dto.AdvancedCustomRoute
converter string
......@@ -49,7 +48,7 @@ func (a *Adaptor) ConvertOpenAIRequest(c *gin.Context, info *relaycommon.RelayIn
if err != nil {
return nil, err
}
if a.fallback || converter == dto.AdvancedCustomConverterNone {
if converter == dto.AdvancedCustomConverterNone {
return a.convertOpenAICompatibleRequest(c, info, request)
}
......@@ -73,9 +72,6 @@ func (a *Adaptor) ConvertClaudeRequest(c *gin.Context, info *relaycommon.RelayIn
if err != nil {
return nil, err
}
if a.fallback {
return a.convertClaudeToOpenAICompatibleRequest(c, info, request)
}
switch converter {
case dto.AdvancedCustomConverterNone:
......@@ -92,9 +88,6 @@ func (a *Adaptor) ConvertGeminiRequest(c *gin.Context, info *relaycommon.RelayIn
if err != nil {
return nil, err
}
if a.fallback {
return a.convertGeminiToOpenAICompatibleRequest(c, info, request)
}
switch converter {
case dto.AdvancedCustomConverterNone:
......@@ -159,11 +152,6 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
if err := a.resolve(nil, info); err != nil {
return "", err
}
if a.fallback {
return a.withTemporaryChannelType(info, constant.ChannelTypeOpenAI, func() (string, error) {
return a.openaiAdaptor.GetRequestURL(info)
})
}
return a.routeURL(info)
}
......@@ -171,13 +159,6 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, header *http.Header, info *
if err := a.resolve(c, info); err != nil {
return err
}
if a.fallback {
old := info.ChannelType
info.ChannelType = constant.ChannelTypeOpenAI
err := a.openaiAdaptor.SetupRequestHeader(c, header, info)
info.ChannelType = old
return err
}
channel.SetupApiRequestHeader(info, c, header)
auth := a.route.Auth
......@@ -205,7 +186,7 @@ func (a *Adaptor) DoRequest(c *gin.Context, info *relaycommon.RelayInfo, request
if err := a.resolve(c, info); err != nil {
return nil, err
}
if !a.converted && (a.fallback || a.converter != dto.AdvancedCustomConverterNone) {
if !a.converted && a.converter != dto.AdvancedCustomConverterNone {
return nil, errors.New("advanced custom converter routes cannot be used with pass-through request body")
}
......@@ -224,9 +205,6 @@ func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, info *relaycom
if err := a.resolve(c, info); err != nil {
return nil, types.NewOpenAIError(err, types.ErrorCodeInvalidRequest, http.StatusBadRequest, types.ErrOptionWithSkipRetry())
}
if a.fallback {
return a.openaiAdaptor.DoResponse(c, resp, info)
}
switch a.converter {
case dto.AdvancedCustomConverterNone:
......@@ -295,9 +273,7 @@ func (a *Adaptor) resolve(c *gin.Context, info *relaycommon.RelayInfo) error {
}
incomingPath := incomingRequestPath(c, info)
route, ok := lo.Find(config.Routes, func(route dto.AdvancedCustomRoute) bool {
return matchIncomingPath(strings.TrimSpace(route.IncomingPath), incomingPath)
})
route, ok := config.MatchPath(incomingPath)
if ok {
route.Converter = strings.TrimSpace(route.Converter)
if route.Converter == "" {
......@@ -308,13 +284,7 @@ func (a *Adaptor) resolve(c *gin.Context, info *relaycommon.RelayInfo) error {
a.resolved = true
return nil
}
if config.Fallback.Enabled {
a.fallback = true
a.converter = dto.AdvancedCustomConverterNone
a.resolved = true
return nil
}
return fmt.Errorf("advanced custom route not found for path: %s", incomingPath)
return fmt.Errorf("advanced custom channel does not support request path: %s", incomingPath)
}
func incomingRequestPath(c *gin.Context, info *relaycommon.RelayInfo) string {
......@@ -327,34 +297,6 @@ func incomingRequestPath(c *gin.Context, info *relaycommon.RelayInfo) string {
return strings.Split(info.RequestURLPath, "?")[0]
}
func matchIncomingPath(configuredPath string, requestPath string) bool {
if matchIncomingPathTemplate(configuredPath, requestPath) {
return true
}
if strings.Contains(configuredPath, ":generateContent") {
streamPath := strings.Replace(configuredPath, ":generateContent", ":streamGenerateContent", 1)
return matchIncomingPathTemplate(streamPath, requestPath)
}
return false
}
func matchIncomingPathTemplate(configuredPath string, requestPath string) bool {
if !strings.Contains(configuredPath, advancedCustomModelPlaceholder) {
return configuredPath == requestPath
}
parts := strings.Split(configuredPath, advancedCustomModelPlaceholder)
if len(parts) != 2 {
return false
}
if !strings.HasPrefix(requestPath, parts[0]) || !strings.HasSuffix(requestPath, parts[1]) {
return false
}
model := strings.TrimSuffix(strings.TrimPrefix(requestPath, parts[0]), parts[1])
return model != "" && !strings.Contains(model, "/")
}
func (a *Adaptor) routeURL(info *relaycommon.RelayInfo) (string, error) {
parsedURL, err := resolveUpstreamTargetURL(applyUpstreamPathTemplate(strings.TrimSpace(a.route.UpstreamPath), info), info)
if err != nil {
......@@ -535,11 +477,3 @@ func (a *Adaptor) convertOpenAICompatibleImageRequest(c *gin.Context, info *rela
info.ChannelType = old
return converted, err
}
func (a *Adaptor) withTemporaryChannelType(info *relaycommon.RelayInfo, channelType int, fn func() (string, error)) (string, error) {
old := info.ChannelType
info.ChannelType = channelType
value, err := fn()
info.ChannelType = old
return value, err
}
......@@ -161,7 +161,7 @@ func TestAdaptorSetupRequestHeaderAddsClaudeDefaultHeaders(t *testing.T) {
assert.Equal(t, "2023-06-01", header.Get("anthropic-version"))
}
func TestAdaptorReturnsErrorWhenNoRouteAndFallbackDisabled(t *testing.T) {
func TestAdaptorReturnsErrorWhenNoRouteMatchesPath(t *testing.T) {
adaptor := &Adaptor{}
info := advancedCustomRelayInfo(&dto.AdvancedCustomConfig{
Routes: []dto.AdvancedCustomRoute{
......@@ -176,20 +176,7 @@ func TestAdaptorReturnsErrorWhenNoRouteAndFallbackDisabled(t *testing.T) {
_, err := adaptor.GetRequestURL(info)
require.Error(t, err)
assert.Contains(t, err.Error(), "route not found")
}
func TestAdaptorFallbackUsesOpenAICompatibleBaseURL(t *testing.T) {
adaptor := &Adaptor{}
info := advancedCustomRelayInfo(&dto.AdvancedCustomConfig{
Fallback: dto.AdvancedCustomFallback{Enabled: true},
})
info.RequestURLPath = "/v1/messages"
info.RelayFormat = types.RelayFormatClaude
requestURL, err := adaptor.GetRequestURL(info)
require.NoError(t, err)
assert.Equal(t, "https://fallback.example/v1/chat/completions", requestURL)
assert.Contains(t, err.Error(), "does not support request path")
}
func TestAdaptorReplacesModelPlaceholderInRouteURL(t *testing.T) {
......
......@@ -15,6 +15,7 @@ type RetryParam struct {
Ctx *gin.Context
TokenGroup string
ModelName string
RequestPath string
Retry *int
resetNextTry bool
}
......@@ -115,7 +116,7 @@ func CacheGetRandomSatisfiedChannel(param *RetryParam) (*model.Channel, string,
}
logger.LogDebug(param.Ctx, "Auto selecting group: %s, priorityRetry: %d", autoGroup, priorityRetry)
channel, _ = model.GetRandomSatisfiedChannel(autoGroup, param.ModelName, priorityRetry)
channel, _ = model.GetRandomSatisfiedChannel(autoGroup, param.ModelName, priorityRetry, param.RequestPath)
if channel == nil {
// Current group has no available channel for this model, try next group
// 当前分组没有该模型的可用渠道,尝试下一个分组
......@@ -153,7 +154,7 @@ func CacheGetRandomSatisfiedChannel(param *RetryParam) (*model.Channel, string,
break
}
} else {
channel, err = model.GetRandomSatisfiedChannel(param.TokenGroup, param.ModelName, param.GetRetry())
channel, err = model.GetRandomSatisfiedChannel(param.TokenGroup, param.ModelName, param.GetRetry(), param.RequestPath)
if err != nil {
return nil, param.TokenGroup, err
}
......
......@@ -20,6 +20,8 @@ import * as React from 'react'
import { Add01Icon } from '@hugeicons/core-free-icons'
import { HugeiconsIcon } from '@hugeicons/react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { copyToClipboard } from '@/lib/copy-to-clipboard'
import { cn } from '@/lib/utils'
import {
Combobox,
......@@ -64,6 +66,11 @@ interface MultiSelectProps {
* Hidden values remain searchable/removable from the dropdown.
*/
maxVisibleChips?: number
/**
* When true, clicking a chip's label copies its value to the clipboard
* instead of being inert. The remove (×) button keeps its own behaviour.
*/
copyChipOnClick?: boolean
}
const COMMA_REGEX = /[,,\n]/
......@@ -109,6 +116,7 @@ export function MultiSelect(props: MultiSelectProps) {
const [inputValue, setInputValue] = React.useState('')
const [open, setOpen] = React.useState(false)
const [expanded, setExpanded] = React.useState(false)
const selectedSet = React.useMemo(
() => new Set(props.selected),
......@@ -195,6 +203,25 @@ export function MultiSelect(props: MultiSelectProps) {
}
}
const handleCopyChip = React.useCallback(
async (
event: React.MouseEvent<HTMLButtonElement>,
value: string,
label: string
) => {
// Prevent the click from toggling the combobox popup or focusing input.
event.preventDefault()
event.stopPropagation()
const ok = await copyToClipboard(value)
if (ok) {
toast.success(t('Copied: {{model}}', { model: label }))
} else {
toast.error(t('Failed to copy'))
}
},
[t]
)
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
// Enter without a highlighted option commits the typed value.
if (event.key === 'Enter' && props.allowCreate && canCreate) {
......@@ -231,26 +258,69 @@ export function MultiSelect(props: MultiSelectProps) {
>
<ComboboxValue>
{(values: string[]) => {
const visibleValues =
typeof props.maxVisibleChips === 'number'
? values.slice(0, props.maxVisibleChips)
: values
const shouldLimit =
typeof props.maxVisibleChips === 'number' && !expanded
const visibleValues = shouldLimit
? values.slice(0, props.maxVisibleChips)
: values
const hiddenCount = values.length - visibleValues.length
return (
<>
{visibleValues.map((value) => (
<ComboboxChip key={value}>
<span className='max-w-[16rem] truncate'>
{labelMap.get(value) ?? value}
</span>
</ComboboxChip>
))}
{visibleValues.map((value) => {
const label = labelMap.get(value) ?? value
return (
<ComboboxChip key={value}>
{props.copyChipOnClick ? (
<button
type='button'
onClick={(event) =>
handleCopyChip(event, value, label)
}
onPointerDown={(event) => event.stopPropagation()}
title={t('Click to copy')}
className='max-w-[16rem] cursor-pointer truncate rounded-sm hover:underline'
>
{label}
</button>
) : (
<span className='max-w-[16rem] truncate'>{label}</span>
)}
</ComboboxChip>
)
})}
{hiddenCount > 0 && (
<span className='bg-muted text-muted-foreground flex h-[calc(--spacing(5.25))] w-fit items-center justify-center rounded-sm px-1.5 text-xs font-medium whitespace-nowrap'>
<button
type='button'
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
setExpanded(true)
}}
onPointerDown={(event) => event.stopPropagation()}
title={t('Show All')}
className='bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground flex h-[calc(--spacing(5.25))] w-fit cursor-pointer items-center justify-center rounded-sm px-1.5 text-xs font-medium whitespace-nowrap transition-colors'
>
{t('+{{count}} more', { count: hiddenCount })}
</span>
</button>
)}
{expanded &&
typeof props.maxVisibleChips === 'number' &&
values.length > props.maxVisibleChips && (
<button
type='button'
onClick={(event) => {
event.preventDefault()
event.stopPropagation()
setExpanded(false)
}}
onPointerDown={(event) => event.stopPropagation()}
title={t('Collapse')}
className='bg-muted text-muted-foreground hover:bg-muted/80 hover:text-foreground flex h-[calc(--spacing(5.25))] w-fit cursor-pointer items-center justify-center rounded-sm px-1.5 text-xs font-medium whitespace-nowrap transition-colors'
>
{t('Collapse')}
</button>
)}
</>
)
}}
......
......@@ -33,7 +33,6 @@ import {
SelectValue,
} from '@/components/ui/select'
import { Separator } from '@/components/ui/separator'
import { Switch } from '@/components/ui/switch'
import { Textarea } from '@/components/ui/textarea'
import { Dialog } from '@/components/dialog'
import {
......@@ -152,13 +151,6 @@ export function AdvancedCustomEditorDialog({
})
}
const setFallbackEnabled = (enabled: boolean) => {
setConfig((current) => ({
...normalizeAdvancedCustomConfig(current),
advanced_fallback: { enabled },
}))
}
const parseJsonEditorConfig = (): AdvancedCustomConfig | null => {
const parsed = parseAdvancedCustomConfig(jsonText)
if (!parsed) {
......@@ -215,11 +207,6 @@ export function AdvancedCustomEditorDialog({
...(base.advanced_routes || []),
...(template.advanced_routes || []),
],
advanced_fallback: {
enabled:
base.advanced_fallback?.enabled === true ||
template.advanced_fallback?.enabled === true,
},
}
}
......@@ -355,23 +342,7 @@ export function AdvancedCustomEditorDialog({
{editMode === 'visual' ? (
<div className='flex flex-col gap-5 p-4'>
<div className='flex flex-col gap-3 border-y py-4 sm:flex-row sm:items-center sm:justify-between'>
<div className='flex items-center gap-3'>
<Switch
checked={normalizedConfig.advanced_fallback?.enabled === true}
onCheckedChange={setFallbackEnabled}
/>
<div className='space-y-1'>
<div className='text-sm font-medium'>
{t('Fallback routing')}
</div>
<div className='text-muted-foreground max-w-2xl text-xs leading-relaxed'>
{t(
'When enabled, requests that do not match any advanced route are forwarded to the channel base URL. When disabled, unmatched requests return an error.'
)}
</div>
</div>
</div>
<div className='flex justify-end border-y py-4'>
<Button
type='button'
variant='outline'
......
......@@ -1845,18 +1845,6 @@ export function ChannelMutateDrawer({
{t('Routes')}:{' '}
{advancedCustomStats.routeCount}
</Badge>
<Badge
variant={
advancedCustomStats.fallbackEnabled
? 'default'
: 'outline'
}
>
{t('Fallback')}:{' '}
{advancedCustomStats.fallbackEnabled
? t('Enabled')
: t('Disabled')}
</Badge>
{!advancedCustomStats.valid && (
<Badge variant='destructive'>
{t('Incomplete')}
......@@ -2254,6 +2242,7 @@ export function ChannelMutateDrawer({
allowCreate
createLabel='Add custom model "{{value}}"'
maxVisibleChips={8}
copyChipOnClick
/>
</FormControl>
{modelMappingGuardrail.exposedTargetModels
......
......@@ -28,14 +28,14 @@ export const CHANNEL_TYPES = {
3: 'Azure',
4: 'Ollama',
5: 'MidjourneyPlus',
6: 'OpenAIMax',
// 6: 'OpenAIMax',
7: 'OhMyGPT',
8: 'Custom',
9: 'AILS',
10: 'AI Proxy',
11: 'PaLM',
12: 'API2GPT',
13: 'AIGC2D',
// 9: 'AILS',
// 10: 'AI Proxy',
// 11: 'PaLM',
// 12: 'API2GPT',
// 13: 'AIGC2D',
14: 'Anthropic',
15: 'Baidu',
16: 'Zhipu',
......@@ -43,7 +43,7 @@ export const CHANNEL_TYPES = {
18: 'Xunfei',
19: '360',
20: 'OpenRouter',
21: 'AI Proxy Library',
// 21: 'AI Proxy Library',
22: 'FastGPT',
23: 'Tencent',
24: 'Gemini',
......@@ -80,8 +80,8 @@ export const CHANNEL_TYPES = {
} as const
const CHANNEL_TYPE_DISPLAY_ORDER: number[] = [
1, 14, 33, 24, 43, 3, 41, 48, 42, 34, 20, 4, 40, 27, 25, 17, 26, 15, 46, 23,
18, 45, 31, 35, 49, 19, 47, 37, 38, 39, 11, 8, 58, 57, 22, 21, 44, 2, 5, 36,
1, 14, 33, 24, 43, 3, 41, 48, 58, 42, 34, 20, 4, 40, 27, 25, 17, 26, 15, 46, 23,
18, 45, 31, 35, 49, 19, 47, 37, 38, 39, 11, 8, 57, 22, 21, 44, 2, 5, 36,
50, 51, 52, 53, 54, 55, 56,
]
......
......@@ -176,12 +176,11 @@ export const ADVANCED_CUSTOM_TEMPLATE_OPTIONS: AdvancedCustomTemplateOption[] =
advanced_routes: [
{
incoming_path: '/v1/chat/completions',
upstream_path: 'https://api.openai.com/v1/chat/completions',
upstream_path: '/v1/chat/completions',
converter: 'none',
auth: bearerHeaderAuth(),
},
],
advanced_fallback: { enabled: false },
},
},
{
......@@ -191,12 +190,11 @@ export const ADVANCED_CUSTOM_TEMPLATE_OPTIONS: AdvancedCustomTemplateOption[] =
advanced_routes: [
{
incoming_path: '/v1/responses',
upstream_path: 'https://api.openai.com/v1/responses',
upstream_path: '/v1/responses',
converter: 'none',
auth: bearerHeaderAuth(),
},
],
advanced_fallback: { enabled: false },
},
},
{
......@@ -206,12 +204,11 @@ export const ADVANCED_CUSTOM_TEMPLATE_OPTIONS: AdvancedCustomTemplateOption[] =
advanced_routes: [
{
incoming_path: '/v1/embeddings',
upstream_path: 'https://api.openai.com/v1/embeddings',
upstream_path: '/v1/embeddings',
converter: 'none',
auth: bearerHeaderAuth(),
},
],
advanced_fallback: { enabled: false },
},
},
{
......@@ -221,18 +218,17 @@ export const ADVANCED_CUSTOM_TEMPLATE_OPTIONS: AdvancedCustomTemplateOption[] =
advanced_routes: [
{
incoming_path: '/v1/images/generations',
upstream_path: 'https://api.openai.com/v1/images/generations',
upstream_path: '/v1/images/generations',
converter: 'none',
auth: bearerHeaderAuth(),
},
{
incoming_path: '/v1/images/edits',
upstream_path: 'https://api.openai.com/v1/images/edits',
upstream_path: '/v1/images/edits',
converter: 'none',
auth: bearerHeaderAuth(),
},
],
advanced_fallback: { enabled: false },
},
},
{
......@@ -242,12 +238,11 @@ export const ADVANCED_CUSTOM_TEMPLATE_OPTIONS: AdvancedCustomTemplateOption[] =
advanced_routes: [
{
incoming_path: '/v1/messages',
upstream_path: 'https://api.anthropic.com/v1/messages',
upstream_path: '/v1/messages',
converter: 'none',
auth: apiKeyHeaderAuth(),
},
],
advanced_fallback: { enabled: false },
},
},
{
......@@ -257,27 +252,23 @@ export const ADVANCED_CUSTOM_TEMPLATE_OPTIONS: AdvancedCustomTemplateOption[] =
advanced_routes: [
{
incoming_path: '/v1beta/models/{model}:generateContent',
upstream_path:
'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent',
upstream_path: '/v1beta/models/{model}:generateContent',
converter: 'none',
auth: geminiQueryAuth(),
},
{
incoming_path: '/v1beta/models/{model}:embedContent',
upstream_path:
'https://generativelanguage.googleapis.com/v1beta/models/{model}:embedContent',
upstream_path: '/v1beta/models/{model}:embedContent',
converter: 'none',
auth: geminiQueryAuth(),
},
{
incoming_path: '/v1beta/models/{model}:batchEmbedContents',
upstream_path:
'https://generativelanguage.googleapis.com/v1beta/models/{model}:batchEmbedContents',
upstream_path: '/v1beta/models/{model}:batchEmbedContents',
converter: 'none',
auth: geminiQueryAuth(),
},
],
advanced_fallback: { enabled: false },
},
},
{
......@@ -287,13 +278,11 @@ export const ADVANCED_CUSTOM_TEMPLATE_OPTIONS: AdvancedCustomTemplateOption[] =
advanced_routes: [
{
incoming_path: '/v1/chat/completions',
upstream_path:
'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent',
upstream_path: '/v1beta/models/{model}:generateContent',
converter: 'openai_chat_completions_to_gemini_generate_content',
auth: geminiQueryAuth(),
},
],
advanced_fallback: { enabled: false },
},
},
]
......@@ -325,7 +314,6 @@ export function createAdvancedCustomRoute(): AdvancedCustomRoute {
export function createAdvancedCustomConfig(): AdvancedCustomConfig {
return {
advanced_routes: [createAdvancedCustomRoute()],
advanced_fallback: { enabled: false },
}
}
......@@ -405,9 +393,6 @@ export function normalizeAdvancedCustomConfig(
return {
advanced_routes: routes,
advanced_fallback: {
enabled: config.advanced_fallback?.enabled === true,
},
}
}
......@@ -420,11 +405,9 @@ export function validateAdvancedCustomConfig(
const normalized = normalizeAdvancedCustomConfig(config)
const routes = normalized.advanced_routes || []
const fallbackEnabled = normalized.advanced_fallback?.enabled === true
if (routes.length === 0 && !fallbackEnabled) {
if (routes.length === 0) {
return {
message:
'Advanced custom configuration requires at least one route or fallback',
message: 'Advanced custom configuration requires at least one route',
}
}
......@@ -492,17 +475,15 @@ export function advancedCustomConfigUsesRelativeUpstreamPath(
export function getAdvancedCustomStats(value: string | undefined): {
routeCount: number
fallbackEnabled: boolean
valid: boolean
} {
const config = parseAdvancedCustomConfig(value)
if (!config) {
return { routeCount: 0, fallbackEnabled: false, valid: false }
return { routeCount: 0, valid: false }
}
const normalized = normalizeAdvancedCustomConfig(config)
return {
routeCount: normalized.advanced_routes?.length || 0,
fallbackEnabled: normalized.advanced_fallback?.enabled === true,
valid: validateAdvancedCustomConfig(normalized) === null,
}
}
......
......@@ -227,16 +227,6 @@ export const channelFormSchema = z
addRequiredIssue(ctx, 'advanced_custom', advancedCustomError.message)
}
if (
advancedCustomConfig?.advanced_fallback?.enabled === true &&
!data.base_url?.trim()
) {
addRequiredIssue(
ctx,
'base_url',
'Base URL is required when fallback is enabled'
)
}
if (
advancedCustomConfigUsesRelativeUpstreamPath(advancedCustomConfig) &&
!data.base_url?.trim()
) {
......
......@@ -110,7 +110,6 @@ export interface ChannelOtherSettings {
export interface AdvancedCustomConfig {
advanced_routes?: AdvancedCustomRoute[]
advanced_fallback?: AdvancedCustomFallback
}
export interface AdvancedCustomRoute {
......@@ -120,10 +119,6 @@ export interface AdvancedCustomRoute {
auth?: AdvancedCustomRouteAuth
}
export interface AdvancedCustomFallback {
enabled?: boolean
}
export interface AdvancedCustomRouteAuth {
type?: AdvancedCustomAuthType
name?: string
......
......@@ -138,11 +138,6 @@
"Active models": "Active models",
"active users": "active users",
"Actual Amount": "Actual Amount",
"Plan Price": "Plan Price",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.",
"Amount the user pays to purchase this plan (USD).": "Amount the user pays to purchase this plan (USD).",
"Plan Quota": "Plan Quota",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "Total quota included in the plan, usable per billing period. 0 means unlimited.",
"Actual Model": "Actual Model",
"Actual Model:": "Actual Model:",
"Adapt `-thinking` suffix requests to Anthropic native thinking behavior while keeping billing predictable.": "Adapt `-thinking` suffix requests to Anthropic native thinking behavior while keeping billing predictable.",
......@@ -231,7 +226,7 @@
"Advanced Configuration": "Advanced Configuration",
"Advanced Custom": "Advanced Custom",
"Advanced custom configuration is required": "Advanced custom configuration is required",
"Advanced custom configuration requires at least one route or fallback": "Advanced custom configuration requires at least one route or fallback",
"Advanced custom configuration requires at least one route": "Advanced custom configuration requires at least one route",
"Advanced Custom Routes": "Advanced Custom Routes",
"Advanced options": "Advanced options",
"Advanced Options": "Advanced Options",
......@@ -286,10 +281,6 @@
"Allocated Memory": "Allocated Memory",
"Allow accountFilter parameter": "Allow accountFilter parameter",
"Allow balance redemption": "Allow balance redemption",
"Allow wallet balance after quota used up": "Allow wallet balance after quota used up",
"Downgrade Group": "Downgrade Group",
"Downgrade to pre-purchase group": "Downgrade to pre-purchase group",
"Downgrade to this group after the subscription expires": "Downgrade to this group after the subscription expires",
"Allow Claude beta query passthrough": "Allow Claude beta query passthrough",
"Allow clients to query configured ratios via `/api/ratio`.": "Allow clients to query configured ratios via `/api/ratio`.",
"Allow HTTP image requests": "Allow HTTP image requests",
......@@ -318,6 +309,7 @@
"Allow users to sign in with this provider": "Allow users to sign in with this provider",
"Allow users to sign in with WeChat": "Allow users to sign in with WeChat",
"Allow using models without price configuration": "Allow using models without price configuration",
"Allow wallet balance after quota used up": "Allow wallet balance after quota used up",
"Allowed": "Allowed",
"Allowed Origins": "Allowed Origins",
"Allowed Ports": "Allowed Ports",
......@@ -331,6 +323,8 @@
"Amount must be greater than 0": "Amount must be greater than 0",
"Amount of quota to credit to user account.": "Amount of quota to credit to user account.",
"Amount options must be a JSON array": "Amount options must be a JSON array",
"Amount the user pays to purchase this plan (USD).": "Amount the user pays to purchase this plan (USD).",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.",
"Amount to pay:": "Amount to pay:",
"An unexpected error occurred": "An unexpected error occurred",
"and": "and",
......@@ -542,7 +536,6 @@
"Base URL": "Base URL",
"Base URL is required for this channel type": "Base URL is required for this channel type",
"Base URL is required when an advanced route uses an upstream path": "Base URL is required when an advanced route uses an upstream path",
"Base URL is required when fallback is enabled": "Base URL is required when fallback is enabled",
"Base URL of your Uptime Kuma instance": "Base URL of your Uptime Kuma instance",
"Basic Authentication": "Basic Authentication",
"Basic Configuration": "Basic Configuration",
......@@ -791,6 +784,7 @@
"Click save when you're done.": "Click save when you're done.",
"Click save when you&apos;re done.": "Click save when you&apos;re done.",
"Click the button below to bind your Telegram account": "Click the button below to bind your Telegram account",
"Click to copy": "Click to copy",
"Click to open deployment": "Click to open deployment",
"Click to preview audio": "Click to preview audio",
"Click to preview video": "Click to preview video",
......@@ -1336,6 +1330,9 @@
"Doubao custom API address editing unlocked": "Doubao custom API address editing unlocked",
"DoubaoVideo": "DoubaoVideo",
"Double check the configuration below. Your system will be locked until initialization is complete.": "Double check the configuration below. Your system will be locked until initialization is complete.",
"Downgrade Group": "Downgrade Group",
"Downgrade to pre-purchase group": "Downgrade to pre-purchase group",
"Downgrade to this group after the subscription expires": "Downgrade to this group after the subscription expires",
"Download": "Download",
"Draw": "Draw",
"Drawing": "Drawing",
......@@ -1774,9 +1771,7 @@
"Failed to update user": "Failed to update user",
"Failure keywords": "Failure keywords",
"Fair": "Fair",
"Fallback": "Fallback",
"Fallback base URL": "Fallback base URL",
"Fallback routing": "Fallback routing",
"Fallback tier": "Fallback tier",
"FAQ": "FAQ",
"FAQ added. Click \"Save Settings\" to apply.": "FAQ added. Click \"Save Settings\" to apply.",
......@@ -2864,8 +2859,8 @@
"OpenAI Chat to Anthropic Messages": "OpenAI Chat to Anthropic Messages",
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat to Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat to OpenAI Responses",
"OpenAI Completions": "OpenAI Completions",
"OpenAI Compatible": "OpenAI Compatible",
"OpenAI Completions": "OpenAI Completions",
"OpenAI Embeddings": "OpenAI Embeddings",
"OpenAI Image Edits": "OpenAI Image Edits",
"OpenAI Image Generations": "OpenAI Image Generations",
......@@ -3072,7 +3067,9 @@
"Ping Interval (seconds)": "Ping Interval (seconds)",
"Plan": "Plan",
"Plan Name": "Plan Name",
"Plan Price": "Plan Price",
"Plan price must be greater than zero": "Plan price must be greater than zero",
"Plan Quota": "Plan Quota",
"Plan Subtitle": "Plan Subtitle",
"Plan Title": "Plan Title",
"Plan title is required": "Plan title is required",
......@@ -4267,6 +4264,7 @@
"Total invitation revenue": "Total invitation revenue",
"Total Log Size": "Total Log Size",
"Total Quota": "Total Quota",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "Total quota included in the plan, usable per billing period. 0 means unlimited.",
"Total requests allowed per period. 0 = unlimited.": "Total requests allowed per period. 0 = unlimited.",
"Total requests made": "Total requests made",
"Total tokens": "Total tokens",
......@@ -4408,10 +4406,10 @@
"Upstream Model Update Check": "Upstream Model Update Check",
"Upstream Model Updates": "Upstream Model Updates",
"Upstream model updates applied: {{added}} added, {{removed}} removed, {{ignored}} ignored this time, {{totalIgnored}} total ignored models": "Upstream model updates applied: {{added}} added, {{removed}} removed, {{ignored}} ignored this time, {{totalIgnored}} total ignored models",
"Upstream price sync": "Upstream price sync",
"Upstream path": "Upstream path",
"Upstream path is required": "Upstream path is required",
"Upstream path must be a full URL or a path starting with /": "Upstream path must be a full URL or a path starting with /",
"Upstream price sync": "Upstream price sync",
"Upstream prices fetched successfully": "Upstream prices fetched successfully",
"Upstream ratios fetched successfully": "Upstream ratios fetched successfully",
"Upstream Request ID": "Upstream Request ID",
......@@ -4443,8 +4441,8 @@
"USD price per 1M input tokens.": "USD price per 1M input tokens.",
"USD price per 1M tokens.": "USD price per 1M tokens.",
"Use +: to add a group, -: to remove a default selectable group, or no prefix to append a group.": "Use +: to add a group, -: to remove a default selectable group, or no prefix to append a group.",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.",
"Use a compatible browser or device with biometric authentication or a security key to register a Passkey.": "Use a compatible browser or device with biometric authentication or a security key to register a Passkey.",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.",
"Use authenticator code": "Use authenticator code",
"Use backup code": "Use backup code",
"Use disk cache when request body exceeds this size": "Use disk cache when request body exceeds this size",
......@@ -4657,7 +4655,6 @@
"When enabled, Midjourney callbacks are accepted (reveals server IP).": "When enabled, Midjourney callbacks are accepted (reveals server IP).",
"When enabled, newly created tokens start in the first auto group.": "When enabled, newly created tokens start in the first auto group.",
"When enabled, prompts are scanned before reaching upstream models.": "When enabled, prompts are scanned before reaching upstream models.",
"When enabled, requests that do not match any advanced route are forwarded to the channel base URL. When disabled, unmatched requests return an error.": "When enabled, requests that do not match any advanced route are forwarded to the channel base URL. When disabled, unmatched requests return an error.",
"When enabled, the store field will be blocked": "When enabled, the store field will be blocked",
"When enabled, users can pick this group when creating tokens.": "When enabled, users can pick this group when creating tokens.",
"When enabled, violation requests will incur additional charges.": "When enabled, violation requests will incur additional charges.",
......
......@@ -138,11 +138,6 @@
"Active models": "Modèles actifs",
"active users": "utilisateurs actifs",
"Actual Amount": "Montant réel",
"Plan Price": "Prix du forfait",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "Montant que l'utilisateur paie pour acheter ce forfait ; la devise réelle dépend de la passerelle de paiement.",
"Amount the user pays to purchase this plan (USD).": "Montant que l'utilisateur paie pour acheter ce forfait (USD).",
"Plan Quota": "Quota du forfait",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "Quota total inclus dans le forfait, utilisable par période de facturation. 0 signifie illimité.",
"Actual Model": "Modèle réel",
"Actual Model:": "Modèle réel :",
"Adapt `-thinking` suffix requests to Anthropic native thinking behavior while keeping billing predictable.": "Adapter les requêtes avec le suffixe `-thinking` au comportement de pensée natif d’Anthropic tout en gardant une facturation prévisible.",
......@@ -231,7 +226,7 @@
"Advanced Configuration": "Configuration avancée",
"Advanced Custom": "Personnalisé avancé",
"Advanced custom configuration is required": "La configuration personnalisée avancée est requise",
"Advanced custom configuration requires at least one route or fallback": "La configuration personnalisée avancée nécessite au moins une route ou un fallback",
"Advanced custom configuration requires at least one route": "La configuration personnalisée avancée nécessite au moins une route",
"Advanced Custom Routes": "Routes personnalisées avancées",
"Advanced options": "Options avancées",
"Advanced Options": "Options avancées",
......@@ -286,10 +281,6 @@
"Allocated Memory": "Mémoire allouée",
"Allow accountFilter parameter": "Autoriser le paramètre accountFilter",
"Allow balance redemption": "Autoriser le paiement avec le solde",
"Allow wallet balance after quota used up": "Autoriser le solde du portefeuille une fois le quota épuisé",
"Downgrade Group": "Groupe de rétrogradation",
"Downgrade to pre-purchase group": "Rétrograder vers le groupe d'avant l'achat",
"Downgrade to this group after the subscription expires": "Rétrograder vers ce groupe après l'expiration de l'abonnement",
"Allow Claude beta query passthrough": "Autoriser le passage des requêtes bêta Claude",
"Allow clients to query configured ratios via `/api/ratio`.": "Autoriser les clients à interroger les ratios configurés via `/api/ratio`.",
"Allow HTTP image requests": "Autoriser les requêtes d'images HTTP",
......@@ -318,6 +309,7 @@
"Allow users to sign in with this provider": "Autoriser les utilisateurs à se connecter avec ce fournisseur",
"Allow users to sign in with WeChat": "Autoriser les utilisateurs à se connecter avec WeChat",
"Allow using models without price configuration": "Autoriser l'utilisation de modèles sans configuration de prix",
"Allow wallet balance after quota used up": "Autoriser le solde du portefeuille une fois le quota épuisé",
"Allowed": "Autorisé",
"Allowed Origins": "Origines autorisées",
"Allowed Ports": "Ports autorisés",
......@@ -331,6 +323,8 @@
"Amount must be greater than 0": "Le montant doit être supérieur à 0",
"Amount of quota to credit to user account.": "Montant du quota à créditer sur le compte utilisateur.",
"Amount options must be a JSON array": "Les options de montant doivent être un tableau JSON",
"Amount the user pays to purchase this plan (USD).": "Montant que l'utilisateur paie pour acheter ce forfait (USD).",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "Montant que l'utilisateur paie pour acheter ce forfait ; la devise réelle dépend de la passerelle de paiement.",
"Amount to pay:": "Montant à payer :",
"An unexpected error occurred": "Une erreur inattendue est survenue",
"and": "et",
......@@ -542,7 +536,6 @@
"Base URL": "URL de base",
"Base URL is required for this channel type": "L'URL de base est requise pour ce type de canal",
"Base URL is required when an advanced route uses an upstream path": "La Base URL est requise lorsqu’une route avancée utilise un chemin amont",
"Base URL is required when fallback is enabled": "La Base URL est requise lorsque le fallback est active",
"Base URL of your Uptime Kuma instance": "URL de base de votre instance Uptime Kuma",
"Basic Authentication": "Authentification de base",
"Basic Configuration": "Configuration de base",
......@@ -791,6 +784,7 @@
"Click save when you're done.": "Cliquez sur Enregistrer lorsque vous avez terminé.",
"Click save when you&apos;re done.": "Cliquez sur Enregistrer lorsque vous avez terminé.",
"Click the button below to bind your Telegram account": "Cliquez sur le bouton ci-dessous pour lier votre compte Telegram",
"Click to copy": "Cliquer pour copier",
"Click to open deployment": "Cliquez pour ouvrir le déploiement",
"Click to preview audio": "Cliquer pour prévisualiser l'audio",
"Click to preview video": "Cliquer pour prévisualiser la vidéo",
......@@ -1336,6 +1330,9 @@
"Doubao custom API address editing unlocked": "Édition d'adresse API personnalisée Doubao déverrouillée",
"DoubaoVideo": "DoubaoVideo",
"Double check the configuration below. Your system will be locked until initialization is complete.": "Vérifiez la configuration ci-dessous. Votre système sera verrouillé jusqu'à ce que l'initialisation soit terminée.",
"Downgrade Group": "Groupe de rétrogradation",
"Downgrade to pre-purchase group": "Rétrograder vers le groupe d'avant l'achat",
"Downgrade to this group after the subscription expires": "Rétrograder vers ce groupe après l'expiration de l'abonnement",
"Download": "Télécharger",
"Draw": "Dessin",
"Drawing": "Dessin",
......@@ -1774,9 +1771,7 @@
"Failed to update user": "Échec de la mise à jour de l'utilisateur",
"Failure keywords": "Mots-clés d'échec",
"Fair": "Correct",
"Fallback": "Fallback",
"Fallback base URL": "Base URL de fallback",
"Fallback routing": "Routage fallback",
"Fallback tier": "Palier de repli",
"FAQ": "FAQ",
"FAQ added. Click \"Save Settings\" to apply.": "FAQ ajouté. Cliquez sur \"Enregistrer les paramètres\" pour appliquer.",
......@@ -2864,8 +2859,8 @@
"OpenAI Chat to Anthropic Messages": "OpenAI Chat vers Anthropic Messages",
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat vers Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat vers OpenAI Responses",
"OpenAI Completions": "OpenAI Completions",
"OpenAI Compatible": "Compatible OpenAI",
"OpenAI Completions": "OpenAI Completions",
"OpenAI Embeddings": "OpenAI Embeddings",
"OpenAI Image Edits": "OpenAI Image Edits",
"OpenAI Image Generations": "OpenAI Image Generations",
......@@ -3072,7 +3067,9 @@
"Ping Interval (seconds)": "Intervalle de ping (secondes)",
"Plan": "Plan",
"Plan Name": "Nom du plan",
"Plan Price": "Prix du forfait",
"Plan price must be greater than zero": "Le prix du forfait doit être supérieur à zéro",
"Plan Quota": "Quota du forfait",
"Plan Subtitle": "Sous-titre du plan",
"Plan Title": "Titre du plan",
"Plan title is required": "Le titre du forfait est requis",
......@@ -4267,6 +4264,7 @@
"Total invitation revenue": "Revenu total des invitations",
"Total Log Size": "Taille totale des journaux",
"Total Quota": "Quota total",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "Quota total inclus dans le forfait, utilisable par période de facturation. 0 signifie illimité.",
"Total requests allowed per period. 0 = unlimited.": "Total des requêtes autorisées par période. 0 = illimité.",
"Total requests made": "Requêtes totales effectuées",
"Total tokens": "Jetons totaux",
......@@ -4408,10 +4406,10 @@
"Upstream Model Update Check": "Vérification des mises à jour des modèles en amont",
"Upstream Model Updates": "Mises à jour des modèles en amont",
"Upstream model updates applied: {{added}} added, {{removed}} removed, {{ignored}} ignored this time, {{totalIgnored}} total ignored models": "Mises à jour des modèles en amont appliquées : {{added}} ajoutés, {{removed}} supprimés, {{ignored}} ignorés cette fois, {{totalIgnored}} modèles ignorés au total",
"Upstream price sync": "Synchronisation amont des prix",
"Upstream path": "Chemin amont",
"Upstream path is required": "Le chemin amont est requis",
"Upstream path must be a full URL or a path starting with /": "Le chemin amont doit être une URL complète ou un chemin commençant par /",
"Upstream price sync": "Synchronisation amont des prix",
"Upstream prices fetched successfully": "Prix amont récupérés avec succès",
"Upstream ratios fetched successfully": "Ratios en amont récupérés avec succès",
"Upstream Request ID": "ID de requête en amont",
......@@ -4443,8 +4441,8 @@
"USD price per 1M input tokens.": "Prix en USD par million de tokens d’entrée.",
"USD price per 1M tokens.": "Prix en USD par million de tokens.",
"Use +: to add a group, -: to remove a default selectable group, or no prefix to append a group.": "Utilisez +: pour ajouter un groupe, -: pour supprimer un groupe sélectionnable par défaut, ou aucun préfixe pour annexer un groupe.",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Utilisez un chemin pour l’ajouter à la Base URL du canal, ou saisissez une URL complète pour remplacer la Base URL pour cette route.",
"Use a compatible browser or device with biometric authentication or a security key to register a Passkey.": "Utilisez un navigateur ou un appareil compatible avec l'authentification biométrique ou une clé de sécurité pour enregistrer une clé d'accès (Passkey).",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Utilisez un chemin pour l’ajouter à la Base URL du canal, ou saisissez une URL complète pour remplacer la Base URL pour cette route.",
"Use authenticator code": "Utiliser le code de l'authentificateur",
"Use backup code": "Utiliser un code de secours",
"Use disk cache when request body exceeds this size": "Utiliser le cache disque quand le corps de requête dépasse cette taille",
......@@ -4657,7 +4655,6 @@
"When enabled, Midjourney callbacks are accepted (reveals server IP).": "Lorsque activé, les callbacks Midjourney sont acceptés (révèle l'IP du serveur).",
"When enabled, newly created tokens start in the first auto group.": "Lorsqu'elle est activée, les jetons nouvellement créés commencent dans le premier groupe automatique.",
"When enabled, prompts are scanned before reaching upstream models.": "Lorsqu'elle est activée, les invites sont scannées avant d'atteindre les modèles en amont.",
"When enabled, requests that do not match any advanced route are forwarded to the channel base URL. When disabled, unmatched requests return an error.": "Lorsque cette option est activée, les requêtes qui ne correspondent à aucune route avancée sont transférées vers l'URL de base du canal. Lorsqu'elle est désactivée, les requêtes sans correspondance renvoient une erreur.",
"When enabled, the store field will be blocked": "Lorsqu'il est activé, le champ de la boutique sera bloqué",
"When enabled, users can pick this group when creating tokens.": "Une fois activé, les utilisateurs peuvent choisir ce groupe lors de la création de jetons.",
"When enabled, violation requests will incur additional charges.": "Lorsqu'activé, les requêtes en violation entraîneront des frais supplémentaires.",
......
......@@ -138,11 +138,6 @@
"Active models": "アクティブなモデル",
"active users": "アクティブユーザー",
"Actual Amount": "実際の金額",
"Plan Price": "プラン価格",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "ユーザーがこのプランを購入する際に支払う金額。実際の通貨は決済ゲートウェイによって異なります。",
"Amount the user pays to purchase this plan (USD).": "ユーザーがこのプランを購入する際に支払う金額(USD)。",
"Plan Quota": "プランのクォータ",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "プランに含まれる合計クォータ。請求期間ごとに使用可能。0 は無制限を意味します。",
"Actual Model": "実際のモデル",
"Actual Model:": "実際のモデル:",
"Adapt `-thinking` suffix requests to Anthropic native thinking behavior while keeping billing predictable.": "`-thinking` サフィックス付きリクエストを Anthropic ネイティブの思考動作に適配し、課金を予測可能に保ちます。",
......@@ -231,7 +226,7 @@
"Advanced Configuration": "詳細設定",
"Advanced Custom": "高度なカスタム",
"Advanced custom configuration is required": "高度なカスタム設定が必要です",
"Advanced custom configuration requires at least one route or fallback": "高度なカスタム設定には少なくとも1つのルートまたはフォールバックが必要です",
"Advanced custom configuration requires at least one route": "高度なカスタム設定には少なくとも1つのルートが必要です",
"Advanced Custom Routes": "高度なカスタムルート",
"Advanced options": "詳細オプション",
"Advanced Options": "詳細オプション",
......@@ -286,10 +281,6 @@
"Allocated Memory": "割り当て済みメモリ",
"Allow accountFilter parameter": "accountFilter パラメータを許可",
"Allow balance redemption": "残高での交換を許可",
"Allow wallet balance after quota used up": "クォータ使い切り後にウォレット残高の使用を許可",
"Downgrade Group": "ダウングレードグループ",
"Downgrade to pre-purchase group": "購入前のグループにダウングレード",
"Downgrade to this group after the subscription expires": "サブスクリプションの有効期限が切れた後、このグループにダウングレードします",
"Allow Claude beta query passthrough": "Claude ベータクエリのパススルーを許可",
"Allow clients to query configured ratios via `/api/ratio`.": "クライアントが `/api/ratio` 経由で設定された比率を照会できるようにします。",
"Allow HTTP image requests": "HTTP画像リクエストを許可",
......@@ -318,6 +309,7 @@
"Allow users to sign in with this provider": "このプロバイダーでサインインを許可する",
"Allow users to sign in with WeChat": "ユーザーがWeChatでサインインできるようにする",
"Allow using models without price configuration": "価格設定なしでモデルの使用を許可",
"Allow wallet balance after quota used up": "クォータ使い切り後にウォレット残高の使用を許可",
"Allowed": "許可",
"Allowed Origins": "許可するオリジン",
"Allowed Ports": "許可するポート",
......@@ -331,6 +323,8 @@
"Amount must be greater than 0": "金額は 0 より大きくなければなりません",
"Amount of quota to credit to user account.": "ユーザーアカウントに付与するクォータの量。",
"Amount options must be a JSON array": "金額オプションは JSON 配列でなければなりません",
"Amount the user pays to purchase this plan (USD).": "ユーザーがこのプランを購入する際に支払う金額(USD)。",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "ユーザーがこのプランを購入する際に支払う金額。実際の通貨は決済ゲートウェイによって異なります。",
"Amount to pay:": "支払い金額:",
"An unexpected error occurred": "予期せぬエラーが発生しました",
"and": "および",
......@@ -542,7 +536,6 @@
"Base URL": "ベースURL",
"Base URL is required for this channel type": "このチャネルタイプには Base URL が必要です",
"Base URL is required when an advanced route uses an upstream path": "高度なルートで上流パスを使う場合は Base URL が必要です",
"Base URL is required when fallback is enabled": "フォールバックを有効にする場合は Base URL が必要です",
"Base URL of your Uptime Kuma instance": "Uptime KumaインスタンスのベースURL",
"Basic Authentication": "基本認証",
"Basic Configuration": "基本設定",
......@@ -791,6 +784,7 @@
"Click save when you're done.": "完了したら「保存」をクリックしてください。",
"Click save when you&apos;re done.": "完了したら保存をクリックしてください。",
"Click the button below to bind your Telegram account": "下のボタンをクリックしてTelegramアカウントをバインドしてください",
"Click to copy": "クリックしてコピー",
"Click to open deployment": "クリックして展開を開く",
"Click to preview audio": "クリックして音声をプレビュー",
"Click to preview video": "クリックして動画をプレビュー",
......@@ -1336,6 +1330,9 @@
"Doubao custom API address editing unlocked": "豆包カスタムAPI アドレス編集がアンロックされました",
"DoubaoVideo": "DoubaoVideo",
"Double check the configuration below. Your system will be locked until initialization is complete.": "下記の設定を再確認してください。初期化が完了するまでシステムはロックされます。",
"Downgrade Group": "ダウングレードグループ",
"Downgrade to pre-purchase group": "購入前のグループにダウングレード",
"Downgrade to this group after the subscription expires": "サブスクリプションの有効期限が切れた後、このグループにダウングレードします",
"Download": "ダウンロード",
"Draw": "描画",
"Drawing": "画像生成",
......@@ -1774,9 +1771,7 @@
"Failed to update user": "ユーザーの更新に失敗しました",
"Failure keywords": "失敗キーワード",
"Fair": "公平",
"Fallback": "フォールバック",
"Fallback base URL": "フォールバック Base URL",
"Fallback routing": "フォールバックルーティング",
"Fallback tier": "フォールバック段階",
"FAQ": "FAQ",
"FAQ added. Click \"Save Settings\" to apply.": "FAQ が追加されました。「設定を保存」をクリックして適用してください。",
......@@ -2864,8 +2859,8 @@
"OpenAI Chat to Anthropic Messages": "OpenAI Chat から Anthropic Messages",
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat から Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat から OpenAI Responses",
"OpenAI Completions": "OpenAI 補完",
"OpenAI Compatible": "OpenAI互換",
"OpenAI Completions": "OpenAI 補完",
"OpenAI Embeddings": "OpenAI 埋め込み",
"OpenAI Image Edits": "OpenAI 画像編集",
"OpenAI Image Generations": "OpenAI 画像生成",
......@@ -3072,7 +3067,9 @@
"Ping Interval (seconds)": "Ping間隔(秒)",
"Plan": "プラン",
"Plan Name": "プラン名",
"Plan Price": "プラン価格",
"Plan price must be greater than zero": "プラン価格は 0 より大きい必要があります",
"Plan Quota": "プランのクォータ",
"Plan Subtitle": "プランサブタイトル",
"Plan Title": "プラン名",
"Plan title is required": "プランタイトルは必須です",
......@@ -4267,6 +4264,7 @@
"Total invitation revenue": "招待による総収益",
"Total Log Size": "ログ合計サイズ",
"Total Quota": "合計クォータ",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "プランに含まれる合計クォータ。請求期間ごとに使用可能。0 は無制限を意味します。",
"Total requests allowed per period. 0 = unlimited.": "期間ごとに許可されるリクエストの総数。0 = 無制限。",
"Total requests made": "合計リクエスト数",
"Total tokens": "合計トークン",
......@@ -4408,10 +4406,10 @@
"Upstream Model Update Check": "アップストリームモデル更新チェック",
"Upstream Model Updates": "上流モデルの更新",
"Upstream model updates applied: {{added}} added, {{removed}} removed, {{ignored}} ignored this time, {{totalIgnored}} total ignored models": "上流モデル更新を処理しました:{{added}} 個追加、{{removed}} 個削除、今回 {{ignored}} 個無視、合計 {{totalIgnored}} 個の無視モデル",
"Upstream price sync": "アップストリーム価格同期",
"Upstream path": "上流パス",
"Upstream path is required": "上流パスは必須です",
"Upstream path must be a full URL or a path starting with /": "上流パスは完全な URL、または / で始まるパスである必要があります",
"Upstream price sync": "アップストリーム価格同期",
"Upstream prices fetched successfully": "上流価格を正常に取得しました",
"Upstream ratios fetched successfully": "アップストリーム比率が正常に取得されました",
"Upstream Request ID": "上流リクエストID",
......@@ -4443,8 +4441,8 @@
"USD price per 1M input tokens.": "100万入力トークンあたりのUSD価格。",
"USD price per 1M tokens.": "100万トークンあたりのUSD価格。",
"Use +: to add a group, -: to remove a default selectable group, or no prefix to append a group.": "+: はグループ追加、-: はデフォルト選択可能グループの削除、接頭辞なしはグループ追記に使います。",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "パスを入力するとチャネルの Base URL に追加されます。完全な URL を入力すると、このルートでは Base URL を使わずその URL を使用します。",
"Use a compatible browser or device with biometric authentication or a security key to register a Passkey.": "生体認証またはセキュリティキーを備えた互換性のあるブラウザまたはデバイスを使用して、パスキーを登録してください。",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "パスを入力するとチャネルの Base URL に追加されます。完全な URL を入力すると、このルートでは Base URL を使わずその URL を使用します。",
"Use authenticator code": "認証コードを使用",
"Use backup code": "バックアップコードを使用",
"Use disk cache when request body exceeds this size": "リクエストボディがこのサイズを超えた場合にディスクキャッシュを使用",
......@@ -4657,7 +4655,6 @@
"When enabled, Midjourney callbacks are accepted (reveals server IP).": "有効にすると、Midjourney のコールバックを受け入れます (サーバーの IP を公開します)。",
"When enabled, newly created tokens start in the first auto group.": "有効にすると、新しく作成されたトークンは最初の自動グループで開始されます。",
"When enabled, prompts are scanned before reaching upstream models.": "有効にすると、プロンプトはアップストリームモデルに到達する前にスキャンされます。",
"When enabled, requests that do not match any advanced route are forwarded to the channel base URL. When disabled, unmatched requests return an error.": "有効にすると、高度なルートに一致しないリクエストはチャネルのベース URL へ転送されます。無効の場合、一致しないリクエストはエラーを返します。",
"When enabled, the store field will be blocked": "有効にすると、ストアフィールドはブロックされます",
"When enabled, users can pick this group when creating tokens.": "有効にすると、ユーザーはトークン作成時にこのグループを選択できます。",
"When enabled, violation requests will incur additional charges.": "有効にすると、違反リクエストに追加料金が発生します。",
......
......@@ -138,11 +138,6 @@
"Active models": "Активные модели",
"active users": "активных пользователей",
"Actual Amount": "Фактическая сумма",
"Plan Price": "Цена тарифа",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "Сумма, которую пользователь платит за покупку тарифа; фактическая валюта зависит от платёжного шлюза.",
"Amount the user pays to purchase this plan (USD).": "Сумма, которую пользователь платит за покупку этого тарифа (USD).",
"Plan Quota": "Квота тарифа",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "Общая квота, включённая в тариф, доступна за каждый расчётный период. 0 означает безлимит.",
"Actual Model": "Фактическая модель",
"Actual Model:": "Фактическая модель:",
"Adapt `-thinking` suffix requests to Anthropic native thinking behavior while keeping billing predictable.": "Адаптировать запросы с суффиксом `-thinking` к собственному режиму размышления Anthropic, сохраняя предсказуемость биллинга.",
......@@ -231,7 +226,7 @@
"Advanced Configuration": "Расширенная конфигурация",
"Advanced Custom": "Расширенный пользовательский",
"Advanced custom configuration is required": "Требуется расширенная пользовательская конфигурация",
"Advanced custom configuration requires at least one route or fallback": "Расширенной пользовательской конфигурации нужен хотя бы один маршрут или fallback",
"Advanced custom configuration requires at least one route": "Расширенной пользовательской конфигурации нужен хотя бы один маршрут",
"Advanced Custom Routes": "Расширенные пользовательские маршруты",
"Advanced options": "Расширенные параметры",
"Advanced Options": "Расширенные параметры",
......@@ -286,10 +281,6 @@
"Allocated Memory": "Выделенная память",
"Allow accountFilter parameter": "Разрешить параметр accountFilter",
"Allow balance redemption": "Разрешить оплату балансом",
"Allow wallet balance after quota used up": "Разрешить использование баланса кошелька после исчерпания квоты",
"Downgrade Group": "Понизить группу",
"Downgrade to pre-purchase group": "Понизить до группы до покупки",
"Downgrade to this group after the subscription expires": "Понизить до этой группы после истечения подписки",
"Allow Claude beta query passthrough": "Разрешить проброс бета-запросов Claude",
"Allow clients to query configured ratios via `/api/ratio`.": "Разрешить клиентам запрашивать настроенные соотношения через `/api/ratio`.",
"Allow HTTP image requests": "Разрешить HTTP-запросы изображений",
......@@ -318,6 +309,7 @@
"Allow users to sign in with this provider": "Разрешить пользователям входить через этого поставщика",
"Allow users to sign in with WeChat": "Разрешить пользователям входить через WeChat",
"Allow using models without price configuration": "Разрешить использование моделей без настройки цен",
"Allow wallet balance after quota used up": "Разрешить использование баланса кошелька после исчерпания квоты",
"Allowed": "Разрешено",
"Allowed Origins": "Разрешенные Origins",
"Allowed Ports": "Разрешенные порты",
......@@ -331,6 +323,8 @@
"Amount must be greater than 0": "Сумма должна быть больше 0",
"Amount of quota to credit to user account.": "Количество квоты для зачисления на счет пользователя.",
"Amount options must be a JSON array": "Варианты сумм должны быть JSON-массивом",
"Amount the user pays to purchase this plan (USD).": "Сумма, которую пользователь платит за покупку этого тарифа (USD).",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "Сумма, которую пользователь платит за покупку тарифа; фактическая валюта зависит от платёжного шлюза.",
"Amount to pay:": "Сумма к оплате:",
"An unexpected error occurred": "Произошла непредвиденная ошибка",
"and": "и",
......@@ -542,7 +536,6 @@
"Base URL": "Адрес API",
"Base URL is required for this channel type": "Для этого типа канала требуется Base URL",
"Base URL is required when an advanced route uses an upstream path": "Base URL требуется, когда расширенный маршрут использует путь upstream",
"Base URL is required when fallback is enabled": "Base URL обязателен при включенном fallback",
"Base URL of your Uptime Kuma instance": "Базовый URL вашего экземпляра Uptime Kuma",
"Basic Authentication": "Базовая аутентификация",
"Basic Configuration": "Базовая конфигурация",
......@@ -791,6 +784,7 @@
"Click save when you're done.": "Нажмите «Сохранить», когда закончите.",
"Click save when you&apos;re done.": "Нажмите сохранить, когда закончите.",
"Click the button below to bind your Telegram account": "Нажмите кнопку ниже, чтобы привязать ваш аккаунт Telegram",
"Click to copy": "Нажмите, чтобы скопировать",
"Click to open deployment": "Нажмите, чтобы открыть развертывание",
"Click to preview audio": "Нажмите для предпросмотра аудио",
"Click to preview video": "Нажмите, чтобы просмотреть видео",
......@@ -1336,6 +1330,9 @@
"Doubao custom API address editing unlocked": "Редактирование пользовательского адреса API Doubao разблокировано",
"DoubaoVideo": "DoubaoVideo",
"Double check the configuration below. Your system will be locked until initialization is complete.": "Дважды проверьте конфигурацию ниже. Ваша система будет заблокирована до завершения инициализации.",
"Downgrade Group": "Понизить группу",
"Downgrade to pre-purchase group": "Понизить до группы до покупки",
"Downgrade to this group after the subscription expires": "Понизить до этой группы после истечения подписки",
"Download": "Скачать",
"Draw": "Рисование",
"Drawing": "Рисование",
......@@ -1774,9 +1771,7 @@
"Failed to update user": "Не удалось обновить пользователя",
"Failure keywords": "Ключевые слова сбоя",
"Fair": "Удовлетворительно",
"Fallback": "Резерв",
"Fallback base URL": "Base URL fallback",
"Fallback routing": "Fallback-маршрутизация",
"Fallback tier": "Резервный уровень",
"FAQ": "Часто задаваемые вопросы",
"FAQ added. Click \"Save Settings\" to apply.": "FAQ добавлен. Нажмите \"Сохранить настройки\" чтобы применить.",
......@@ -2864,8 +2859,8 @@
"OpenAI Chat to Anthropic Messages": "OpenAI Chat в Anthropic Messages",
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat в Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat в OpenAI Responses",
"OpenAI Completions": "Завершения OpenAI",
"OpenAI Compatible": "Совместимо с OpenAI",
"OpenAI Completions": "Завершения OpenAI",
"OpenAI Embeddings": "Векторные представления OpenAI",
"OpenAI Image Edits": "Редактирование изображений OpenAI",
"OpenAI Image Generations": "Генерация изображений OpenAI",
......@@ -3072,7 +3067,9 @@
"Ping Interval (seconds)": "Интервал Ping (секунды)",
"Plan": "План",
"Plan Name": "Название плана",
"Plan Price": "Цена тарифа",
"Plan price must be greater than zero": "Цена плана должна быть больше нуля",
"Plan Quota": "Квота тарифа",
"Plan Subtitle": "Подзаголовок плана",
"Plan Title": "Название плана",
"Plan title is required": "Название плана обязательно",
......@@ -4267,6 +4264,7 @@
"Total invitation revenue": "Общий доход от приглашений",
"Total Log Size": "Общий размер журналов",
"Total Quota": "Общая квота",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "Общая квота, включённая в тариф, доступна за каждый расчётный период. 0 означает безлимит.",
"Total requests allowed per period. 0 = unlimited.": "Общее количество запросов, разрешенных за период. 0 = без ограничений.",
"Total requests made": "Всего сделанных запросов",
"Total tokens": "Всего токенов",
......@@ -4408,10 +4406,10 @@
"Upstream Model Update Check": "Проверка обновлений моделей провайдера",
"Upstream Model Updates": "Обновления моделей источника",
"Upstream model updates applied: {{added}} added, {{removed}} removed, {{ignored}} ignored this time, {{totalIgnored}} total ignored models": "Обновления моделей применены: {{added}} добавлено, {{removed}} удалено, {{ignored}} проигнорировано, всего {{totalIgnored}} проигнорированных моделей",
"Upstream price sync": "Синхронизация цен upstream",
"Upstream path": "Путь upstream",
"Upstream path is required": "Путь upstream обязателен",
"Upstream path must be a full URL or a path starting with /": "Путь upstream должен быть полным URL или путем, начинающимся с /",
"Upstream price sync": "Синхронизация цен upstream",
"Upstream prices fetched successfully": "Цены провайдера успешно получены",
"Upstream ratios fetched successfully": "Коэффициенты upstream успешно получены",
"Upstream Request ID": "ID вышестоящего запроса",
......@@ -4443,8 +4441,8 @@
"USD price per 1M input tokens.": "Цена в USD за 1 млн входных токенов.",
"USD price per 1M tokens.": "Цена в USD за 1 млн токенов.",
"Use +: to add a group, -: to remove a default selectable group, or no prefix to append a group.": "Используйте +: для добавления группы, -: для удаления выбираемой по умолчанию группы, без префикса — для добавления в конец.",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Укажите путь, чтобы добавить его к Base URL канала, или введите полный URL, чтобы переопределить Base URL для этого маршрута.",
"Use a compatible browser or device with biometric authentication or a security key to register a Passkey.": "Используйте совместимый браузер или устройство с биометрической аутентификацией или ключ безопасности для регистрации ключа доступа.",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Укажите путь, чтобы добавить его к Base URL канала, или введите полный URL, чтобы переопределить Base URL для этого маршрута.",
"Use authenticator code": "Использовать код аутентификатора",
"Use backup code": "Использовать резервный код",
"Use disk cache when request body exceeds this size": "Использовать дисковый кэш, когда тело запроса превышает этот размер",
......@@ -4657,7 +4655,6 @@
"When enabled, Midjourney callbacks are accepted (reveals server IP).": "При включении принимаются обратные вызовы Midjourney (раскрывает IP сервера).",
"When enabled, newly created tokens start in the first auto group.": "При включении вновь созданные токены начинаются в первой автогруппе.",
"When enabled, prompts are scanned before reaching upstream models.": "При включении запросы сканируются перед достижением вышестоящих моделей.",
"When enabled, requests that do not match any advanced route are forwarded to the channel base URL. When disabled, unmatched requests return an error.": "Если включено, запросы, не соответствующие ни одному расширенному маршруту, перенаправляются на базовый URL канала. Если отключено, несопоставленные запросы возвращают ошибку.",
"When enabled, the store field will be blocked": "Если включено, поле магазина будет заблокировано",
"When enabled, users can pick this group when creating tokens.": "Если включено, пользователи могут выбрать эту группу при создании токенов.",
"When enabled, violation requests will incur additional charges.": "При включении за нарушения будут начисляться дополнительные расходы.",
......
......@@ -138,11 +138,6 @@
"Active models": "Mô hình đang hoạt động",
"active users": "Người dùng tích cực",
"Actual Amount": "Số tiền thực tế",
"Plan Price": "Giá gói",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "Số tiền người dùng trả để mua gói này; loại tiền tệ thực tế tùy thuộc vào cổng thanh toán.",
"Amount the user pays to purchase this plan (USD).": "Số tiền người dùng trả để mua gói này (USD).",
"Plan Quota": "Hạn ngạch gói",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "Tổng hạn ngạch bao gồm trong gói, dùng được mỗi kỳ thanh toán. 0 nghĩa là không giới hạn.",
"Actual Model": "Mô hình thực tế",
"Actual Model:": "Mô hình thực tế:",
"Adapt `-thinking` suffix requests to Anthropic native thinking behavior while keeping billing predictable.": "Điều chỉnh các yêu cầu có hậu tố `-thinking` sang hành vi suy luận gốc của Anthropic trong khi vẫn giữ tính phí dễ dự đoán.",
......@@ -231,7 +226,7 @@
"Advanced Configuration": "Cấu hình nâng cao",
"Advanced Custom": "Tùy chỉnh nâng cao",
"Advanced custom configuration is required": "Bắt buộc cấu hình tùy chỉnh nâng cao",
"Advanced custom configuration requires at least one route or fallback": "Cấu hình tùy chỉnh nâng cao cần ít nhất một route hoặc fallback",
"Advanced custom configuration requires at least one route": "Cấu hình tùy chỉnh nâng cao cần ít nhất một route",
"Advanced Custom Routes": "Route tùy chỉnh nâng cao",
"Advanced options": "Tùy chọn nâng cao",
"Advanced Options": "Tùy chọn nâng cao",
......@@ -286,10 +281,6 @@
"Allocated Memory": "Bộ nhớ đã cấp phát",
"Allow accountFilter parameter": "Cho phép tham số accountFilter",
"Allow balance redemption": "Cho phép thanh toán bằng số dư",
"Allow wallet balance after quota used up": "Cho phép dùng số dư ví sau khi dùng hết hạn ngạch",
"Downgrade Group": "Nhóm hạ cấp",
"Downgrade to pre-purchase group": "Hạ xuống nhóm trước khi mua",
"Downgrade to this group after the subscription expires": "Hạ xuống nhóm này sau khi đăng ký hết hạn",
"Allow Claude beta query passthrough": "Cho phép chuyển tiếp truy vấn beta Claude",
"Allow clients to query configured ratios via `/api/ratio`.": "Cho phép khách hàng truy vấn các tỷ lệ đã cấu hình thông qua `/api/ratio`.",
"Allow HTTP image requests": "Cho phép yêu cầu hình ảnh HTTP",
......@@ -318,6 +309,7 @@
"Allow users to sign in with this provider": "Cho phép người dùng đăng nhập bằng nhà cung cấp này",
"Allow users to sign in with WeChat": "Cho phép người dùng đăng nhập bằng WeChat",
"Allow using models without price configuration": "Cho phép sử dụng mô hình không có cấu hình giá",
"Allow wallet balance after quota used up": "Cho phép dùng số dư ví sau khi dùng hết hạn ngạch",
"Allowed": "Cho phép",
"Allowed Origins": "Nguồn gốc được phép",
"Allowed Ports": "Cổng được phép",
......@@ -331,6 +323,8 @@
"Amount must be greater than 0": "Số tiền phải lớn hơn 0",
"Amount of quota to credit to user account.": "Số lượng quota để ghi có vào tài khoản người dùng.",
"Amount options must be a JSON array": "Tùy chọn số tiền phải là mảng JSON",
"Amount the user pays to purchase this plan (USD).": "Số tiền người dùng trả để mua gói này (USD).",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "Số tiền người dùng trả để mua gói này; loại tiền tệ thực tế tùy thuộc vào cổng thanh toán.",
"Amount to pay:": "Amount due:",
"An unexpected error occurred": "Đã xảy ra lỗi không mong muốn",
"and": "and",
......@@ -542,7 +536,6 @@
"Base URL": "URL cơ sở",
"Base URL is required for this channel type": "Loại kênh này yêu cầu Base URL",
"Base URL is required when an advanced route uses an upstream path": "Cần Base URL khi tuyến nâng cao dùng đường dẫn upstream",
"Base URL is required when fallback is enabled": "Cần Base URL khi bật fallback",
"Base URL of your Uptime Kuma instance": "URL cơ sở của phiên bản Uptime Kuma của bạn",
"Basic Authentication": "Xác thực cơ bản",
"Basic Configuration": "Cấu hình cơ bản",
......@@ -791,6 +784,7 @@
"Click save when you're done.": "Nhấn lưu khi bạn hoàn tất.",
"Click save when you&apos;re done.": "Nhấn lưu khi bạn hoàn tất.",
"Click the button below to bind your Telegram account": "Nhấp vào nút bên dưới để liên kết tài khoản Telegram của bạn",
"Click to copy": "Nhấp để sao chép",
"Click to open deployment": "Nhấp để mở triển khai",
"Click to preview audio": "Nhấp để xem trước âm thanh",
"Click to preview video": "Nhấp để xem trước video",
......@@ -1336,6 +1330,9 @@
"Doubao custom API address editing unlocked": "Đã mở khóa chỉnh sửa địa chỉ API tùy chỉnh Doubao",
"DoubaoVideo": "DoubaoVideo",
"Double check the configuration below. Your system will be locked until initialization is complete.": "Kiểm tra kỹ lại cấu hình bên dưới. Hệ thống của bạn sẽ bị khóa cho đến khi quá trình khởi tạo hoàn tất.",
"Downgrade Group": "Nhóm hạ cấp",
"Downgrade to pre-purchase group": "Hạ xuống nhóm trước khi mua",
"Downgrade to this group after the subscription expires": "Hạ xuống nhóm này sau khi đăng ký hết hạn",
"Download": "Tải xuống",
"Draw": "Vẽ",
"Drawing": "Vẽ",
......@@ -1774,9 +1771,7 @@
"Failed to update user": "Không thể cập nhật người dùng",
"Failure keywords": "Từ khóa thất bại",
"Fair": "Công bằng",
"Fallback": "Fallback",
"Fallback base URL": "Base URL fallback",
"Fallback routing": "Định tuyến fallback",
"Fallback tier": "Tầng dự phòng",
"FAQ": "FAQ",
"FAQ added. Click \"Save Settings\" to apply.": "Đã thêm FAQ. Nhấp \"Lưu cài đặt\" để áp dụng.",
......@@ -2864,8 +2859,8 @@
"OpenAI Chat to Anthropic Messages": "OpenAI Chat sang Anthropic Messages",
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat sang Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat sang OpenAI Responses",
"OpenAI Completions": "OpenAI Completions",
"OpenAI Compatible": "Tương thích OpenAI",
"OpenAI Completions": "OpenAI Completions",
"OpenAI Embeddings": "OpenAI Embeddings",
"OpenAI Image Edits": "OpenAI Image Edits",
"OpenAI Image Generations": "OpenAI Image Generations",
......@@ -3072,7 +3067,9 @@
"Ping Interval (seconds)": "Thời gian Ping (giây)",
"Plan": "Gói",
"Plan Name": "Tên gói",
"Plan Price": "Giá gói",
"Plan price must be greater than zero": "Giá gói phải lớn hơn 0",
"Plan Quota": "Hạn ngạch gói",
"Plan Subtitle": "Phụ đề gói",
"Plan Title": "Tiêu đề gói",
"Plan title is required": "Tiêu đề gói là bắt buộc",
......@@ -4267,6 +4264,7 @@
"Total invitation revenue": "Tổng doanh thu mời",
"Total Log Size": "Tổng dung lượng nhật ký",
"Total Quota": "Tổng hạn mức",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "Tổng hạn ngạch bao gồm trong gói, dùng được mỗi kỳ thanh toán. 0 nghĩa là không giới hạn.",
"Total requests allowed per period. 0 = unlimited.": "Tổng số yêu cầu được phép mỗi kỳ. 0 = không giới hạn.",
"Total requests made": "Tổng lượt yêu cầu",
"Total tokens": "Tổng số token",
......@@ -4408,10 +4406,10 @@
"Upstream Model Update Check": "Kiểm tra cập nhật mô hình nguồn",
"Upstream Model Updates": "Cập nhật mô hình upstream",
"Upstream model updates applied: {{added}} added, {{removed}} removed, {{ignored}} ignored this time, {{totalIgnored}} total ignored models": "Đã áp dụng cập nhật mô hình upstream: {{added}} đã thêm, {{removed}} đã xóa, {{ignored}} bỏ qua lần này, {{totalIgnored}} tổng mô hình đã bỏ qua",
"Upstream price sync": "Đồng bộ giá thượng nguồn",
"Upstream path": "Đường dẫn upstream",
"Upstream path is required": "Cần nhập đường dẫn upstream",
"Upstream path must be a full URL or a path starting with /": "Đường dẫn upstream phải là URL đầy đủ hoặc đường dẫn bắt đầu bằng /",
"Upstream price sync": "Đồng bộ giá thượng nguồn",
"Upstream prices fetched successfully": "Lấy giá upstream thành công",
"Upstream ratios fetched successfully": "Đã lấy tỷ lệ upstream thành công",
"Upstream Request ID": "ID yêu cầu thượng nguồn",
......@@ -4443,8 +4441,8 @@
"USD price per 1M input tokens.": "Giá USD cho mỗi 1 triệu token đầu vào.",
"USD price per 1M tokens.": "Giá USD cho mỗi 1 triệu token.",
"Use +: to add a group, -: to remove a default selectable group, or no prefix to append a group.": "Dùng +: để thêm nhóm, -: để xóa nhóm có thể chọn mặc định, hoặc không có tiền tố để nối nhóm.",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Dùng đường dẫn để nối vào Base URL của kênh, hoặc nhập URL đầy đủ để ghi đè Base URL cho tuyến này.",
"Use a compatible browser or device with biometric authentication or a security key to register a Passkey.": "Sử dụng trình duyệt hoặc thiết bị tương thích có xác thực sinh trắc học hoặc khóa bảo mật để đăng ký Khóa truy cập.",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "Dùng đường dẫn để nối vào Base URL của kênh, hoặc nhập URL đầy đủ để ghi đè Base URL cho tuyến này.",
"Use authenticator code": "Sử dụng mã xác thực",
"Use backup code": "Sử dụng mã dự phòng",
"Use disk cache when request body exceeds this size": "Sử dụng bộ nhớ đệm đĩa khi nội dung yêu cầu vượt quá kích thước này",
......@@ -4657,7 +4655,6 @@
"When enabled, Midjourney callbacks are accepted (reveals server IP).": "Khi được bật, các callback của Midjourney được chấp nhận (lộ IP máy chủ).",
"When enabled, newly created tokens start in the first auto group.": "Khi được bật, các token mới được tạo sẽ bắt đầu trong nhóm tự động đầu tiên.",
"When enabled, prompts are scanned before reaching upstream models.": "Khi được bật,",
"When enabled, requests that do not match any advanced route are forwarded to the channel base URL. When disabled, unmatched requests return an error.": "Khi bật, các yêu cầu không khớp với tuyến nâng cao nào sẽ được chuyển tiếp đến URL cơ sở của kênh. Khi tắt, các yêu cầu không khớp sẽ trả về lỗi.",
"When enabled, the store field will be blocked": "Khi được bật, trường store sẽ bị chặn",
"When enabled, users can pick this group when creating tokens.": "Khi bật, người dùng có thể chọn nhóm này khi tạo token.",
"When enabled, violation requests will incur additional charges.": "Khi bật, các yêu cầu vi phạm sẽ phải chịu phí bổ sung.",
......
......@@ -138,11 +138,6 @@
"Active models": "活跃模型",
"active users": "活跃用户",
"Actual Amount": "实付金额",
"Plan Price": "套餐价格",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "用户购买该套餐需支付的金额,具体币种由支付渠道决定",
"Amount the user pays to purchase this plan (USD).": "用户购买该套餐需支付的金额(美元)。",
"Plan Quota": "套餐额度",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "套餐包含的总额度,每个计费周期可用;0 表示不限量",
"Actual Model": "实际模型",
"Actual Model:": "实际模型:",
"Adapt `-thinking` suffix requests to Anthropic native thinking behavior while keeping billing predictable.": "将带 `-thinking` 后缀的请求适配为 Anthropic 原生思考请求,并保持计费可预测。",
......@@ -231,7 +226,7 @@
"Advanced Configuration": "高级配置",
"Advanced Custom": "高级自定义",
"Advanced custom configuration is required": "必须配置高级自定义",
"Advanced custom configuration requires at least one route or fallback": "高级自定义至少需要一个路由或启用兜底",
"Advanced custom configuration requires at least one route": "高级自定义至少需要一个路由",
"Advanced Custom Routes": "高级自定义路由",
"Advanced options": "高级选项",
"Advanced Options": "高级选项",
......@@ -286,10 +281,6 @@
"Allocated Memory": "已分配内存",
"Allow accountFilter parameter": "允许 accountFilter 参数",
"Allow balance redemption": "允许余额兑换",
"Allow wallet balance after quota used up": "额度用尽后允许使用钱包余额",
"Downgrade Group": "降级分组",
"Downgrade to pre-purchase group": "降级到购买前分组",
"Downgrade to this group after the subscription expires": "订阅过期后降级到该分组",
"Allow Claude beta query passthrough": "允许 Claude beta 查询透传",
"Allow clients to query configured ratios via `/api/ratio`.": "允许客户端通过 `/api/ratio` 查询配置的比例。",
"Allow HTTP image requests": "允许 HTTP 图像请求",
......@@ -318,6 +309,7 @@
"Allow users to sign in with this provider": "允许用户使用此提供商登录",
"Allow users to sign in with WeChat": "允许用户使用微信登录",
"Allow using models without price configuration": "允许使用未配置价格的模型",
"Allow wallet balance after quota used up": "额度用尽后允许使用钱包余额",
"Allowed": "允许",
"Allowed Origins": "允许的 Origins",
"Allowed Ports": "允许的端口",
......@@ -331,6 +323,8 @@
"Amount must be greater than 0": "金额必须大于 0",
"Amount of quota to credit to user account.": "添加到用户账户的配额数量。",
"Amount options must be a JSON array": "金额选项必须是 JSON 数组",
"Amount the user pays to purchase this plan (USD).": "用户购买该套餐需支付的金额(美元)。",
"Amount the user pays to purchase this plan; the actual currency depends on the payment gateway.": "用户购买该套餐需支付的金额,具体币种由支付渠道决定",
"Amount to pay:": "待支付金额:",
"An unexpected error occurred": "发生意外错误",
"and": "和",
......@@ -542,7 +536,6 @@
"Base URL": "API 地址",
"Base URL is required for this channel type": "此渠道类型需要填写 Base URL",
"Base URL is required when an advanced route uses an upstream path": "高级路由使用上游路径时必须填写 Base URL",
"Base URL is required when fallback is enabled": "启用兜底时必须填写 Base URL",
"Base URL of your Uptime Kuma instance": "您的 Uptime Kuma 实例的基础 URL",
"Basic Authentication": "基本身份验证",
"Basic Configuration": "基本配置",
......@@ -791,6 +784,7 @@
"Click save when you're done.": "完成後點擊保存。",
"Click save when you&apos;re done.": "完成后点击保存。",
"Click the button below to bind your Telegram account": "点击下方按钮绑定您的 Telegram 账户",
"Click to copy": "点击复制",
"Click to open deployment": "点击打开部署",
"Click to preview audio": "点击预览音乐",
"Click to preview video": "点击预览视频",
......@@ -1336,6 +1330,9 @@
"Doubao custom API address editing unlocked": "已解锁豆包自定义 API 地址编辑",
"DoubaoVideo": "DoubaoVideo",
"Double check the configuration below. Your system will be locked until initialization is complete.": "仔细检查以下配置。您的系统将在初始化完成前保持锁定状态。",
"Downgrade Group": "降级分组",
"Downgrade to pre-purchase group": "降级到购买前分组",
"Downgrade to this group after the subscription expires": "订阅过期后降级到该分组",
"Download": "下载",
"Draw": "绘图",
"Drawing": "绘图",
......@@ -1774,9 +1771,7 @@
"Failed to update user": "更新用户失败",
"Failure keywords": "失败关键词",
"Fair": "公平",
"Fallback": "兜底",
"Fallback base URL": "兜底 Base URL",
"Fallback routing": "兜底路由",
"Fallback tier": "兜底阶梯",
"FAQ": "常见问答",
"FAQ added. Click \"Save Settings\" to apply.": "FAQ 已添加。点击 \"保存设置\" 以应用。",
......@@ -2864,8 +2859,8 @@
"OpenAI Chat to Anthropic Messages": "OpenAI Chat 到 Anthropic Messages",
"OpenAI Chat to Gemini Generate Content": "OpenAI Chat 到 Gemini Generate Content",
"OpenAI Chat to OpenAI Responses": "OpenAI Chat 到 OpenAI Responses",
"OpenAI Completions": "OpenAI 文本补全",
"OpenAI Compatible": "兼容 OpenAI",
"OpenAI Completions": "OpenAI 文本补全",
"OpenAI Embeddings": "OpenAI 嵌入",
"OpenAI Image Edits": "OpenAI 图像编辑",
"OpenAI Image Generations": "OpenAI 图像生成",
......@@ -3072,7 +3067,9 @@
"Ping Interval (seconds)": "Ping 间隔(秒)",
"Plan": "套餐",
"Plan Name": "套餐名称",
"Plan Price": "套餐价格",
"Plan price must be greater than zero": "套餐价格必须大于 0",
"Plan Quota": "套餐额度",
"Plan Subtitle": "套餐副标题",
"Plan Title": "套餐标题",
"Plan title is required": "套餐标题为必填项",
......@@ -4267,6 +4264,7 @@
"Total invitation revenue": "总邀请收入",
"Total Log Size": "日志总大小",
"Total Quota": "总额度",
"Total quota included in the plan, usable per billing period. 0 means unlimited.": "套餐包含的总额度,每个计费周期可用;0 表示不限量",
"Total requests allowed per period. 0 = unlimited.": "每周期允许的总请求数。0 = 无限制。",
"Total requests made": "总请求数",
"Total tokens": "总 Token",
......@@ -4408,10 +4406,10 @@
"Upstream Model Update Check": "上游模型更新检查",
"Upstream Model Updates": "上游模型更新",
"Upstream model updates applied: {{added}} added, {{removed}} removed, {{ignored}} ignored this time, {{totalIgnored}} total ignored models": "已处理上游模型更新:加入 {{added}} 个,删除 {{removed}} 个,本次忽略 {{ignored}} 个,当前已忽略模型 {{totalIgnored}} 个",
"Upstream price sync": "上游价格同步",
"Upstream path": "上游路径",
"Upstream path is required": "上游路径不能为空",
"Upstream path must be a full URL or a path starting with /": "上游路径必须是完整 URL,或以 / 开头的路径",
"Upstream price sync": "上游价格同步",
"Upstream prices fetched successfully": "已成功获取上游价格",
"Upstream ratios fetched successfully": "上游比率获取成功",
"Upstream Request ID": "上游请求 ID",
......@@ -4443,8 +4441,8 @@
"USD price per 1M input tokens.": "每 100 万输入 token 的美元价格。",
"USD price per 1M tokens.": "每 100 万 token 的美元价格。",
"Use +: to add a group, -: to remove a default selectable group, or no prefix to append a group.": "使用 +: 添加分组,使用 -: 移除默认可选分组,不加前缀则追加分组。",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "填写以 / 开头的路径时会自动拼接渠道 Base URL;填写完整 URL 时,此路由会直接使用该 URL。",
"Use a compatible browser or device with biometric authentication or a security key to register a Passkey.": "请使用支持生物识别认证或安全密钥的兼容浏览器或设备来注册通行密钥。",
"Use a path to append it to the channel Base URL, or enter a full URL to override the Base URL for this route.": "填写以 / 开头的路径时会自动拼接渠道 Base URL;填写完整 URL 时,此路由会直接使用该 URL。",
"Use authenticator code": "使用验证器代码",
"Use backup code": "使用备用代码",
"Use disk cache when request body exceeds this size": "请求体超过此大小时使用磁盘缓存",
......@@ -4657,7 +4655,6 @@
"When enabled, Midjourney callbacks are accepted (reveals server IP).": "启用时,接受 Midjourney 回调 (会泄露服务器 IP)。",
"When enabled, newly created tokens start in the first auto group.": "启用后,新创建的令牌将从第一个自动分组开始。",
"When enabled, prompts are scanned before reaching upstream models.": "启用后,提示将在到达上游模型之前被扫描。",
"When enabled, requests that do not match any advanced route are forwarded to the channel base URL. When disabled, unmatched requests return an error.": "开启后,未命中任何高级路由的请求会转发到该渠道的 Base URL;关闭时,未匹配请求会返回错误。",
"When enabled, the store field will be blocked": "开启后将阻止 store 字段透传",
"When enabled, users can pick this group when creating tokens.": "启用后,用户创建令牌时可以选择该分组。",
"When enabled, violation requests will incur additional charges.": "开启后,违规请求将额外扣费。",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment