From cf9707326ce2c39222c61e600ca130fd83e626fa Mon Sep 17 00:00:00 2001 From: KimiSwitch Dev Date: Fri, 7 Aug 2026 23:19:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(website):=20=E9=A1=B5=E8=84=9A=E4=BD=8E?= =?UTF-8?q?=E8=B0=83=E5=B1=95=E7=A4=BA=20GitHub=20=E7=B4=AF=E8=AE=A1?= =?UTF-8?q?=E4=B8=8B=E8=BD=BD=E9=87=8F=EF=BC=88=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E5=AE=9E=E6=97=B6=E6=8B=89=E5=8F=96=EF=BC=8C1h=20=E7=BC=93?= =?UTF-8?q?=E5=AD=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- website/src/components/Footer.tsx | 62 +++++++++++++++++++++++++++++++ website/src/i18n.tsx | 2 + 2 files changed, 64 insertions(+) diff --git a/website/src/components/Footer.tsx b/website/src/components/Footer.tsx index 55f104c..6fb54a1 100644 --- a/website/src/components/Footer.tsx +++ b/website/src/components/Footer.tsx @@ -1,3 +1,4 @@ +import { useEffect, useState } from "react"; import { recommendLinks } from "../data/recommendLinks"; import { useLang } from "../i18n"; import { asset } from "../lib/asset"; @@ -12,8 +13,62 @@ const logoFor: Record = { "基元律动": "logos/tokenrhythm.svg", }; +// 下载统计:只拉 GitHub(公开 API 带 CORS),镜像站 Gitea 未开 CORS 不计入。 +const STATS_URL = + "https://api.github.com/repos/billowliu2/KimiSwitch/releases?per_page=100"; +const STATS_CACHE_KEY = "kimi-switch-downloads"; +const STATS_CACHE_TTL = 60 * 60 * 1000; // 1 hour + +/** GitHub Release 全部资产 download_count 之和;拉取失败返回 null(不展示)。 */ +function useDownloadCount(): number | null { + const [count, setCount] = useState(null); + useEffect(() => { + let cancelled = false; + try { + const cached = localStorage.getItem(STATS_CACHE_KEY); + if (cached) { + const { ts, total } = JSON.parse(cached) as { ts: number; total: number }; + if (Date.now() - ts < STATS_CACHE_TTL) { + setCount(total); + return; + } + } + } catch { + /* ignore corrupt cache */ + } + fetch(STATS_URL) + .then((r) => (r.ok ? r.json() : Promise.reject(new Error(String(r.status))))) + .then((releases: Array<{ assets?: Array<{ download_count?: number }> }>) => { + if (cancelled) return; + const total = (releases ?? []).reduce( + (sum, rel) => + sum + + (rel.assets ?? []).reduce((s, a) => s + (a.download_count ?? 0), 0), + 0 + ); + setCount(total); + try { + localStorage.setItem( + STATS_CACHE_KEY, + JSON.stringify({ ts: Date.now(), total }) + ); + } catch { + /* ignore quota errors */ + } + }) + .catch(() => { + /* 拉取失败则不显示 */ + }); + return () => { + cancelled = true; + }; + }, []); + return count; +} + export default function Footer() { const { t } = useLang(); + const downloadCount = useDownloadCount(); return ( ); diff --git a/website/src/i18n.tsx b/website/src/i18n.tsx index 8aaf0d8..dc8382c 100644 --- a/website/src/i18n.tsx +++ b/website/src/i18n.tsx @@ -345,6 +345,7 @@ const zh = { footer: { recommend: "推荐供应商", mirror: "国内镜像", + downloads: "GitHub 累计下载 {n} 次", creditBefore: "仪表盘与会话管理功能基于 ", creditAfter: " (MIT, © JochenYang) 移植", recommendText: { @@ -698,6 +699,7 @@ const en: Dict = { footer: { recommend: "Recommended Providers", mirror: "China mirror", + downloads: "Total GitHub downloads: {n}", creditBefore: "Dashboard & session management adapted from ", creditAfter: " (MIT, © JochenYang)", recommendText: {