From 2464500ef56da548a076855be48fcdecfee6622a Mon Sep 17 00:00:00 2001 From: KimiSwitch Dev Date: Sat, 1 Aug 2026 10:51:54 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=A3=80=E6=9F=A5=E9=80=BB=E8=BE=91=20+=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=8C=89=E9=87=8F=E4=BD=99=E9=A2=9D=E6=98=BE=E7=A4=BA=EF=BC=88?= =?UTF-8?q?v0.6.5=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/HANDOVER-usage-billing.md | 7 ++ package.json | 2 +- src-tauri/Cargo.lock | 2 +- src-tauri/Cargo.toml | 2 +- src-tauri/src/commands.rs | 60 ++++++++++----- src-tauri/tauri.conf.json | 2 +- src/components/SettingsModal.tsx | 90 ++++++++++++++++++---- src/components/UsageFooter.tsx | 15 +++- src/components/dashboard/DashboardPage.tsx | 14 +--- src/components/sessions/SessionsPage.tsx | 11 --- src/i18n/en.ts | 3 +- src/i18n/zh.ts | 3 +- 12 files changed, 141 insertions(+), 70 deletions(-) diff --git a/docs/HANDOVER-usage-billing.md b/docs/HANDOVER-usage-billing.md index 824af73..57df607 100644 --- a/docs/HANDOVER-usage-billing.md +++ b/docs/HANDOVER-usage-billing.md @@ -11,6 +11,13 @@ - GitHub Actions Release workflow 已触发(三平台构建 → Release draft 自动上传 msi/dmg/AppImage/deb/rpm) - 本地 0.6.4 安装包:`src-tauri/target/release/bundle/{nsis,msi}/` +**已提交并发布 v0.6.5**: +- `feat: 优化更新检查逻辑 + 修复按量余额显示(v0.6.5)` +- 检查更新优先私有库(git.codingplan.site),8s 超时自动回退 GitHub +- 设置-关于:合并移植声明为一句、新增 cc-switch 链接(openUrl + window.open 兜底)、移除仪表盘/会话页脚重复声明 +- 修复按量余额不显示:UsageFooter isPlan 判定增加配额字段(total/used)条件——余额条目(DeepSeek/StepFun/SiliconFlow/Novita/Kimi 开放平台)现在正确显示 `💰 ¥x.xx`,套餐类(有 total/used)不受影响。实测 DeepSeek 官方接口返回 200 + balance_infos +- 两仓库 main + Dev_20260801 + tag v0.6.5 已同步 + **历史提交**:`12dcb17` — docs: 账单查询适配调研 + OpenCode Zen 预设补推荐链接 **工作区未提交改动**——用户硬约束:**实现完成不提交,等用户逐批测试确认后才提交**。 diff --git a/package.json b/package.json index a3029d8..d768b80 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "kimiswitch", "private": true, - "version": "0.6.4", + "version": "0.6.5", "type": "module", "scripts": { "dev": "vite", diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index c4476cc..9377873 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -1958,7 +1958,7 @@ dependencies = [ [[package]] name = "kimiswitch" -version = "0.6.4" +version = "0.6.5" dependencies = [ "anyhow", "chrono", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 841be4c..21019d7 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "kimiswitch" -version = "0.6.4" +version = "0.6.5" description = "Kimi Switch - model config manager" authors = ["you"] edition = "2021" diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index b3a118c..45da9c3 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -801,25 +801,55 @@ fn version_lt(current: &str, latest: &str) -> bool { #[tauri::command] pub async fn check_for_update() -> Result { let current = env!("CARGO_PKG_VERSION").to_string(); - // GitHub Releases API for billowliu2/KimiSwitch. Historical versions - // (v0.5.x and earlier) live on git.codingplan.site and are not exposed - // here — only v0.6.0+ is published via GitHub. - let url = "https://api.github.com/repos/billowliu2/KimiSwitch/releases?per_page=1"; + // Prefer the private Gitea repo (primary release channel, all versions + // since v0.1.0). Fall back to GitHub Releases when it is unreachable + // (e.g. off the LAN/VPN) — v0.6.0+ is mirrored there. + let sources = [ + "https://git.codingplan.site/api/v1/repos/admin/KimiCodeSwitch/releases?limit=1", + "https://api.github.com/repos/billowliu2/KimiSwitch/releases?per_page=1", + ]; - // GitHub returns 403 without a User-Agent header. Bundle the app version - // so the source is identifiable in any rate-limit / abuse reports. + // Both hosts require a User-Agent header (GitHub returns 403 without one). + // Bundle the app version so the source is identifiable in any rate-limit + // / abuse reports. A short timeout keeps the fallback responsive when the + // private server is unreachable. let client = reqwest::Client::builder() .user_agent(concat!("KimiSwitch/", env!("CARGO_PKG_VERSION"))) + .timeout(std::time::Duration::from_secs(8)) .build() .map_err(|e| format!("client build failed: {e}"))?; - let resp = client + let mut errors: Vec = Vec::new(); + for url in sources { + match fetch_latest_release(&client, url).await { + Ok((latest, release_url, download_url)) => { + let update_available = version_lt(¤t, &latest); + return Ok(UpdateInfo { + current, + latest, + update_available, + release_url, + download_url, + }); + } + Err(e) => errors.push(format!("{url}: {e}")), + } + } + + Err(format!("all update sources failed: {}", errors.join("; "))) +} + +/// GET a releases API (Gitea or GitHub, same array shape) and extract the +/// latest tag, release page URL and the installer asset for the current OS. +async fn fetch_latest_release( + client: &reqwest::Client, + url: &str, +) -> Result<(String, String, Option), String> { + let releases: serde_json::Value = client .get(url) .send() .await - .map_err(|e| format!("request failed: {e}"))?; - - let releases: serde_json::Value = resp + .map_err(|e| format!("request failed: {e}"))? .json() .await .map_err(|e| format!("parse failed: {e}"))?; @@ -846,15 +876,7 @@ pub async fn check_for_update() -> Result { .and_then(|a| a.as_array()) .and_then(|a| pick_asset_for_current_os(a)); - let update_available = version_lt(¤t, &latest); - - Ok(UpdateInfo { - current, - latest, - update_available, - release_url, - download_url, - }) + Ok((latest, release_url, download_url)) } // --------------------------------------------------------------------------- diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index e355f30..4f612b8 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "productName": "Kimi Switch", - "version": "0.6.4", + "version": "0.6.5", "identifier": "com.kimiswitch.app", "build": { "beforeDevCommand": "npm run dev", diff --git a/src/components/SettingsModal.tsx b/src/components/SettingsModal.tsx index 2f8b13e..7ca828e 100644 --- a/src/components/SettingsModal.tsx +++ b/src/components/SettingsModal.tsx @@ -110,6 +110,15 @@ export function SettingsModal({ invoke("open_installer", { path: downloadedPath }).catch(() => {}); }; + // Open an external link in the system browser. Falls back to a new tab + // when the opener plugin call fails, so a broken click never stays silent. + const openExternal = (url: string) => { + openUrl(url).catch(() => { + const w = window.open(url, "_blank"); + if (!w) console.error(`failed to open ${url}`); + }); + }; + if (!open) return null; const themeOptions: { value: Theme; icon: ReactNode; label: string }[] = [ @@ -253,26 +262,66 @@ export function SettingsModal({ type="button" onClick={onCheckUpdate} disabled={checking} - className="px-3 py-1.5 text-sm rounded border border-border hover:bg-hover-2 disabled:opacity-50 transition-colors" + className="px-3 py-1.5 text-sm rounded border border-border hover:bg-hover-2 disabled:opacity-50 transition-colors flex items-center gap-1.5" > + + + + {checking ? t("checking") : t("checkUpdate")} {updateInfo?.updateAvailable && downloadProgress === null && !downloadedPath && ( )} {downloadedPath && ( )} @@ -323,18 +372,25 @@ export function SettingsModal({
Kimi Switch
{t("appDescription")}
v{appVersion} · MIT License
-
- {t("referenceProject")}: - - (MIT, © JochenYang) +
+
+ {t("attributionPorted")}{" "} + kimicode-dashboard + {t("attributionSuffix")} +
+
+ {t("referenceProject")}: + + (MIT, © Jason Young) +
diff --git a/src/components/UsageFooter.tsx b/src/components/UsageFooter.tsx index 4918e78..8cf1753 100644 --- a/src/components/UsageFooter.tsx +++ b/src/components/UsageFooter.tsx @@ -245,7 +245,13 @@ export function UsageFooter({ const compactSummary = (): { icon: string; text: string; color: string } | null => { const d = data[0]; if (!d) return null; - const isPlan = !!d.planName && d.planName !== "NewAPI"; + // A "plan" is a quota tier (used/total, e.g. 5h / weekly limit). Balance + // entries carry only remaining + unit — their plan_name is a currency / + // brand label and must render as an amount, not a percentage. + const isPlan = + !!d.planName && + d.planName !== "NewAPI" && + (d.total != null || d.used != null); if (d.isValid === false) { return { icon: "⚡", text: t("usageInvalidKey"), color: "text-red-500 dark:text-red-400" }; } @@ -360,7 +366,12 @@ export function UsageFooter({ }`} > {data.map((d, i) => { - const isPlan = !!d.planName; + // Same plan-vs-balance rule as compactSummary: balance rows have no + // quota fields (total/used), so they render as an amount. + const isPlan = + !!d.planName && + d.planName !== "NewAPI" && + (d.total != null || d.used != null); const pct = isPlan ? percentOf(d) : null; const color = pct == null diff --git a/src/components/dashboard/DashboardPage.tsx b/src/components/dashboard/DashboardPage.tsx index 999a9ce..7811081 100644 --- a/src/components/dashboard/DashboardPage.tsx +++ b/src/components/dashboard/DashboardPage.tsx @@ -1,5 +1,4 @@ import { useState, type ReactNode } from "react"; -import { openUrl } from "@tauri-apps/plugin-opener"; import { useDashboard, type DashboardRange } from "../../hooks/useDashboard"; import { useTranslation } from "../../i18n"; import { fmtInt, fmtPct, fmtTime, fmtTokens, fmtUsd } from "../../lib/dashboard-format"; @@ -420,23 +419,12 @@ export function DashboardPage() { )} - {/* Footer — meta + attribution at the end of the content flow */} + {/* Footer — meta at the end of the content flow */}
{t("footerScanned", { n: data.meta.filesScanned, m: fmtInt(data.meta.recordCount) })} ·{" "} {data.meta.home}
-
- {t("dashboardAttribution")}{" "} - {" "} - {t("attributionSuffix")} -
); diff --git a/src/components/sessions/SessionsPage.tsx b/src/components/sessions/SessionsPage.tsx index 218b250..2090275 100644 --- a/src/components/sessions/SessionsPage.tsx +++ b/src/components/sessions/SessionsPage.tsx @@ -1,5 +1,4 @@ import { useMemo, useState } from "react"; -import { openUrl } from "@tauri-apps/plugin-opener"; import { useSessions } from "../../hooks/useSessions"; import { fmtInt, fmtTime } from "../../lib/dashboard-format"; import { useTranslation } from "../../i18n"; @@ -547,16 +546,6 @@ export function SessionsPage() { {/* Attribution */}
{data?.home && <>{data.home}} -
- {t("sessionsAttribution")}{" "} - {" "} - {t("attributionSuffix")}
{/* Confirm dialog */} diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 4337a08..c3898bb 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -318,8 +318,7 @@ export const enTranslations: Record = { // Dashboard — footer / attribution footerScanned: "Scanned {n} files · {m} records", - dashboardAttribution: "Dashboard ported from", - sessionsAttribution: "Session manager ported from", + attributionPorted: "Dashboard & session manager ported from", attributionSuffix: "(MIT, © JochenYang)", // Sessions — preview diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index 84f2c48..a25c983 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -313,8 +313,7 @@ export const zhTranslations = { // Dashboard — footer / attribution footerScanned: "扫描 {n} 个文件 · {m} 条记录", - dashboardAttribution: "仪表盘功能基于", - sessionsAttribution: "会话管理功能基于", + attributionPorted: "仪表盘与会话管理功能基于", attributionSuffix: "(MIT,© JochenYang)移植", // Sessions — preview