Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 413968a0fd | |||
| 281ebacb8c | |||
| 3c5edc54b7 | |||
| 8b0e710053 | |||
| ae30b4d15f | |||
| ffb1931906 | |||
| c87deaa7d9 | |||
| 85ecad90a7 | |||
| 8279be2380 |
+1
-1
@@ -28,7 +28,7 @@ RUN go build -ldflags "-s -w -X 'github.com/QuantumNous/new-api/common.Version=$
|
||||
FROM debian:bookworm-slim
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates tzdata libasan8 \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates tzdata libasan8 wget \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& update-ca-certificates
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ func SendEmail(subject string, receiver string, content string) error {
|
||||
}
|
||||
encodedSubject := fmt.Sprintf("=?UTF-8?B?%s?=", base64.StdEncoding.EncodeToString([]byte(subject)))
|
||||
mail := []byte(fmt.Sprintf("To: %s\r\n"+
|
||||
"From: %s<%s>\r\n"+
|
||||
"From: %s <%s>\r\n"+
|
||||
"Subject: %s\r\n"+
|
||||
"Date: %s\r\n"+
|
||||
"Message-ID: %s\r\n"+ // 添加 Message-ID 头
|
||||
|
||||
@@ -18,8 +18,10 @@ const (
|
||||
ContextKeyTokenSpecificChannelId ContextKey = "specific_channel_id"
|
||||
ContextKeyTokenModelLimitEnabled ContextKey = "token_model_limit_enabled"
|
||||
ContextKeyTokenModelLimit ContextKey = "token_model_limit"
|
||||
ContextKeyTokenCrossGroupRetry ContextKey = "token_cross_group_retry"
|
||||
|
||||
/* channel related keys */
|
||||
ContextKeyAutoGroupIndex ContextKey = "auto_group_index"
|
||||
ContextKeyChannelId ContextKey = "channel_id"
|
||||
ContextKeyChannelName ContextKey = "channel_name"
|
||||
ContextKeyChannelCreateTime ContextKey = "channel_create_time"
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/QuantumNous/new-api/constant"
|
||||
"github.com/QuantumNous/new-api/middleware"
|
||||
"github.com/QuantumNous/new-api/model"
|
||||
relaycommon "github.com/QuantumNous/new-api/relay/common"
|
||||
"github.com/QuantumNous/new-api/types"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -31,8 +32,11 @@ func Playground(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
group := common.GetContextKeyString(c, constant.ContextKeyUsingGroup)
|
||||
modelName := c.GetString("original_model")
|
||||
relayInfo, err := relaycommon.GenRelayInfo(c, types.RelayFormatOpenAI, nil, nil)
|
||||
if err != nil {
|
||||
newAPIError = types.NewError(err, types.ErrorCodeInvalidRequest, types.ErrOptionWithSkipRetry())
|
||||
return
|
||||
}
|
||||
|
||||
userId := c.GetInt("id")
|
||||
|
||||
@@ -46,11 +50,11 @@ func Playground(c *gin.Context) {
|
||||
|
||||
tempToken := &model.Token{
|
||||
UserId: userId,
|
||||
Name: fmt.Sprintf("playground-%s", group),
|
||||
Group: group,
|
||||
Name: fmt.Sprintf("playground-%s", relayInfo.UsingGroup),
|
||||
Group: relayInfo.UsingGroup,
|
||||
}
|
||||
_ = middleware.SetupContextForToken(c, tempToken)
|
||||
_, newAPIError = getChannel(c, group, modelName, 0)
|
||||
_, newAPIError = getChannel(c, relayInfo, 0)
|
||||
if newAPIError != nil {
|
||||
return
|
||||
}
|
||||
|
||||
+13
-11
@@ -64,8 +64,8 @@ func geminiRelayHandler(c *gin.Context, info *relaycommon.RelayInfo) *types.NewA
|
||||
func Relay(c *gin.Context, relayFormat types.RelayFormat) {
|
||||
|
||||
requestId := c.GetString(common.RequestIdKey)
|
||||
group := common.GetContextKeyString(c, constant.ContextKeyUsingGroup)
|
||||
originalModel := common.GetContextKeyString(c, constant.ContextKeyOriginalModel)
|
||||
//group := common.GetContextKeyString(c, constant.ContextKeyUsingGroup)
|
||||
//originalModel := common.GetContextKeyString(c, constant.ContextKeyOriginalModel)
|
||||
|
||||
var (
|
||||
newAPIError *types.NewAPIError
|
||||
@@ -158,7 +158,7 @@ func Relay(c *gin.Context, relayFormat types.RelayFormat) {
|
||||
}()
|
||||
|
||||
for i := 0; i <= common.RetryTimes; i++ {
|
||||
channel, err := getChannel(c, group, originalModel, i)
|
||||
channel, err := getChannel(c, relayInfo, i)
|
||||
if err != nil {
|
||||
logger.LogError(c, err.Error())
|
||||
newAPIError = err
|
||||
@@ -211,7 +211,7 @@ func addUsedChannel(c *gin.Context, channelId int) {
|
||||
c.Set("use_channel", useChannel)
|
||||
}
|
||||
|
||||
func getChannel(c *gin.Context, group, originalModel string, retryCount int) (*model.Channel, *types.NewAPIError) {
|
||||
func getChannel(c *gin.Context, info *relaycommon.RelayInfo, retryCount int) (*model.Channel, *types.NewAPIError) {
|
||||
if retryCount == 0 {
|
||||
autoBan := c.GetBool("auto_ban")
|
||||
autoBanInt := 1
|
||||
@@ -225,14 +225,18 @@ func getChannel(c *gin.Context, group, originalModel string, retryCount int) (*m
|
||||
AutoBan: &autoBanInt,
|
||||
}, nil
|
||||
}
|
||||
channel, selectGroup, err := service.CacheGetRandomSatisfiedChannel(c, group, originalModel, retryCount)
|
||||
channel, selectGroup, err := service.CacheGetRandomSatisfiedChannel(c, info.TokenGroup, info.OriginModelName, retryCount)
|
||||
|
||||
info.PriceData.GroupRatioInfo = helper.HandleGroupRatio(c, info)
|
||||
|
||||
if err != nil {
|
||||
return nil, types.NewError(fmt.Errorf("获取分组 %s 下模型 %s 的可用渠道失败(retry): %s", selectGroup, originalModel, err.Error()), types.ErrorCodeGetChannelFailed, types.ErrOptionWithSkipRetry())
|
||||
return nil, types.NewError(fmt.Errorf("获取分组 %s 下模型 %s 的可用渠道失败(retry): %s", selectGroup, info.OriginModelName, err.Error()), types.ErrorCodeGetChannelFailed, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
if channel == nil {
|
||||
return nil, types.NewError(fmt.Errorf("分组 %s 下模型 %s 的可用渠道不存在(retry)", selectGroup, originalModel), types.ErrorCodeGetChannelFailed, types.ErrOptionWithSkipRetry())
|
||||
return nil, types.NewError(fmt.Errorf("分组 %s 下模型 %s 的可用渠道不存在(retry)", selectGroup, info.OriginModelName), types.ErrorCodeGetChannelFailed, types.ErrOptionWithSkipRetry())
|
||||
}
|
||||
newAPIError := middleware.SetupContextForSelectedChannel(c, channel, originalModel)
|
||||
|
||||
newAPIError := middleware.SetupContextForSelectedChannel(c, channel, info.OriginModelName)
|
||||
if newAPIError != nil {
|
||||
return nil, newAPIError
|
||||
}
|
||||
@@ -392,8 +396,6 @@ func RelayNotFound(c *gin.Context) {
|
||||
func RelayTask(c *gin.Context) {
|
||||
retryTimes := common.RetryTimes
|
||||
channelId := c.GetInt("channel_id")
|
||||
group := c.GetString("group")
|
||||
originalModel := c.GetString("original_model")
|
||||
c.Set("use_channel", []string{fmt.Sprintf("%d", channelId)})
|
||||
relayInfo, err := relaycommon.GenRelayInfo(c, types.RelayFormatTask, nil, nil)
|
||||
if err != nil {
|
||||
@@ -404,7 +406,7 @@ func RelayTask(c *gin.Context) {
|
||||
retryTimes = 0
|
||||
}
|
||||
for i := 0; shouldRetryTaskRelay(c, channelId, taskErr, retryTimes) && i < retryTimes; i++ {
|
||||
channel, newAPIError := getChannel(c, group, originalModel, i)
|
||||
channel, newAPIError := getChannel(c, relayInfo, i)
|
||||
if newAPIError != nil {
|
||||
logger.LogError(c, fmt.Sprintf("CacheGetRandomSatisfiedChannel failed: %s", newAPIError.Error()))
|
||||
taskErr = service.TaskErrorWrapperLocal(newAPIError.Err, "get_channel_failed", http.StatusInternalServerError)
|
||||
|
||||
@@ -248,6 +248,7 @@ func UpdateToken(c *gin.Context) {
|
||||
cleanToken.ModelLimits = token.ModelLimits
|
||||
cleanToken.AllowIps = token.AllowIps
|
||||
cleanToken.Group = token.Group
|
||||
cleanToken.CrossGroupRetry = token.CrossGroupRetry
|
||||
}
|
||||
err = cleanToken.Update()
|
||||
if err != nil {
|
||||
|
||||
@@ -308,6 +308,7 @@ func SetupContextForToken(c *gin.Context, token *model.Token, parts ...string) e
|
||||
c.Set("token_model_limit_enabled", false)
|
||||
}
|
||||
c.Set("token_group", token.Group)
|
||||
c.Set("token_cross_group_retry", token.CrossGroupRetry)
|
||||
if len(parts) > 1 {
|
||||
if model.IsAdmin(token.UserId) {
|
||||
c.Set("specific_channel_id", parts[1])
|
||||
|
||||
+2
-1
@@ -27,6 +27,7 @@ type Token struct {
|
||||
AllowIps *string `json:"allow_ips" gorm:"default:''"`
|
||||
UsedQuota int `json:"used_quota" gorm:"default:0"` // used quota
|
||||
Group string `json:"group" gorm:"default:''"`
|
||||
CrossGroupRetry bool `json:"cross_group_retry" gorm:"default:false"` // 跨分组重试,仅auto分组有效
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
}
|
||||
|
||||
@@ -185,7 +186,7 @@ func (token *Token) Update() (err error) {
|
||||
}
|
||||
}()
|
||||
err = DB.Model(token).Select("name", "status", "expired_time", "remain_quota", "unlimited_quota",
|
||||
"model_limits_enabled", "model_limits", "allow_ips", "group").Updates(token).Error
|
||||
"model_limits_enabled", "model_limits", "allow_ips", "group", "cross_group_retry").Updates(token).Error
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/QuantumNous/new-api/setting/model_setting"
|
||||
"github.com/aws/aws-sdk-go-v2/aws"
|
||||
"github.com/aws/aws-sdk-go-v2/credentials"
|
||||
"github.com/aws/aws-sdk-go-v2/service/bedrockruntime"
|
||||
@@ -129,7 +130,7 @@ func doAwsClientRequest(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor,
|
||||
Accept: aws.String("application/json"),
|
||||
ContentType: aws.String("application/json"),
|
||||
}
|
||||
awsReq.Body, err = common.Marshal(awsClaudeReq)
|
||||
awsReq.Body, err = buildAwsRequestBody(c, info, awsClaudeReq)
|
||||
if err != nil {
|
||||
return nil, types.NewError(errors.Wrap(err, "marshal aws request fail"), types.ErrorCodeBadRequestBody)
|
||||
}
|
||||
@@ -141,7 +142,7 @@ func doAwsClientRequest(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor,
|
||||
Accept: aws.String("application/json"),
|
||||
ContentType: aws.String("application/json"),
|
||||
}
|
||||
awsReq.Body, err = common.Marshal(awsClaudeReq)
|
||||
awsReq.Body, err = buildAwsRequestBody(c, info, awsClaudeReq)
|
||||
if err != nil {
|
||||
return nil, types.NewError(errors.Wrap(err, "marshal aws request fail"), types.ErrorCodeBadRequestBody)
|
||||
}
|
||||
@@ -151,6 +152,24 @@ func doAwsClientRequest(c *gin.Context, info *relaycommon.RelayInfo, a *Adaptor,
|
||||
}
|
||||
}
|
||||
|
||||
// buildAwsRequestBody prepares the payload for AWS requests, applying passthrough rules when enabled.
|
||||
func buildAwsRequestBody(c *gin.Context, info *relaycommon.RelayInfo, awsClaudeReq any) ([]byte, error) {
|
||||
if model_setting.GetGlobalSettings().PassThroughRequestEnabled || info.ChannelSetting.PassThroughBodyEnabled {
|
||||
body, err := common.GetRequestBody(c)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "get request body for pass-through fail")
|
||||
}
|
||||
var data map[string]interface{}
|
||||
if err := common.Unmarshal(body, &data); err != nil {
|
||||
return nil, errors.Wrap(err, "pass-through unmarshal request body fail")
|
||||
}
|
||||
delete(data, "model")
|
||||
delete(data, "stream")
|
||||
return common.Marshal(data)
|
||||
}
|
||||
return common.Marshal(awsClaudeReq)
|
||||
}
|
||||
|
||||
func getAwsRegionPrefix(awsRegionId string) string {
|
||||
parts := strings.Split(awsRegionId, "-")
|
||||
regionPrefix := ""
|
||||
|
||||
@@ -172,7 +172,7 @@ func handleLastResponse(lastStreamData string, responseId *string, createAt *int
|
||||
shouldSendLastResp *bool) error {
|
||||
|
||||
var lastStreamResponse dto.ChatCompletionsStreamResponse
|
||||
if err := json.Unmarshal(common.StringToByteSlice(lastStreamData), &lastStreamResponse); err != nil {
|
||||
if err := common.Unmarshal(common.StringToByteSlice(lastStreamData), &lastStreamResponse); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -81,6 +81,7 @@ type TokenCountMeta struct {
|
||||
type RelayInfo struct {
|
||||
TokenId int
|
||||
TokenKey string
|
||||
TokenGroup string
|
||||
UserId int
|
||||
UsingGroup string // 使用的分组
|
||||
UserGroup string // 用户所在分组
|
||||
@@ -400,6 +401,7 @@ func genBaseRelayInfo(c *gin.Context, request dto.Request) *RelayInfo {
|
||||
TokenId: common.GetContextKeyInt(c, constant.ContextKeyTokenId),
|
||||
TokenKey: common.GetContextKeyString(c, constant.ContextKeyTokenKey),
|
||||
TokenUnlimited: common.GetContextKeyBool(c, constant.ContextKeyTokenUnlimited),
|
||||
TokenGroup: common.GetContextKeyString(c, constant.ContextKeyTokenGroup),
|
||||
|
||||
isFirstResponse: true,
|
||||
RelayMode: relayconstant.Path2RelayMode(c.Request.URL.Path),
|
||||
|
||||
@@ -11,31 +11,47 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func CacheGetRandomSatisfiedChannel(c *gin.Context, group string, modelName string, retry int) (*model.Channel, string, error) {
|
||||
// CacheGetRandomSatisfiedChannel tries to get a random channel that satisfies the requirements.
|
||||
func CacheGetRandomSatisfiedChannel(c *gin.Context, tokenGroup string, modelName string, retry int) (*model.Channel, string, error) {
|
||||
var channel *model.Channel
|
||||
var err error
|
||||
selectGroup := group
|
||||
selectGroup := tokenGroup
|
||||
userGroup := common.GetContextKeyString(c, constant.ContextKeyUserGroup)
|
||||
if group == "auto" {
|
||||
if tokenGroup == "auto" {
|
||||
if len(setting.GetAutoGroups()) == 0 {
|
||||
return nil, selectGroup, errors.New("auto groups is not enabled")
|
||||
}
|
||||
for _, autoGroup := range GetUserAutoGroup(userGroup) {
|
||||
logger.LogDebug(c, "Auto selecting group:", autoGroup)
|
||||
channel, _ = model.GetRandomSatisfiedChannel(autoGroup, modelName, retry)
|
||||
autoGroups := GetUserAutoGroup(userGroup)
|
||||
// 如果 token 启用了跨分组重试,获取上次失败的 auto group 索引,从下一个开始尝试
|
||||
startIndex := 0
|
||||
crossGroupRetry := common.GetContextKeyBool(c, constant.ContextKeyTokenCrossGroupRetry)
|
||||
if crossGroupRetry && retry > 0 {
|
||||
logger.LogDebug(c, "Auto group retry cross group, retry: %d", retry)
|
||||
if lastIndex, exists := common.GetContextKey(c, constant.ContextKeyAutoGroupIndex); exists {
|
||||
if idx, ok := lastIndex.(int); ok {
|
||||
startIndex = idx + 1
|
||||
}
|
||||
}
|
||||
logger.LogDebug(c, "Auto group retry cross group, start index: %d", startIndex)
|
||||
}
|
||||
for i := startIndex; i < len(autoGroups); i++ {
|
||||
autoGroup := autoGroups[i]
|
||||
logger.LogDebug(c, "Auto selecting group: %s", autoGroup)
|
||||
channel, _ = model.GetRandomSatisfiedChannel(autoGroup, modelName, 0)
|
||||
if channel == nil {
|
||||
continue
|
||||
} else {
|
||||
c.Set("auto_group", autoGroup)
|
||||
common.SetContextKey(c, constant.ContextKeyAutoGroupIndex, i)
|
||||
selectGroup = autoGroup
|
||||
logger.LogDebug(c, "Auto selected group:", autoGroup)
|
||||
logger.LogDebug(c, "Auto selected group: %s", autoGroup)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
channel, err = model.GetRandomSatisfiedChannel(group, modelName, retry)
|
||||
channel, err = model.GetRandomSatisfiedChannel(tokenGroup, modelName, retry)
|
||||
if err != nil {
|
||||
return nil, group, err
|
||||
return nil, tokenGroup, err
|
||||
}
|
||||
}
|
||||
return channel, selectGroup, nil
|
||||
|
||||
@@ -317,7 +317,7 @@ func EstimateRequestToken(c *gin.Context, meta *types.TokenCountMeta, info *rela
|
||||
for i, file := range meta.Files {
|
||||
switch file.FileType {
|
||||
case types.FileTypeImage:
|
||||
if common.IsOpenAITextModel(info.OriginModelName) {
|
||||
if common.IsOpenAITextModel(model) {
|
||||
token, err := getImageToken(file, model, info.IsStream)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("error counting image token, media index[%d], original data[%s], err: %v", i, file.OriginData, err)
|
||||
|
||||
@@ -88,7 +88,7 @@ const renderStatus = (text, record, t) => {
|
||||
};
|
||||
|
||||
// Render group column
|
||||
const renderGroupColumn = (text, t) => {
|
||||
const renderGroupColumn = (text, record, t) => {
|
||||
if (text === 'auto') {
|
||||
return (
|
||||
<Tooltip
|
||||
@@ -98,8 +98,8 @@ const renderGroupColumn = (text, t) => {
|
||||
position='top'
|
||||
>
|
||||
<Tag color='white' shape='circle'>
|
||||
{' '}
|
||||
{t('智能熔断')}{' '}
|
||||
{t('智能熔断')}
|
||||
{record && record.cross_group_retry ? `(${t('跨分组')})` : ''}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
);
|
||||
@@ -455,7 +455,7 @@ export const getTokensColumns = ({
|
||||
title: t('分组'),
|
||||
dataIndex: 'group',
|
||||
key: 'group',
|
||||
render: (text) => renderGroupColumn(text, t),
|
||||
render: (text, record) => renderGroupColumn(text, record, t),
|
||||
},
|
||||
{
|
||||
title: t('密钥'),
|
||||
|
||||
@@ -73,6 +73,7 @@ const EditTokenModal = (props) => {
|
||||
model_limits: [],
|
||||
allow_ips: '',
|
||||
group: '',
|
||||
cross_group_retry: false,
|
||||
tokenCount: 1,
|
||||
});
|
||||
|
||||
@@ -377,6 +378,16 @@ const EditTokenModal = (props) => {
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
<Col span={24} style={{ display: values.group === 'auto' ? 'block' : 'none' }}>
|
||||
<Form.Switch
|
||||
field='cross_group_retry'
|
||||
label={t('跨分组重试')}
|
||||
size='default'
|
||||
extraText={t(
|
||||
'开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道',
|
||||
)}
|
||||
/>
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={24} lg={10} xl={10}>
|
||||
<Form.DatePicker
|
||||
field='expired_time'
|
||||
@@ -499,7 +510,7 @@ const EditTokenModal = (props) => {
|
||||
<Form.Switch
|
||||
field='unlimited_quota'
|
||||
label={t('无限额度')}
|
||||
size='large'
|
||||
size='default'
|
||||
extraText={t(
|
||||
'令牌的额度仅用于限制令牌本身的最大额度使用量,实际的使用受到账户的剩余额度限制',
|
||||
)}
|
||||
|
||||
@@ -2177,6 +2177,9 @@
|
||||
"默认区域,如: us-central1": "Default region, e.g.: us-central1",
|
||||
"默认折叠侧边栏": "Default collapse sidebar",
|
||||
"默认测试模型": "Default Test Model",
|
||||
"默认补全倍率": "Default completion ratio"
|
||||
"默认补全倍率": "Default completion ratio",
|
||||
"跨分组重试": "Cross-group retry",
|
||||
"跨分组": "Cross-group",
|
||||
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "After enabling, when the current group channel fails, it will try the next group's channel in order"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2226,6 +2226,9 @@
|
||||
"默认助手消息": "Bonjour ! Comment puis-je vous aider aujourd'hui ?",
|
||||
"可选,用于复现结果": "Optionnel, pour des résultats reproductibles",
|
||||
"随机种子 (留空为随机)": "Graine aléatoire (laisser vide pour aléatoire)",
|
||||
"默认补全倍率": "Taux de complétion par défaut"
|
||||
"默认补全倍率": "Taux de complétion par défaut",
|
||||
"跨分组重试": "Nouvelle tentative inter-groupes",
|
||||
"跨分组": "Inter-groupes",
|
||||
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "Après activation, lorsque le canal du groupe actuel échoue, il essaiera le canal du groupe suivant dans l'ordre"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2125,6 +2125,9 @@
|
||||
"默认用户消息": "こんにちは",
|
||||
"默认助手消息": "こんにちは!何かお手伝いできることはありますか?",
|
||||
"可选,用于复现结果": "オプション、結果の再現用",
|
||||
"随机种子 (留空为随机)": "ランダムシード(空欄でランダム)"
|
||||
"随机种子 (留空为随机)": "ランダムシード(空欄でランダム)",
|
||||
"跨分组重试": "グループ間リトライ",
|
||||
"跨分组": "グループ間",
|
||||
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "有効にすると、現在のグループチャネルが失敗した場合、次のグループのチャネルを順番に試行します"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2236,6 +2236,9 @@
|
||||
"默认用户消息": "Здравствуйте",
|
||||
"默认助手消息": "Здравствуйте! Чем я могу вам помочь?",
|
||||
"可选,用于复现结果": "Необязательно, для воспроизводимых результатов",
|
||||
"随机种子 (留空为随机)": "Случайное зерно (оставьте пустым для случайного)"
|
||||
"随机种子 (留空为随机)": "Случайное зерно (оставьте пустым для случайного)",
|
||||
"跨分组重试": "Повторная попытка между группами",
|
||||
"跨分组": "Межгрупповой",
|
||||
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "После включения, когда канал текущей группы не работает, он будет пытаться использовать канал следующей группы по порядку"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2736,6 +2736,9 @@
|
||||
"默认用户消息": "Xin chào",
|
||||
"默认助手消息": "Xin chào! Tôi có thể giúp gì cho bạn?",
|
||||
"可选,用于复现结果": "Tùy chọn, để tái tạo kết quả",
|
||||
"随机种子 (留空为随机)": "Hạt giống ngẫu nhiên (để trống cho ngẫu nhiên)"
|
||||
"随机种子 (留空为随机)": "Hạt giống ngẫu nhiên (để trống cho ngẫu nhiên)",
|
||||
"跨分组重试": "Thử lại giữa các nhóm",
|
||||
"跨分组": "Giữa các nhóm",
|
||||
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "Sau khi bật, khi kênh nhóm hiện tại thất bại, nó sẽ thử kênh của nhóm tiếp theo theo thứ tự"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2203,6 +2203,9 @@
|
||||
"默认用户消息": "你好",
|
||||
"默认助手消息": "你好!有什么我可以帮助你的吗?",
|
||||
"可选,用于复现结果": "可选,用于复现结果",
|
||||
"随机种子 (留空为随机)": "随机种子 (留空为随机)"
|
||||
"随机种子 (留空为随机)": "随机种子 (留空为随机)",
|
||||
"跨分组重试": "跨分组重试",
|
||||
"跨分组": "跨分组",
|
||||
"开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道": "开启后,当前分组渠道失败时会按顺序尝试下一个分组的渠道"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user