feat(usage): Sub2API 中转站账单查询 + 模型发现列表全选/反选 — bump to v0.7.17
Release / Version consistency (push) Canceled after 0s
Release / Build (macos-latest) (push) Canceled after 0s
Release / Build (ubuntu-latest) (push) Canceled after 0s
Release / Build (windows-latest) (push) Canceled after 0s
Release / Attach macOS install script (push) Canceled after 0s
Release / Version consistency (push) Canceled after 0s
Release / Build (macos-latest) (push) Canceled after 0s
Release / Build (ubuntu-latest) (push) Canceled after 0s
Release / Build (windows-latest) (push) Canceled after 0s
Release / Attach macOS install script (push) Canceled after 0s
- 新增 balance:sub2api:GET {base}/v1/usage 复用推理 sk- key,解析 quota /
5h·1d·7d rate_limits / 订阅组日周月额度 / 钱包余额(USD)
- detect_provider 修正 codingplan.site 家族:主域=Sub2API,ai.=NewAPI
- UsageConfigModal 新增 Sub2API 模板(无需凭据,可选 Base URL 覆盖)
- ProviderEdit 模型发现列表新增 全选/取消全选/反选
- cargo test 124 通过(+6 sub2api 解析用例)、tsc 零错误
This commit is contained in:
1 parent
b9002281a3
commit
00cf194b5c
17 files changed
+397
-31
No files matched your search
@@ -527,6 +527,18 @@ function ModelMapping({
|
||||
});
|
||||
};
|
||||
|
||||
const selectAllModels = () => {
|
||||
if (!discovered) return;
|
||||
setSelected(new Set(discovered.map((m) => m.id)));
|
||||
};
|
||||
|
||||
const deselectAllModels = () => setSelected(new Set());
|
||||
|
||||
const invertSelection = () => {
|
||||
if (!discovered) return;
|
||||
setSelected((prev) => new Set(discovered.filter((m) => !prev.has(m.id)).map((m) => m.id)));
|
||||
};
|
||||
|
||||
const handleAddSelected = () => {
|
||||
if (!discovered || selected.size === 0) return;
|
||||
const existingIds = new Set(models.filter((m) => m.model).map((m) => m.model));
|
||||
@@ -596,8 +608,33 @@ function ModelMapping({
|
||||
|
||||
{discovered && (
|
||||
<div className="bg-panel border border-border rounded-xl p-4 space-y-3">
|
||||
<div className="text-sm font-medium">
|
||||
{t("discoveredModels", { count: discovered.length })}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="text-sm font-medium">
|
||||
{t("discoveredModels", { count: discovered.length })}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<button
|
||||
type="button"
|
||||
onClick={selectAllModels}
|
||||
className="px-2 py-0.5 text-xs border border-border rounded hover:bg-hover-2 text-content-primary transition-colors"
|
||||
>
|
||||
{t("selectAll")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={deselectAllModels}
|
||||
className="px-2 py-0.5 text-xs border border-border rounded hover:bg-hover-2 text-content-primary transition-colors"
|
||||
>
|
||||
{t("deselectAll")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={invertSelection}
|
||||
className="px-2 py-0.5 text-xs border border-border rounded hover:bg-hover-2 text-content-primary transition-colors"
|
||||
>
|
||||
{t("invertSelection")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<label className="flex items-center gap-2 text-sm text-content-primary cursor-pointer">
|
||||
<input
|
||||
|
||||
@@ -30,12 +30,13 @@ interface UsageConfigModalProps {
|
||||
}
|
||||
|
||||
const TEMPLATES: ReadonlyArray<{
|
||||
id: "auto" | "newapi";
|
||||
id: "auto" | "newapi" | "sub2api";
|
||||
labelKey: TranslationKey;
|
||||
hintKey: TranslationKey;
|
||||
}> = [
|
||||
{ id: "auto", labelKey: "usageTemplateAuto", hintKey: "usageAutoDetectHint" },
|
||||
{ id: "newapi", labelKey: "usageTemplateNewapi", hintKey: "usageNewapiHint" },
|
||||
{ id: "sub2api", labelKey: "usageTemplateSub2api", hintKey: "usageSub2apiHint" },
|
||||
];
|
||||
|
||||
function defaultConfig(provider: Provider): UsageConfig {
|
||||
@@ -225,7 +226,9 @@ export function UsageConfigModal({ open, agent, provider, onClose, onSave }: Usa
|
||||
<p className="text-xs text-content-muted">
|
||||
{cfg.templateType === "newapi"
|
||||
? t("usageNewapiHint")
|
||||
: t("usageAutoDetectHint")}
|
||||
: cfg.templateType === "sub2api"
|
||||
? t("usageSub2apiHint")
|
||||
: t("usageAutoDetectHint")}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
@@ -237,9 +240,9 @@ export function UsageConfigModal({ open, agent, provider, onClose, onSave }: Usa
|
||||
<span className="text-blue-500">{"{{baseUrl}}"}</span>
|
||||
<span className="text-content-muted">=</span>
|
||||
<span className="text-content-primary break-all">
|
||||
{cfg.templateType === "newapi"
|
||||
? cfg.baseUrl || provider.base_url || "-"
|
||||
: provider.base_url || "-"}
|
||||
{cfg.templateType === "auto"
|
||||
? provider.base_url || "-"
|
||||
: cfg.baseUrl || provider.base_url || "-"}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs font-mono">
|
||||
@@ -344,6 +347,21 @@ export function UsageConfigModal({ open, agent, provider, onClose, onSave }: Usa
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* sub2api: no extra credentials (reuses the inference key), optional gateway override */}
|
||||
{!isManaged && cfg.templateType === "sub2api" && (
|
||||
<div className="space-y-3">
|
||||
<div>
|
||||
<label className="block text-sm text-content-muted mb-1.5">{t("usageBaseUrl")}</label>
|
||||
<input
|
||||
type="text"
|
||||
className="w-full bg-input border border-border rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
|
||||
value={cfg.baseUrl ?? ""}
|
||||
placeholder={provider.base_url ?? "https://your-sub2api-site.com"}
|
||||
onChange={(e) => setCfg({ ...cfg, baseUrl: e.target.value || undefined })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* timeout + auto query interval */}
|
||||
|
||||
@@ -15,6 +15,7 @@ export type UsageKind =
|
||||
| "balance:novita"
|
||||
| "balance:kimi"
|
||||
| "balance:newapi"
|
||||
| "balance:sub2api"
|
||||
| "plan:kimi_coding"
|
||||
| "plan:zhipu"
|
||||
| "plan:minimax"
|
||||
@@ -32,6 +33,7 @@ export const SUPPORTED_USAGE_KINDS: ReadonlySet<string> = new Set<UsageKind>([
|
||||
"balance:novita",
|
||||
"balance:kimi",
|
||||
"balance:newapi",
|
||||
"balance:sub2api",
|
||||
"plan:kimi_coding",
|
||||
"plan:zhipu",
|
||||
"plan:minimax",
|
||||
|
||||
@@ -190,6 +190,7 @@ export const enTranslations: Record<TranslationKey, string> = {
|
||||
statusAll: "All",
|
||||
selectAll: "Select all",
|
||||
deselectAll: "Deselect all",
|
||||
invertSelection: "Invert selection",
|
||||
selectedCount: "{n} selected",
|
||||
archive: "Archive",
|
||||
unarchive: "Unarchive",
|
||||
@@ -408,8 +409,10 @@ export const enTranslations: Record<TranslationKey, string> = {
|
||||
usageTemplate: "Query template",
|
||||
usageTemplateAuto: "Auto-detect",
|
||||
usageTemplateNewapi: "NewAPI gateway",
|
||||
usageTemplateSub2api: "Sub2API gateway",
|
||||
usageAutoDetectHint: "Query balance / plan with the provider's API Key and Base URL",
|
||||
usageNewapiHint: "Query a NewAPI / OneAPI gateway balance (requires a web-console Access Token, not the sk- inference key)",
|
||||
usageSub2apiHint: "Query a Sub2API (Wei-Shaw/sub2api) gateway balance / quota (GET /v1/usage). Reuses the inference sk- API key — no extra credentials; the query URL is optionally overridable",
|
||||
usageOAuthManagedHint: "Official OAuth managed: queries the subscription with your Kimi Code login credentials",
|
||||
usageDetectedKinds: "Detected query kinds",
|
||||
usageNoKinds: "Not detected (switch to the NewAPI template to configure manually)",
|
||||
@@ -430,6 +433,7 @@ export const enTranslations: Record<TranslationKey, string> = {
|
||||
usageSwitchToNewapi: "Switch to NewAPI template",
|
||||
usageUnsupportedProvider: "This provider is not supported by auto query",
|
||||
usageTier5h: "5-hour",
|
||||
usageTierDaily: "Daily",
|
||||
usageTierWeekly: "Weekly",
|
||||
usageTierMonthly: "Monthly",
|
||||
usageErrNoKey: "No API key configured",
|
||||
|
||||
@@ -187,6 +187,7 @@ export const zhTranslations = {
|
||||
statusAll: "全部",
|
||||
selectAll: "全选",
|
||||
deselectAll: "取消全选",
|
||||
invertSelection: "反选",
|
||||
selectedCount: "已选 {n} 项",
|
||||
archive: "归档",
|
||||
unarchive: "取消归档",
|
||||
@@ -403,8 +404,10 @@ export const zhTranslations = {
|
||||
usageTemplate: "查询模板",
|
||||
usageTemplateAuto: "自动检测",
|
||||
usageTemplateNewapi: "NewAPI 中转站",
|
||||
usageTemplateSub2api: "Sub2API 中转站",
|
||||
usageAutoDetectHint: "自动使用供应商的 API Key 和 Base URL 查询余额 / 套餐",
|
||||
usageNewapiHint: "查询 NewAPI / OneAPI 中转站的账号余额(需网页后台 Access Token,不是 sk- 推理 Key)",
|
||||
usageSub2apiHint: "查询 Sub2API (Wei-Shaw/sub2api) 面板的余额 / 额度(GET /v1/usage),自动复用推理 sk- API Key,无需额外凭据;可选覆盖查询地址",
|
||||
usageOAuthManagedHint: "官方 OAuth 托管:自动使用 Kimi Code 登录凭据查询订阅用量",
|
||||
usageDetectedKinds: "检测到的查询类型",
|
||||
usageNoKinds: "未识别(可切换到 NewAPI 模板手动配置)",
|
||||
@@ -425,6 +428,7 @@ export const zhTranslations = {
|
||||
usageSwitchToNewapi: "切换到 NewAPI 模板",
|
||||
usageUnsupportedProvider: "此供应商暂不支持自动查询",
|
||||
usageTier5h: "5小时",
|
||||
usageTierDaily: "每日",
|
||||
usageTierWeekly: "7天",
|
||||
usageTierMonthly: "30天",
|
||||
usageErrNoKey: "未配置 API Key",
|
||||
|
||||
@@ -19,6 +19,8 @@ export function planLabel(name: string, t: TranslateFn): string {
|
||||
switch (name) {
|
||||
case "five_hour":
|
||||
return t("usageTier5h");
|
||||
case "daily_limit":
|
||||
return t("usageTierDaily");
|
||||
case "weekly_limit":
|
||||
return t("usageTierWeekly");
|
||||
case "monthly_limit":
|
||||
|
||||
+3
-2
@@ -75,11 +75,12 @@ export interface Config {
|
||||
/**
|
||||
* Usage query configuration (mirrors the Rust `UsageConfig`, camelCase).
|
||||
* "auto" = query kinds from usageKinds/host detect; "newapi" = query a
|
||||
* NewAPI/OneAPI gateway with accessToken + userId.
|
||||
* NewAPI/OneAPI gateway with accessToken + userId; "sub2api" = query a
|
||||
* Sub2API (Wei-Shaw/sub2api) gateway, reusing the inference API key.
|
||||
*/
|
||||
export interface UsageConfig {
|
||||
enabled: boolean;
|
||||
templateType: "auto" | "newapi";
|
||||
templateType: "auto" | "newapi" | "sub2api";
|
||||
baseUrl?: string;
|
||||
/** NewAPI web-console access token (NOT the sk- inference key). */
|
||||
accessToken?: string;
|
||||
|
||||
Reference in new issue
Block a user