mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-07-27 16:26:16 +08:00
feat(providers): add Volcengine Ark usage query with AK/SK signing
Query coding-plan / agent-plan usage for Volcengine Ark via the control-plane OpenAPI (open.volcengineapi.com), which requires account-level AccessKey signing rather than the inference API key. - Implement Volcengine Signature V4 (an AWS SigV4 variant: fixed canonical header order, "HMAC-SHA256" algorithm without the AWS4 prefix, scope ending in "request", service "ark") in services/coding_plan.rs - Auto-detect the plan by probing GetAFPUsage (Agent Plan, AFP five-hour/weekly/monthly quotas) first, falling back to GetCodingPlanUsage (Coding Plan, session/weekly/monthly percentages); a single credential covers whichever plan the account holds - Parse the real response shape: the window label lives in the `Level` field; guard ResetTimestamp <= 0 (session returns -1) - Store account-level credentials on UsageScript (accessKeyId / secretAccessKey), threaded through both the test command and the TOKEN_PLAN auto-refresh path - Add an independent AK/SK input block with a detailed hint pointing to the Volcengine console -> API Access Keys - Add the `monthly` tier label (frontend TIER_I18N_KEYS + tray TIER_LABEL_GROUPS + subscription service) - Sync zh / en / ja / zh-TW locales and the usage-query docs
This commit is contained in:
@@ -33,6 +33,8 @@ export const TIER_I18N_KEYS: Record<string, string> = {
|
||||
gemini_flash_lite: "subscription.geminiFlashLite",
|
||||
// Token Plan(five_hour 已在上方官方映射中)
|
||||
weekly_limit: "subscription.sevenDay",
|
||||
// 火山方舟 Agent Plan / Coding Plan 的月窗口
|
||||
monthly: "subscription.monthly",
|
||||
// GitHub Copilot
|
||||
premium: "subscription.copilotPremium",
|
||||
};
|
||||
|
||||
@@ -538,8 +538,10 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
|
||||
// Coding Plan 模板使用专用 API
|
||||
if (selectedTemplate === TEMPLATE_TYPES.TOKEN_PLAN) {
|
||||
// ZenMux 使用用户在脚本配置中手动填入的 API Key 和 Base URL
|
||||
// ZenMux 手填 baseUrl/apiKey;火山是 native 供应商,baseUrl 走推理配置,
|
||||
// 另用账号 AK/SK 签名查询控制面用量。
|
||||
const isZenMux = script.codingPlanProvider === "zenmux";
|
||||
const isVolcengine = script.codingPlanProvider === "volcengine";
|
||||
const baseUrl = isZenMux
|
||||
? (script.baseUrl ?? "")
|
||||
: (providerCredentials.baseUrl ?? "");
|
||||
@@ -547,7 +549,12 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
? (script.apiKey ?? "")
|
||||
: (providerCredentials.apiKey ?? "");
|
||||
const { subscriptionApi } = await import("@/lib/api/subscription");
|
||||
const quota = await subscriptionApi.getCodingPlanQuota(baseUrl, apiKey);
|
||||
const quota = await subscriptionApi.getCodingPlanQuota(
|
||||
baseUrl,
|
||||
apiKey,
|
||||
isVolcengine ? script.accessKeyId : undefined,
|
||||
isVolcengine ? script.secretAccessKey : undefined,
|
||||
);
|
||||
if (quota.success && quota.tiers.length > 0) {
|
||||
const summary = quota.tiers
|
||||
.map((tier) => `${tier.name}: ${Math.round(tier.utilization)}%`)
|
||||
@@ -726,8 +733,9 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
providerCredentials.baseUrl,
|
||||
);
|
||||
const provider = script.codingPlanProvider || autoDetected || "kimi";
|
||||
// ZenMux 允许手动填写 API Key 和 Base URL,不清除
|
||||
// ZenMux 保留手填 baseUrl/apiKey;火山保留账号 AK/SK;其余清除。
|
||||
const isZenMux = provider === "zenmux";
|
||||
const isVolcengine = provider === "volcengine";
|
||||
setScript({
|
||||
...script,
|
||||
code: "",
|
||||
@@ -735,6 +743,8 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
baseUrl: isZenMux ? script.baseUrl : undefined,
|
||||
accessToken: undefined,
|
||||
userId: undefined,
|
||||
accessKeyId: isVolcengine ? script.accessKeyId : undefined,
|
||||
secretAccessKey: isVolcengine ? script.secretAccessKey : undefined,
|
||||
codingPlanProvider: provider,
|
||||
});
|
||||
} else if (presetName === TEMPLATE_TYPES.BALANCE) {
|
||||
@@ -1246,6 +1256,83 @@ const UsageScriptModal: React.FC<UsageScriptModalProps> = ({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 火山方舟:控制面用量查询需账号 AK/SK(与推理 Key 是两套凭据) */}
|
||||
{selectedTemplate === TEMPLATE_TYPES.TOKEN_PLAN &&
|
||||
script.codingPlanProvider === "volcengine" && (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<h4 className="text-sm font-medium text-foreground">
|
||||
{t("usageScript.credentialsConfig")}
|
||||
</h4>
|
||||
<p className="text-xs text-muted-foreground mt-1 leading-relaxed">
|
||||
{t("usageScript.volcengineAkSkHint")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="usage-volcengine-ak">
|
||||
{t("usageScript.accessKeyId")}
|
||||
</Label>
|
||||
<Input
|
||||
id="usage-volcengine-ak"
|
||||
type="text"
|
||||
value={script.accessKeyId || ""}
|
||||
onChange={(e) =>
|
||||
setScript({
|
||||
...script,
|
||||
accessKeyId: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="AKLT..."
|
||||
autoComplete="off"
|
||||
className="border-white/10"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="usage-volcengine-sk">
|
||||
{t("usageScript.secretAccessKey")}
|
||||
</Label>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id="usage-volcengine-sk"
|
||||
type={showApiKey ? "text" : "password"}
|
||||
value={script.secretAccessKey || ""}
|
||||
onChange={(e) =>
|
||||
setScript({
|
||||
...script,
|
||||
secretAccessKey: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder="••••••••"
|
||||
autoComplete="off"
|
||||
className="border-white/10"
|
||||
/>
|
||||
{script.secretAccessKey && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowApiKey(!showApiKey)}
|
||||
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground transition-colors"
|
||||
aria-label={
|
||||
showApiKey
|
||||
? t("apiKeyInput.hide")
|
||||
: t("apiKeyInput.show")
|
||||
}
|
||||
>
|
||||
{showApiKey ? (
|
||||
<EyeOff size={16} />
|
||||
) : (
|
||||
<Eye size={16} />
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 通用配置(始终显示) */}
|
||||
<div className="grid gap-4 md:grid-cols-2 pt-4 border-t border-white/10">
|
||||
{/* 超时时间 */}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { TEMPLATE_TYPES } from "@/config/constants";
|
||||
|
||||
export interface CodingPlanProviderEntry {
|
||||
/** 与后端 QuotaTier 的 `codingPlanProvider` 取值对齐 */
|
||||
id: "kimi" | "zhipu" | "minimax" | "zenmux";
|
||||
id: "kimi" | "zhipu" | "minimax" | "zenmux" | "volcengine";
|
||||
/** UsageScriptModal 下拉显示用 */
|
||||
label: string;
|
||||
/** base_url 匹配规则 */
|
||||
@@ -35,6 +35,14 @@ export const CODING_PLAN_PROVIDERS: readonly CodingPlanProviderEntry[] = [
|
||||
label: "ZenMux",
|
||||
pattern: /zenmux\./i,
|
||||
},
|
||||
{
|
||||
// 火山方舟 Agent Plan / Coding Plan。base_url 形如
|
||||
// ark.cn-beijing.volces.com/api/coding[/v3];与后端 detect_provider 的
|
||||
// `volces.com/api/coding` 子串判断同效。
|
||||
id: "volcengine",
|
||||
label: "火山方舟 (Volcengine)",
|
||||
pattern: /volces\.com\/api\/coding/i,
|
||||
},
|
||||
] as const;
|
||||
|
||||
/** 根据 Base URL 自动检测 Coding Plan 供应商;未命中返回 null */
|
||||
|
||||
@@ -1557,6 +1557,9 @@
|
||||
"accessTokenPlaceholder": "Generate in 'Security Settings'",
|
||||
"userId": "User ID",
|
||||
"userIdPlaceholder": "e.g., 114514",
|
||||
"accessKeyId": "AccessKey ID",
|
||||
"secretAccessKey": "SecretAccessKey",
|
||||
"volcengineAkSkHint": "Volcengine usage query needs an account-level AccessKey ID / Secret (different from the inference API key). Create them in the Volcengine console via the top-right account menu → 'API Access Keys'.",
|
||||
"defaultPlan": "Default Plan",
|
||||
"queryFailedMessage": "Query failed",
|
||||
"queryScript": "Query script (JavaScript)",
|
||||
@@ -2856,6 +2859,7 @@
|
||||
"geminiFlash": "Flash",
|
||||
"geminiFlashLite": "Flash Lite",
|
||||
"weeklyLimit": "Weekly",
|
||||
"monthly": "Monthly",
|
||||
"copilotPremium": "Premium",
|
||||
"utilization": "{{value}}%",
|
||||
"resetsIn": "Resets in {{time}}",
|
||||
|
||||
@@ -1557,6 +1557,9 @@
|
||||
"accessTokenPlaceholder": "「Security Settings」で生成",
|
||||
"userId": "ユーザー ID",
|
||||
"userIdPlaceholder": "例: 114514",
|
||||
"accessKeyId": "AccessKey ID",
|
||||
"secretAccessKey": "SecretAccessKey",
|
||||
"volcengineAkSkHint": "Volcengine の使用量照会にはアカウントレベルの AccessKey ID / Secret が必要です(推論 API キーとは別物)。Volcengine コンソール右上のアカウントメニュー →「API アクセスキー」で作成してください。",
|
||||
"defaultPlan": "デフォルトプラン",
|
||||
"queryFailedMessage": "照会に失敗しました",
|
||||
"queryScript": "照会スクリプト (JavaScript)",
|
||||
@@ -2856,6 +2859,7 @@
|
||||
"geminiFlash": "Flash",
|
||||
"geminiFlashLite": "Flash Lite",
|
||||
"weeklyLimit": "週間",
|
||||
"monthly": "月間",
|
||||
"copilotPremium": "プレミアム",
|
||||
"utilization": "{{value}}%",
|
||||
"resetsIn": "{{time}}後にリセット",
|
||||
|
||||
@@ -1529,6 +1529,9 @@
|
||||
"accessTokenPlaceholder": "在「安全設定」裡產生",
|
||||
"userId": "使用者 ID",
|
||||
"userIdPlaceholder": "例如:114514",
|
||||
"accessKeyId": "AccessKey ID",
|
||||
"secretAccessKey": "SecretAccessKey",
|
||||
"volcengineAkSkHint": "火山用量查詢需帳號級 AccessKey ID / Secret(與推理 API Key 不同)。請在火山引擎主控台右上角帳號選單 →「API存取金鑰」中建立。",
|
||||
"defaultPlan": "預設方案",
|
||||
"queryFailedMessage": "查詢失敗",
|
||||
"queryScript": "查詢腳本(JavaScript)",
|
||||
@@ -2828,6 +2831,7 @@
|
||||
"geminiFlash": "Flash",
|
||||
"geminiFlashLite": "Flash Lite",
|
||||
"weeklyLimit": "每週",
|
||||
"monthly": "每月",
|
||||
"copilotPremium": "進階請求",
|
||||
"utilization": "{{value}}%",
|
||||
"resetsIn": "{{time}} 後重設",
|
||||
|
||||
@@ -1557,6 +1557,9 @@
|
||||
"accessTokenPlaceholder": "在'安全设置'里生成",
|
||||
"userId": "用户 ID",
|
||||
"userIdPlaceholder": "例如:114514",
|
||||
"accessKeyId": "AccessKey ID",
|
||||
"secretAccessKey": "SecretAccessKey",
|
||||
"volcengineAkSkHint": "火山用量查询需账号级 AccessKey ID / Secret(与推理 API Key 不同)。请在火山引擎控制台右上角账号菜单 →「API访问密钥」中创建。",
|
||||
"defaultPlan": "默认套餐",
|
||||
"queryFailedMessage": "查询失败",
|
||||
"queryScript": "查询脚本(JavaScript)",
|
||||
@@ -2856,6 +2859,7 @@
|
||||
"geminiFlash": "Flash",
|
||||
"geminiFlashLite": "Flash Lite",
|
||||
"weeklyLimit": "每周",
|
||||
"monthly": "每月",
|
||||
"copilotPremium": "高级请求",
|
||||
"utilization": "{{value}}%",
|
||||
"resetsIn": "{{time}}后重置",
|
||||
|
||||
@@ -9,8 +9,16 @@ export const subscriptionApi = {
|
||||
getCodingPlanQuota: (
|
||||
baseUrl: string,
|
||||
apiKey: string,
|
||||
// 火山方舟用账号 AK/SK 签名查询用量;其他供应商不传。
|
||||
accessKeyId?: string,
|
||||
secretAccessKey?: string,
|
||||
): Promise<SubscriptionQuota> =>
|
||||
invoke("get_coding_plan_quota", { baseUrl, apiKey }),
|
||||
invoke("get_coding_plan_quota", {
|
||||
baseUrl,
|
||||
apiKey,
|
||||
accessKeyId,
|
||||
secretAccessKey,
|
||||
}),
|
||||
getBalance: (
|
||||
baseUrl: string,
|
||||
apiKey: string,
|
||||
|
||||
@@ -62,6 +62,8 @@ export interface UsageScript {
|
||||
baseUrl?: string; // 用量查询专用的 Base URL(通用和 NewAPI 模板使用)
|
||||
accessToken?: string; // 访问令牌(NewAPI 模板使用)
|
||||
userId?: string; // 用户ID(NewAPI 模板使用)
|
||||
accessKeyId?: string; // 火山方舟 AccessKey ID(用量查询签名用,与推理 Key 分离)
|
||||
secretAccessKey?: string; // 火山方舟 SecretAccessKey
|
||||
codingPlanProvider?: string; // Coding Plan 供应商标识(如 "kimi", "zhipu", "minimax")
|
||||
autoQueryInterval?: number; // 自动查询间隔(单位:分钟,0 表示禁用)
|
||||
autoIntervalMinutes?: number; // 自动查询间隔(分钟)- 别名字段
|
||||
|
||||
Reference in New Issue
Block a user