chore: update Codex channel (#5461)

This commit is contained in:
Seefs
2026-06-12 23:45:15 +08:00
committed by GitHub
parent d0c4305a16
commit 1292b8b2d5
27 changed files with 100 additions and 1039 deletions
+1 -1
View File
@@ -174,7 +174,7 @@ var ChannelTypeNames = map[int]string{
ChannelTypeDoubaoVideo: "DoubaoVideo",
ChannelTypeSora: "Sora",
ChannelTypeReplicate: "Replicate",
ChannelTypeCodex: "Codex",
ChannelTypeCodex: "ChatGPT Subscription (Codex)",
}
func GetChannelTypeName(channelType int) string {
-247
View File
@@ -1,247 +0,0 @@
package controller
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/constant"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/relay/channel/codex"
"github.com/QuantumNous/new-api/service"
"github.com/gin-contrib/sessions"
"github.com/gin-gonic/gin"
)
type codexOAuthCompleteRequest struct {
Input string `json:"input"`
}
func codexOAuthSessionKey(channelID int, field string) string {
return fmt.Sprintf("codex_oauth_%s_%d", field, channelID)
}
func parseCodexAuthorizationInput(input string) (code string, state string, err error) {
v := strings.TrimSpace(input)
if v == "" {
return "", "", errors.New("empty input")
}
if strings.Contains(v, "#") {
parts := strings.SplitN(v, "#", 2)
code = strings.TrimSpace(parts[0])
state = strings.TrimSpace(parts[1])
return code, state, nil
}
if strings.Contains(v, "code=") {
u, parseErr := url.Parse(v)
if parseErr == nil {
q := u.Query()
code = strings.TrimSpace(q.Get("code"))
state = strings.TrimSpace(q.Get("state"))
return code, state, nil
}
q, parseErr := url.ParseQuery(v)
if parseErr == nil {
code = strings.TrimSpace(q.Get("code"))
state = strings.TrimSpace(q.Get("state"))
return code, state, nil
}
}
code = v
return code, "", nil
}
func StartCodexOAuth(c *gin.Context) {
startCodexOAuthWithChannelID(c, 0)
}
func StartCodexOAuthForChannel(c *gin.Context) {
channelID, err := strconv.Atoi(c.Param("id"))
if err != nil {
common.ApiError(c, fmt.Errorf("invalid channel id: %w", err))
return
}
startCodexOAuthWithChannelID(c, channelID)
}
func startCodexOAuthWithChannelID(c *gin.Context, channelID int) {
if channelID > 0 {
ch, err := model.GetChannelById(channelID, false)
if err != nil {
common.ApiError(c, err)
return
}
if ch == nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "channel not found"})
return
}
if ch.Type != constant.ChannelTypeCodex {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "channel type is not Codex"})
return
}
}
flow, err := service.CreateCodexOAuthAuthorizationFlow()
if err != nil {
common.ApiError(c, err)
return
}
session := sessions.Default(c)
session.Set(codexOAuthSessionKey(channelID, "state"), flow.State)
session.Set(codexOAuthSessionKey(channelID, "verifier"), flow.Verifier)
session.Set(codexOAuthSessionKey(channelID, "created_at"), time.Now().Unix())
_ = session.Save()
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": gin.H{
"authorize_url": flow.AuthorizeURL,
},
})
}
func CompleteCodexOAuth(c *gin.Context) {
completeCodexOAuthWithChannelID(c, 0)
}
func CompleteCodexOAuthForChannel(c *gin.Context) {
channelID, err := strconv.Atoi(c.Param("id"))
if err != nil {
common.ApiError(c, fmt.Errorf("invalid channel id: %w", err))
return
}
completeCodexOAuthWithChannelID(c, channelID)
}
func completeCodexOAuthWithChannelID(c *gin.Context, channelID int) {
req := codexOAuthCompleteRequest{}
if err := c.ShouldBindJSON(&req); err != nil {
common.ApiError(c, err)
return
}
code, state, err := parseCodexAuthorizationInput(req.Input)
if err != nil {
common.SysError("failed to parse codex authorization input: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "解析授权信息失败,请检查输入格式"})
return
}
if strings.TrimSpace(code) == "" {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "missing authorization code"})
return
}
if strings.TrimSpace(state) == "" {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "missing state in input"})
return
}
channelProxy := ""
if channelID > 0 {
ch, err := model.GetChannelById(channelID, false)
if err != nil {
common.ApiError(c, err)
return
}
if ch == nil {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "channel not found"})
return
}
if ch.Type != constant.ChannelTypeCodex {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "channel type is not Codex"})
return
}
channelProxy = ch.GetSetting().Proxy
}
session := sessions.Default(c)
expectedState, _ := session.Get(codexOAuthSessionKey(channelID, "state")).(string)
verifier, _ := session.Get(codexOAuthSessionKey(channelID, "verifier")).(string)
if strings.TrimSpace(expectedState) == "" || strings.TrimSpace(verifier) == "" {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "oauth flow not started or session expired"})
return
}
if state != expectedState {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "state mismatch"})
return
}
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
defer cancel()
tokenRes, err := service.ExchangeCodexAuthorizationCodeWithProxy(ctx, code, verifier, channelProxy)
if err != nil {
common.SysError("failed to exchange codex authorization code: " + err.Error())
c.JSON(http.StatusOK, gin.H{"success": false, "message": "授权码交换失败,请重试"})
return
}
accountID, ok := service.ExtractCodexAccountIDFromJWT(tokenRes.AccessToken)
if !ok {
c.JSON(http.StatusOK, gin.H{"success": false, "message": "failed to extract account_id from access_token"})
return
}
email, _ := service.ExtractEmailFromJWT(tokenRes.AccessToken)
key := codex.OAuthKey{
AccessToken: tokenRes.AccessToken,
RefreshToken: tokenRes.RefreshToken,
AccountID: accountID,
LastRefresh: time.Now().Format(time.RFC3339),
Expired: tokenRes.ExpiresAt.Format(time.RFC3339),
Email: email,
Type: "codex",
}
encoded, err := common.Marshal(key)
if err != nil {
common.ApiError(c, err)
return
}
session.Delete(codexOAuthSessionKey(channelID, "state"))
session.Delete(codexOAuthSessionKey(channelID, "verifier"))
session.Delete(codexOAuthSessionKey(channelID, "created_at"))
_ = session.Save()
if channelID > 0 {
if err := model.DB.Model(&model.Channel{}).Where("id = ?", channelID).Update("key", string(encoded)).Error; err != nil {
common.ApiError(c, err)
return
}
model.InitChannelCache()
service.ResetProxyClientCache()
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "saved",
"data": gin.H{
"channel_id": channelID,
"account_id": accountID,
"email": email,
"expires_at": key.Expired,
"last_refresh": key.LastRefresh,
},
})
return
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "generated",
"data": gin.H{
"key": string(encoded),
"account_id": accountID,
"email": email,
"expires_at": key.Expired,
"last_refresh": key.LastRefresh,
},
})
}
-4
View File
@@ -249,10 +249,6 @@ func SetApiRouter(router *gin.Engine) {
channelRoute.POST("/fix", controller.FixChannelsAbilities)
channelRoute.GET("/fetch_models/:id", controller.FetchUpstreamModels)
channelRoute.POST("/fetch_models", middleware.RootAuth(), controller.FetchModels)
channelRoute.POST("/codex/oauth/start", controller.StartCodexOAuth)
channelRoute.POST("/codex/oauth/complete", controller.CompleteCodexOAuth)
channelRoute.POST("/:id/codex/oauth/start", controller.StartCodexOAuthForChannel)
channelRoute.POST("/:id/codex/oauth/complete", controller.CompleteCodexOAuthForChannel)
channelRoute.POST("/:id/codex/refresh", controller.RefreshCodexChannelCredential)
channelRoute.GET("/:id/codex/usage", controller.GetCodexChannelUsage)
channelRoute.POST("/ollama/pull", controller.OllamaPullModel)
+1 -148
View File
@@ -2,10 +2,7 @@ package service
import (
"context"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
@@ -18,10 +15,7 @@ import (
const (
codexOAuthClientID = "app_EMoamEEZ73f0CkXaXp7hrann"
codexOAuthAuthorizeURL = "https://auth.openai.com/oauth/authorize"
codexOAuthTokenURL = "https://auth.openai.com/oauth/token"
codexOAuthRedirectURI = "http://localhost:1455/auth/callback"
codexOAuthScope = "openid profile email offline_access"
codexJWTClaimPath = "https://api.openai.com/auth"
defaultHTTPTimeout = 20 * time.Second
)
@@ -32,13 +26,6 @@ type CodexOAuthTokenResult struct {
ExpiresAt time.Time
}
type CodexOAuthAuthorizationFlow struct {
State string
Verifier string
Challenge string
AuthorizeURL string
}
func RefreshCodexOAuthToken(ctx context.Context, refreshToken string) (*CodexOAuthTokenResult, error) {
return RefreshCodexOAuthTokenWithProxy(ctx, refreshToken, "")
}
@@ -51,39 +38,6 @@ func RefreshCodexOAuthTokenWithProxy(ctx context.Context, refreshToken string, p
return refreshCodexOAuthToken(ctx, client, codexOAuthTokenURL, codexOAuthClientID, refreshToken)
}
func ExchangeCodexAuthorizationCode(ctx context.Context, code string, verifier string) (*CodexOAuthTokenResult, error) {
return ExchangeCodexAuthorizationCodeWithProxy(ctx, code, verifier, "")
}
func ExchangeCodexAuthorizationCodeWithProxy(ctx context.Context, code string, verifier string, proxyURL string) (*CodexOAuthTokenResult, error) {
client, err := getCodexOAuthHTTPClient(proxyURL)
if err != nil {
return nil, err
}
return exchangeCodexAuthorizationCode(ctx, client, codexOAuthTokenURL, codexOAuthClientID, code, verifier, codexOAuthRedirectURI)
}
func CreateCodexOAuthAuthorizationFlow() (*CodexOAuthAuthorizationFlow, error) {
state, err := createStateHex(16)
if err != nil {
return nil, err
}
verifier, challenge, err := generatePKCEPair()
if err != nil {
return nil, err
}
u, err := buildCodexAuthorizeURL(state, challenge)
if err != nil {
return nil, err
}
return &CodexOAuthAuthorizationFlow{
State: state,
Verifier: verifier,
Challenge: challenge,
AuthorizeURL: u,
}, nil
}
func refreshCodexOAuthToken(
ctx context.Context,
client *http.Client,
@@ -138,65 +92,6 @@ func refreshCodexOAuthToken(
}, nil
}
func exchangeCodexAuthorizationCode(
ctx context.Context,
client *http.Client,
tokenURL string,
clientID string,
code string,
verifier string,
redirectURI string,
) (*CodexOAuthTokenResult, error) {
c := strings.TrimSpace(code)
v := strings.TrimSpace(verifier)
if c == "" {
return nil, errors.New("empty authorization code")
}
if v == "" {
return nil, errors.New("empty code_verifier")
}
form := url.Values{}
form.Set("grant_type", "authorization_code")
form.Set("client_id", clientID)
form.Set("code", c)
form.Set("code_verifier", v)
form.Set("redirect_uri", redirectURI)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, tokenURL, strings.NewReader(form.Encode()))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var payload struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn int `json:"expires_in"`
}
if err := common.DecodeJson(resp.Body, &payload); err != nil {
return nil, err
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("codex oauth code exchange failed: status=%d", resp.StatusCode)
}
if strings.TrimSpace(payload.AccessToken) == "" || strings.TrimSpace(payload.RefreshToken) == "" || payload.ExpiresIn <= 0 {
return nil, errors.New("codex oauth token response missing fields")
}
return &CodexOAuthTokenResult{
AccessToken: strings.TrimSpace(payload.AccessToken),
RefreshToken: strings.TrimSpace(payload.RefreshToken),
ExpiresAt: time.Now().Add(time.Duration(payload.ExpiresIn) * time.Second),
}, nil
}
func getCodexOAuthHTTPClient(proxyURL string) (*http.Client, error) {
baseClient, err := GetHttpClientWithProxy(strings.TrimSpace(proxyURL))
if err != nil {
@@ -210,48 +105,6 @@ func getCodexOAuthHTTPClient(proxyURL string) (*http.Client, error) {
return &clientCopy, nil
}
func buildCodexAuthorizeURL(state string, challenge string) (string, error) {
u, err := url.Parse(codexOAuthAuthorizeURL)
if err != nil {
return "", err
}
q := u.Query()
q.Set("response_type", "code")
q.Set("client_id", codexOAuthClientID)
q.Set("redirect_uri", codexOAuthRedirectURI)
q.Set("scope", codexOAuthScope)
q.Set("code_challenge", challenge)
q.Set("code_challenge_method", "S256")
q.Set("state", state)
q.Set("id_token_add_organizations", "true")
q.Set("codex_cli_simplified_flow", "true")
q.Set("originator", "codex_cli_rs")
u.RawQuery = q.Encode()
return u.String(), nil
}
func createStateHex(nBytes int) (string, error) {
if nBytes <= 0 {
return "", errors.New("invalid state bytes length")
}
b := make([]byte, nBytes)
if _, err := rand.Read(b); err != nil {
return "", err
}
return fmt.Sprintf("%x", b), nil
}
func generatePKCEPair() (verifier string, challenge string, err error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", "", err
}
verifier = base64.RawURLEncoding.EncodeToString(b)
sum := sha256.Sum256([]byte(verifier))
challenge = base64.RawURLEncoding.EncodeToString(sum[:])
return verifier, challenge, nil
}
func ExtractCodexAccountIDFromJWT(token string) (string, bool) {
claims, ok := decodeJWTClaims(token)
if !ok {
@@ -310,7 +163,7 @@ func decodeJWTClaims(token string) (map[string]any, bool) {
return nil, false
}
var claims map[string]any
if err := json.Unmarshal(payloadRaw, &claims); err != nil {
if err := common.Unmarshal(payloadRaw, &claims); err != nil {
return nil, false
}
return claims, true
@@ -1,172 +0,0 @@
/*
Copyright (C) 2025 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import {
Modal,
Button,
Space,
Typography,
Input,
Banner,
} from '@douyinfe/semi-ui';
import { API, copy, showError, showSuccess } from '../../../../helpers';
const { Text } = Typography;
const CodexOAuthModal = ({ visible, onCancel, onSuccess }) => {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [authorizeUrl, setAuthorizeUrl] = useState('');
const [input, setInput] = useState('');
const startOAuth = async () => {
setLoading(true);
try {
const res = await API.post(
'/api/channel/codex/oauth/start',
{},
{ skipErrorHandler: true },
);
if (!res?.data?.success) {
console.error('Codex OAuth start failed:', res?.data?.message);
throw new Error(t('启动授权失败'));
}
const url = res?.data?.data?.authorize_url || '';
if (!url) {
console.error(
'Codex OAuth start response missing authorize_url:',
res?.data,
);
throw new Error(t('响应缺少授权链接'));
}
setAuthorizeUrl(url);
window.open(url, '_blank', 'noopener,noreferrer');
showSuccess(t('已打开授权页面'));
} catch (error) {
showError(error?.message || t('启动授权失败'));
} finally {
setLoading(false);
}
};
const completeOAuth = async () => {
if (!input || !input.trim()) {
showError(t('请先粘贴回调 URL'));
return;
}
setLoading(true);
try {
const res = await API.post(
'/api/channel/codex/oauth/complete',
{ input },
{ skipErrorHandler: true },
);
if (!res?.data?.success) {
console.error('Codex OAuth complete failed:', res?.data?.message);
throw new Error(t('授权失败'));
}
const key = res?.data?.data?.key || '';
if (!key) {
console.error('Codex OAuth complete response missing key:', res?.data);
throw new Error(t('响应缺少凭据'));
}
onSuccess && onSuccess(key);
showSuccess(t('已生成授权凭据'));
onCancel && onCancel();
} catch (error) {
showError(error?.message || t('授权失败'));
} finally {
setLoading(false);
}
};
useEffect(() => {
if (!visible) return;
setAuthorizeUrl('');
setInput('');
}, [visible]);
return (
<Modal
title={t('Codex 授权')}
visible={visible}
onCancel={onCancel}
maskClosable={false}
closeOnEsc
width={720}
footer={
<Space>
<Button theme='borderless' onClick={onCancel} disabled={loading}>
{t('取消')}
</Button>
<Button
theme='solid'
type='primary'
onClick={completeOAuth}
loading={loading}
>
{t('生成并填入')}
</Button>
</Space>
}
>
<Space vertical spacing='tight' style={{ width: '100%' }}>
<Banner
type='info'
description={t(
'1) 点击「打开授权页面」完成登录;2) 浏览器会跳转到 localhost(页面打不开也没关系);3) 复制地址栏完整 URL 粘贴到下方;4) 点击「生成并填入」。',
)}
/>
<Space wrap>
<Button type='primary' onClick={startOAuth} loading={loading}>
{t('打开授权页面')}
</Button>
<Button
theme='outline'
disabled={!authorizeUrl || loading}
onClick={() => copy(authorizeUrl)}
>
{t('复制授权链接')}
</Button>
</Space>
<Input
value={input}
onChange={(value) => setInput(value)}
placeholder={t('请粘贴完整回调 URL(包含 code 与 state')}
showClear
/>
<Text type='tertiary' size='small'>
{t(
'说明:生成结果是可直接粘贴到渠道密钥里的 JSON(包含 access_token / refresh_token / account_id)。',
)}
</Text>
</Space>
</Modal>
);
};
export default CodexOAuthModal;
@@ -60,7 +60,6 @@ import {
import ModelSelectModal from './ModelSelectModal';
import SingleModelSelectModal from './SingleModelSelectModal';
import OllamaModelModal from './OllamaModelModal';
import CodexOAuthModal from './CodexOAuthModal';
import ParamOverrideEditorModal from './ParamOverrideEditorModal';
import JSONEditor from '../../../common/ui/JSONEditor';
import SecureVerificationModal from '../../../common/modals/SecureVerificationModal';
@@ -381,7 +380,6 @@ const EditChannelModal = (props) => {
}, [inputs.param_override, t]);
const [isIonetChannel, setIsIonetChannel] = useState(false);
const [ionetMetadata, setIonetMetadata] = useState(null);
const [codexOAuthModalVisible, setCodexOAuthModalVisible] = useState(false);
const [codexCredentialRefreshing, setCodexCredentialRefreshing] =
useState(false);
const [paramOverrideEditorVisible, setParamOverrideEditorVisible] =
@@ -1227,11 +1225,6 @@ const EditChannelModal = (props) => {
}
};
const handleCodexOAuthGenerated = (key) => {
handleInputChange('key', key);
formatJsonField('key');
};
const handleRefreshCodexCredential = async () => {
if (!isEdit) return;
@@ -2847,17 +2840,6 @@ const EditChannelModal = (props) => {
</Text>
<Space wrap spacing='tight'>
<Button
size='small'
type='primary'
theme='outline'
onClick={() =>
setCodexOAuthModalVisible(true)
}
disabled={isIonetLocked}
>
{t('Codex 授权')}
</Button>
{isEdit && (
<Button
size='small'
@@ -2897,12 +2879,6 @@ const EditChannelModal = (props) => {
autosize
showClear
/>
<CodexOAuthModal
visible={codexOAuthModalVisible}
onCancel={() => setCodexOAuthModalVisible(false)}
onSuccess={handleCodexOAuthGenerated}
/>
</>
) : inputs.type === 41 &&
(inputs.vertex_key_type || 'json') === 'json' ? (
+1 -1
View File
@@ -187,7 +187,7 @@ export const CHANNEL_OPTIONS = [
{
value: 57,
color: 'blue',
label: 'Codex (OpenAI OAuth)',
label: 'ChatGPT Subscription (Codex)',
},
];
-6
View File
@@ -46,7 +46,6 @@
"0.002-1之间的小数": "Decimal between 0.002-1",
"0.1以上的小数": "Decimal above 0.1",
"1. 管理员在此创建分组并设置倍率": "1. Admin creates groups and sets ratios here",
"1) 点击「打开授权页面」完成登录;2) 浏览器会跳转到 localhost(页面打不开也没关系);3) 复制地址栏完整 URL 粘贴到下方;4) 点击「生成并填入」。": "1) Click \"Open Authorization Page\" to complete login; 2) The browser will redirect to localhost (it's OK if the page doesn't load); 3) Copy the full URL from the address bar and paste it below; 4) Click \"Generate and Fill\".",
"10 - 最高": "10 - Highest",
"1h缓存创建 {{price}} / 1M tokens": "1h cache creation {{price}} / 1M tokens",
"1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}": "1h cache creation {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}",
@@ -115,7 +114,6 @@
"Claude请求头追加": "Claude request header append",
"Client ID": "Client ID",
"Client Secret": "Client Secret",
"Codex 授权": "Codex Authorization",
"Codex 渠道不支持批量创建": "Codex channel does not support batch creation",
"common.changeLanguage": "Change Language",
"Completion tokens": "Completion tokens",
@@ -1166,7 +1164,6 @@
"复制所有模型": "Copy all models",
"复制所选令牌": "Copy selected token",
"复制所选兑换码到剪贴板": "Copy selected redemption codes to clipboard",
"复制授权链接": "Copy Authorization Link",
"复制日志": "Copy Logs",
"复制渠道的所有信息": "Copy all information for a channel",
"复制版本号": "Copy Version",
@@ -1392,7 +1389,6 @@
"已开启全局请求透传:参数覆写、模型重定向、渠道适配等 NewAPI 内置功能将失效,非最佳实践;如因此产生问题,请勿提交 issue 反馈。": "Global request pass-through is enabled. Built-in NewAPI features such as parameter overrides, model redirection, and channel adaptation will be disabled. This is not a best practice. If this causes issues, please do not submit an issue.",
"已忽略模型": "",
"已成功开始测试所有已启用通道,请刷新页面查看结果。": "Successfully started testing all enabled channels. Please refresh page to view results.",
"已打开授权页面": "Authorization page opened",
"已打开支付页面": "Payment page opened",
"已批量处理上游模型更新:渠道 {{channels}} 个,加入 {{added}} 个,删除 {{removed}} 个,失败 {{fails}} 个": "",
"已提交": "Submitted",
@@ -1415,7 +1411,6 @@
"已清理 {{count}} 个日志文件,释放 {{size}}_other": "",
"已清空": "Cleared",
"已清空测试结果": "Cleared test results",
"已生成授权凭据": "Authorization credentials generated",
"已用": "Used",
"已用/剩余": "Used/Remaining",
"已用额度": "Quota used",
@@ -1599,7 +1594,6 @@
"手动输入": "Manual input",
"打开 CC Switch": "Open CC Switch",
"打开侧边栏": "Open sidebar",
"打开授权页面": "Open Authorization Page",
"扣费": "Charge",
"执行 GC": "Run GC",
"执行中": "processing",
-6
View File
@@ -50,7 +50,6 @@
"0.002-1之间的小数": "Décimal entre 0,002-1",
"0.1以上的小数": "Décimal supérieur à 0,1",
"1. 管理员在此创建分组并设置倍率": "1. Admin creates groups and sets ratios here",
"1) 点击「打开授权页面」完成登录;2) 浏览器会跳转到 localhost(页面打不开也没关系);3) 复制地址栏完整 URL 粘贴到下方;4) 点击「生成并填入」。": "1) Cliquez sur « Ouvrir la page d'autorisation » pour vous connecter ; 2) Le navigateur redirigera vers localhost (ce n'est pas grave si la page ne s'ouvre pas) ; 3) Copiez l'URL complète de la barre d'adresse et collez-la ci-dessous ; 4) Cliquez sur « Générer et remplir ».",
"10 - 最高": "10 - La plus haute",
"1h缓存创建 {{price}} / 1M tokens": "Création de cache 1h {{price}} / 1M tokens",
"1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}": "Création de cache 1h {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}",
@@ -117,7 +116,6 @@
"Claude请求头追加": "Ajout des en-tetes de requete Claude",
"Client ID": "ID client",
"Client Secret": "Secret client",
"Codex 授权": "Autorisation Codex",
"Codex 渠道不支持批量创建": "Le canal Codex ne prend pas en charge la création par lot",
"common.changeLanguage": "Changer de langue",
"Completion tokens": "Completion tokens",
@@ -1162,7 +1160,6 @@
"复制所有模型": "Copier tous les modèles",
"复制所选令牌": "Copier le jeton sélectionné",
"复制所选兑换码到剪贴板": "Copier les codes d'échange sélectionnés dans le presse-papiers",
"复制授权链接": "Copier le lien d'autorisation",
"复制日志": "Copy Logs",
"复制渠道的所有信息": "Copier toutes les informations d'un canal",
"复制版本号": "Copy Version",
@@ -1394,7 +1391,6 @@
"已开启全局请求透传:参数覆写、模型重定向、渠道适配等 NewAPI 内置功能将失效,非最佳实践;如因此产生问题,请勿提交 issue 反馈。": "La transmission globale des requêtes est activée. Les fonctionnalités intégrées de NewAPI (surcharge des paramètres, redirection de modèle, adaptation du canal, etc.) seront désactivées. Ce n'est pas une bonne pratique. Si cela cause des problèmes, merci de ne pas ouvrir d'issue.",
"已忽略模型": "",
"已成功开始测试所有已启用通道,请刷新页面查看结果。": "Le test de tous les canaux activés a démarré avec succès. Veuillez actualiser la page pour voir les résultats.",
"已打开授权页面": "Page d'autorisation ouverte",
"已打开支付页面": "Page de paiement ouverte",
"已批量处理上游模型更新:渠道 {{channels}} 个,加入 {{added}} 个,删除 {{removed}} 个,失败 {{fails}} 个": "",
"已提交": "Soumis",
@@ -1420,7 +1416,6 @@
"已清理 {{count}} 个日志文件,释放 {{size}}_other": "",
"已清空": "Vidé",
"已清空测试结果": "Résultats de test effacés",
"已生成授权凭据": "Identifiants d'autorisation générés",
"已用": "Used",
"已用/剩余": "Utilisé/Restant",
"已用额度": "Quota utilisé",
@@ -1603,7 +1598,6 @@
"手动输入": "Saisie manuelle",
"打开 CC Switch": "Ouvrir CC Switch",
"打开侧边栏": "Ouvrir la barre latérale",
"打开授权页面": "Ouvrir la page d'autorisation",
"扣费": "Déduction",
"执行 GC": "Exécuter le GC",
"执行中": "En cours",
-6
View File
@@ -44,7 +44,6 @@
"0.002-1之间的小数": "0.0021の小数",
"0.1以上的小数": "0.1以上の小数",
"1. 管理员在此创建分组并设置倍率": "1. 管理者がここでグループを作成しレートを設定",
"1) 点击「打开授权页面」完成登录;2) 浏览器会跳转到 localhost(页面打不开也没关系);3) 复制地址栏完整 URL 粘贴到下方;4) 点击「生成并填入」。": "1) 「認可ページを開く」をクリックしてログインを完了します。2) ブラウザがlocalhostにリダイレクトされます(ページが開かなくても問題ありません)。3) アドレスバーの完全なURLをコピーして下に貼り付けます。4)「生成して入力」をクリックします。",
"10 - 最高": "10 - 最高",
"1h缓存创建 {{price}} / 1M tokens": "1h キャッシュ作成 {{price}} / 1M tokens",
"1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}": "1h キャッシュ作成 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}",
@@ -111,7 +110,6 @@
"Claude请求头追加": "Claudeリクエストヘッダーの追加",
"Client ID": "Client ID",
"Client Secret": "Client Secret",
"Codex 授权": "Codex 認可",
"Codex 渠道不支持批量创建": "Codexチャネルはバッチ作成をサポートしていません",
"common.changeLanguage": "common.changeLanguage",
"Completion tokens": "Completion tokens",
@@ -1149,7 +1147,6 @@
"复制所有模型": "すべてのモデルをコピー",
"复制所选令牌": "選択したトークンをコピー",
"复制所选兑换码到剪贴板": "選択した引き換えコードをクリップボードにコピー",
"复制授权链接": "認可リンクをコピー",
"复制日志": "Copy Logs",
"复制渠道的所有信息": "チャネルのすべての情報をコピー",
"复制版本号": "Copy Version",
@@ -1371,7 +1368,6 @@
"已开启全局请求透传:参数覆写、模型重定向、渠道适配等 NewAPI 内置功能将失效,非最佳实践;如因此产生问题,请勿提交 issue 反馈。": "全体のリクエストパススルーが有効です。パラメータ上書き、モデルリダイレクト、チャネル適応などの NewAPI 内蔵機能は無効になります。ベストプラクティスではありません。これにより問題が発生しても issue を投稿しないでください。",
"已忽略模型": "",
"已成功开始测试所有已启用通道,请刷新页面查看结果。": "有効なすべてのチャネルのテストを開始しました。ページを更新して結果を確認してください。",
"已打开授权页面": "認可ページを開きました",
"已打开支付页面": "決済ページを開きました",
"已批量处理上游模型更新:渠道 {{channels}} 个,加入 {{added}} 个,删除 {{removed}} 个,失败 {{fails}} 个": "",
"已提交": "送信済み",
@@ -1392,7 +1388,6 @@
"已清理 {{count}} 个日志文件,释放 {{size}}_other": "",
"已清空": "クリア済み",
"已清空测试结果": "テスト結果がクリアされました",
"已生成授权凭据": "認可資格情報が生成されました",
"已用": "Used",
"已用/剩余": "使用済み/残り",
"已用额度": "使用済みクォータ",
@@ -1574,7 +1569,6 @@
"手动输入": "手動入力",
"打开 CC Switch": "CC Switchを開く",
"打开侧边栏": "サイドバーを展開",
"打开授权页面": "認可ページを開く",
"扣费": "課金",
"执行 GC": "GCを実行",
"执行中": "実行中",
-6
View File
@@ -54,7 +54,6 @@
"0.002-1之间的小数": "Десятичное число между 0.002-1",
"0.1以上的小数": "Десятичное число выше 0.1",
"1. 管理员在此创建分组并设置倍率": "1. Admin creates groups and sets ratios here",
"1) 点击「打开授权页面」完成登录;2) 浏览器会跳转到 localhost(页面打不开也没关系);3) 复制地址栏完整 URL 粘贴到下方;4) 点击「生成并填入」。": "1) Нажмите «Открыть страницу авторизации» для входа; 2) Браузер перенаправит на localhost (ничего страшного, если страница не откроется); 3) Скопируйте полный URL из адресной строки и вставьте ниже; 4) Нажмите «Сгенерировать и заполнить».",
"10 - 最高": "10 - Максимум",
"1h缓存创建 {{price}} / 1M tokens": "Создание кэша 1h {{price}} / 1M tokens",
"1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}": "Создание кэша 1h {{tokens}} токенов / 1M токенов * {{symbol}}{{price}}",
@@ -121,7 +120,6 @@
"Claude请求头追加": "Добавление заголовков запроса Claude",
"Client ID": "ID клиента",
"Client Secret": "Секрет клиента",
"Codex 授权": "Авторизация Codex",
"Codex 渠道不支持批量创建": "Канал Codex не поддерживает пакетное создание",
"common.changeLanguage": "common.changeLanguage",
"Completion tokens": "Completion tokens",
@@ -1170,7 +1168,6 @@
"复制所有模型": "Копировать все модели",
"复制所选令牌": "Копировать выбранные токены",
"复制所选兑换码到剪贴板": "Копировать выбранные коды обмена в буфер обмена",
"复制授权链接": "Скопировать ссылку авторизации",
"复制日志": "Copy Logs",
"复制渠道的所有信息": "Копировать всю информацию о канале",
"复制版本号": "Copy Version",
@@ -1408,7 +1405,6 @@
"已开启全局请求透传:参数覆写、模型重定向、渠道适配等 NewAPI 内置功能将失效,非最佳实践;如因此产生问题,请勿提交 issue 反馈。": "Глобальная сквозная передача запросов включена. Встроенные возможности NewAPI, такие как переопределение параметров, перенаправление моделей и адаптация канала, будут отключены. Это не является лучшей практикой. Если из-за этого возникнут проблемы, пожалуйста, не создавайте issue.",
"已忽略模型": "",
"已成功开始测试所有已启用通道,请刷新页面查看结果。": "Успешно начато тестирование всех включенных каналов, обновите страницу для просмотра результатов.",
"已打开授权页面": "Страница авторизации открыта",
"已打开支付页面": "Страница оплаты открыта",
"已批量处理上游模型更新:渠道 {{channels}} 个,加入 {{added}} 个,删除 {{removed}} 个,失败 {{fails}} 个": "",
"已提交": "Отправлено",
@@ -1437,7 +1433,6 @@
"已清理 {{count}} 个日志文件,释放 {{size}}_other": "",
"已清空": "Очищено",
"已清空测试结果": "Результаты тестов очищены",
"已生成授权凭据": "Учётные данные авторизации сгенерированы",
"已用": "Used",
"已用/剩余": "Использовано/Осталось",
"已用额度": "Использованная квота",
@@ -1621,7 +1616,6 @@
"手动输入": "Ввести вручную",
"打开 CC Switch": "Открыть CC Switch",
"打开侧边栏": "Открыть боковую панель",
"打开授权页面": "Открыть страницу авторизации",
"扣费": "Списание",
"执行 GC": "Выполнить GC",
"执行中": "Выполняется",
-6
View File
@@ -44,7 +44,6 @@
"0.002-1之间的小数": "Số thập phân giữa 0.002-1",
"0.1以上的小数": "Số thập phân trên 0.1",
"1. 管理员在此创建分组并设置倍率": "1. Admin creates groups and sets ratios here",
"1) 点击「打开授权页面」完成登录;2) 浏览器会跳转到 localhost(页面打不开也没关系);3) 复制地址栏完整 URL 粘贴到下方;4) 点击「生成并填入」。": "1) Nhấn \"Mở trang xác thực\" để đăng nhập; 2) Trình duyệt sẽ chuyển hướng đến localhost (không sao nếu trang không mở được); 3) Sao chép URL đầy đủ từ thanh địa chỉ và dán vào bên dưới; 4) Nhấn \"Tạo và điền\".",
"10 - 最高": "10 - Cao nhất",
"1h缓存创建 {{price}} / 1M tokens": "Tạo bộ nhớ đệm 1h {{price}} / 1M tokens",
"1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}": "Tạo bộ nhớ đệm 1h {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}",
@@ -111,7 +110,6 @@
"Claude请求头追加": "Thêm tiêu đề yêu cầu Claude",
"Client ID": "Client ID",
"Client Secret": "Client Secret",
"Codex 授权": "Xác thực Codex",
"Codex 渠道不支持批量创建": "Kênh Codex không hỗ trợ tạo hàng loạt",
"common.changeLanguage": "Thay đổi ngôn ngữ",
"Completion tokens": "Completion tokens",
@@ -1150,7 +1148,6 @@
"复制所有模型": "Sao chép tất cả mô hình",
"复制所选令牌": "Sao chép mã thông báo đã chọn",
"复制所选兑换码到剪贴板": "Sao chép mã đổi thưởng đã chọn vào khay nhớ tạm",
"复制授权链接": "Sao chép liên kết xác thực",
"复制日志": "Copy Logs",
"复制渠道的所有信息": "Sao chép tất cả thông tin của kênh",
"复制版本号": "Copy Version",
@@ -1372,7 +1369,6 @@
"已开启全局请求透传:参数覆写、模型重定向、渠道适配等 NewAPI 内置功能将失效,非最佳实践;如因此产生问题,请勿提交 issue 反馈。": "Đã bật truyền qua yêu cầu toàn cục. Các tính năng tích hợp của NewAPI như ghi đè tham số, chuyển hướng mô hình và thích ứng kênh sẽ bị vô hiệu hóa. Đây không phải là thực hành tốt nhất. Nếu phát sinh vấn đề, vui lòng không gửi issue.",
"已忽略模型": "",
"已成功开始测试所有已启用通道,请刷新页面查看结果。": "Đã bắt đầu kiểm tra tất cả các kênh đã bật thành công. Vui lòng làm mới trang để xem kết quả.",
"已打开授权页面": "Đã mở trang xác thực",
"已打开支付页面": "Đã mở trang thanh toán",
"已批量处理上游模型更新:渠道 {{channels}} 个,加入 {{added}} 个,删除 {{removed}} 个,失败 {{fails}} 个": "",
"已提交": "Đã gửi",
@@ -1393,7 +1389,6 @@
"已清理 {{count}} 个日志文件,释放 {{size}}_other": "",
"已清空": "Đã xóa sạch",
"已清空测试结果": "Đã xóa kết quả kiểm tra",
"已生成授权凭据": "Đã tạo thông tin xác thực",
"已用": "Used",
"已用/剩余": "Đã dùng/Còn lại",
"已用额度": "Hạn ngạch đã dùng",
@@ -1575,7 +1570,6 @@
"手动输入": "Nhập thủ công",
"打开 CC Switch": "Mở CC Switch",
"打开侧边栏": "Mở thanh bên",
"打开授权页面": "Mở trang xác thực",
"扣费": "Khấu phí",
"执行 GC": "Thực thi GC",
"执行中": "đang xử lý",
-6
View File
@@ -40,7 +40,6 @@
"0 表示不限": "0 表示不限",
"0.002-1之间的小数": "0.002-1之间的小数",
"0.1以上的小数": "0.1以上的小数",
"1) 点击「打开授权页面」完成登录;2) 浏览器会跳转到 localhost(页面打不开也没关系);3) 复制地址栏完整 URL 粘贴到下方;4) 点击「生成并填入」。": "1) 点击「打开授权页面」完成登录;2) 浏览器会跳转到 localhost(页面打不开也没关系);3) 复制地址栏完整 URL 粘贴到下方;4) 点击「生成并填入」。",
"10 - 最高": "10 - 最高",
"1h缓存创建 {{price}} / 1M tokens": "1h缓存创建 {{price}} / 1M tokens",
"1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}": "1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}",
@@ -104,7 +103,6 @@
"Claude请求头追加": "Claude请求头追加",
"Client ID": "Client ID",
"Client Secret": "Client Secret",
"Codex 授权": "Codex 授权",
"Codex 渠道不支持批量创建": "Codex 渠道不支持批量创建",
"common.changeLanguage": "common.changeLanguage",
"Completion tokens": "Completion tokens",
@@ -1139,7 +1137,6 @@
"复制所有模型": "复制所有模型",
"复制所选令牌": "复制所选令牌",
"复制所选兑换码到剪贴板": "复制所选兑换码到剪贴板",
"复制授权链接": "复制授权链接",
"复制日志": "复制日志",
"复制渠道的所有信息": "复制渠道的所有信息",
"复制版本号": "复制版本号",
@@ -1358,7 +1355,6 @@
"已开启全局请求透传:参数覆写、模型重定向、渠道适配等 NewAPI 内置功能将失效,非最佳实践;如因此产生问题,请勿提交 issue 反馈。": "已开启全局请求透传:参数覆写、模型重定向、渠道适配等 NewAPI 内置功能将失效,非最佳实践;如因此产生问题,请勿提交 issue 反馈。",
"已忽略模型": "已忽略模型",
"已成功开始测试所有已启用通道,请刷新页面查看结果。": "已成功开始测试所有已启用通道,请刷新页面查看结果。",
"已打开授权页面": "已打开授权页面",
"已打开支付页面": "已打开支付页面",
"已批量处理上游模型更新:渠道 {{channels}} 个,加入 {{added}} 个,删除 {{removed}} 个,失败 {{fails}} 个": "已批量处理上游模型更新:渠道 {{channels}} 个,加入 {{added}} 个,删除 {{removed}} 个,失败 {{fails}} 个",
"已提交": "已提交",
@@ -1378,7 +1374,6 @@
"已清理 {{count}} 个日志文件,释放 {{size}}_other": "已清理 {{count}} 个日志文件,释放 {{size}}",
"已清空": "已清空",
"已清空测试结果": "已清空测试结果",
"已生成授权凭据": "已生成授权凭据",
"已用": "已用",
"已用/剩余": "已用/剩余",
"已用额度": "已用额度",
@@ -1561,7 +1556,6 @@
"手动输入": "手动输入",
"打开 CC Switch": "打开 CC Switch",
"打开侧边栏": "打开侧边栏",
"打开授权页面": "打开授权页面",
"扣费": "扣费",
"执行 GC": "执行 GC",
"执行中": "执行中",
-6
View File
@@ -43,7 +43,6 @@
"0.002-1之间的小数": "0.002-1之間的小數",
"0.1以上的小数": "0.1以上的小數",
"1. 管理员在此创建分组并设置倍率": "1. 管理員在此建立分組並設定倍率",
"1) 点击「打开授权页面」完成登录;2) 浏览器会跳转到 localhost(页面打不开也没关系);3) 复制地址栏完整 URL 粘贴到下方;4) 点击「生成并填入」。": "",
"10 - 最高": "10 - 最高",
"1h缓存创建 {{price}} / 1M tokens": "1h快取建立 {{price}} / 1M tokens",
"1h缓存创建 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}": "1h快取建立 {{tokens}} tokens / 1M tokens * {{symbol}}{{price}}",
@@ -110,7 +109,6 @@
"Claude请求头追加": "Claude請求頭追加",
"Client ID": "Client ID",
"Client Secret": "Client Secret",
"Codex 授权": "",
"Codex 渠道不支持批量创建": "",
"common.changeLanguage": "common.changeLanguage",
"Completion tokens": "",
@@ -1148,7 +1146,6 @@
"复制所有模型": "複製所有模型",
"复制所选令牌": "複製所選令牌",
"复制所选兑换码到剪贴板": "複製所選兌換碼到剪貼板",
"复制授权链接": "",
"复制日志": "複製日誌",
"复制渠道的所有信息": "複製管道的所有資訊",
"复制版本号": "複製版本號",
@@ -1368,7 +1365,6 @@
"已开启全局请求透传:参数覆写、模型重定向、渠道适配等 NewAPI 内置功能将失效,非最佳实践;如因此产生问题,请勿提交 issue 反馈。": "已開啟全域請求透傳:參數覆寫、模型重定向、管道相容等 NewAPI 內置功能將失效,非最佳實踐;如因此產生問題,請勿提交 issue 回饋。",
"已忽略模型": "",
"已成功开始测试所有已启用通道,请刷新页面查看结果。": "已成功開始測試所有已啟用通道,請刷新頁面查看結果。",
"已打开授权页面": "",
"已打开支付页面": "已打開支付頁面",
"已批量处理上游模型更新:渠道 {{channels}} 个,加入 {{added}} 个,删除 {{removed}} 个,失败 {{fails}} 个": "",
"已提交": "已提交",
@@ -1388,7 +1384,6 @@
"已清理 {{count}} 个日志文件,释放 {{size}}_other": "",
"已清空": "",
"已清空测试结果": "已清空測試結果",
"已生成授权凭据": "",
"已用": "已用",
"已用/剩余": "已用/剩餘",
"已用额度": "已用額度",
@@ -1571,7 +1566,6 @@
"手动输入": "手動輸入",
"打开 CC Switch": "",
"打开侧边栏": "打開側邊欄",
"打开授权页面": "",
"扣费": "扣費",
"执行 GC": "執行 GC",
"执行中": "執行中",
+1 -1
View File
@@ -40,6 +40,7 @@ const BRAND_AND_LITERAL_KEYS = new Set([
'AZURE_OPENAI_ENDPOINT *',
'Baidu V2',
'ChatGPT',
'ChatGPT Subscription (Codex)',
'Claude',
'Client ID',
'Client Secret',
@@ -317,4 +318,3 @@ main().catch((err) => {
process.exitCode = 1
})
+12 -3
View File
@@ -36,13 +36,22 @@ export function ProviderBadge({
const icon = iconKey ? getLobeIcon(iconKey, iconSize) : null
return (
<div data-slot='provider-badge' className={cn('flex items-center gap-1.5', className)}>
{icon}
<div
data-slot='provider-badge'
className={cn(
'flex min-w-0 max-w-full items-center gap-1.5',
className
)}
>
{icon && <span className='flex shrink-0 items-center'>{icon}</span>}
<StatusBadge
label={label}
autoColor={label}
size='sm'
className={!icon ? 'pl-0' : undefined}
className={cn(
'min-w-0 shrink overflow-hidden',
!icon && 'pl-0'
)}
{...badgeProps}
/>
</div>
-40
View File
@@ -46,26 +46,6 @@ const channelActionConfig = (
skipErrorHandler: true,
})
export type CodexOAuthStartResponse = {
success: boolean
message?: string
data?: {
authorize_url?: string
}
}
export type CodexOAuthCompleteResponse = {
success: boolean
message?: string
data?: {
key?: string
account_id?: string
email?: string
expires_at?: string
last_refresh?: string
}
}
export type CodexUsageResponse = {
success: boolean
message?: string
@@ -286,26 +266,6 @@ export async function getChannelKey(
// Codex Channel Operations
// ============================================================================
export async function startCodexOAuth(): Promise<CodexOAuthStartResponse> {
const res = await api.post(
'/api/channel/codex/oauth/start',
{},
channelActionConfig()
)
return res.data
}
export async function completeCodexOAuth(
input: string
): Promise<CodexOAuthCompleteResponse> {
const res = await api.post(
'/api/channel/codex/oauth/complete',
{ input },
channelActionConfig()
)
return res.data
}
export async function refreshCodexCredential(
channelId: number
): Promise<CodexCredentialRefreshResponse> {
@@ -622,7 +622,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
: undefined
return (
<div className='flex items-center gap-2'>
<div className='flex min-w-0 max-w-full items-center gap-2 overflow-hidden'>
{isMultiKey && (
<TooltipProvider delay={100}>
<Tooltip>
@@ -637,12 +637,24 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
</Tooltip>
</TooltipProvider>
)}
<TooltipProvider delay={300}>
<Tooltip>
<TooltipTrigger
render={
<div className='min-w-0 max-w-full overflow-hidden' />
}
>
<ProviderBadge
iconKey={iconName}
label={typeName}
copyable={false}
showDot={false}
className='min-w-0 max-w-full overflow-hidden'
/>
</TooltipTrigger>
<TooltipContent side='top'>{typeName}</TooltipContent>
</Tooltip>
</TooltipProvider>
{isIonet && (
<TooltipProvider delay={100}>
<Tooltip>
@@ -692,7 +704,7 @@ export function useChannelsColumns(): ColumnDef<Channel>[] {
if (!value || value.length === 0 || value.includes('all')) return true
return value.includes(String(row.getValue(id)))
},
size: 140,
size: 220,
enableSorting: false,
},
@@ -1,215 +0,0 @@
/*
Copyright (C) 2023-2026 QuantumNous
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useEffect, useMemo, useState } from 'react'
import { ExternalLink, Copy, Check, Loader2 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { tryPrettyJson } from '@/lib/utils'
import { useCopyToClipboard } from '@/hooks/use-copy-to-clipboard'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Dialog } from '@/components/dialog'
import { completeCodexOAuth, startCodexOAuth } from '../../api'
type CodexOAuthDialogProps = {
open: boolean
onOpenChange: (open: boolean) => void
onKeyGenerated: (key: string) => void
}
export function CodexOAuthDialog({
open,
onOpenChange,
onKeyGenerated,
}: CodexOAuthDialogProps) {
const { t } = useTranslation()
const { copiedText, copyToClipboard } = useCopyToClipboard({ notify: false })
const [state, setState] = useState({
authorizeUrl: '',
callbackUrl: '',
isStarting: false,
isCompleting: false,
})
useEffect(() => {
if (!open) {
setState({
authorizeUrl: '',
callbackUrl: '',
isStarting: false,
isCompleting: false,
})
}
}, [open])
const canCopyAuthorizeUrl = Boolean(state.authorizeUrl && !state.isStarting)
const canComplete = useMemo(
() => Boolean(state.callbackUrl.trim()) && !state.isCompleting,
[state.callbackUrl, state.isCompleting]
)
const handleStart = async () => {
setState((prev) => ({ ...prev, isStarting: true }))
try {
const res = await startCodexOAuth()
if (!res.success) {
throw new Error(res.message || 'Failed to start OAuth')
}
const url = res.data?.authorize_url || ''
if (!url) {
throw new Error('Missing authorize_url in response')
}
setState((prev) => ({ ...prev, authorizeUrl: url }))
try {
window.open(url, '_blank', 'noopener,noreferrer')
toast.success(t('Opened authorization page'))
} catch (error) {
// eslint-disable-next-line no-console
console.warn('Failed to open authorization page:', error)
toast.warning(t('Please manually copy and open the authorization link'))
}
} catch (error) {
toast.error(
error instanceof Error ? error.message : t('OAuth start failed')
)
} finally {
setState((prev) => ({ ...prev, isStarting: false }))
}
}
const handleComplete = async () => {
if (!state.callbackUrl.trim()) return
setState((prev) => ({ ...prev, isCompleting: true }))
try {
const res = await completeCodexOAuth(state.callbackUrl.trim())
if (!res.success) {
throw new Error(res.message || 'OAuth failed')
}
const rawKey = res.data?.key || ''
if (!rawKey) {
throw new Error('Missing key in response')
}
onKeyGenerated(tryPrettyJson(rawKey))
toast.success(t('Credential generated'))
onOpenChange(false)
} catch (error) {
toast.error(error instanceof Error ? error.message : t('OAuth failed'))
} finally {
setState((prev) => ({ ...prev, isCompleting: false }))
}
}
return (
<Dialog
open={open}
onOpenChange={onOpenChange}
title={t('Codex Authorization')}
description={t(
'Generate a Codex OAuth credential and paste it into the channel key field.'
)}
contentClassName='sm:max-w-2xl'
contentHeight='auto'
bodyClassName='space-y-4'
footer={
<>
<Button
type='button'
variant='outline'
onClick={() => onOpenChange(false)}
disabled={state.isStarting || state.isCompleting}
>
{t('Cancel')}
</Button>
<Button onClick={handleComplete} disabled={!canComplete}>
{state.isCompleting && (
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
)}
{state.isCompleting ? t('Generating...') : t('Generate credential')}
</Button>
</>
}
>
<div className='space-y-4'>
<Alert>
<AlertDescription>
{t(
'1) Click "Open authorization page" and complete login. 2) Your browser may redirect to localhost (it is OK if the page does not load). 3) Copy the full URL from the address bar and paste it below. 4) Click "Generate credential".'
)}
</AlertDescription>
</Alert>
<div className='flex flex-wrap gap-2'>
<Button onClick={handleStart} disabled={state.isStarting}>
{state.isStarting ? (
<Loader2 className='mr-2 h-4 w-4 animate-spin' />
) : (
<ExternalLink className='mr-2 h-4 w-4' />
)}
{t('Open authorization page')}
</Button>
<Button
type='button'
variant='outline'
disabled={!canCopyAuthorizeUrl}
onClick={async () => {
if (!state.authorizeUrl) return
await copyToClipboard(state.authorizeUrl)
}}
aria-label={t('Copy authorization link')}
title={t('Copy authorization link')}
>
{copiedText === state.authorizeUrl ? (
<Check className='mr-2 h-4 w-4 text-green-600' />
) : (
<Copy className='mr-2 h-4 w-4' />
)}
{t('Copy authorization link')}
</Button>
</div>
<div className='space-y-2'>
<div className='text-sm font-medium'>{t('Callback URL')}</div>
<Input
value={state.callbackUrl}
onChange={(e) =>
setState((prev) => ({ ...prev, callbackUrl: e.target.value }))
}
placeholder={t(
'Paste the full callback URL (includes code & state)'
)}
autoComplete='off'
spellCheck={false}
/>
<div className='text-muted-foreground text-xs'>
{t(
'Tip: The generated key is a JSON credential including access_token / refresh_token / account_id.'
)}
</div>
</div>
</div>
</Dialog>
)
}
@@ -38,7 +38,6 @@ import {
Eraser,
Plus,
Eye,
Link2,
RefreshCw,
Code,
Route,
@@ -148,7 +147,6 @@ import {
} from '../../lib/status-code-risk-guard'
import type { Channel } from '../../types'
import { useChannels } from '../channels-provider'
import { CodexOAuthDialog } from '../dialogs/codex-oauth-dialog'
import { FetchModelsDialog } from '../dialogs/fetch-models-dialog'
import {
MissingModelsConfirmationDialog,
@@ -281,7 +279,6 @@ export function ChannelMutateDrawer({
const [fetchModelsDialogOpen, setFetchModelsDialogOpen] = useState(false)
const [channelKey, setChannelKey] = useState<string | null>(null)
const [isChannelKeyLoading, setIsChannelKeyLoading] = useState(false)
const [codexOAuthDialogOpen, setCodexOAuthDialogOpen] = useState(false)
const [isCodexCredentialRefreshing, setIsCodexCredentialRefreshing] =
useState(false)
const initialModelsRef = useRef<string[]>([])
@@ -373,6 +370,7 @@ export function ChannelMutateDrawer({
const currentName = form.watch('name')
const currentModelMapping = form.watch('model_mapping')
const awsKeyType = form.watch('aws_key_type')
const vertexKeyType = form.watch('vertex_key_type')
const upstreamModelUpdateCheckEnabled = form.watch(
'upstream_model_update_check_enabled'
)
@@ -399,6 +397,15 @@ export function ChannelMutateDrawer({
const isBatchMode =
multiKeyMode === 'batch' || multiKeyMode === 'multi_to_single'
const isChannelDetailLoading = isEditing && isChannelLoading
const supportsMultiKeyAddMode =
currentType !== 57 && !(currentType === 41 && vertexKeyType === 'api_key')
const addModeOptions = useMemo(
() =>
supportsMultiKeyAddMode
? ADD_MODE_OPTIONS
: ADD_MODE_OPTIONS.filter((option) => option.value === 'single'),
[supportsMultiKeyAddMode]
)
// Get all models list
const allModelsList = useMemo(
@@ -622,6 +629,25 @@ export function ChannelMutateDrawer({
}
}, [currentType, isEditing, form])
useEffect(() => {
if (currentType !== 45 || currentBaseUrl !== 'doubao-coding-plan') return
form.setValue('base_url', 'https://ark.cn-beijing.volces.com', {
shouldDirty: false,
shouldValidate: true,
})
}, [currentBaseUrl, currentType, form])
useEffect(() => {
if (isEditing || supportsMultiKeyAddMode) return
if (multiKeyMode && multiKeyMode !== 'single') {
form.setValue('multi_key_mode', 'single', {
shouldDirty: true,
shouldValidate: true,
})
}
}, [form, isEditing, multiKeyMode, supportsMultiKeyAddMode])
// Validate base_url - warn if it ends with /v1
useEffect(() => {
if (!currentBaseUrl || !currentBaseUrl.endsWith('/v1')) return
@@ -1550,7 +1576,7 @@ export function ChannelMutateDrawer({
</FormItem>
)}
/>
{form.watch('vertex_key_type') === 'json' && (
{vertexKeyType === 'json' && (
<FormItem>
<FormLabel>
{t('Service account JSON file(s)')}
@@ -1682,14 +1708,12 @@ export function ChannelMutateDrawer({
'https://ark.ap-southeast.bytepluses.com'
),
},
{
value: 'doubao-coding-plan',
label: t('Doubao Coding Plan'),
},
]}
onValueChange={field.onChange}
value={
field.value ||
field.value === 'doubao-coding-plan'
? 'https://ark.cn-beijing.volces.com'
: field.value ||
'https://ark.cn-beijing.volces.com'
}
>
@@ -1708,9 +1732,6 @@ export function ChannelMutateDrawer({
'https://ark.ap-southeast.bytepluses.com'
)}
</SelectItem>
<SelectItem value='doubao-coding-plan'>
{t('Doubao Coding Plan')}
</SelectItem>
</SelectGroup>
</SelectContent>
</Select>
@@ -1806,7 +1827,7 @@ export function ChannelMutateDrawer({
<FormLabel>{t('Add Mode')}</FormLabel>
<Select
items={[
...ADD_MODE_OPTIONS.map((option) => ({
...addModeOptions.map((option) => ({
value: option.value,
label: t(option.label),
})),
@@ -1821,7 +1842,7 @@ export function ChannelMutateDrawer({
</FormControl>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{ADD_MODE_OPTIONS.map((option) => (
{addModeOptions.map((option) => (
<SelectItem
key={option.value}
value={option.value}
@@ -1833,7 +1854,11 @@ export function ChannelMutateDrawer({
</SelectContent>
</Select>
<FormDescription>
{t(FIELD_DESCRIPTIONS.BATCH_ADD)}
{t(
supportsMultiKeyAddMode
? FIELD_DESCRIPTIONS.BATCH_ADD
: FIELD_DESCRIPTIONS.KEY
)}
</FormDescription>
<FormMessage />
</FormItem>
@@ -1988,26 +2013,12 @@ export function ChannelMutateDrawer({
{currentType === 57 && (
<div className='border-border/60 flex flex-col gap-3 border-y py-4'>
<div className='flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between'>
<div className='flex flex-col gap-0.5'>
<div className='text-sm font-semibold'>
{t('Codex Authorization')}
</div>
<div className='text-muted-foreground text-xs'>
{t(
'Codex channels use an OAuth JSON credential as the key.'
)}
</div>
</div>
<div className='flex flex-wrap items-center gap-2'>
<Button
type='button'
variant='outline'
size='sm'
onClick={() => setCodexOAuthDialogOpen(true)}
>
<Link2 className='mr-2 h-4 w-4' />
{t('Authorize')}
</Button>
{isEditing && channelId && (
<Button
type='button'
@@ -2028,24 +2039,16 @@ export function ChannelMutateDrawer({
)}
</div>
</div>
<Alert>
<Alert className='border-amber-200 bg-amber-50 text-amber-900 dark:border-amber-500/40 dark:bg-amber-500/10 dark:text-amber-50'>
<AlertDescription>
{t(
'If authorization succeeds, the generated JSON will be inserted into the key field. You still need to save the channel to persist it.'
"Disclaimer: Personal use only. Do not distribute or share any credentials. This channel has prerequisites and requires prior setup; use it only if you understand the flow and risks, and comply with OpenAI's terms and policies. Credentials and configuration are for Codex CLI integration only, and are not intended for any other client, platform, or channel."
)}
</AlertDescription>
</Alert>
</div>
)}
<CodexOAuthDialog
open={codexOAuthDialogOpen}
onOpenChange={setCodexOAuthDialogOpen}
onKeyGenerated={(key) => {
form.setValue('key', key, { shouldDirty: true })
}}
/>
{isEditing && isMultiKeyChannel && (
<FormField
control={form.control}
+1 -1
View File
@@ -75,7 +75,7 @@ export const CHANNEL_TYPES = {
54: 'DoubaoVideo',
55: 'Sora',
56: 'Replicate',
57: 'Codex',
57: 'ChatGPT Subscription (Codex)',
} as const
const CHANNEL_TYPE_DISPLAY_ORDER: number[] = [
+2 -14
View File
@@ -78,7 +78,6 @@
"1 week ago": "1 week ago",
"1 year ago": "1 year ago",
"1. Create an application in your Gotify server": "1. Create an application in your Gotify server",
"1) Click \"Open authorization page\" and complete login. 2) Your browser may redirect to localhost (it is OK if the page does not load). 3) Copy the full URL from the address bar and paste it below. 4) Click \"Generate credential\".": "1) Click \"Open authorization page\" and complete login. 2) Your browser may redirect to localhost (it is OK if the page does not load). 3) Copy the full URL from the address bar and paste it below. 4) Click \"Generate credential\".",
"10 / page": "10 / page",
"100 / page": "100 / page",
"14 Days": "14 Days",
@@ -627,7 +626,6 @@
"Callback Caller IP": "Callback Caller IP",
"Callback notification URL": "Callback notification URL",
"Callback Payment Method": "Callback Payment Method",
"Callback URL": "Callback URL",
"Cancel": "Cancel",
"Cancelled": "Cancelled",
"Cancelled at": "Cancelled at",
@@ -701,6 +699,7 @@
"Checking updates...": "Checking updates...",
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"ChatGPT Subscription (Codex)": "ChatGPT Subscription (Codex)",
"Chinese": "Chinese",
"Choose a username": "Choose a username",
"Choose an amount and payment method": "Choose an amount and payment method",
@@ -782,7 +781,6 @@
"Codes copied!": "Codes copied!",
"Codex": "Codex",
"Codex Account & Usage": "Codex Account & Usage",
"Codex Authorization": "Codex Authorization",
"Codex channels do not support batch creation": "Codex channels do not support batch creation",
"Codex channels use an OAuth JSON credential as the key.": "Codex channels use an OAuth JSON credential as the key.",
"Codex CLI Header Passthrough": "Codex CLI Header Passthrough",
@@ -955,7 +953,6 @@
"Copy all backup codes": "Copy all backup codes",
"Copy All Codes": "Copy All Codes",
"Copy API key": "Copy API key",
"Copy authorization link": "Copy authorization link",
"Copy Channel": "Copy Channel",
"Copy code": "Copy code",
"Copy Connection Info": "Copy Connection Info",
@@ -1039,7 +1036,6 @@
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.",
"Creating...": "Creating...",
"Creation failed": "Creation failed",
"Credential generated": "Credential generated",
"Credential refreshed": "Credential refreshed",
"Credentials": "Credentials",
"Credentials verification failed": "Credentials verification failed",
@@ -1250,6 +1246,7 @@
"Discount rate must be greater than 0": "Discount rate must be greater than 0",
"Discount Rate:": "Discount Rate:",
"Discount ratio for cache hits.": "Discount ratio for cache hits.",
"Disclaimer: Personal use only. Do not distribute or share any credentials. This channel has prerequisites and requires prior setup; use it only if you understand the flow and risks, and comply with OpenAI's terms and policies. Credentials and configuration are for Codex CLI integration only, and are not intended for any other client, platform, or channel.": "Disclaimer: Personal use only. Do not distribute or share any credentials. This channel has prerequisites and requires prior setup; use it only if you understand the flow and risks, and comply with OpenAI's terms and policies. Credentials and configuration are for Codex CLI integration only, and are not intended for any other client, platform, or channel.",
"Discouraged": "Discouraged",
"Discover curated AI models, compare pricing and capabilities, and choose the right model for every scenario.": "Discover curated AI models, compare pricing and capabilities, and choose the right model for every scenario.",
"Discover the leading models in each domain": "Discover the leading models in each domain",
@@ -1288,7 +1285,6 @@
"Domain Filter Mode": "Domain Filter Mode",
"Don't have an account?": "Don't have an account?",
"Done": "Done",
"Doubao Coding Plan": "Doubao Coding Plan",
"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.",
@@ -1865,9 +1861,7 @@
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.",
"General": "General",
"General Settings": "General Settings",
"Generate a Codex OAuth credential and paste it into the channel key field.": "Generate a Codex OAuth credential and paste it into the channel key field.",
"Generate and manage your API access token": "Generate and manage your API access token",
"Generate credential": "Generate credential",
"Generate Lyrics": "Generate Lyrics",
"Generate Music": "Generate Music",
"Generate new backup codes for account recovery": "Generate new backup codes for account recovery",
@@ -2727,7 +2721,6 @@
"OAuth Client Secret": "OAuth Client Secret",
"OAuth failed": "OAuth failed",
"OAuth Integrations": "OAuth Integrations",
"OAuth start failed": "OAuth start failed",
"Object Prune Rules": "Object Prune Rules",
"Observability": "Observability",
"Obtain the API key, merchant ID, and RSA key pair from the Waffo dashboard, and configure the callback URL.": "Obtain the API key, merchant ID, and RSA key pair from the Waffo dashboard, and configure the callback URL.",
@@ -2771,7 +2764,6 @@
"Oops! Something went wrong": "Oops! Something went wrong",
"Open": "Open",
"Open a source model first": "Open a source model first",
"Open authorization page": "Open authorization page",
"Open CC Switch": "Open CC Switch",
"Open in chat": "Open in chat",
"Open in new tab": "Open in new tab",
@@ -2790,7 +2782,6 @@
"OpenAI, Anthropic, etc.": "OpenAI, Anthropic, etc.",
"OpenAI, Anthropic, Google, etc.": "OpenAI, Anthropic, Google, etc.",
"OpenAIMax": "OpenAIMax",
"Opened authorization page": "Opened authorization page",
"OpenRouter": "OpenRouter",
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.",
"Operation": "Operation",
@@ -2910,7 +2901,6 @@
"Password reset and copied to clipboard: {{password}}": "Password reset and copied to clipboard: {{password}}",
"Password reset: {{password}}": "Password reset: {{password}}",
"Passwords do not match": "Passwords do not match",
"Paste the full callback URL (includes code & state)": "Paste the full callback URL (includes code & state)",
"Path": "Path",
"Path not set": "Path not set",
"Path Regex (one per line)": "Path Regex (one per line)",
@@ -3022,7 +3012,6 @@
"Please fix JSON errors before saving": "Please fix JSON errors before saving",
"Please fix the highlighted fields before saving": "Please fix the highlighted fields before saving",
"Please log in with the appropriate credentials": "Please log in with the appropriate credentials",
"Please manually copy and open the authorization link": "Please manually copy and open the authorization link",
"Please select a container": "Please select a container",
"Please select a payment method": "Please select a payment method",
"Please select a primary model": "Please select a primary model",
@@ -4071,7 +4060,6 @@
"Timeline": "Timeline",
"times": "times",
"Timing": "Timing",
"Tip: The generated key is a JSON credential including access_token / refresh_token / account_id.": "Tip: The generated key is a JSON credential including access_token / refresh_token / account_id.",
"to access this resource.": "to access this resource.",
"to confirm": "to confirm",
"To Lower": "To Lower",
+2 -14
View File
@@ -78,7 +78,6 @@
"1 week ago": "Il y a 1 semaine",
"1 year ago": "Il y a 1 an",
"1. Create an application in your Gotify server": "1. Créez une application sur votre serveur Gotify",
"1) Click \"Open authorization page\" and complete login. 2) Your browser may redirect to localhost (it is OK if the page does not load). 3) Copy the full URL from the address bar and paste it below. 4) Click \"Generate credential\".": "1) Cliquez sur « Ouvrir la page d'autorisation » et connectez-vous. 2) Votre navigateur peut rediriger vers localhost (la page peut ne pas s'afficher). 3) Copiez l'URL complète de la barre d'adresse et collez-la ci-dessous. 4) Cliquez sur « Générer l'identifiant ».",
"10 / page": "10 / page",
"100 / page": "100 / page",
"14 Days": "14 jours",
@@ -627,7 +626,6 @@
"Callback Caller IP": "IP de lappelant du callback",
"Callback notification URL": "URL de notification de rappel",
"Callback Payment Method": "Moyen de paiement (callback)",
"Callback URL": "URL de callback",
"Cancel": "Annuler",
"Cancelled": "Annulé",
"Cancelled at": "Annulé le",
@@ -701,6 +699,7 @@
"Checking updates...": "Vérification des mises à jour...",
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"ChatGPT Subscription (Codex)": "ChatGPT Subscription (Codex)",
"Chinese": "Chinois",
"Choose a username": "Choisir un nom d'utilisateur",
"Choose an amount and payment method": "Choisir un montant et un mode de paiement",
@@ -782,7 +781,6 @@
"Codes copied!": "Codes copiés !",
"Codex": "Codex",
"Codex Account & Usage": "Compte et utilisation Codex",
"Codex Authorization": "Autorisation Codex",
"Codex channels do not support batch creation": "Les canaux Codex ne prennent pas en charge la création par lot",
"Codex channels use an OAuth JSON credential as the key.": "Les canaux Codex utilisent un identifiant OAuth JSON comme clé.",
"Codex CLI Header Passthrough": "Passthrough en-tête Codex CLI",
@@ -955,7 +953,6 @@
"Copy all backup codes": "Copier tous les codes de sauvegarde",
"Copy All Codes": "Copier tous les codes",
"Copy API key": "Copier la clé API",
"Copy authorization link": "Copier le lien d'autorisation",
"Copy Channel": "Copier le canal",
"Copy code": "Copier le code",
"Copy Connection Info": "Copier les infos de connexion",
@@ -1039,7 +1036,6 @@
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Crée un produit Pancake dans la boutique enregistrée avec le titre et le prix de ce forfait. Waffo Pancake doit dabord être entièrement configuré dans les paramètres de paiement.",
"Creating...": "Création...",
"Creation failed": "Échec de la création",
"Credential generated": "Identifiant généré",
"Credential refreshed": "Identifiant actualisé",
"Credentials": "Identifiants",
"Credentials verification failed": "Échec de la vérification des identifiants",
@@ -1250,6 +1246,7 @@
"Discount rate must be greater than 0": "Le taux de remise doit être supérieur à 0",
"Discount Rate:": "Taux de réduction :",
"Discount ratio for cache hits.": "Ratio de réduction pour les accès au cache.",
"Disclaimer: Personal use only. Do not distribute or share any credentials. This channel has prerequisites and requires prior setup; use it only if you understand the flow and risks, and comply with OpenAI's terms and policies. Credentials and configuration are for Codex CLI integration only, and are not intended for any other client, platform, or channel.": "Avertissement : usage personnel uniquement. Ne distribuez ni ne partagez aucun identifiant. Ce canal a des prerequis et necessite une configuration prealable ; utilisez-le uniquement si vous comprenez la procedure et les risques, et respectez les conditions et politiques d'OpenAI. Les identifiants et la configuration sont reserves a l'integration Codex CLI et ne sont pas destines a d'autres clients, plateformes ou canaux.",
"Discouraged": "Déconseillé",
"Discover curated AI models, compare pricing and capabilities, and choose the right model for every scenario.": "Découvrez une sélection de modèles IA, comparez les tarifs et les capacités, et choisissez le modèle adapté à chaque scénario.",
"Discover the leading models in each domain": "Découvrez les modèles leaders dans chaque domaine",
@@ -1288,7 +1285,6 @@
"Domain Filter Mode": "Mode de filtre de domaine",
"Don't have an account?": "Vous n'avez pas de compte ?",
"Done": "Terminé",
"Doubao Coding Plan": "Plan Doubao Coding",
"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.",
@@ -1865,9 +1861,7 @@
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "Gemini continuera à détecter automatiquement le mode de pensée même avec l'adaptateur désactivé. Activez ceci uniquement lorsque vous avez besoin d'un contrôle plus fin sur la tarification et le budget.",
"General": "Général",
"General Settings": "Paramètres généraux",
"Generate a Codex OAuth credential and paste it into the channel key field.": "Générer un identifiant OAuth Codex et le coller dans le champ clé du canal.",
"Generate and manage your API access token": "Générer et gérer votre jeton d'accès API",
"Generate credential": "Générer l'identifiant",
"Generate Lyrics": "Générer des paroles",
"Generate Music": "Générer de la musique",
"Generate new backup codes for account recovery": "Générer de nouveaux codes de secours pour la récupération du compte",
@@ -2727,7 +2721,6 @@
"OAuth Client Secret": "Secret client OAuth",
"OAuth failed": "Échec de l'OAuth",
"OAuth Integrations": "Intégrations OAuth",
"OAuth start failed": "Échec du démarrage OAuth",
"Object Prune Rules": "Règles de nettoyage d'objets",
"Observability": "Observabilité",
"Obtain the API key, merchant ID, and RSA key pair from the Waffo dashboard, and configure the callback URL.": "Obtenez la clé API, l'ID du commerçant et la paire de clés RSA depuis le tableau de bord Waffo, et configurez l'URL de rappel.",
@@ -2771,7 +2764,6 @@
"Oops! Something went wrong": "Oups ! Quelque chose s'est mal passé",
"Open": "Ouvrir",
"Open a source model first": "Ouvrez dabord un modèle source",
"Open authorization page": "Ouvrir la page d'autorisation",
"Open CC Switch": "Ouvrir le commutateur CC",
"Open in chat": "Ouvrir dans le chat",
"Open in new tab": "Ouvrir dans un nouvel onglet",
@@ -2790,7 +2782,6 @@
"OpenAI, Anthropic, etc.": "OpenAI, Anthropic, etc.",
"OpenAI, Anthropic, Google, etc.": "OpenAI, Anthropic, Google, etc.",
"OpenAIMax": "OpenAIMax",
"Opened authorization page": "Page d'autorisation ouverte",
"OpenRouter": "OpenRouter",
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "s'ouvre dans un client externe. Déclenchez-le depuis la barre latérale ou les actions de clé API pour lancer l'application configurée.",
"Operation": "Opération",
@@ -2910,7 +2901,6 @@
"Password reset and copied to clipboard: {{password}}": "Mot de passe réinitialisé et copié dans le presse-papiers : {{password}}",
"Password reset: {{password}}": "Mot de passe réinitialisé : {{password}}",
"Passwords do not match": "Les mots de passe ne correspondent pas",
"Paste the full callback URL (includes code & state)": "Coller l'URL de callback complète (inclut code et state)",
"Path": "Chemin",
"Path not set": "Chemin non défini",
"Path Regex (one per line)": "Regex du chemin (un par ligne)",
@@ -3022,7 +3012,6 @@
"Please fix JSON errors before saving": "Veuillez corriger les erreurs JSON avant denregistrer",
"Please fix the highlighted fields before saving": "Veuillez corriger les champs en surbrillance avant denregistrer",
"Please log in with the appropriate credentials": "Veuillez vous connecter avec les identifiants appropriés",
"Please manually copy and open the authorization link": "Veuillez copier et ouvrir manuellement le lien d'autorisation",
"Please select a container": "Veuillez sélectionner un conteneur",
"Please select a payment method": "Veuillez sélectionner un mode de paiement",
"Please select a primary model": "Veuillez sélectionner un modèle principal",
@@ -4071,7 +4060,6 @@
"Timeline": "Chronologie",
"times": "Fois",
"Timing": "Durée",
"Tip: The generated key is a JSON credential including access_token / refresh_token / account_id.": "Conseil : La clé générée est un identifiant JSON incluant access_token / refresh_token / account_id.",
"to access this resource.": "pour accéder à cette ressource.",
"to confirm": "pour confirmer",
"To Lower": "En minuscules",
+2 -14
View File
@@ -78,7 +78,6 @@
"1 week ago": "1週間前",
"1 year ago": "1年前",
"1. Create an application in your Gotify server": "1. Gotifyサーバーでアプリケーションを作成します",
"1) Click \"Open authorization page\" and complete login. 2) Your browser may redirect to localhost (it is OK if the page does not load). 3) Copy the full URL from the address bar and paste it below. 4) Click \"Generate credential\".": "1)「認証ページを開く」をクリックしてログイン。2) ブラウザが localhost にリダイレクトしても構いません。3) アドレスバーのURL全体をコピーして下に貼り付け。4)「認証情報を生成」をクリック。",
"10 / page": "10 / ページ",
"100 / page": "100 / ページ",
"14 Days": "14日",
@@ -627,7 +626,6 @@
"Callback Caller IP": "コールバック呼び出し元 IP",
"Callback notification URL": "コールバック通知URL",
"Callback Payment Method": "コールバック支払い方法",
"Callback URL": "コールバックURL",
"Cancel": "キャンセル",
"Cancelled": "キャンセル",
"Cancelled at": "キャンセル日時",
@@ -701,6 +699,7 @@
"Checking updates...": "更新を確認中...",
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"ChatGPT Subscription (Codex)": "ChatGPT Subscription (Codex)",
"Chinese": "中国語",
"Choose a username": "ユーザー名を選択",
"Choose an amount and payment method": "金額と支払い方法を選択してください",
@@ -782,7 +781,6 @@
"Codes copied!": "コードをコピーしました!",
"Codex": "Codex",
"Codex Account & Usage": "Codex アカウントと使用量",
"Codex Authorization": "Codex認証",
"Codex channels do not support batch creation": "Codex チャネルは一括作成をサポートしていません",
"Codex channels use an OAuth JSON credential as the key.": "CodexチャネルはOAuth JSON認証情報をキーとして使用します。",
"Codex CLI Header Passthrough": "Codex CLI ヘッダーパススルー",
@@ -955,7 +953,6 @@
"Copy all backup codes": "すべてのバックアップコードをコピー",
"Copy All Codes": "すべてのコードをコピー",
"Copy API key": "APIキーをコピー",
"Copy authorization link": "認証リンクをコピー",
"Copy Channel": "チャネルをコピー",
"Copy code": "コードをコピー",
"Copy Connection Info": "接続情報をコピー",
@@ -1039,7 +1036,6 @@
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "保存済みストアに、このプランのタイトルと価格を使って Pancake 商品を作成します。事前に支払い設定で Waffo Pancake を完全に設定する必要があります。",
"Creating...": "作成中...",
"Creation failed": "作成に失敗しました",
"Credential generated": "認証情報を生成しました",
"Credential refreshed": "認証情報を更新しました",
"Credentials": "認証情報",
"Credentials verification failed": "認証情報の検証に失敗しました",
@@ -1250,6 +1246,7 @@
"Discount rate must be greater than 0": "割引率は 0 より大きくなければなりません",
"Discount Rate:": "割引率:",
"Discount ratio for cache hits.": "キャッシュヒットに対する割引率。",
"Disclaimer: Personal use only. Do not distribute or share any credentials. This channel has prerequisites and requires prior setup; use it only if you understand the flow and risks, and comply with OpenAI's terms and policies. Credentials and configuration are for Codex CLI integration only, and are not intended for any other client, platform, or channel.": "免責事項:個人利用に限ります。認証情報を配布・共有しないでください。このチャネルには前提条件があり、事前の設定が必要です。手順とリスクを理解した上で利用し、OpenAI の利用規約および関連ポリシーを遵守してください。認証情報と設定は Codex CLI 連携専用であり、他のクライアント、プラットフォーム、またはチャネルでは利用できません。",
"Discouraged": "非推奨",
"Discover curated AI models, compare pricing and capabilities, and choose the right model for every scenario.": "厳選された AI モデルを見つけ、価格と機能を比較し、あらゆるシナリオに適したモデルを選択できます。",
"Discover the leading models in each domain": "各分野をリードするモデルを発見",
@@ -1288,7 +1285,6 @@
"Domain Filter Mode": "ドメインフィルターモード",
"Don't have an account?": "アカウントをお持ちでないですか?",
"Done": "完了",
"Doubao Coding Plan": "豆包 Coding Plan",
"Doubao custom API address editing unlocked": "豆包カスタムAPI アドレス編集がアンロックされました",
"DoubaoVideo": "DoubaoVideo",
"Double check the configuration below. Your system will be locked until initialization is complete.": "下記の設定を再確認してください。初期化が完了するまでシステムはロックされます。",
@@ -1865,9 +1861,7 @@
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "アダプターが無効になっていても、Geminiは思考モードを自動検出します。価格設定と予算編成をより細かく制御する必要がある場合にのみ、これを有効にしてください。",
"General": "一般",
"General Settings": "一般設定",
"Generate a Codex OAuth credential and paste it into the channel key field.": "Codex OAuth認証情報を生成し、チャネルキー欄に貼り付けてください。",
"Generate and manage your API access token": "API アクセス トークンを生成および管理",
"Generate credential": "認証情報を生成",
"Generate Lyrics": "歌詞を生成",
"Generate Music": "音楽を生成",
"Generate new backup codes for account recovery": "アカウント復旧用の新しいバックアップコードを生成",
@@ -2727,7 +2721,6 @@
"OAuth Client Secret": "OAuthクライアントシークレット",
"OAuth failed": "OAuth に失敗しました",
"OAuth Integrations": "OAuth連携",
"OAuth start failed": "OAuth開始に失敗しました",
"Object Prune Rules": "オブジェクト削除ルール",
"Observability": "可観測性",
"Obtain the API key, merchant ID, and RSA key pair from the Waffo dashboard, and configure the callback URL.": "Waffoダッシュボードから APIキー、マーチャントID、RSAキーペアを取得し、コールバックURLを設定してください。",
@@ -2771,7 +2764,6 @@
"Oops! Something went wrong": "おっと!何か問題が発生しました",
"Open": "開く",
"Open a source model first": "先にソースモデルを開いてください",
"Open authorization page": "認証ページを開く",
"Open CC Switch": "CC Switch を開く",
"Open in chat": "チャットで開く",
"Open in new tab": "新しいタブで開く",
@@ -2790,7 +2782,6 @@
"OpenAI, Anthropic, etc.": "OpenAI、Anthropicなど",
"OpenAI, Anthropic, Google, etc.": "OpenAI、Anthropic、Googleなど",
"OpenAIMax": "OpenAIMax",
"Opened authorization page": "認証ページを開きました",
"OpenRouter": "OpenRouter",
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "外部クライアントで開きます。サイドバーまたはAPIキーアクションからトリガーして、設定されたアプリケーションを起動します。",
"Operation": "操作",
@@ -2910,7 +2901,6 @@
"Password reset and copied to clipboard: {{password}}": "パスワードがリセットされ、クリップボードにコピーされました:{{password}}",
"Password reset: {{password}}": "パスワードがリセットされました:{{password}}",
"Passwords do not match": "パスワードが一致しません",
"Paste the full callback URL (includes code & state)": "コールバックURL全体を貼り付け(code と state を含む)",
"Path": "パス",
"Path not set": "パス未設定",
"Path Regex (one per line)": "パス正規表現(1行に1つ)",
@@ -3022,7 +3012,6 @@
"Please fix JSON errors before saving": "保存する前に JSON エラーを直してください",
"Please fix the highlighted fields before saving": "保存する前に強調表示された項目を修正してください",
"Please log in with the appropriate credentials": "適切な認証情報でログインしてください",
"Please manually copy and open the authorization link": "認証リンクを手動でコピーして開いてください",
"Please select a container": "コンテナを選択してください",
"Please select a payment method": "お支払い方法を選択してください",
"Please select a primary model": "プライマリモデルを選択してください",
@@ -4071,7 +4060,6 @@
"Timeline": "タイムライン",
"times": "回",
"Timing": "所要時間",
"Tip: The generated key is a JSON credential including access_token / refresh_token / account_id.": "ヒント:生成されたキーは access_token / refresh_token / account_id を含むJSON認証情報です。",
"to access this resource.": "このリソースにアクセスするには。",
"to confirm": "確認する",
"To Lower": "小文字に変換",
+2 -14
View File
@@ -78,7 +78,6 @@
"1 week ago": "1 неделю назад",
"1 year ago": "1 год назад",
"1. Create an application in your Gotify server": "1. Создайте приложение на вашем сервере Gotify",
"1) Click \"Open authorization page\" and complete login. 2) Your browser may redirect to localhost (it is OK if the page does not load). 3) Copy the full URL from the address bar and paste it below. 4) Click \"Generate credential\".": "1) Нажмите «Открыть страницу авторизации» и войдите. 2) Браузер может перенаправить на localhost — это нормально. 3) Скопируйте полный URL из адресной строки и вставьте ниже. 4) Нажмите «Создать учётные данные».",
"10 / page": "10 / страница",
"100 / page": "100 / страница",
"14 Days": "14 дней",
@@ -627,7 +626,6 @@
"Callback Caller IP": "IP вызывающей стороны callback",
"Callback notification URL": "URL обратного вызова",
"Callback Payment Method": "Способ оплаты (callback)",
"Callback URL": "URL обратного вызова",
"Cancel": "Отмена",
"Cancelled": "Отменено",
"Cancelled at": "Отменено",
@@ -701,6 +699,7 @@
"Checking updates...": "Проверка обновлений...",
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"ChatGPT Subscription (Codex)": "ChatGPT Subscription (Codex)",
"Chinese": "Китайский",
"Choose a username": "Выберите имя пользователя",
"Choose an amount and payment method": "Выберите сумму и способ оплаты",
@@ -782,7 +781,6 @@
"Codes copied!": "Коды скопированы!",
"Codex": "Codex",
"Codex Account & Usage": "Аккаунт и использование Codex",
"Codex Authorization": "Авторизация Codex",
"Codex channels do not support batch creation": "Каналы Codex не поддерживают пакетное создание",
"Codex channels use an OAuth JSON credential as the key.": "Каналы Codex используют учётные данные OAuth в формате JSON в качестве ключа.",
"Codex CLI Header Passthrough": "Проброс заголовков Codex CLI",
@@ -955,7 +953,6 @@
"Copy all backup codes": "Скопировать все резервные коды",
"Copy All Codes": "Скопировать все коды",
"Copy API key": "Скопировать ключ API",
"Copy authorization link": "Копировать ссылку авторизации",
"Copy Channel": "Скопировать канал",
"Copy code": "Копировать код",
"Copy Connection Info": "Копировать данные подключения",
@@ -1039,7 +1036,6 @@
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Создает продукт Pancake в сохраненном магазине с названием и ценой этого плана. Сначала необходимо полностью настроить Waffo Pancake в настройках платежей.",
"Creating...": "Создание...",
"Creation failed": "Создание не удалось",
"Credential generated": "Учётные данные созданы",
"Credential refreshed": "Учётные данные обновлены",
"Credentials": "Учетные данные",
"Credentials verification failed": "Не удалось проверить учетные данные",
@@ -1250,6 +1246,7 @@
"Discount rate must be greater than 0": "Ставка скидки должна быть больше 0",
"Discount Rate:": "Ставка скидки:",
"Discount ratio for cache hits.": "Коэффициент скидки для попаданий в кэш.",
"Disclaimer: Personal use only. Do not distribute or share any credentials. This channel has prerequisites and requires prior setup; use it only if you understand the flow and risks, and comply with OpenAI's terms and policies. Credentials and configuration are for Codex CLI integration only, and are not intended for any other client, platform, or channel.": "Предупреждение: только для личного использования. Не распространяйте и не передавайте учетные данные. Для этого канала требуются предварительные условия и начальная настройка; используйте его только если понимаете процедуру и риски, и соблюдайте условия и политики OpenAI. Учетные данные и конфигурация предназначены только для интеграции с Codex CLI и не предназначены для других клиентов, платформ или каналов.",
"Discouraged": "Не рекомендуется",
"Discover curated AI models, compare pricing and capabilities, and choose the right model for every scenario.": "Откройте для себя подобранные AI-модели, сравнивайте цены и возможности и выбирайте подходящую модель для каждого сценария.",
"Discover the leading models in each domain": "Откройте для себя ведущие модели в каждой области",
@@ -1288,7 +1285,6 @@
"Domain Filter Mode": "Режим фильтра домена",
"Don't have an account?": "У вас нет аккаунта?",
"Done": "Готово",
"Doubao Coding Plan": "План кодирования Doubao",
"Doubao custom API address editing unlocked": "Редактирование пользовательского адреса API Doubao разблокировано",
"DoubaoVideo": "DoubaoVideo",
"Double check the configuration below. Your system will be locked until initialization is complete.": "Дважды проверьте конфигурацию ниже. Ваша система будет заблокирована до завершения инициализации.",
@@ -1865,9 +1861,7 @@
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "Gemini продолжит автоматически определять режим мышления, даже если адаптер отключен. Включайте это только тогда, когда вам нужен более тонкий контроль над ценообразованием и бюджетированием.",
"General": "Общие",
"General Settings": "Общие настройки",
"Generate a Codex OAuth credential and paste it into the channel key field.": "Создайте учётные данные Codex OAuth и вставьте их в поле ключа канала.",
"Generate and manage your API access token": "Генерировать и управлять вашим токеном доступа API",
"Generate credential": "Создать учётные данные",
"Generate Lyrics": "Создать текст песни",
"Generate Music": "Создать музыку",
"Generate new backup codes for account recovery": "Сгенерировать новые резервные коды для восстановления аккаунта",
@@ -2727,7 +2721,6 @@
"OAuth Client Secret": "OAuth Client Secret",
"OAuth failed": "OAuth не удался",
"OAuth Integrations": "Интеграции OAuth",
"OAuth start failed": "Ошибка запуска OAuth",
"Object Prune Rules": "Правила очистки объектов",
"Observability": "Наблюдаемость",
"Obtain the API key, merchant ID, and RSA key pair from the Waffo dashboard, and configure the callback URL.": "Получите API-ключ, ID мерчанта и пару RSA-ключей в панели управления Waffo и настройте URL обратного вызова.",
@@ -2771,7 +2764,6 @@
"Oops! Something went wrong": "Ой! Что-то пошло не так",
"Open": "Открыть",
"Open a source model first": "Сначала откройте исходную модель",
"Open authorization page": "Открыть страницу авторизации",
"Open CC Switch": "Открыть CC Switch",
"Open in chat": "Открыть в чате",
"Open in new tab": "Открыть в новой вкладке",
@@ -2790,7 +2782,6 @@
"OpenAI, Anthropic, etc.": "OpenAI, Anthropic и т.д.",
"OpenAI, Anthropic, Google, etc.": "OpenAI, Anthropic, Google и т.д.",
"OpenAIMax": "OpenAIMax",
"Opened authorization page": "Страница авторизации открыта",
"OpenRouter": "OpenRouter",
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "открывается во внешнем клиенте. Запустите его из боковой панели или действий с ключом API, чтобы запустить настроенное приложение.",
"Operation": "Операция",
@@ -2910,7 +2901,6 @@
"Password reset and copied to clipboard: {{password}}": "Пароль сброшен и скопирован в буфер обмена: {{password}}",
"Password reset: {{password}}": "Пароль сброшен: {{password}}",
"Passwords do not match": "Пароли не совпадают",
"Paste the full callback URL (includes code & state)": "Вставьте полный callback URL (включая code и state)",
"Path": "Путь",
"Path not set": "Путь не задан",
"Path Regex (one per line)": "Регулярное выражение пути (по одному на строку)",
@@ -3022,7 +3012,6 @@
"Please fix JSON errors before saving": "Исправьте ошибки JSON перед сохранением",
"Please fix the highlighted fields before saving": "Исправьте выделенные поля перед сохранением",
"Please log in with the appropriate credentials": "Пожалуйста, войдите с соответствующими учетными данными",
"Please manually copy and open the authorization link": "Скопируйте и откройте ссылку авторизации вручную",
"Please select a container": "Пожалуйста, выберите контейнер",
"Please select a payment method": "Пожалуйста, выберите способ оплаты",
"Please select a primary model": "Пожалуйста, выберите основную модель",
@@ -4071,7 +4060,6 @@
"Timeline": "Хронология",
"times": "раз",
"Timing": "Время",
"Tip: The generated key is a JSON credential including access_token / refresh_token / account_id.": "Подсказка: сгенерированный ключ — это учётные данные JSON с access_token / refresh_token / account_id.",
"to access this resource.": "для доступа к этому ресурсу.",
"to confirm": "для подтверждения",
"To Lower": "В нижний регистр",
+2 -14
View File
@@ -78,7 +78,6 @@
"1 week ago": "1 tuần trước",
"1 year ago": "1 năm trước",
"1. Create an application in your Gotify server": "1. Tạo một ứng dụng trong máy chủ Gotify của bạn",
"1) Click \"Open authorization page\" and complete login. 2) Your browser may redirect to localhost (it is OK if the page does not load). 3) Copy the full URL from the address bar and paste it below. 4) Click \"Generate credential\".": "1) Nhấp «Mở trang ủy quyền» và đăng nhập. 2) Trình duyệt có thể chuyển tới localhost (trang không tải cũng được). 3) Sao chép toàn bộ URL từ thanh địa chỉ và dán xuống. 4) Nhấp «Tạo thông tin xác thực».",
"10 / page": "10 / trang",
"100 / page": "100 / trang",
"14 Days": "14 ngày",
@@ -627,7 +626,6 @@
"Callback Caller IP": "IP người gọi callback",
"Callback notification URL": "URL thông báo callback",
"Callback Payment Method": "Phương thức thanh toán callback",
"Callback URL": "URL callback",
"Cancel": "Hủy bỏ",
"Cancelled": "Đã hủy",
"Cancelled at": "Đã hủy lúc",
@@ -701,6 +699,7 @@
"Checking updates...": "Đang kiểm tra cập nhật...",
"checkout.session.completed": "thanh toán.phiên.hoàn thành",
"checkout.session.expired": "Phiên thanh toán đã hết hạn.",
"ChatGPT Subscription (Codex)": "ChatGPT Subscription (Codex)",
"Chinese": "Tiếng Trung",
"Choose a username": "Chọn tên người dùng",
"Choose an amount and payment method": "Chọn số tiền và phương thức thanh toán",
@@ -782,7 +781,6 @@
"Codes copied!": "Đã sao chép mã!",
"Codex": "Codex",
"Codex Account & Usage": "Tài khoản và sử dụng Codex",
"Codex Authorization": "Ủy quyền Codex",
"Codex channels do not support batch creation": "Kênh Codex không hỗ trợ tạo hàng loạt",
"Codex channels use an OAuth JSON credential as the key.": "Kênh Codex dùng thông tin xác thực OAuth JSON làm khóa.",
"Codex CLI Header Passthrough": "Chuyển tiếp header Codex CLI",
@@ -955,7 +953,6 @@
"Copy all backup codes": "Sao chép tất cả mã dự phòng",
"Copy All Codes": "Sao chép Tất cả Mã",
"Copy API key": "Sao chép khóa API",
"Copy authorization link": "Sao chép liên kết ủy quyền",
"Copy Channel": "Sao chép kênh",
"Copy code": "Sao chép mã",
"Copy Connection Info": "Sao chép thông tin kết nối",
@@ -1039,7 +1036,6 @@
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "Tạo một sản phẩm Pancake trong cửa hàng đã lưu bằng tiêu đề và giá của gói này. Trước tiên cần cấu hình đầy đủ Waffo Pancake trong cài đặt Thanh toán.",
"Creating...": "Đang tạo...",
"Creation failed": "Tạo thất bại",
"Credential generated": "Đã tạo thông tin xác thực",
"Credential refreshed": "Đã làm mới thông tin xác thực",
"Credentials": "Thông tin xác thực",
"Credentials verification failed": "Xác minh thông tin xác thực thất bại",
@@ -1250,6 +1246,7 @@
"Discount rate must be greater than 0": "Tỷ lệ giảm giá phải lớn hơn 0",
"Discount Rate:": "Tỷ lệ chiết khấu:",
"Discount ratio for cache hits.": "Tỷ lệ chiết khấu cho lượt truy cập bộ nhớ đệm thành công.",
"Disclaimer: Personal use only. Do not distribute or share any credentials. This channel has prerequisites and requires prior setup; use it only if you understand the flow and risks, and comply with OpenAI's terms and policies. Credentials and configuration are for Codex CLI integration only, and are not intended for any other client, platform, or channel.": "Tuyên bố miễn trừ: Chỉ dùng cho mục đích cá nhân. Không phân phối hoặc chia sẻ bất kỳ thông tin xác thực nào. Kênh này có điều kiện tiên quyết và yêu cầu thiết lập trước; chỉ sử dụng khi bạn hiểu rõ quy trình và rủi ro, và tuân thủ điều khoản và chính sách của OpenAI. Thông tin xác thực và cấu hình chỉ dành cho tích hợp Codex CLI, không áp dụng cho các client, nền tảng hoặc kênh khác.",
"Discouraged": "Tuyệt vọng",
"Discover curated AI models, compare pricing and capabilities, and choose the right model for every scenario.": "Khám phá các mô hình AI được tuyển chọn, so sánh giá và khả năng, rồi chọn mô hình phù hợp cho từng kịch bản.",
"Discover the leading models in each domain": "Khám phá các mô hình hàng đầu trong từng lĩnh vực",
@@ -1288,7 +1285,6 @@
"Domain Filter Mode": "Chế độ lọc miền",
"Don't have an account?": "Chưa có tài khoản?",
"Done": "Xong",
"Doubao Coding Plan": "Kế hoạch lập trình Doubao",
"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.",
@@ -1865,9 +1861,7 @@
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "Gemini sẽ tiếp tục tự động phát hiện chế độ suy nghĩ ngay cả khi bộ điều hợp bị tắt. Chỉ bật tính năng này khi bạn cần kiểm soát chi tiết hơn về giá cả và lập ngân sách.",
"General": "Chung",
"General Settings": "General settings",
"Generate a Codex OAuth credential and paste it into the channel key field.": "Tạo thông tin xác thực OAuth Codex và dán vào trường khóa kênh.",
"Generate and manage your API access token": "Tạo và quản lý mã thông báo truy cập API của bạn",
"Generate credential": "Tạo thông tin xác thực",
"Generate Lyrics": "Tạo lời bài hát",
"Generate Music": "Tạo nhạc",
"Generate new backup codes for account recovery": "Tạo mã dự phòng mới để khôi phục tài khoản",
@@ -2727,7 +2721,6 @@
"OAuth Client Secret": "Bí mật OAuth Client",
"OAuth failed": "OAuth thất bại",
"OAuth Integrations": "Tích hợp OAuth",
"OAuth start failed": "Bắt đầu OAuth thất bại",
"Object Prune Rules": "Quy tắc dọn dẹp đối tượng",
"Observability": "Khả năng quan sát",
"Obtain the API key, merchant ID, and RSA key pair from the Waffo dashboard, and configure the callback URL.": "Lấy API key, mã thương gia và cặp khóa RSA từ bảng điều khiển Waffo, đồng thời cấu hình URL callback.",
@@ -2771,7 +2764,6 @@
"Oops! Something went wrong": "Oops! An error occurred.",
"Open": "Mở",
"Open a source model first": "Mở một mô hình nguồn trước",
"Open authorization page": "Mở trang ủy quyền",
"Open CC Switch": "Mở công tắc CC",
"Open in chat": "Mở trong trò chuyện",
"Open in new tab": "Mở trong tab mới",
@@ -2790,7 +2782,6 @@
"OpenAI, Anthropic, etc.": "OpenAI, Anthropic, v.v.",
"OpenAI, Anthropic, Google, etc.": "OpenAI, Anthropic, Google, v.v.",
"OpenAIMax": "OpenAIMax",
"Opened authorization page": "Đã mở trang ủy quyền",
"OpenRouter": "OpenRouter",
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "mở trong một ứng dụng bên ngoài. Kích hoạt nó từ thanh bên hoặc các hành động khóa API để khởi chạy ứng dụng đã cấu hình.",
"Operation": "Thao tác",
@@ -2910,7 +2901,6 @@
"Password reset and copied to clipboard: {{password}}": "Mật khẩu đã đặt lại và sao chép vào clipboard: {{password}}",
"Password reset: {{password}}": "Mật khẩu đã đặt lại: {{password}}",
"Passwords do not match": "Mật khẩu không khớp",
"Paste the full callback URL (includes code & state)": "Dán toàn bộ URL callback (gồm code và state)",
"Path": "Đường dẫn",
"Path not set": "Chưa đặt đường dẫn",
"Path Regex (one per line)": "Regex đường dẫn (mỗi dòng một mục)",
@@ -3022,7 +3012,6 @@
"Please fix JSON errors before saving": "Vui lòng sửa lỗi JSON trước khi lưu",
"Please fix the highlighted fields before saving": "Vui lòng sửa các trường được đánh dấu trước khi lưu",
"Please log in with the appropriate credentials": "Vui lòng đăng nhập bằng thông tin xác thực phù hợp",
"Please manually copy and open the authorization link": "Vui lòng tự sao chép và mở liên kết ủy quyền",
"Please select a container": "Vui lòng chọn một container",
"Please select a payment method": "Vui lòng chọn phương thức thanh toán",
"Please select a primary model": "Vui lòng chọn một mô hình chính",
@@ -4071,7 +4060,6 @@
"Timeline": "Dòng thời gian",
"times": "lần",
"Timing": "Thời gian",
"Tip: The generated key is a JSON credential including access_token / refresh_token / account_id.": "Mẹo: Khóa tạo ra là thông tin xác thực JSON gồm access_token / refresh_token / account_id.",
"to access this resource.": "để truy cập tài nguyên này.",
"to confirm": "Chờ xác nhận",
"To Lower": "Chữ thường",
+2 -14
View File
@@ -78,7 +78,6 @@
"1 week ago": "1 周前",
"1 year ago": "1 年前",
"1. Create an application in your Gotify server": "1. 在您的 Gotify 服务器中创建一个应用程序",
"1) Click \"Open authorization page\" and complete login. 2) Your browser may redirect to localhost (it is OK if the page does not load). 3) Copy the full URL from the address bar and paste it below. 4) Click \"Generate credential\".": "1) 点击「打开授权页」并完成登录。2) 浏览器可能跳转到 localhost(页面打不开也正常)。3) 从地址栏复制完整 URL 粘贴到下方。4) 点击「生成凭据」。",
"10 / page": "10 条/页",
"100 / page": "100 条/页",
"14 Days": "14 天",
@@ -627,7 +626,6 @@
"Callback Caller IP": "回调调用者 IP",
"Callback notification URL": "回调通知地址",
"Callback Payment Method": "回调支付方式",
"Callback URL": "回调 URL",
"Cancel": "取消",
"Cancelled": "已取消",
"Cancelled at": "作废于",
@@ -701,6 +699,7 @@
"Checking updates...": "检查更新中...",
"checkout.session.completed": "checkout.session.completed",
"checkout.session.expired": "checkout.session.expired",
"ChatGPT Subscription (Codex)": "ChatGPT Subscription (Codex)",
"Chinese": "中文",
"Choose a username": "选择一个用户名",
"Choose an amount and payment method": "选择金额和支付方式",
@@ -782,7 +781,6 @@
"Codes copied!": "代码已复制!",
"Codex": "Codex",
"Codex Account & Usage": "Codex 账户和用量",
"Codex Authorization": "Codex 授权",
"Codex channels do not support batch creation": "Codex 渠道不支持批量创建",
"Codex channels use an OAuth JSON credential as the key.": "Codex 渠道使用 OAuth JSON 凭据作为密钥。",
"Codex CLI Header Passthrough": "Codex CLI 请求头透传",
@@ -955,7 +953,6 @@
"Copy all backup codes": "复制所有备份代码",
"Copy All Codes": "复制所有代码",
"Copy API key": "复制 API 密钥",
"Copy authorization link": "复制授权链接",
"Copy Channel": "复制渠道",
"Copy code": "复制代码",
"Copy Connection Info": "复制连接信息",
@@ -1039,7 +1036,6 @@
"Creates a Pancake product in the saved store using this plans title and price. Requires Waffo Pancake to be fully configured in Payment settings first.": "使用此套餐的标题和价格,在已保存的店铺中创建 Pancake 产品。需要先在支付设置中完整配置 Waffo Pancake。",
"Creating...": "创建中...",
"Creation failed": "创建失败",
"Credential generated": "凭据已生成",
"Credential refreshed": "凭据已刷新",
"Credentials": "凭证",
"Credentials verification failed": "凭证验证失败",
@@ -1250,6 +1246,7 @@
"Discount rate must be greater than 0": "折扣率必须大于 0",
"Discount Rate:": "折扣率:",
"Discount ratio for cache hits.": "缓存命中时的折扣比例。",
"Disclaimer: Personal use only. Do not distribute or share any credentials. This channel has prerequisites and requires prior setup; use it only if you understand the flow and risks, and comply with OpenAI's terms and policies. Credentials and configuration are for Codex CLI integration only, and are not intended for any other client, platform, or channel.": "免责声明:仅限个人使用,请勿分发或共享任何凭证。该渠道存在前置条件与使用门槛,请在充分了解流程与风险后使用,并遵守 OpenAI 的相关条款与政策。相关凭证与配置仅限接入 Codex CLI 使用,不适用于其他客户端、平台或渠道。",
"Discouraged": "不推荐",
"Discover curated AI models, compare pricing and capabilities, and choose the right model for every scenario.": "探索精选 AI 模型,清晰比较价格与能力,为不同场景选择合适的模型。",
"Discover the leading models in each domain": "发掘各个领域的领先模型",
@@ -1288,7 +1285,6 @@
"Domain Filter Mode": "域名过滤模式",
"Don't have an account?": "没有账号?",
"Done": "完成",
"Doubao Coding Plan": "豆包 Coding Plan",
"Doubao custom API address editing unlocked": "已解锁豆包自定义 API 地址编辑",
"DoubaoVideo": "DoubaoVideo",
"Double check the configuration below. Your system will be locked until initialization is complete.": "仔细检查以下配置。您的系统将在初始化完成前保持锁定状态。",
@@ -1865,9 +1861,7 @@
"Gemini will continue to auto-detect thinking mode even with the adapter disabled. Enable this only when you need finer control over pricing and budgeting.": "即使禁用适配器,Gemini 也会继续自动检测思维模式。仅当您需要对定价和预算进行更精细的控制时才启用此选项。",
"General": "常规",
"General Settings": "通用设置",
"Generate a Codex OAuth credential and paste it into the channel key field.": "生成 Codex OAuth 凭据并粘贴到渠道密钥字段。",
"Generate and manage your API access token": "生成和管理您的 API 访问令牌",
"Generate credential": "生成凭据",
"Generate Lyrics": "生成歌词",
"Generate Music": "生成音乐",
"Generate new backup codes for account recovery": "生成新的备份代码用于账户恢复",
@@ -2727,7 +2721,6 @@
"OAuth Client Secret": "OAuth 客户端密钥",
"OAuth failed": "OAuth 失败",
"OAuth Integrations": "OAuth 集成",
"OAuth start failed": "OAuth 启动失败",
"Object Prune Rules": "对象清理规则",
"Observability": "可观测性",
"Obtain the API key, merchant ID, and RSA key pair from the Waffo dashboard, and configure the callback URL.": "请在 Waffo 后台获取 API 密钥、商户 ID 以及 RSA 密钥对,并配置回调地址。",
@@ -2771,7 +2764,6 @@
"Oops! Something went wrong": "糟糕!出错了",
"Open": "打开",
"Open a source model first": "请先打开一个源模型",
"Open authorization page": "打开授权页",
"Open CC Switch": "打开 CC Switch",
"Open in chat": "在聊天中打开",
"Open in new tab": "在新标签页中打开",
@@ -2790,7 +2782,6 @@
"OpenAI, Anthropic, etc.": "OpenAI、Anthropic 等",
"OpenAI, Anthropic, Google, etc.": "OpenAI、Anthropic、Google 等",
"OpenAIMax": "OpenAIMax",
"Opened authorization page": "已打开授权页",
"OpenRouter": "OpenRouter",
"opens in an external client. Trigger it from the sidebar or API key actions to launch the configured application.": "在外部客户端中打开。从侧边栏或 API 密钥操作中触发,以启动配置的应用。",
"Operation": "操作",
@@ -2910,7 +2901,6 @@
"Password reset and copied to clipboard: {{password}}": "密码已重置并复制到剪贴板:{{password}}",
"Password reset: {{password}}": "密码已重置:{{password}}",
"Passwords do not match": "密码不匹配",
"Paste the full callback URL (includes code & state)": "粘贴完整回调 URL(含 code 和 state",
"Path": "路径",
"Path not set": "未设置路径",
"Path Regex (one per line)": "路径正则(每行一个)",
@@ -3022,7 +3012,6 @@
"Please fix JSON errors before saving": "请先修复 JSON 错误再保存",
"Please fix the highlighted fields before saving": "请先修复高亮字段后再保存",
"Please log in with the appropriate credentials": "请使用适当的凭据登录",
"Please manually copy and open the authorization link": "请手动复制并打开授权链接",
"Please select a container": "请选择一个容器",
"Please select a payment method": "请选择支付方式",
"Please select a primary model": "请选择主模型",
@@ -4071,7 +4060,6 @@
"Timeline": "时间线",
"times": "次",
"Timing": "耗时",
"Tip: The generated key is a JSON credential including access_token / refresh_token / account_id.": "提示:生成的密钥为包含 access_token / refresh_token / account_id 的 JSON 凭据。",
"to access this resource.": "访问此资源。",
"to confirm": "以确认",
"To Lower": "转小写",