feat(website): 官网多页面 + 中英文切换 + Kimi 风格科技风重设计
- 多页路由:首页 / 功能 / 更新日志 / 下载(HashRouter,适配 GitHub Pages) - 中英文切换(zh/en 双语文案 + localStorage 记忆) - Kimi 品牌配色(中性灰 + 蓝)+ 科技风(网格底纹 / 辉光按钮 / 渐变强调词) - 更新日志页:运行时同步 GitHub Releases,localStorage 缓存 1h,网络失败回退内置列表 - 功能页 8 张截图卡片;下载页含自动更新说明 - 修复资源路径 404(asset() + %BASE_URL%);自托管 Inter/Fira Code 字体
No files matched your search
@@ -0,0 +1,54 @@
|
|||||||
|
# 工作交接(2026-08-01 第二轮)
|
||||||
|
|
||||||
|
> 新会话续作入口:先读本文件,再按「六、下一步」继续。历史交接见 `docs/HANDOVER-usage-billing.md`(账单功能,已发布完)。
|
||||||
|
|
||||||
|
## 一、当前未完成任务(按优先级)
|
||||||
|
|
||||||
|
1. **官网截图归档**(`docs/screenshots/site-2026-08-01/`,14 张 Snipaste 图):
|
||||||
|
- **当前模型无视觉输入,无法识图**——需用户在会话里切换到支持视觉的模型后逐个查看
|
||||||
|
- 确认每张对应界面 → 按官网区块重命名(hero / feature-usage / kimi-oauth / settings…)
|
||||||
|
- 注意:`.gitignore` 忽略 `Snipaste_*.png`,这些图不进 git,归档目录也只是暂存
|
||||||
|
2. **官网规划待拍板**(`docs/WEBSITE-PLAN.md` v2,3 项待定):
|
||||||
|
- 素材时机:等 v0.7.0 稳定版(推荐)还是用当前 v0.6.x 截图
|
||||||
|
- 技术栈:Vite + React + Tailwind(已写入方案,等确认)
|
||||||
|
- 推荐链接:官网页脚放基元律动等推荐链接,还是不放
|
||||||
|
|
||||||
|
## 二、v0.6.7 发布状态详情(已完成)
|
||||||
|
|
||||||
|
- **提交**:`ce28252` feat: Kimi 设备授权登录(应用内置 kimi login)+ 外链打开统一走 Rust 命令(v0.6.7)
|
||||||
|
- **版本**:三处 0.6.7(package.json / Cargo.toml / tauri.conf.json),`check-version.sh 0.6.7` 通过
|
||||||
|
- **两仓库**:origin(git.codingplan.site/admin/KimiCodeSwitch)+ github(billowliu2/KimiSwitch)的 main / Dev_20260801 / tag v0.6.7 全部同步
|
||||||
|
- **私有库 Release**:https://git.codingplan.site/admin/KimiCodeSwitch/releases/tag/v0.6.7(MSI 6.9MB)
|
||||||
|
- **GitHub Release**:https://github.com/billowliu2/KimiSwitch/releases/tag/v0.6.7(三平台构建成功,6 个产物齐全,已转正式)
|
||||||
|
- **本地包**:`src-tauri/target/release/bundle/{nsis,msi}/Kimi Switch_0.6.7_*`
|
||||||
|
- **v0.6.7 功能**:Kimi 设备授权登录(Device Code Flow,等同 `kimi login`)——`kimi_oauth_start` / `kimi_oauth_poll` 命令 + `KimiOAuthDialog` 前端;授权页/设置页外链统一改走 Rust `open_external_url` 命令(JS 插件版 openUrl 在此环境静默失败,已弃用)
|
||||||
|
|
||||||
|
## 三、关键环境与凭据获取
|
||||||
|
|
||||||
|
- **代理**:git 全局 `http.proxy=http://127.0.0.1:7897`;推 origin 需 `git -c http.proxy= push` 绕过(schannel 握手失败);调 GitHub API 必须 `-x http://127.0.0.1:7897`
|
||||||
|
- **GitHub API token**:`printf "protocol=https\nhost=github.com\n\n" | git credential fill` → password 行(临时文件 /tmp/ghcred.txt 可能已失效,重新获取)
|
||||||
|
- **私有库 API 凭据**:同上 host=git.codingplan.site(/tmp/pcred.txt)
|
||||||
|
- **models.dev 同步**:`npm run fetch-models-dev`(脚本自动读 git http.proxy 走代理);打包前 `pretauri` 钩子自动同步;离线时用本地已提交 `src/lib/models-dev.json` + last-good 备份恢复
|
||||||
|
- **版本发布流程**:check-version → tauri build → commit → 推两仓库 → 私有库 API 建 Release+传 MSI → 推 github tag → CI 完成转正式
|
||||||
|
|
||||||
|
## 四、官网规划要点(docs/WEBSITE-PLAN.md v2)
|
||||||
|
|
||||||
|
- 定位:多供应商统一管理 + 用量监控(非 Kimi 单一产品)
|
||||||
|
- 部署:仅 GitHub Pages(**私有库 Gitea Pages 实测未启用**,勿再做镜像方案)
|
||||||
|
- 技术栈:Vite + React + Tailwind(独立 `website/` 子目录)
|
||||||
|
- 结构:Hero(供应商列表截图)→ 功能 6 卡 → 性能 → 更新 → 隐私 → 平台 → CTA
|
||||||
|
- 素材:真实截图+动图;性能指标需实测后如实写
|
||||||
|
|
||||||
|
## 五、已有交接/文档
|
||||||
|
|
||||||
|
- `docs/HANDOVER-usage-billing.md`:账单查询功能全量交接(已随 v0.6.4-v0.6.6 发布)
|
||||||
|
- `docs/USAGE-QUERY-ADAPTATION.md`:账单查询调研
|
||||||
|
- `docs/WEBSITE-PLAN.md`:官网规划 v2
|
||||||
|
- `docs/screenshots/site-2026-08-01/`:14 张待归档产品截图(需视觉模型)
|
||||||
|
|
||||||
|
## 六、下一步 TODO
|
||||||
|
|
||||||
|
1. 查看后台轮询结果(`TaskList` / `TaskOutput` 或读 `/tasks`):v0.6.7 GitHub CI 完成后转正式 Release
|
||||||
|
2. 用户切视觉模型后:识图 → 重命名归档 14 张截图 → 更新素材清单
|
||||||
|
3. 用户对 3 个待拍板项确认后更新 WEBSITE-PLAN.md
|
||||||
|
4. 后续:官网 M0-M4 里程碑(等拍板 + 稳定版)
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
# KimiSwitch 官网建设规划(Landing Page)v4
|
||||||
|
|
||||||
|
> 状态:方案 v4 定稿(待启动实现)。参考对象:[KimiCodeBar 官网](https://xifandev.github.io/KimiCodeBar/)(GitHub Pages 单页营销站)。
|
||||||
|
> v4 变更:3 项待拍板全部定案(素材用 v0.6.7 / 技术栈 Vite+React+Tailwind / 推荐链接页脚);素材完整度 ~95%;性能实测数据已写入(WS 93 MB / Private 34 MB)。
|
||||||
|
|
||||||
|
## 一、目标与定位
|
||||||
|
|
||||||
|
为 KimiSwitch 打造一个**单页营销官网(landing page)**:
|
||||||
|
- 展示核心卖点:**多 LLM 供应商统一管理 + 一键切换 + 用量/账单监控**(主角,不是单一 Kimi 产品)
|
||||||
|
- 建立可信度:开源(MIT)、隐私(本地存储)、官方直连
|
||||||
|
- 提供下载入口:Windows 安装包(GitHub Releases + 私有库 Releases)
|
||||||
|
- 引流与推广:推荐链接低调放页脚(不喧宾夺主)
|
||||||
|
|
||||||
|
## 二、参考拆解:KimiCodeBar 官网结构
|
||||||
|
|
||||||
|
| 区块 | 内容 | 可借鉴点 | 是否照搬 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Hero | 实时用量卡片 + 主标题 + CTA | 产品实拍做主视觉 | 部分(见 3.1) |
|
||||||
|
| Features | "为 Kimi Code 而生":轻量菜单栏 | 一句话定位 + 简短特性 | 是 |
|
||||||
|
| Kimi Login | 一键授权登录 / API Key 切换 | 认证方式展示 | **否**(降级为功能卡) |
|
||||||
|
| Performance | Swift 原生、内存 30-50MB、任务管理器对比图 | 硬指标 + 对比图 | 是(指标需实测) |
|
||||||
|
| Auto Update | 自动探测更新、一键安装 | 功能卖点 | 是 |
|
||||||
|
| Showcase | 明暗双主题截图 | 真实截图轮播 | 是 |
|
||||||
|
| Privacy | 本地存储 / 官方直连 / 开源可审计 | 三卡片信任区 | 是 |
|
||||||
|
| Platforms | macOS 已发布 / Windows 开发中 | 平台状态 | 是(镜像反转) |
|
||||||
|
| 尾屏 CTA | 强行动号召 + 下载按钮 | 是 | 是 |
|
||||||
|
|
||||||
|
风格特征:深色主题、大字号排版、真实截图/动图、数据指标卡片、滚动锚点导航。
|
||||||
|
|
||||||
|
## 三、KimiSwitch 官网结构规划(修订版)
|
||||||
|
|
||||||
|
### 3.1 Hero
|
||||||
|
- 主标题:**"一个应用,管理你所有的 AI 供应商"**(主)+ 副标题"一键切换 · 用量监控 · 账单一目了然"
|
||||||
|
- 主视觉:**主界面供应商列表 + 切换**截图(核心差异点,非用量卡片——用量卡是 KimiCodeBar 的产品本体,不是我们的)
|
||||||
|
- CTA:下载 Windows 版(主)+ 查看 GitHub 源码(次)
|
||||||
|
|
||||||
|
### 3.2 功能区块(6 卡,一屏一组)
|
||||||
|
1. **多供应商管理**:Kimi / 智谱 / MiniMax / DeepSeek / OpenRouter / 基元律动等预设,一键添加与切换
|
||||||
|
2. **用量与账单监控**:套餐类(5 小时 / 每周用量)+ 按量余额(models.dev 真实价格计算成本)卡片直显
|
||||||
|
3. **Kimi 一键授权登录**:内置设备码授权(等同 `kimi login`),浏览器授权即用、自动续期 ← **Kimi 相关只此一张卡**
|
||||||
|
4. **配置用量查询**:全屏配置页、NewAPI 模板、超时/自动刷新、测试查询
|
||||||
|
5. **智能模型发现**:模型能力 / 上下文自动同步 models.dev
|
||||||
|
6. **自动更新**:检查更新(私有库优先、GitHub 回退),一键下载安装
|
||||||
|
|
||||||
|
### 3.3 认证方式区块
|
||||||
|
- **删除**独立大区块。Kimi 授权并入 3.2 功能卡 3;API Key 方式在主界面截图里自然体现(普通供应商编辑 API Key 输入框)
|
||||||
|
|
||||||
|
### 3.4 性能 / 技术区块
|
||||||
|
- Tauri + Rust 内核 + WebView2;常驻托盘
|
||||||
|
- 实测(28 核 Windows,用户运行 v0.6.7):
|
||||||
|
- **Working Set 93 MB / Private WS 34 MB**(程序空闲态)
|
||||||
|
- 52 线程 / 1086 句柄;CPU 空闲 0%,查询时短暂升高
|
||||||
|
- 对比 KimiCodeBar(Swift)自报 38 MB —— Tauri 因 WebView2 内核偏重是事实,**不作"碾压"话术**
|
||||||
|
- 配任务管理器对比图(M2 实测打安装包时补)
|
||||||
|
|
||||||
|
### 3.5 更新区块
|
||||||
|
- 检查更新:私有库优先 → GitHub 回退;一键下载 / 安装(截图展示设置面板更新区)
|
||||||
|
|
||||||
|
### 3.6 隐私与信任区块(三卡片)
|
||||||
|
- 本地存储:配置与用量仅存本机(`~/.kimi-switch` / config.toml)
|
||||||
|
- 官方直连:账单查询直连各厂商官方 API
|
||||||
|
- 开源可审计:MIT 全量开源(GitHub)
|
||||||
|
|
||||||
|
### 3.7 平台与下载
|
||||||
|
- **Windows:已发布**(.msi / .exe)——主推平台
|
||||||
|
- macOS / Linux:规划中(GitHub Actions 已能构建,但未正式适配/发布,如实标注"开发中")
|
||||||
|
- 下载入口:GitHub Releases 为主 + **私有库 Releases 链接**(国内下载快)
|
||||||
|
|
||||||
|
### 3.8 尾屏 CTA
|
||||||
|
- **"现在就把所有供应商,放进一个应用"** + 下载按钮 + 当前版本号(构建时注入)
|
||||||
|
|
||||||
|
## 四、视觉与交互方向
|
||||||
|
|
||||||
|
- 深色为主(对齐 app)、大字号对比排版、圆角卡片、品牌蓝紫渐变(`#3b82f6 → #8b5cf6`)
|
||||||
|
- 素材:真实截图 + 动图,避免插画风
|
||||||
|
- 动效:滚动渐入、数字滚动、截图 hover 缩放,克制
|
||||||
|
- 响应式:桌面优先,移动端可看结构与下载按钮
|
||||||
|
|
||||||
|
## 五、技术选型与部署(已收敛)
|
||||||
|
|
||||||
|
| 项 | 方案 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 技术栈 | **Vite + React + Tailwind**(与主应用技能栈一致) | 定案;后续扩展英文版/多页顺 |
|
||||||
|
| 构建 | 独立工程 `website/` 子目录,`vite build` 产物纯静态 | 与 tauri 构建隔离 |
|
||||||
|
| 部署 | **仅 GitHub Pages**(`gh-pages` 分支或独立 repo) | 私有库 Pages **确认未启用**,不做镜像 |
|
||||||
|
| 域名 | `https://billowliu2.github.io/KimiSwitch/`(默认) | 自定义域名待定 |
|
||||||
|
| CI | GitHub Actions:website 变更自动构建 → 部署 gh-pages | 独立 workflow |
|
||||||
|
| 下载链接 | 构建时注入**版本号常量**(v0.6.x),链接指向 GitHub + 私有库 Releases | 发版时改一个常量,不做脚本自动化 |
|
||||||
|
| SEO | meta description / OG 分享图 / favicon(用 `public/kimi.svg`) | 补进 M1 |
|
||||||
|
|
||||||
|
## 六、素材清单(已就位 ✅)
|
||||||
|
|
||||||
|
- [x] app 主界面截图 ×5:供应商列表(hero)、编辑基本信息、模型映射、Kimi OAuth 对话框、Kimi OAuth 授权成功页
|
||||||
|
- [x] 用量仪表盘截图 ×4:浅色/深色/完整版/每日详情
|
||||||
|
- [x] 会话管理截图 ×2:浅色/深色
|
||||||
|
- [x] 编辑器截图 ×3:基本信息、模型映射、配置 JSON
|
||||||
|
- [x] 设置弹窗截图 ×1(更新区+超链接+关于)
|
||||||
|
- [x] 配置用量查询截图 ×1(OAuth 托管模板/变量/超时/测试结果)
|
||||||
|
- [x] 选择预设供应商截图 ×1(22 个预设卡片)
|
||||||
|
- [x] 套餐用量卡片特写 ×1(5h/7天)
|
||||||
|
- [x] kimi CLI 模型选择 TUI ×1(辅助素材)
|
||||||
|
- [x] 性能实测指标:WS 93 MB / Private 34 MB / 52 threads / 1086 handles
|
||||||
|
- [ ] 任务管理器对比图(M2 阶段补:打安装包后跑 5-10 分钟采样,含对比标注)
|
||||||
|
- [ ] OG 分享图(M1 阶段用 hero.png 合成 1200×630)
|
||||||
|
- [ ] 版本号与下载链接注入(构建常量)
|
||||||
|
|
||||||
|
## 七、里程碑(绑定稳定版)
|
||||||
|
|
||||||
|
1. **M0 前提**:app 进入稳定版(**建议 v0.7.0 发布后**)——截图/动图才不过时
|
||||||
|
2. **M1 结构骨架**:布局 + 导航 + 区块占位 + SEO/meta(1 天)
|
||||||
|
3. **M2 内容填充**:文案定稿 + 截图/动图就位(1-2 天)
|
||||||
|
4. **M3 打磨上线**:动效、响应式、GitHub Pages 部署、下载链接注入(1 天)
|
||||||
|
5. **M4 持续维护**:发版时更新版本号/截图(随 Release 流程)
|
||||||
|
|
||||||
|
## 八、不做的事(当前阶段)
|
||||||
|
|
||||||
|
- 不做多语言(先中文,英文版按需)
|
||||||
|
- 不做博客 / 文档站(README / docs 已有)
|
||||||
|
- 不做在线购买 / 支付(免费开源)
|
||||||
|
- 不采集统计埋点(与隐私定位一致)
|
||||||
|
- **不做私有库 Pages 镜像**(服务器未启用该功能)
|
||||||
|
|
||||||
|
## 九、已定案(原 3 项待拍板)
|
||||||
|
|
||||||
|
1. **素材时机** ✅ **用当前 v0.6.7**(用户确认,不等 v0.7.0)
|
||||||
|
2. **技术栈** ✅ **Vite + React + Tailwind**(与主应用技能栈一致)
|
||||||
|
3. **推荐链接** ✅ **页脚放 6 个供应商官网**(KimiSwitch 已全部内置):
|
||||||
|
|
||||||
|
| # | 供应商 | 推荐文案 | 链接 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 1 | **Kimi** | 完成 Kimi 注册,你我都能 100% 拿奖,最高可得 1 年会员等值权益 | https://kimi-bot.com/activities/zh-cn/invite/share?scenario=invite&from=share_poster&invitation_code=6UJX7J |
|
||||||
|
| 2 | **智谱 GLM** | 我正在智谱大模型开放平台 BigModel.cn 上打造 AI 应用,智谱新一代旗舰模型 GLM-5.2 已上线,在推理、代码、智能体综合能力达到开源模型 SOTA 水平,通过我的邀请链接注册即可获得 2000 万 Tokens 大礼包,期待和你一起在 BigModel 上畅享卓越模型能力 | https://www.bigmodel.cn/invite?icode=mQwTVj1MeLnqI2wa7Ivg7unfet45IvM%2BqDogImfeLyI%3D |
|
||||||
|
| 3 | **DeepSeek** | (无推荐链接,官方平台) | https://platform.deepseek.com |
|
||||||
|
| 4 | **OpenCodeGo** | (无特定文案,官方推荐链接) | https://opencode.ai/go?ref=DFCNADQCEM |
|
||||||
|
| 5 | **MiniMax** | 🚀 MiniMax Token Plan 订阅一份套餐,解锁最新模型 — 前沿 Coding 能力、1M 超长上下文、原生多模态,图文音视频共用套餐额度。🎁 邀友双赢福利 好友订阅享 9 折 + Builder 权益,邀请人得 10% 返利 + 社区特权!立即订阅 | https://platform.minimaxi.com/subscribe/token-plan?code=GmmZA629b5&source=link |
|
||||||
|
| 6 | **基元律动** | (无特定文案,新供应商推荐链接) | https://tokenrhythm.studio/i/rf_tr_v0QXuBTzyl3-bJOS2uFFPg6x |
|
||||||
|
|
||||||
|
> 方案 v4 定稿,可启动 M1(结构骨架 + SEO)。
|
||||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 245 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 70 KiB |
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
*.tsbuildinfo
|
||||||
|
vite.config.js
|
||||||
|
vite.config.d.ts
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# KimiSwitch 官网(M1:结构骨架 + SEO)
|
||||||
|
|
||||||
|
独立 Vite + React + Tailwind 工程,与主 Tauri 应用隔离。
|
||||||
|
|
||||||
|
## 开发
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd website
|
||||||
|
npm install
|
||||||
|
npm run dev # http://localhost:5173/KimiSwitch/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 构建
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build # tsc + vite build → dist/
|
||||||
|
npm run preview # 本地预览生产产物
|
||||||
|
```
|
||||||
|
|
||||||
|
## 部署
|
||||||
|
|
||||||
|
GitHub Pages(项目仓库 Pages):设置 → Pages → Source: `gh-pages` 分支 / root。
|
||||||
|
|
||||||
|
Vite `base` 已配置为 `/KimiSwitch/`(仓库子路径),构建产物可直接发布到 `gh-pages` 分支根目录。
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 推送 dist/ 到 gh-pages 分支(任选方式:gh-pages 包 / git subtree)
|
||||||
|
npx gh-pages -d dist
|
||||||
|
```
|
||||||
|
|
||||||
|
## 里程碑
|
||||||
|
|
||||||
|
- ✅ **M1**(当前):结构骨架 + SEO(meta、关键词、语义化)
|
||||||
|
- ⏭ **M2**:截图填充(替换 17 张归档素材占位)
|
||||||
|
- ⏭ **M3**:打磨动效 + 响应式 + GitHub Pages 部署 + OG 图合成
|
||||||
|
|
||||||
|
参考 `docs/WEBSITE-PLAN.md` v4。
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="%BASE_URL%kimi.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="theme-color" content="#efebe1" media="(prefers-color-scheme: light)" />
|
||||||
|
<meta name="theme-color" content="#231f1a" media="(prefers-color-scheme: dark)" />
|
||||||
|
<title>Kimi Switch — 多 LLM 供应商统一管理 · 用量监控 · 账单一目了然</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="Kimi Switch / KimiCodeSwitch — Windows 桌面端多 LLM 供应商统一管理器,一键切换 Kimi / 智谱 GLM / MiniMax / DeepSeek / OpenRouter / 基元律动,用量账单实时监控,开源 MIT。"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
name="keywords"
|
||||||
|
content="KimiSwitch, KimiCodeSwitch, SwitchKimi, Kimi Code Switch, Kimi 切换, LLM 供应商管理, AI 供应商切换, 多 LLM 管理, Kimi 用量监控, 智谱 GLM, MiniMax, DeepSeek, OpenRouter, 基元律动, 用量查询, 账单监控, Token 套餐, Claude, GPT, Windows LLM 桌面工具"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!-- Open Graph -->
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
<meta property="og:title" content="Kimi Switch — 多 LLM 供应商统一管理" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="一键切换 Kimi / 智谱 GLM / MiniMax / DeepSeek 等供应商,用量与账单一目了然,开源 MIT。"
|
||||||
|
/>
|
||||||
|
<meta property="og:url" content="https://billowliu2.github.io/KimiSwitch/" />
|
||||||
|
<meta property="og:image" content="%BASE_URL%og.png" />
|
||||||
|
|
||||||
|
<!-- Twitter -->
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta name="twitter:title" content="Kimi Switch — 多 LLM 供应商统一管理" />
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content="一键切换 Kimi / 智谱 GLM / MiniMax / DeepSeek 等供应商,用量与账单一目了然,开源 MIT。"
|
||||||
|
/>
|
||||||
|
<meta name="twitter:image" content="%BASE_URL%og.png" />
|
||||||
|
|
||||||
|
<!-- Canonical -->
|
||||||
|
<link rel="canonical" href="https://billowliu2.github.io/KimiSwitch/" />
|
||||||
|
|
||||||
|
<!-- JSON-LD: SoftwareApplication + Organization + WebSite -->
|
||||||
|
<script type="application/ld+json">
|
||||||
|
{
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@graph": [
|
||||||
|
{
|
||||||
|
"@type": "SoftwareApplication",
|
||||||
|
"name": "Kimi Switch",
|
||||||
|
"alternateName": ["KimiSwitch", "KimiCodeSwitch"],
|
||||||
|
"url": "https://billowliu2.github.io/KimiSwitch/",
|
||||||
|
"applicationCategory": "DeveloperApplication",
|
||||||
|
"operatingSystem": "Windows 10, Windows 11",
|
||||||
|
"softwareVersion": "0.6.7",
|
||||||
|
"dateModified": "2026-08-01",
|
||||||
|
"description": "Windows 桌面端多 LLM 供应商统一管理器,一键切换 Kimi / 智谱 GLM / MiniMax / DeepSeek / OpenRouter / 基元律动,用量账单实时监控,开源 MIT。",
|
||||||
|
"downloadUrl": "https://github.com/billowliu2/KimiSwitch/releases",
|
||||||
|
"softwareHelp": "https://github.com/billowliu2/KimiSwitch",
|
||||||
|
"license": "https://opensource.org/licenses/MIT",
|
||||||
|
"offers": {
|
||||||
|
"@type": "Offer",
|
||||||
|
"price": "0",
|
||||||
|
"priceCurrency": "USD"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "KimiSwitch",
|
||||||
|
"url": "https://github.com/billowliu2/KimiSwitch"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "Organization",
|
||||||
|
"name": "KimiSwitch",
|
||||||
|
"url": "https://github.com/billowliu2/KimiSwitch",
|
||||||
|
"logo": "https://billowliu2.github.io/KimiSwitch/kimi.svg",
|
||||||
|
"sameAs": [
|
||||||
|
"https://github.com/billowliu2/KimiSwitch",
|
||||||
|
"https://git.codingplan.site/admin/KimiCodeSwitch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "WebSite",
|
||||||
|
"name": "Kimi Switch",
|
||||||
|
"url": "https://billowliu2.github.io/KimiSwitch/",
|
||||||
|
"inLanguage": "zh-CN"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "kimiswitch-website",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.6.7",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc -b && vite build",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/fira-code": "^5.3.0",
|
||||||
|
"@fontsource/inter": "^5.3.0",
|
||||||
|
"@phosphor-icons/react": "^2.1.10",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1",
|
||||||
|
"react-router-dom": "^7.18.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
|
"autoprefixer": "^10.4.20",
|
||||||
|
"postcss": "^8.4.41",
|
||||||
|
"tailwindcss": "^3.4.10",
|
||||||
|
"typescript": "^5.5.4",
|
||||||
|
"vite": "^5.4.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
||||||
|
<stop offset="0%" stop-color="#3b82f6"/>
|
||||||
|
<stop offset="100%" stop-color="#8b5cf6"/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<rect width="100" height="100" rx="22" fill="url(#g)"/>
|
||||||
|
<text x="48" y="70" text-anchor="middle" font-size="52" font-weight="700"
|
||||||
|
fill="white" font-family="system-ui, -apple-system, 'Segoe UI', sans-serif">K</text>
|
||||||
|
<circle cx="74" cy="26" r="9" fill="white" opacity="0.35"/>
|
||||||
|
<circle cx="74" cy="26" r="6.5" fill="white" opacity="0.92"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 615 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>DeepSeek</title><path d="M23.748 4.482c-.254-.124-.364.113-.512.234-.051.039-.094.09-.137.136-.372.397-.806.657-1.373.626-.829-.046-1.537.214-2.163.848-.133-.782-.575-1.248-1.247-1.548-.352-.156-.708-.311-.955-.65-.172-.241-.219-.51-.305-.774-.055-.16-.11-.323-.293-.35-.2-.031-.278.136-.356.276-.313.572-.434 1.202-.422 1.84.027 1.436.633 2.58 1.838 3.393.137.093.172.187.129.323-.082.28-.18.552-.266.833-.055.179-.137.217-.329.14a5.526 5.526 0 01-1.736-1.18c-.857-.828-1.631-1.742-2.597-2.458a11.365 11.365 0 00-.689-.471c-.985-.957.13-1.743.388-1.836.27-.098.093-.432-.779-.428-.872.004-1.67.295-2.687.684a3.055 3.055 0 01-.465.137 9.597 9.597 0 00-2.883-.102c-1.885.21-3.39 1.102-4.497 2.623C.082 8.606-.231 10.684.152 12.85c.403 2.284 1.569 4.175 3.36 5.653 1.858 1.533 3.997 2.284 6.438 2.14 1.482-.085 3.133-.284 4.994-1.86.47.234.962.327 1.78.397.63.059 1.236-.03 1.705-.128.735-.156.684-.837.419-.961-2.155-1.004-1.682-.595-2.113-.926 1.096-1.296 2.746-2.642 3.392-7.003.05-.347.007-.565 0-.845-.004-.17.035-.237.23-.256a4.173 4.173 0 001.545-.475c1.396-.763 1.96-2.015 2.093-3.517.02-.23-.004-.467-.247-.588zM11.581 18c-2.089-1.642-3.102-2.183-3.52-2.16-.392.024-.321.471-.235.763.09.288.207.486.371.739.114.167.192.416-.113.603-.673.416-1.842-.14-1.897-.167-1.361-.802-2.5-1.86-3.301-3.307-.774-1.393-1.224-2.887-1.298-4.482-.02-.386.093-.522.477-.592a4.696 4.696 0 011.529-.039c2.132.312 3.946 1.265 5.468 2.774.868.86 1.525 1.887 2.202 2.891.72 1.066 1.494 2.082 2.48 2.914.348.292.625.514.891.677-.802.09-2.14.11-3.054-.614zm1-6.44a.306.306 0 01.415-.287.302.302 0 01.2.288.306.306 0 01-.31.307.303.303 0 01-.304-.308zm3.11 1.596c-.2.081-.399.151-.59.16a1.245 1.245 0 01-.798-.254c-.274-.23-.47-.358-.552-.758a1.73 1.73 0 01.016-.588c.07-.327-.008-.537-.239-.727-.187-.156-.426-.199-.688-.199a.559.559 0 01-.254-.078c-.11-.054-.2-.19-.114-.358.028-.054.16-.186.192-.21.356-.202.767-.136 1.146.016.352.144.618.408 1.001.782.391.451.462.576.685.914.176.265.336.537.445.848.067.195-.019.354-.25.452z" fill="#4D6BFE"></path></svg>
|
||||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#0F62FE"><path d="M12 2L4 6v6c0 5 3.5 8.5 8 10 4.5-1.5 8-5 8-10V6l-8-4zm0 4l4 2v4c0 3-1.5 5-4 6-2.5-1-4-3-4-6V8l4-2z"/></svg>
|
||||||
|
After Width: | Height: | Size: 192 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg fill="currentColor" fill-rule="evenodd" height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Kimi</title><path d="M19.738 5.776c.163-.209.306-.4.457-.585.07-.087.064-.153-.004-.244-.655-.861-.717-1.817-.34-2.787.283-.73.909-1.072 1.674-1.145.477-.045.945.004 1.379.236.57.305.902.77 1.01 1.412.086.512.07 1.012-.075 1.508-.257.878-.888 1.333-1.753 1.448-.718.096-1.446.108-2.17.157-.056.004-.113 0-.178 0z" fill="#027AFF"></path><path d="M17.962 1.844h-4.326l-3.425 7.81H5.369V1.878H1.5V22h3.87v-8.477h6.824a3.025 3.025 0 002.743-1.75V22h3.87v-8.477a3.87 3.87 0 00-3.588-3.86v-.01h-2.125a3.94 3.94 0 002.323-2.12l2.545-5.689z"></path></svg>
|
||||||
|
After Width: | Height: | Size: 711 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Minimax</title><defs><linearGradient id="lobe-icons-minimax-fill" x1="0%" x2="100.182%" y1="50.057%" y2="50.057%"><stop offset="0%" stop-color="#E2167E"></stop><stop offset="100%" stop-color="#FE603C"></stop></linearGradient></defs><path d="M16.278 2c1.156 0 2.093.927 2.093 2.07v12.501a.74.74 0 00.744.709.74.74 0 00.743-.709V9.099a2.06 2.06 0 012.071-2.049A2.06 2.06 0 0124 9.1v6.561a.649.649 0 01-.652.645.649.649 0 01-.653-.645V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v7.472a2.037 2.037 0 01-2.048 2.026 2.037 2.037 0 01-2.048-2.026v-12.5a.785.785 0 00-.788-.753.785.785 0 00-.789.752l-.001 15.904A2.037 2.037 0 0113.441 22a2.037 2.037 0 01-2.048-2.026V18.04c0-.356.292-.645.652-.645.36 0 .652.289.652.645v1.934c0 .263.142.506.372.638.23.131.514.131.744 0a.734.734 0 00.372-.638V4.07c0-1.143.937-2.07 2.093-2.07zm-5.674 0c1.156 0 2.093.927 2.093 2.07v11.523a.648.648 0 01-.652.645.648.648 0 01-.652-.645V4.07a.785.785 0 00-.789-.78.785.785 0 00-.789.78v14.013a2.06 2.06 0 01-2.07 2.048 2.06 2.06 0 01-2.071-2.048V9.1a.762.762 0 00-.766-.758.762.762 0 00-.766.758v3.8a2.06 2.06 0 01-2.071 2.049A2.06 2.06 0 010 12.9v-1.378c0-.357.292-.646.652-.646.36 0 .653.29.653.646V12.9c0 .418.343.757.766.757s.766-.339.766-.757V9.099a2.06 2.06 0 012.07-2.048 2.06 2.06 0 012.071 2.048v8.984c0 .419.343.758.767.758.423 0 .766-.339.766-.758V4.07c0-1.143.937-2.07 2.093-2.07z" fill="url(#lobe-icons-minimax-fill)" fill-rule="nonzero"></path></svg>
|
||||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 36" fill="none">
|
||||||
|
<path d="M18 30H6V18H18V30Z" fill="#CFCECD"/>
|
||||||
|
<path d="M18 12H6V30H18V12ZM24 36H0V6H24V36Z" fill="#1A1A1E"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 193 B |
@@ -0,0 +1,27 @@
|
|||||||
|
<svg preserveAspectRatio="none" width="100%" height="100%" overflow="visible" style="display: block;" viewBox="0 0 189 68" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<g id="基元律动logo0630 2 [Vectorized]" opacity="0.75">
|
||||||
|
<path id="Vector" d="M28.2869 15.3313L52.3032 15.3318C56.6495 15.332 61.1409 15.3885 65.4782 15.3038C62.4476 17.8251 58.7745 21.2659 55.8259 23.9557C54.451 24.0375 52.5027 23.9839 51.0859 23.9842C48.459 23.9946 45.832 23.9874 43.2052 23.9626C44.8543 25.4287 47.4485 28.1941 49.1119 29.8604L61.0355 41.7999L68.4647 49.2348C68.9123 49.6831 71.0764 51.7891 71.2735 52.1321L71.1537 52.1688C67.1352 52.0344 62.9706 52.1522 58.9688 51.9728L45.811 38.8082C42.8897 35.885 39.8219 32.8966 36.9616 29.9257L36.9693 44.1437C34.1378 46.911 31.1624 49.6754 28.2845 52.4082L28.2869 15.3313Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_2" d="M98.7525 30.6261L100.21 30.6376L100.194 32.1506L103.548 32.1483L103.537 34.4298L100.204 34.4441C100.223 36.862 100.217 39.28 100.188 41.6978C101.416 41.6652 102.754 41.6897 103.991 41.6874C103.956 42.3315 103.988 43.3183 103.992 43.9834C102.257 43.9898 100.451 44.0225 98.723 43.9532C100.293 45.3913 102.261 45.9617 104.247 46.4987C104.03 47.4093 103.842 48.4144 103.65 49.3374C102.178 48.9282 99.9596 48.0446 98.7359 47.1392L98.7303 48.6004C97.2215 48.6082 95.7126 48.6069 94.2028 48.5966V50.1824C97.2187 50.1304 100.388 50.1795 103.416 50.1805C103.358 50.8001 103.384 51.7645 103.382 52.4095L85.6626 52.41L82.2408 52.4069C82.1845 51.6493 82.0959 50.9327 82.0033 50.1798L91.2904 50.1778L91.3044 48.6046L86.8554 48.6069L86.7349 47.176C84.9932 48.2367 83.7603 48.7314 81.8149 49.3328C81.5976 48.432 81.4066 47.4129 81.2142 46.4981C83.4621 45.9232 84.8957 45.4246 86.7564 43.9544C85.2596 44.0446 83.2197 43.9867 81.6781 43.9816C81.6749 43.3384 81.5832 42.3121 81.4779 41.6911L85.2003 41.6896L85.202 34.4349L82.4254 34.4319C82.3711 33.6655 82.326 32.9125 82.2188 32.1509L85.1936 32.1508L85.1881 30.652C86.1541 30.6266 87.1534 30.6348 88.1224 30.631L88.0896 32.1462L97.2732 32.1504L97.2824 30.6572L98.7525 30.6261ZM91.2808 44.7054L94.2028 44.7065L94.2038 46.2956C94.5083 46.5151 96.9954 46.3764 97.6672 46.4214C96.8117 45.5574 96.2802 45.0332 95.6194 43.9894C93.6999 44.0104 91.7815 44.0086 89.8628 43.984C89.1274 45.024 88.7137 45.528 87.7696 46.42C88.9413 46.3968 90.1131 46.3965 91.2848 46.419L91.2808 44.7054ZM97.2824 35.4409L97.2852 34.439C94.5175 34.4424 90.8129 34.5608 88.1224 34.4435L88.0839 35.4132L94.0229 35.4172C94.9817 35.4175 96.3485 35.3769 97.2824 35.4409ZM97.2741 41.6939C97.2824 41.4051 97.3359 40.9901 97.1892 40.7622C96.3189 40.6695 94.8212 40.7158 93.9066 40.7186L88.1216 40.7332C88.112 41.0237 88.088 41.3984 88.0937 41.6812C91.1174 41.683 94.2601 41.6422 97.2741 41.6939ZM88.1146 37.5911C88.1072 37.8571 88.0611 38.2777 88.1768 38.4975C88.6462 38.6161 90.4817 38.56 91.0733 38.5629C93.1342 38.5734 95.2069 38.5231 97.2695 38.5662C97.2759 38.3047 97.3248 37.8151 97.2058 37.6159C94.4695 37.475 90.9224 37.6123 88.1146 37.5911Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_3" d="M143.557 30.6421C144.519 30.6365 145.481 30.6369 146.444 30.6431L146.416 32.0838C147.712 32.0952 150.587 31.7416 151.532 32.6334C152.238 33.2995 152.073 34.9116 152.037 35.836L153.455 35.8341L153.452 38.2574L152.061 38.2665C152.068 38.6574 152.073 39.0484 152.074 39.4393C152.077 40.817 151.714 41.9762 150.071 42.0028C148.863 42.0222 147.644 41.9835 146.435 41.9641L146.417 43.4188L152.471 43.4164C152.45 44.172 152.462 44.9907 152.458 45.7513C150.451 45.767 148.443 45.7646 146.434 45.7444L146.421 47.307L153.455 47.3061L153.448 49.7244L146.433 49.7289C146.427 50.7883 146.426 51.8478 146.432 52.9071C145.466 52.9302 144.528 52.9131 143.562 52.8971L143.57 52.0394L143.562 49.7232C141.674 49.7332 139.786 49.7317 137.898 49.7187L137.652 47.305C139.595 47.2752 141.613 47.3037 143.563 47.3043V45.7518L138.841 45.7562C138.807 44.9585 138.72 44.2115 138.634 43.4192L143.567 43.4181L143.557 41.9638C142.283 41.9729 141.01 41.9746 139.735 41.9686C139.676 41.1971 139.589 40.4064 139.512 39.6348L143.557 39.6422L143.567 38.2609C142.025 38.2767 140.451 38.2581 138.907 38.2555C138.812 37.4492 138.73 36.6414 138.659 35.8325C140.283 35.8153 141.935 35.8318 143.561 35.8329L143.563 34.4377L139.734 34.4393C139.706 33.6753 139.59 32.8396 139.5 32.0747C140.831 32.1063 142.213 32.0809 143.561 32.1059L143.557 30.6421ZM146.418 35.8352C147.335 35.8344 148.302 35.817 149.215 35.8342C149.226 35.4685 149.29 35.001 149.104 34.7061C148.57 34.1891 147.223 34.4624 146.434 34.4293L146.418 35.8352ZM146.415 39.6429C147.046 39.6429 148.618 39.6765 149.164 39.578C149.198 39.1228 149.226 38.7194 149.231 38.263C148.344 38.2616 147.306 38.287 146.433 38.2546L146.415 39.6429Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_4" d="M106.528 38.04C113.768 38.1537 121.294 38.0359 128.569 38.0446L128.562 40.5572L122.09 40.5609C122.088 41.6707 121.918 49.1898 122.298 49.5794C122.677 49.9669 123.385 49.993 123.896 49.9976C124.384 50.0018 125.079 50.0346 125.443 49.658C126.096 48.9854 125.794 46.7277 125.787 45.8074C126.773 45.9027 127.712 46.0629 128.733 46.1458C128.717 47.4305 128.81 48.9848 128.679 50.2335C128.37 53.174 123.248 52.7047 121.193 52.341C118.598 51.6953 119.124 49.0866 119.126 46.9824L119.136 40.5538L115.088 40.5599C115.11 42.4962 115.153 44.316 114.498 46.1768C113.261 49.6902 110.052 51.4181 106.92 52.9188L106.102 49.9209C107.583 49.2936 109.128 48.4728 110.259 47.3244C112.25 45.3015 112.152 43.1551 112.126 40.5521L106.708 40.5516C106.715 39.8733 106.589 38.7478 106.528 38.04Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_5" d="M66.8075 16.3432C66.9854 16.5409 66.8988 26.5212 66.8939 27.8531C64.2096 30.6301 61.1162 33.5951 58.3448 36.3093L55.4318 33.3274C54.3728 32.2341 53.3067 31.1479 52.2334 30.0688L66.8075 16.3432Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_6" d="M168.959 30.6309C169.884 30.6563 170.931 30.6308 171.866 30.6301L171.852 34.4054C173.207 34.3801 175.717 34.1215 176.702 34.9053C177.766 36.6628 177.13 47.7169 177.116 50.1618C177.1 53.3678 173.527 52.8804 171.243 52.5637L170.593 50.0131C171.16 50.153 171.784 50.2022 172.368 50.2301C172.828 50.2521 173.663 50.2678 173.997 49.902C174.784 49.0385 174.376 39.5355 174.327 38.0864C174.322 37.7564 174.344 37.497 174.196 37.1993C173.703 36.7025 172.784 36.9975 171.856 36.8896C171.872 43.0678 171.482 47.246 168.791 52.9199L165.627 52.9227C168.446 46.8398 168.968 43.577 168.974 36.942L166.948 36.9497C166.846 36.141 166.779 35.2157 166.701 34.3979L168.97 34.3965L168.959 30.6309Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_7" d="M20.3634 15.2565C22.2816 15.2079 24.3745 15.2491 26.3065 15.2484C26.241 18.0694 26.2922 21.1045 26.2817 23.9411L16.4729 23.9386C14.5285 23.9342 12.3458 23.8846 10.4213 23.964C13.7597 21.1535 17.0176 18.0347 20.3634 15.2565Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_8" d="M155.77 38.0545L166.671 38.0592L166.66 40.4752C165.053 40.4843 163.446 40.4852 161.84 40.4778C161.528 43.4002 160.536 45.7653 159.075 48.266C160.563 48.2528 162.053 48.2562 163.542 48.2759C163.189 46.7011 162.709 45.1377 162.374 43.5428L162.451 43.4572L163.137 43.4397L165.225 43.4383C165.596 44.9311 165.967 46.4514 166.333 47.9353C166.75 49.6312 166.649 50.8135 164.561 50.8055C163.779 50.8026 162.918 50.8113 162.128 50.8104L155.98 50.8004C155.944 50.318 155.704 48.6508 155.865 48.2955L156.187 48.2595C156.442 48.0579 156.735 47.3129 156.883 46.9886C157.864 44.8345 158.443 42.8159 158.779 40.4811L155.978 40.4739L155.77 38.0545Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_9" d="M135.661 36.5098L138.529 36.5075C137.667 39.5604 136.341 41.2684 133.853 43.125C135.823 43.0944 136.683 42.9556 136.651 45.3019C136.642 45.9935 136.648 46.7679 136.646 47.4658L136.62 52.91C135.725 52.9391 134.714 52.9162 133.81 52.9155L133.813 44.8194L131.69 44.8038C131.463 43.9161 131.164 42.9683 130.903 42.0839C133.129 40.7958 134.759 38.9216 135.661 36.5098Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_10" d="M108.577 31.6073C114.355 31.739 120.686 31.6114 126.507 31.6117L126.501 34.1803C120.771 34.0572 114.56 34.1742 108.794 34.1751C108.692 33.3151 108.638 32.4706 108.577 31.6073Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_11" d="M119.479 15.71C120.798 15.693 123.164 15.5224 124.308 16.0558C126.299 16.9837 126.076 20.1731 123.91 20.9264L126.019 24.5984L124.235 24.5917C123.99 24.2657 122.699 21.6603 122.451 21.1619L121.062 21.1583C121.058 22.307 121.06 23.4556 121.068 24.6042L119.473 24.5985C119.566 21.8236 119.51 18.5035 119.479 15.71ZM121.051 19.7961C121.945 19.8033 123.018 19.9234 123.753 19.3913C124.995 17.011 122.631 17.0745 121.058 17.0804L121.051 19.7961Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_12" d="M155.37 18.0461C155.609 18.0631 156.076 18.0928 156.278 18.1888C156.662 18.3716 156.969 18.8524 157.439 18.8598C157.913 18.7376 157.723 18.2102 158.869 18.0577C162.365 17.5926 161.518 22.3556 161.493 24.6266C161.146 24.5826 160.371 24.5993 159.991 24.5975C160.008 23.7007 160.273 20.0376 159.597 19.6128C157.02 17.9932 157.607 22.7534 157.616 23.9524C157.627 24.1804 157.666 24.3672 157.523 24.5406C157.209 24.6959 156.557 24.606 156.131 24.6033C156.14 23.7629 156.368 20.0944 155.742 19.655C153.081 17.7873 153.733 23.3044 153.748 24.6107L152.219 24.596L152.224 18.1901L153.622 18.1893C153.646 18.4011 153.643 18.5972 153.772 18.7625C154.361 18.5998 154.068 18.2095 155.37 18.0461Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_13" d="M91.1429 18.0522C94.3302 18.122 94.6993 21.3866 93.4369 23.7784C93.088 24.4389 92.238 24.6456 91.5331 24.7415C89.621 24.7439 88.6302 23.9073 88.4773 21.9881C88.3105 19.8932 88.8263 18.2411 91.1429 18.0522ZM91.5428 23.479C92.8444 22.7222 93.0262 19.9867 91.5137 19.3544C91.3955 19.305 91.0173 19.3148 90.8734 19.3189C89.5574 20.1837 89.4669 23.4529 91.5428 23.479Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_14" d="M145.02 15.4482L146.555 15.456C146.534 16.612 146.558 17.8251 146.565 18.9856C146.847 18.6568 147.078 18.4626 147.403 18.1796C151.439 17.4693 150.425 21.8162 150.425 24.6083C149.943 24.5952 149.421 24.6041 148.934 24.6027L148.939 21.4117C148.948 20.7508 149.021 20.2024 148.532 19.6668C148.255 19.2434 147.363 18.9955 147.009 19.481C146.226 20.5554 146.536 23.3853 146.553 24.6374C146.158 24.5887 145.445 24.6029 145.024 24.5998C145.105 21.8847 145.105 18.1604 145.02 15.4482Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_15" d="M127.242 15.4528L128.754 15.4523L128.731 19.0125C129.054 18.6436 129.23 18.4927 129.609 18.1793C133.617 17.4115 132.634 21.915 132.617 24.626C132.225 24.5865 131.519 24.6004 131.104 24.5962C131.138 23.6667 131.118 22.6056 131.122 21.6658C131.127 21.2822 131.185 19.819 130.796 19.5937C128.046 18.0022 128.721 23.0514 128.732 24.6298C128.356 24.5837 127.656 24.6016 127.253 24.6009C127.299 21.6611 127.302 18.3882 127.242 15.4528Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_16" d="M104.703 18.0539C107.273 18.0208 107.522 19.7472 107.536 21.7736C107.485 21.7663 107.282 21.7393 107.24 21.7437C106.531 21.8174 103.885 21.4971 103.447 21.9005C103.203 22.6803 103.891 23.0805 104.486 23.4144C105.403 23.9286 105.93 23.2584 106.671 23.0228L107.281 23.9938C105.121 25.5435 102.096 24.8569 101.965 21.8757C101.873 19.7994 102.385 18.2445 104.703 18.0539ZM105.228 20.7215C105.635 20.7127 105.722 20.7826 106.002 20.5611C106.26 19.6405 105.264 19.2138 104.479 19.2263C103.984 19.3874 103.424 19.9315 103.375 20.4601C103.611 20.9109 104.71 20.7275 105.228 20.7215Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_17" d="M95.613 15.4501L97.1495 15.4595C97.0489 16.8341 97.132 19.5081 97.1476 20.9768C97.8592 20.0398 98.5873 19.1154 99.332 18.204L101.148 18.1842C100.305 19.0863 99.3081 20.3303 98.4959 21.2937C99.0164 21.8404 99.6947 22.698 100.195 23.2939C100.563 23.7308 100.935 24.1646 101.311 24.5952L99.44 24.5937C98.6621 23.6374 97.8536 22.6688 97.1255 21.6764C97.1273 22.6237 97.1089 23.6683 97.1532 24.6062C96.6299 24.6007 96.1288 24.6149 95.6056 24.6313C95.6683 21.6443 95.6314 18.4428 95.613 15.4501Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_18" d="M138.155 30.6314L138.216 30.6632C138.225 30.6912 138.25 30.7914 138.242 30.8154C137.368 33.7396 135.557 36.4317 132.96 38.0842C132.725 38.2333 132.282 38.5592 132.026 38.5579L131.893 38.3232L131.215 36.0397C133.275 34.5304 134.685 33.1812 135.475 30.6529C136.336 30.6234 137.286 30.6371 138.155 30.6314Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_19" d="M138.031 18.1923L139.634 18.187C139.113 19.4098 138.609 20.91 138.147 22.1743C137.664 23.3967 137.181 25.3724 136.413 26.3367C135.914 26.9644 135.196 26.9582 134.462 26.9886C134.433 25.9751 134.301 25.8813 135.281 25.5299C135.564 25.4284 135.723 24.84 135.83 24.5486L133.541 18.1904L135.218 18.1897C135.53 19.4879 136.212 21.5023 136.617 22.8346L138.031 18.1923Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_20" d="M112.102 18.046C115.346 18.1544 114.465 22.2111 114.441 24.6366C114.084 24.5819 113.334 24.6005 112.944 24.6C112.968 23.2884 113.132 21.3365 112.826 20.0538C112.623 19.2009 111.715 19.2205 110.994 19.5094C110.308 20.3316 110.553 23.3734 110.563 24.6263C110.172 24.5862 109.502 24.601 109.088 24.5984L109.092 18.1938C109.399 18.1918 110.308 18.1335 110.496 18.2688L110.576 18.8185C110.925 18.811 110.951 18.218 112.102 18.046Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_21" d="M157.03 32.3117L165.63 32.3144C165.624 33.1227 165.608 33.931 165.584 34.7389C165.222 34.8005 163.328 34.7749 162.89 34.7741L157.199 34.7598C157.159 33.9511 157.089 33.1214 157.03 32.3117Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_22" d="M81.1911 15.7075L87.5055 15.7099L87.4955 17.1429C86.7222 17.1164 85.9486 17.1026 85.1749 17.1016L85.1705 22.3787L85.1727 24.6281C84.799 24.5835 84.0131 24.599 83.6075 24.596L83.6191 17.1192C82.8556 17.1088 82.092 17.115 81.3288 17.1376C81.3098 16.6005 81.267 16.2407 81.1911 15.7075Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
<path id="Vector_23" d="M140.921 16.2497L142.45 16.2545C142.414 16.8395 142.427 17.6034 142.421 18.2023L143.71 18.1906L143.703 19.4062L142.427 19.3996C142.426 20.1072 142.287 22.5398 142.611 23.1728C142.719 23.383 143.27 23.3172 143.532 23.3359C143.762 23.5842 143.697 23.5858 143.741 23.9739L143.686 24.4864L143.561 24.5789C139.822 24.8423 141.248 21.9367 140.879 19.4935C140.535 19.4014 139.948 19.3017 139.914 18.8681C139.846 18.0247 140.318 18.2036 140.926 18.1833L140.921 16.2497Z" fill="var(--fill-0, #0D0D0C)"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 14 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg height="1em" style="flex:none;line-height:1" viewBox="0 0 24 24" width="1em" xmlns="http://www.w3.org/2000/svg"><title>Zhipu</title><path d="M11.991 23.503a.24.24 0 00-.244.248.24.24 0 00.244.249.24.24 0 00.245-.249.24.24 0 00-.22-.247l-.025-.001zM9.671 5.365a1.697 1.697 0 011.099 2.132l-.071.172-.016.04-.018.054c-.07.16-.104.32-.104.498-.035.71.47 1.279 1.186 1.314h.366c1.309.053 2.338 1.173 2.286 2.523-.052 1.332-1.152 2.38-2.478 2.327h-.174c-.715.018-1.274.64-1.239 1.368 0 .124.018.23.053.337.209.373.54.658.96.8.75.23 1.517-.125 1.9-.782l.018-.035c.402-.64 1.17-.96 1.92-.711.854.284 1.378 1.226 1.099 2.167a1.661 1.661 0 01-2.077 1.102 1.711 1.711 0 01-.907-.711l-.017-.035c-.2-.323-.463-.58-.851-.711l-.056-.018a1.646 1.646 0 00-1.954.746 1.66 1.66 0 01-1.065.764 1.677 1.677 0 01-1.989-1.279c-.209-.906.332-1.83 1.257-2.043a1.51 1.51 0 01.296-.035h.018c.68-.071 1.151-.622 1.116-1.333a1.307 1.307 0 00-.227-.693 2.515 2.515 0 01-.366-1.403 2.39 2.39 0 01.366-1.208c.14-.195.21-.444.227-.693.018-.71-.506-1.261-1.186-1.332l-.07-.018a1.43 1.43 0 01-.299-.07l-.05-.019a1.7 1.7 0 01-1.047-2.114 1.68 1.68 0 012.094-1.101zm-5.575 10.11c.26-.264.639-.367.994-.27.355.096.633.379.728.74.095.362-.007.748-.267 1.013-.402.41-1.053.41-1.455 0a1.062 1.062 0 010-1.482zm14.845-.294c.359-.09.738.024.992.297.254.274.344.665.237 1.025-.107.36-.396.634-.756.718-.551.128-1.1-.22-1.23-.781a1.05 1.05 0 01.757-1.26zm-.064-4.39c.314.32.49.753.49 1.206 0 .452-.176.886-.49 1.206-.315.32-.74.5-1.185.5-.444 0-.87-.18-1.184-.5a1.727 1.727 0 010-2.412 1.654 1.654 0 012.369 0zm-11.243.163c.364.484.447 1.128.218 1.691a1.665 1.665 0 01-2.188.923c-.855-.36-1.26-1.358-.907-2.228a1.68 1.68 0 011.33-1.038c.593-.08 1.183.169 1.547.652zm11.545-4.221c.368 0 .708.2.892.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.892.524c-.568 0-1.03-.47-1.03-1.048 0-.579.462-1.048 1.03-1.048zm-14.358 0c.368 0 .707.2.891.524.184.324.184.724 0 1.048a1.026 1.026 0 01-.891.524c-.569 0-1.03-.47-1.03-1.048 0-.579.461-1.048 1.03-1.048zm10.031-1.475c.925 0 1.675.764 1.675 1.706s-.75 1.705-1.675 1.705-1.674-.763-1.674-1.705c0-.942.75-1.706 1.674-1.706zm-2.626-.684c.362-.082.653-.356.761-.718a1.062 1.062 0 00-.238-1.028 1.017 1.017 0 00-.996-.294c-.547.14-.881.7-.752 1.257.13.558.675.907 1.225.783zm0 16.876c.359-.087.644-.36.75-.72a1.062 1.062 0 00-.237-1.019 1.018 1.018 0 00-.985-.301 1.037 1.037 0 00-.762.717c-.108.361-.017.754.239 1.028.245.263.606.377.953.305l.043-.01zM17.19 3.5a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64a.631.631 0 00-.628.64c0 .355.28.64.628.64zm-10.38 0a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64a.631.631 0 00-.628.64c0 .355.279.64.628.64zm-5.182 7.852a.631.631 0 00-.628.64c0 .354.28.639.628.639a.63.63 0 00.627-.606l.001-.034a.62.62 0 00-.628-.64zm5.182 9.13a.631.631 0 00-.628.64c0 .355.279.64.628.64a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm10.38.018a.631.631 0 00-.628.64c0 .355.28.64.628.64a.631.631 0 00.628-.64c0-.355-.279-.64-.628-.64zm5.182-9.148a.631.631 0 00-.628.64c0 .354.279.639.628.639a.631.631 0 00.628-.64c0-.355-.28-.64-.628-.64zm-.384-4.992a.24.24 0 00.244-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249c0 .142.122.249.244.249zM11.991.497a.24.24 0 00.245-.248A.24.24 0 0011.99 0a.24.24 0 00-.244.249c0 .133.108.236.223.247l.021.001zM2.011 6.36a.24.24 0 00.245-.249.24.24 0 00-.244-.249.24.24 0 00-.244.249.24.24 0 00.244.249zm0 11.263a.24.24 0 00-.243.248.24.24 0 00.244.249.24.24 0 00.244-.249.252.252 0 00-.244-.248zm19.995-.018a.24.24 0 00-.245.248.24.24 0 00.245.25.24.24 0 00.244-.25.252.252 0 00-.244-.248z" fill="#3859FF" fill-rule="nonzero"></path></svg>
|
||||||
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 50 KiB |
@@ -0,0 +1,4 @@
|
|||||||
|
User-agent: *
|
||||||
|
Allow: /
|
||||||
|
|
||||||
|
Sitemap: https://billowliu2.github.io/KimiSwitch/sitemap.xml
|
||||||
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 208 KiB |
|
After Width: | Height: | Size: 205 KiB |
|
After Width: | Height: | Size: 245 KiB |
|
After Width: | Height: | Size: 83 KiB |
|
After Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 125 KiB |
|
After Width: | Height: | Size: 143 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 38 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 124 KiB |
|
After Width: | Height: | Size: 290 KiB |
|
After Width: | Height: | Size: 49 KiB |
|
After Width: | Height: | Size: 70 KiB |
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||||
|
<url>
|
||||||
|
<loc>https://billowliu2.github.io/KimiSwitch/</loc>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>1.0</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://billowliu2.github.io/KimiSwitch/#/features</loc>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://billowliu2.github.io/KimiSwitch/#/showcase</loc>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://billowliu2.github.io/KimiSwitch/#/performance</loc>
|
||||||
|
<changefreq>monthly</changefreq>
|
||||||
|
<priority>0.7</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://billowliu2.github.io/KimiSwitch/#/changelog</loc>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>0.7</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://billowliu2.github.io/KimiSwitch/#/download</loc>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>0.9</priority>
|
||||||
|
</url>
|
||||||
|
<url>
|
||||||
|
<loc>https://github.com/billowliu2/KimiSwitch</loc>
|
||||||
|
<changefreq>weekly</changefreq>
|
||||||
|
<priority>0.8</priority>
|
||||||
|
</url>
|
||||||
|
</urlset>
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { Route, Routes, useLocation } from "react-router-dom";
|
||||||
|
import Nav from "./components/Nav";
|
||||||
|
import Hero from "./sections/Hero";
|
||||||
|
import Features from "./sections/Features";
|
||||||
|
import Changelog from "./sections/Changelog";
|
||||||
|
import Download from "./sections/Download";
|
||||||
|
import Privacy from "./sections/Privacy";
|
||||||
|
import CTA from "./sections/CTA";
|
||||||
|
import Footer from "./components/Footer";
|
||||||
|
|
||||||
|
function ScrollToTop() {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
useEffect(() => {
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
}, [pathname]);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const homePage = (
|
||||||
|
<>
|
||||||
|
<Hero />
|
||||||
|
<CTA />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<div className="flex min-h-screen flex-col">
|
||||||
|
<Nav />
|
||||||
|
<main className="flex-1">
|
||||||
|
<ScrollToTop />
|
||||||
|
<Routes>
|
||||||
|
<Route path="/" element={homePage} />
|
||||||
|
<Route
|
||||||
|
path="/features"
|
||||||
|
element={
|
||||||
|
<>
|
||||||
|
<Features />
|
||||||
|
<Privacy />
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Route path="/changelog" element={<Changelog />} />
|
||||||
|
<Route path="/download" element={<Download />} />
|
||||||
|
<Route path="*" element={homePage} />
|
||||||
|
</Routes>
|
||||||
|
</main>
|
||||||
|
<Footer />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { recommendLinks } from "../data/recommendLinks";
|
||||||
|
import { useLang } from "../i18n";
|
||||||
|
import { asset } from "../lib/asset";
|
||||||
|
|
||||||
|
/** Map recommendLink.name → logo file in public/logos (only when we have one). */
|
||||||
|
const logoFor: Record<string, string> = {
|
||||||
|
"Kimi": "logos/kimi.svg",
|
||||||
|
"智谱 GLM": "logos/glm.svg",
|
||||||
|
"DeepSeek": "logos/deepseek.svg",
|
||||||
|
"OpenCodeGo": "logos/opencode.svg",
|
||||||
|
"MiniMax": "logos/minimax.svg",
|
||||||
|
"基元律动": "logos/tokenrhythm.svg",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
const { t } = useLang();
|
||||||
|
return (
|
||||||
|
<footer className="border-t border-border py-12">
|
||||||
|
<div className="container-page space-y-8">
|
||||||
|
{/* 推荐链接 */}
|
||||||
|
<div>
|
||||||
|
<h3 className="mb-4 text-sm font-semibold uppercase tracking-wider text-muted-foreground">
|
||||||
|
{t.footer.recommend}
|
||||||
|
</h3>
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||||
|
{recommendLinks.map((link) => {
|
||||||
|
const logo = logoFor[link.name];
|
||||||
|
const text = t.footer.recommendText[link.name] ?? "";
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
key={link.name}
|
||||||
|
href={link.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="group flex items-start gap-3 rounded-xl border border-border bg-card p-4 transition-all hover:-translate-y-0.5 hover:border-primary/50"
|
||||||
|
>
|
||||||
|
<div className="flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded-lg border border-border bg-white">
|
||||||
|
{logo ? (
|
||||||
|
<img
|
||||||
|
src={asset(logo)}
|
||||||
|
alt={`${link.name} logo`}
|
||||||
|
className="h-7 w-7 object-contain"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span className="text-base font-bold text-muted-foreground">
|
||||||
|
{link.name.slice(0, 1)}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="min-w-0 flex-1">
|
||||||
|
<div className="font-semibold group-hover:text-primary">
|
||||||
|
{link.name}
|
||||||
|
</div>
|
||||||
|
{text && (
|
||||||
|
<p className="mt-1 line-clamp-3 text-xs text-muted-foreground">
|
||||||
|
{text}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 版权与开源 */}
|
||||||
|
<div className="flex flex-col gap-2 border-t border-border pt-6 text-sm text-muted-foreground md:flex-row md:items-center md:justify-between">
|
||||||
|
<div>
|
||||||
|
© 2026 Kimi Switch ·{" "}
|
||||||
|
<a
|
||||||
|
href="https://github.com/billowliu2/KimiSwitch"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="underline hover:text-foreground"
|
||||||
|
>
|
||||||
|
GitHub
|
||||||
|
</a>{" "}
|
||||||
|
·{" "}
|
||||||
|
<a
|
||||||
|
href="https://git.codingplan.site/admin/KimiCodeSwitch"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="underline hover:text-foreground"
|
||||||
|
>
|
||||||
|
{t.footer.mirror}
|
||||||
|
</a>{" "}
|
||||||
|
· MIT License
|
||||||
|
</div>
|
||||||
|
<div className="text-xs opacity-80">
|
||||||
|
{t.footer.creditBefore}
|
||||||
|
<a
|
||||||
|
href="https://github.com/JochenYang/kimicode-dashboard"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="underline hover:text-foreground"
|
||||||
|
>
|
||||||
|
kimicode-dashboard
|
||||||
|
</a>
|
||||||
|
{t.footer.creditAfter}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import { NavLink } from "react-router-dom";
|
||||||
|
import { useLang } from "../i18n";
|
||||||
|
import { asset } from "../lib/asset";
|
||||||
|
|
||||||
|
export default function Nav() {
|
||||||
|
const { lang, setLang, t } = useLang();
|
||||||
|
const links = [
|
||||||
|
{ to: "/", label: t.nav.home, end: true },
|
||||||
|
{ to: "/features", label: t.nav.features },
|
||||||
|
{ to: "/changelog", label: t.nav.changelog },
|
||||||
|
{ to: "/download", label: t.nav.download },
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<header className="sticky top-0 z-40 border-b border-border bg-background/80 backdrop-blur">
|
||||||
|
<div className="container-page flex h-14 items-center justify-between gap-4">
|
||||||
|
<NavLink
|
||||||
|
to="/"
|
||||||
|
className="flex shrink-0 items-center gap-2 text-sm font-semibold tracking-tight"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={asset("kimi.svg")}
|
||||||
|
alt="Kimi Switch logo"
|
||||||
|
className="h-7 w-7 rounded-lg"
|
||||||
|
/>
|
||||||
|
<span>Kimi Switch</span>
|
||||||
|
</NavLink>
|
||||||
|
<nav className="flex items-center gap-5 overflow-x-auto overflow-y-hidden text-sm text-muted-foreground [scrollbar-width:none] [&::-webkit-scrollbar]:hidden">
|
||||||
|
{links.map((l) => (
|
||||||
|
<NavLink
|
||||||
|
key={l.to}
|
||||||
|
to={l.to}
|
||||||
|
end={l.end}
|
||||||
|
className={({ isActive }) =>
|
||||||
|
`relative shrink-0 pb-1 transition-colors ${
|
||||||
|
isActive ? "text-foreground" : "hover:text-foreground"
|
||||||
|
}`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{({ isActive }) => (
|
||||||
|
<>
|
||||||
|
{l.label}
|
||||||
|
<span
|
||||||
|
className={`absolute -bottom-1 left-0 h-0.5 bg-primary transition-all duration-300 ${
|
||||||
|
isActive ? "w-full" : "w-0"
|
||||||
|
}`}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</NavLink>
|
||||||
|
))}
|
||||||
|
</nav>
|
||||||
|
<div className="flex shrink-0 items-center gap-2">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => setLang(lang === "zh" ? "en" : "zh")}
|
||||||
|
className="rounded-lg border border-border px-2.5 py-1 text-xs font-medium text-muted-foreground transition-colors hover:bg-accent hover:text-foreground"
|
||||||
|
aria-label="Switch language / 切换语言"
|
||||||
|
>
|
||||||
|
{lang === "zh" ? "EN" : "中文"}
|
||||||
|
</button>
|
||||||
|
<NavLink to="/download" className="btn-primary hidden text-xs sm:inline-flex md:text-sm">
|
||||||
|
{t.nav.downloadBtn}
|
||||||
|
</NavLink>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { useInView } from "../hooks";
|
||||||
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
|
interface SectionShellProps {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
subtitle?: string;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SectionShell({ id, title, subtitle, children }: SectionShellProps) {
|
||||||
|
const [ref, inView] = useInView<HTMLElement>();
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
ref={ref}
|
||||||
|
id={id}
|
||||||
|
aria-labelledby={`${id}-title`}
|
||||||
|
className={`py-20 transition-all duration-700 ease-out ${
|
||||||
|
inView
|
||||||
|
? "motion-safe:opacity-100 motion-safe:translate-y-0"
|
||||||
|
: "motion-safe:opacity-0 motion-safe:translate-y-6"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="container-page">
|
||||||
|
<div className="mb-12 max-w-3xl">
|
||||||
|
<h2
|
||||||
|
id={`${id}-title`}
|
||||||
|
className="text-3xl font-bold tracking-tight md:text-4xl"
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</h2>
|
||||||
|
{subtitle && (
|
||||||
|
<p className="mt-3 text-base text-muted-foreground">{subtitle}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
export interface RecommendLink {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const recommendLinks: RecommendLink[] = [
|
||||||
|
{
|
||||||
|
name: "Kimi",
|
||||||
|
url: "https://kimi-bot.com/activities/zh-cn/invite/share?scenario=invite&from=share_poster&invitation_code=6UJX7J",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "智谱 GLM",
|
||||||
|
url: "https://www.bigmodel.cn/invite?icode=s2o6ZfY5VjEPIJj05K%2BhW2czbXFgPRGIalpycrEwJ28%3D",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DeepSeek",
|
||||||
|
url: "https://platform.deepseek.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "OpenCodeGo",
|
||||||
|
url: "https://opencode.ai/go?ref=DFCNADQCEM",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MiniMax",
|
||||||
|
url: "https://platform.minimaxi.com/subscribe/token-plan?code=GmmZA629b5&source=link",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "基元律动",
|
||||||
|
url: "https://tokenrhythm.studio/i/rf_tr_v0QXuBTzyl3-bJOS2uFFPg6x",
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
/** Fires once when the element scrolls into view. */
|
||||||
|
export function useInView<T extends Element = Element>(
|
||||||
|
options: IntersectionObserverInit = { threshold: 0.15 },
|
||||||
|
) {
|
||||||
|
const ref = useRef<T | null>(null);
|
||||||
|
const [inView, setInView] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const node = ref.current;
|
||||||
|
if (!node || inView) return;
|
||||||
|
const io = new IntersectionObserver(([entry]) => {
|
||||||
|
if (entry.isIntersecting) {
|
||||||
|
setInView(true);
|
||||||
|
io.disconnect();
|
||||||
|
}
|
||||||
|
}, options);
|
||||||
|
io.observe(node);
|
||||||
|
return () => io.disconnect();
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [inView]);
|
||||||
|
|
||||||
|
return [ref, inView] as const;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Animates a numeric value once `trigger` flips to true. */
|
||||||
|
export function useCountUp(target: number, trigger: boolean, durationMs = 900) {
|
||||||
|
const [value, setValue] = useState(0);
|
||||||
|
useEffect(() => {
|
||||||
|
if (!trigger) return;
|
||||||
|
const start = performance.now();
|
||||||
|
const easeOut = (t: number) => 1 - Math.pow(1 - t, 3);
|
||||||
|
let raf = 0;
|
||||||
|
const tick = (now: number) => {
|
||||||
|
const p = Math.min(1, (now - start) / durationMs);
|
||||||
|
setValue(target * easeOut(p));
|
||||||
|
if (p < 1) raf = requestAnimationFrame(tick);
|
||||||
|
};
|
||||||
|
raf = requestAnimationFrame(tick);
|
||||||
|
return () => cancelAnimationFrame(raf);
|
||||||
|
}, [target, trigger, durationMs]);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
@@ -0,0 +1,575 @@
|
|||||||
|
import {
|
||||||
|
createContext,
|
||||||
|
useContext,
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
type ReactNode,
|
||||||
|
} from "react";
|
||||||
|
|
||||||
|
export type Lang = "zh" | "en";
|
||||||
|
|
||||||
|
const zh = {
|
||||||
|
nav: {
|
||||||
|
home: "首页",
|
||||||
|
features: "功能",
|
||||||
|
showcase: "演示",
|
||||||
|
performance: "性能",
|
||||||
|
changelog: "更新日志",
|
||||||
|
download: "下载",
|
||||||
|
downloadBtn: "下载 v0.6.7",
|
||||||
|
},
|
||||||
|
hero: {
|
||||||
|
badge: "开源 MIT · Windows 10 / 11",
|
||||||
|
titleBefore: "统一管理你的",
|
||||||
|
titleAccent: "AI 供应商",
|
||||||
|
titleAfter: "",
|
||||||
|
subtitle:
|
||||||
|
"一键切换 Kimi / 智谱 GLM / MiniMax / DeepSeek / OpenRouter / 基元律动,用量与账单一目了然。",
|
||||||
|
download: "免费下载",
|
||||||
|
source: "查看源码",
|
||||||
|
stats: [
|
||||||
|
{ value: "22", label: "预设供应商" },
|
||||||
|
{ value: "5910", label: "模型价格库" },
|
||||||
|
{ value: "100%", label: "开源免费" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
title: "核心功能",
|
||||||
|
subtitle: "从多供应商管理到用量监控,一站搞定。",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "multi-provider",
|
||||||
|
title: "多供应商管理",
|
||||||
|
desc: "Kimi / 智谱 GLM / MiniMax / DeepSeek / OpenRouter / 基元律动等 22 个预设,一键添加与切换。",
|
||||||
|
img: "screenshots/preset-picker.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "custom-provider",
|
||||||
|
title: "自定义供应商",
|
||||||
|
desc: "自定义 Base URL / API Key / 模型映射,支持 JSON 高级编辑。",
|
||||||
|
img: "screenshots/edit-provider-basic-managed.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "usage",
|
||||||
|
title: "用量与账单监控",
|
||||||
|
desc: "套餐类 5 小时 / 每周用量 + 按量余额,models.dev 真实价格计算成本,卡片直显。",
|
||||||
|
img: "screenshots/dashboard-light.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sessions",
|
||||||
|
title: "会话管理",
|
||||||
|
desc: "跨供应商浏览、搜索与恢复历史会话。",
|
||||||
|
img: "screenshots/sessions-light.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "kimi-auth",
|
||||||
|
title: "Kimi 一键授权登录",
|
||||||
|
desc: "应用内置设备码授权(等同 kimi login),浏览器授权即用,15 分钟过期自动续期。",
|
||||||
|
img: "screenshots/kimi-oauth-dialog.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "usage-config",
|
||||||
|
title: "配置用量查询",
|
||||||
|
desc: "全屏配置页:NewAPI 中转站模板、超时时间、自动刷新间隔、测试查询。",
|
||||||
|
img: "screenshots/usage-config-page.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "model-discovery",
|
||||||
|
title: "智能模型发现",
|
||||||
|
desc: "模型能力 / 上下文大小自动从 models.dev 同步,构建时自动拉取,5910 模型覆盖。",
|
||||||
|
img: "screenshots/kimi-cli-select-model.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "auto-update",
|
||||||
|
title: "自动更新",
|
||||||
|
desc: "自动检测新版本,一键下载安装,始终保持最新。",
|
||||||
|
img: "screenshots/settings.png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
showcase: {
|
||||||
|
title: "界面演示",
|
||||||
|
subtitle: "真实界面截图,所见即所得。",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
src: "screenshots/usage-config-page.png",
|
||||||
|
title: "配置用量查询",
|
||||||
|
desc: "NewAPI 中转站模板、超时与自动刷新、测试查询一屏搞定",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "screenshots/preset-picker.png",
|
||||||
|
title: "预设供应商",
|
||||||
|
desc: "22 个预设开箱即用,一键添加常用厂商",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "screenshots/kimi-oauth-dialog.png",
|
||||||
|
title: "Kimi 授权登录",
|
||||||
|
desc: "设备码授权等同 kimi login,过期自动续期",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "screenshots/dashboard-daily-detail.png",
|
||||||
|
title: "用量明细",
|
||||||
|
desc: "按天查看 Token 与费用走势",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "screenshots/sessions-light.png",
|
||||||
|
title: "会话管理",
|
||||||
|
desc: "跨供应商浏览与恢复历史会话",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "screenshots/kimi-plan-quota-card.png",
|
||||||
|
title: "套餐余量卡",
|
||||||
|
desc: "5 小时 / 每周额度直读,余量一眼可见",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
title: "轻量原生",
|
||||||
|
subtitle: "Tauri + Rust 内核,WebView2 前端,常驻托盘零负担。",
|
||||||
|
metrics: [
|
||||||
|
{ target: 93, suffix: " MB", label: "Working Set", desc: "程序空闲态工作集内存" },
|
||||||
|
{ target: 34, suffix: " MB", label: "Private WS", desc: "独占内存(WebView2 共享部分扣除)" },
|
||||||
|
{ target: 0.3, decimals: 1, suffix: "%", label: "CPU 空闲", desc: "无活跃任务时几乎零占用" },
|
||||||
|
],
|
||||||
|
lightCaption: "浅色主题",
|
||||||
|
darkCaption: "深色主题",
|
||||||
|
},
|
||||||
|
changelog: {
|
||||||
|
title: "更新日志",
|
||||||
|
subtitle: "每个版本的重要变更记录。",
|
||||||
|
syncedNote: "数据已同步 GitHub Releases",
|
||||||
|
fallbackNote: "内置版本记录",
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
version: "v0.6.7",
|
||||||
|
date: "2026-08-01",
|
||||||
|
items: [
|
||||||
|
"应用内置 Kimi 设备授权登录(等同 kimi login),浏览器授权即用,过期自动续期",
|
||||||
|
"外链打开统一走 Rust 命令,修复部分链接无法跳转",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.6",
|
||||||
|
date: "2026-08-01",
|
||||||
|
items: [
|
||||||
|
"模型价格对标 models.dev,覆盖 5910 个模型",
|
||||||
|
"新增基元律动供应商与推荐链接",
|
||||||
|
"设置页文案与参考来源说明优化",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.5",
|
||||||
|
date: "2026-08-01",
|
||||||
|
items: [
|
||||||
|
"更新检查优化:私有仓库优先,GitHub 回退,8 秒超时",
|
||||||
|
"修复按量余额显示",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.4",
|
||||||
|
date: "2026-08-01",
|
||||||
|
items: [
|
||||||
|
"新增套餐账单查询(5 小时 / 每周用量卡片直显)",
|
||||||
|
"OpenCode Zen 预设补充推荐链接",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.3",
|
||||||
|
date: "2026-07-30",
|
||||||
|
items: [
|
||||||
|
"添加默认供应商支持,新增 StepFun Plan 套餐预设",
|
||||||
|
"供应商编辑页新增 API Key 获取链接(支持推广链接)",
|
||||||
|
"修复链接无法打开浏览器的问题",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.2",
|
||||||
|
date: "2026-07-30",
|
||||||
|
items: [
|
||||||
|
"仪表盘布局重构:每日用量趋势折线图(Token / 缓存命中率 / 请求数)",
|
||||||
|
"热力图独立整行,格子放大",
|
||||||
|
"新增 OpenCode Go / Zen 预设",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.0",
|
||||||
|
date: "2026-07-29",
|
||||||
|
items: [
|
||||||
|
"预设供应商与供应商账单查询上线",
|
||||||
|
"多平台打包:macOS / Linux / Windows CI 同步发布",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.5.2",
|
||||||
|
date: "2026-07-29",
|
||||||
|
items: ["用量趋势改为 tab 容器,新增供应商模型两级下钻"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.5.1",
|
||||||
|
date: "2026-07-29",
|
||||||
|
items: ["窗口与托盘行为优化;模型表格与能力编辑 UI 打磨"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
privacy: {
|
||||||
|
title: "你的数据,只在你的电脑上",
|
||||||
|
subtitle: "三句话讲清楚隐私与开源承诺。",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "local",
|
||||||
|
title: "本地存储",
|
||||||
|
desc: "配置与用量仅存本机 ~/.kimi-switch 与 config.toml,绝不上传。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "direct",
|
||||||
|
title: "官方直连",
|
||||||
|
desc: "账单查询直连各厂商官方 API,无中间商、无统计埋点。",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "open",
|
||||||
|
title: "开源可审计",
|
||||||
|
desc: "MIT 协议全量开源(GitHub),欢迎审阅与共建。",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
title: "下载 Kimi Switch",
|
||||||
|
subtitle: "当前版本 v0.6.7 · Windows 10 / 11 主推,macOS 与 Linux 路线图上。",
|
||||||
|
autoUpdate: "应用内置自动检测更新,新版本发布后在设置页一键升级。",
|
||||||
|
ready: "已发布",
|
||||||
|
wip: "开发中",
|
||||||
|
githubBtn: "GitHub 下载",
|
||||||
|
mirrorBtn: "镜像下载",
|
||||||
|
items: [
|
||||||
|
{ id: "windows", name: "Windows", ready: true, note: ".msi 与 .exe 安装包,系统托盘常驻" },
|
||||||
|
{ id: "macos", name: "macOS", ready: false, note: "GitHub Actions 已能构建 .dmg,待官方适配" },
|
||||||
|
{ id: "linux", name: "Linux", ready: false, note: ".AppImage / .deb / .rpm 构建中" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cta: {
|
||||||
|
title: "现在就把所有供应商,放进一个应用",
|
||||||
|
subtitle: "免费 · 开源 MIT · 一分钟完成安装",
|
||||||
|
download: "免费下载",
|
||||||
|
source: "GitHub 源码",
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
recommend: "推荐供应商",
|
||||||
|
mirror: "国内镜像",
|
||||||
|
creditBefore: "仪表盘与会话管理功能基于 ",
|
||||||
|
creditAfter: " (MIT, © JochenYang) 移植",
|
||||||
|
recommendText: {
|
||||||
|
Kimi: "完成 Kimi 注册,你我都能 100% 拿奖,最高可得 1 年会员等值权益",
|
||||||
|
"智谱 GLM":
|
||||||
|
"通过我的邀请链接注册 BigModel.cn,即可获得 2000 万 Tokens 大礼包,畅享新一代旗舰模型 GLM-5.2",
|
||||||
|
DeepSeek: "",
|
||||||
|
OpenCodeGo: "",
|
||||||
|
MiniMax:
|
||||||
|
"MiniMax Token Plan:订阅套餐解锁最新模型,好友订阅享 9 折 + Builder 权益,邀请人得 10% 返利",
|
||||||
|
基元律动: "",
|
||||||
|
} as Record<string, string>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Dict = typeof zh;
|
||||||
|
|
||||||
|
const en: Dict = {
|
||||||
|
nav: {
|
||||||
|
home: "Home",
|
||||||
|
features: "Features",
|
||||||
|
showcase: "Demo",
|
||||||
|
performance: "Performance",
|
||||||
|
changelog: "Changelog",
|
||||||
|
download: "Download",
|
||||||
|
downloadBtn: "Download v0.6.7",
|
||||||
|
},
|
||||||
|
hero: {
|
||||||
|
badge: "Open source MIT · Windows 10 / 11",
|
||||||
|
titleBefore: "One app for all your ",
|
||||||
|
titleAccent: "AI providers",
|
||||||
|
titleAfter: "",
|
||||||
|
subtitle:
|
||||||
|
"Switch between Kimi, Zhipu GLM, MiniMax, DeepSeek, OpenRouter and TokenRhythm in one click, with usage and billing at a glance.",
|
||||||
|
download: "Free Download",
|
||||||
|
source: "View Source",
|
||||||
|
stats: [
|
||||||
|
{ value: "22", label: "Provider presets" },
|
||||||
|
{ value: "5910", label: "Model price DB" },
|
||||||
|
{ value: "100%", label: "Open source" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
title: "Core Features",
|
||||||
|
subtitle: "From multi-provider management to usage monitoring, all in one place.",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "multi-provider",
|
||||||
|
title: "Multi-Provider Management",
|
||||||
|
desc: "22 presets including Kimi, Zhipu GLM, MiniMax, DeepSeek, OpenRouter and TokenRhythm. Add and switch in one click.",
|
||||||
|
img: "screenshots/preset-picker.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "custom-provider",
|
||||||
|
title: "Custom Providers",
|
||||||
|
desc: "Custom Base URL, API Key and model mapping, with advanced JSON editing.",
|
||||||
|
img: "screenshots/edit-provider-basic-managed.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "usage",
|
||||||
|
title: "Usage & Billing",
|
||||||
|
desc: "5-hour / weekly plan quotas plus pay-as-you-go balance, priced with real models.dev rates and shown on cards.",
|
||||||
|
img: "screenshots/dashboard-light.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sessions",
|
||||||
|
title: "Session Manager",
|
||||||
|
desc: "Browse, search and resume past sessions across providers.",
|
||||||
|
img: "screenshots/sessions-light.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "kimi-auth",
|
||||||
|
title: "One-Click Kimi Login",
|
||||||
|
desc: "Built-in device-code authorization (same as kimi login). Authorize in the browser; 15-minute expiry auto-renews.",
|
||||||
|
img: "screenshots/kimi-oauth-dialog.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "usage-config",
|
||||||
|
title: "Usage Query Setup",
|
||||||
|
desc: "Full-page config: NewAPI relay templates, timeout, auto-refresh interval, and a test query.",
|
||||||
|
img: "screenshots/usage-config-page.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "model-discovery",
|
||||||
|
title: "Smart Model Discovery",
|
||||||
|
desc: "Model capabilities and context sizes sync from models.dev at build time, covering 5910 models.",
|
||||||
|
img: "screenshots/kimi-cli-select-model.png",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "auto-update",
|
||||||
|
title: "Auto Updates",
|
||||||
|
desc: "Automatically detects new versions and installs them in one click.",
|
||||||
|
img: "screenshots/settings.png",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
showcase: {
|
||||||
|
title: "Screenshots",
|
||||||
|
subtitle: "Real screenshots from the app. What you see is what you get.",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
src: "screenshots/usage-config-page.png",
|
||||||
|
title: "Usage Query Setup",
|
||||||
|
desc: "NewAPI relay templates, timeout and auto-refresh, plus a test query, all on one page",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "screenshots/preset-picker.png",
|
||||||
|
title: "Provider Presets",
|
||||||
|
desc: "22 presets ready out of the box, add popular vendors in one click",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "screenshots/kimi-oauth-dialog.png",
|
||||||
|
title: "Kimi Authorization",
|
||||||
|
desc: "Device-code flow identical to kimi login, auto-renews on expiry",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "screenshots/dashboard-daily-detail.png",
|
||||||
|
title: "Usage Details",
|
||||||
|
desc: "Daily token and cost trends",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "screenshots/sessions-light.png",
|
||||||
|
title: "Session Manager",
|
||||||
|
desc: "Browse and resume past sessions across providers",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: "screenshots/kimi-plan-quota-card.png",
|
||||||
|
title: "Plan Quota Card",
|
||||||
|
desc: "5-hour / weekly quotas at a glance",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
performance: {
|
||||||
|
title: "Lightweight & Native",
|
||||||
|
subtitle: "Tauri + Rust core with a WebView2 frontend. Zero tray overhead.",
|
||||||
|
metrics: [
|
||||||
|
{ target: 93, suffix: " MB", label: "Working Set", desc: "Idle working-set memory" },
|
||||||
|
{ target: 34, suffix: " MB", label: "Private WS", desc: "Private memory (shared WebView2 excluded)" },
|
||||||
|
{ target: 0.3, decimals: 1, suffix: "%", label: "Idle CPU", desc: "Nearly zero when idle" },
|
||||||
|
],
|
||||||
|
lightCaption: "Light theme",
|
||||||
|
darkCaption: "Dark theme",
|
||||||
|
},
|
||||||
|
changelog: {
|
||||||
|
title: "Changelog",
|
||||||
|
subtitle: "Notable changes in every release.",
|
||||||
|
syncedNote: "Synced from GitHub Releases",
|
||||||
|
fallbackNote: "Built-in release notes",
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
version: "v0.6.7",
|
||||||
|
date: "2026-08-01",
|
||||||
|
items: [
|
||||||
|
"Built-in Kimi device-code login (same as kimi login): authorize in the browser, auto-renews on expiry",
|
||||||
|
"External links now open via a unified Rust command, fixing broken jumps",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.6",
|
||||||
|
date: "2026-08-01",
|
||||||
|
items: [
|
||||||
|
"Model pricing aligned with models.dev, covering 5910 models",
|
||||||
|
"New TokenRhythm provider with referral link",
|
||||||
|
"Settings copy and source attribution polish",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.5",
|
||||||
|
date: "2026-08-01",
|
||||||
|
items: [
|
||||||
|
"Update check rework: private mirror first, GitHub fallback, 8s timeout",
|
||||||
|
"Fix pay-as-you-go balance display",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.4",
|
||||||
|
date: "2026-08-01",
|
||||||
|
items: [
|
||||||
|
"Plan billing queries (5-hour / weekly usage shown on cards)",
|
||||||
|
"OpenCode Zen preset gains a referral link",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.3",
|
||||||
|
date: "2026-07-30",
|
||||||
|
items: [
|
||||||
|
"Default provider support and a new StepFun Plan preset",
|
||||||
|
"API Key helper links on the provider edit page (referral links supported)",
|
||||||
|
"Fix links failing to open the browser",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.2",
|
||||||
|
date: "2026-07-30",
|
||||||
|
items: [
|
||||||
|
"Dashboard layout rework: daily trend line chart (tokens / cache hit rate / requests)",
|
||||||
|
"Heatmap gets its own full-width row with larger cells",
|
||||||
|
"New OpenCode Go / Zen presets",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.6.0",
|
||||||
|
date: "2026-07-29",
|
||||||
|
items: [
|
||||||
|
"Provider presets and per-provider billing queries launch",
|
||||||
|
"Multi-platform packaging: macOS / Linux / Windows CI releases",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.5.2",
|
||||||
|
date: "2026-07-29",
|
||||||
|
items: ["Usage trends moved into a tab container with provider-model two-level drill-down"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
version: "v0.5.1",
|
||||||
|
date: "2026-07-29",
|
||||||
|
items: ["Window and tray behavior polish; model table and capability editor UI refinements"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
privacy: {
|
||||||
|
title: "Your Data Stays on Your Machine",
|
||||||
|
subtitle: "Our privacy and open-source commitments in three sentences.",
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
id: "local",
|
||||||
|
title: "Local Storage",
|
||||||
|
desc: "Configs and usage data live only in ~/.kimi-switch and config.toml. Nothing is uploaded.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "direct",
|
||||||
|
title: "Direct Connections",
|
||||||
|
desc: "Billing queries go straight to each vendor's official API. No middlemen, no tracking.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "open",
|
||||||
|
title: "Open & Auditable",
|
||||||
|
desc: "Fully open source under MIT on GitHub. Reviews and contributions welcome.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
download: {
|
||||||
|
title: "Download Kimi Switch",
|
||||||
|
subtitle: "Current version v0.6.7 · Windows 10 / 11 first, macOS and Linux on the roadmap.",
|
||||||
|
autoUpdate: "Built-in update detection: upgrade in one click from Settings when a new version ships.",
|
||||||
|
ready: "Available",
|
||||||
|
wip: "In development",
|
||||||
|
githubBtn: "Download from GitHub",
|
||||||
|
mirrorBtn: "China mirror",
|
||||||
|
items: [
|
||||||
|
{ id: "windows", name: "Windows", ready: true, note: ".msi and .exe installers, lives in the system tray" },
|
||||||
|
{ id: "macos", name: "macOS", ready: false, note: "GitHub Actions already builds .dmg, official support pending" },
|
||||||
|
{ id: "linux", name: "Linux", ready: false, note: ".AppImage / .deb / .rpm builds in progress" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
cta: {
|
||||||
|
title: "Bring every provider into one app",
|
||||||
|
subtitle: "Free · Open source MIT · One-minute install",
|
||||||
|
download: "Free Download",
|
||||||
|
source: "GitHub Source",
|
||||||
|
},
|
||||||
|
footer: {
|
||||||
|
recommend: "Recommended Providers",
|
||||||
|
mirror: "China mirror",
|
||||||
|
creditBefore: "Dashboard & session management adapted from ",
|
||||||
|
creditAfter: " (MIT, © JochenYang)",
|
||||||
|
recommendText: {
|
||||||
|
Kimi: "Sign up with my invite link and we both win rewards, up to a 1-year membership equivalent",
|
||||||
|
"智谱 GLM":
|
||||||
|
"Register on BigModel.cn via my invite link and get a 20M token gift pack for the new GLM-5.2 flagship",
|
||||||
|
DeepSeek: "",
|
||||||
|
OpenCodeGo: "",
|
||||||
|
MiniMax:
|
||||||
|
"MiniMax Token Plan: subscribe to unlock the latest models. Friends get 10% off, inviters get 10% rebate",
|
||||||
|
基元律动: "",
|
||||||
|
} as Record<string, string>,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const dicts: Record<Lang, Dict> = { zh, en };
|
||||||
|
|
||||||
|
const STORAGE_KEY = "kimi-switch-site-lang";
|
||||||
|
|
||||||
|
function detect(): Lang {
|
||||||
|
if (typeof window === "undefined") return "zh";
|
||||||
|
const saved = localStorage.getItem(STORAGE_KEY);
|
||||||
|
if (saved === "zh" || saved === "en") return saved;
|
||||||
|
return navigator.language.toLowerCase().startsWith("zh") ? "zh" : "en";
|
||||||
|
}
|
||||||
|
|
||||||
|
const LangContext = createContext<{
|
||||||
|
lang: Lang;
|
||||||
|
setLang: (l: Lang) => void;
|
||||||
|
t: Dict;
|
||||||
|
}>({ lang: "zh", setLang: () => {}, t: zh });
|
||||||
|
|
||||||
|
export function LangProvider({ children }: { children: ReactNode }) {
|
||||||
|
const [lang, setLangState] = useState<Lang>(detect);
|
||||||
|
|
||||||
|
const setLang = (l: Lang) => {
|
||||||
|
setLangState(l);
|
||||||
|
localStorage.setItem(STORAGE_KEY, l);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.lang = lang === "zh" ? "zh-CN" : "en";
|
||||||
|
}, [lang]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LangContext.Provider value={{ lang, setLang, t: dicts[lang] }}>
|
||||||
|
{children}
|
||||||
|
</LangContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useLang() {
|
||||||
|
return useContext(LangContext);
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
@import "@fontsource/inter/400.css";
|
||||||
|
@import "@fontsource/inter/500.css";
|
||||||
|
@import "@fontsource/inter/600.css";
|
||||||
|
@import "@fontsource/inter/700.css";
|
||||||
|
@import "@fontsource/inter/800.css";
|
||||||
|
@import "@fontsource/fira-code/400.css";
|
||||||
|
@import "@fontsource/fira-code/500.css";
|
||||||
|
|
||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* Design tokens — Kimi brand: neutral gray + blue accent (matches the app UI).
|
||||||
|
Light is the default; dark follows prefers-color-scheme. */
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 240 11% 97%;
|
||||||
|
--foreground: 240 9% 11%;
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 240 9% 11%;
|
||||||
|
--muted: 240 6% 94%;
|
||||||
|
--muted-foreground: 220 9% 46%;
|
||||||
|
--accent: 240 6% 94%;
|
||||||
|
--accent-foreground: 240 9% 11%;
|
||||||
|
--border: 240 6% 90%;
|
||||||
|
--primary: 221 83% 53%;
|
||||||
|
--primary-foreground: 0 0% 100%;
|
||||||
|
--ring: 221 83% 53%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
:root {
|
||||||
|
--background: 222 47% 6%;
|
||||||
|
--foreground: 220 20% 92%;
|
||||||
|
--card: 222 32% 10%;
|
||||||
|
--card-foreground: 220 20% 92%;
|
||||||
|
--muted: 222 25% 14%;
|
||||||
|
--muted-foreground: 220 12% 62%;
|
||||||
|
--accent: 222 25% 14%;
|
||||||
|
--accent-foreground: 220 20% 92%;
|
||||||
|
--border: 222 22% 17%;
|
||||||
|
--primary: 217 91% 60%;
|
||||||
|
--primary-foreground: 0 0% 100%;
|
||||||
|
--ring: 217 91% 60%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground antialiased;
|
||||||
|
}
|
||||||
|
/* 科技风底纹:固定网格线,极低透明度,不干扰阅读 */
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(to right, hsl(var(--primary) / 0.05) 1px, transparent 1px),
|
||||||
|
linear-gradient(to bottom, hsl(var(--primary) / 0.05) 1px, transparent 1px);
|
||||||
|
background-size: 44px 44px;
|
||||||
|
mask-image: radial-gradient(ellipse 80% 60% at 50% 0%, black 30%, transparent 75%);
|
||||||
|
}
|
||||||
|
#root {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer components {
|
||||||
|
.container-page {
|
||||||
|
@apply mx-auto w-full max-w-6xl px-6;
|
||||||
|
}
|
||||||
|
.btn {
|
||||||
|
@apply inline-flex items-center justify-center gap-2 rounded-xl px-5 py-2.5 text-sm font-medium transition-colors;
|
||||||
|
}
|
||||||
|
.btn-primary {
|
||||||
|
@apply btn bg-primary text-primary-foreground hover:opacity-90;
|
||||||
|
box-shadow: 0 0 20px hsl(var(--primary) / 0.35);
|
||||||
|
}
|
||||||
|
.btn-ghost {
|
||||||
|
@apply btn border border-border text-foreground hover:bg-accent;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
@apply rounded-xl border border-border bg-card p-6 transition-shadow;
|
||||||
|
}
|
||||||
|
.card:hover {
|
||||||
|
box-shadow: 0 0 28px hsl(var(--primary) / 0.12);
|
||||||
|
}
|
||||||
|
/* 截图容器:限定最大宽高,避免撑满页面 */
|
||||||
|
.screenshot {
|
||||||
|
@apply overflow-hidden rounded-xl border border-border bg-muted;
|
||||||
|
box-shadow: 0 0 36px hsl(var(--primary) / 0.1);
|
||||||
|
}
|
||||||
|
.screenshot > img {
|
||||||
|
@apply block h-auto w-full;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/**
|
||||||
|
* Resolve a public-asset path against Vite's base URL.
|
||||||
|
* All site assets live under `/KimiSwitch/` in dev and on GitHub Pages,
|
||||||
|
* so bare absolute paths like `/logos/x.svg` would 404 — always go through here.
|
||||||
|
*/
|
||||||
|
export const asset = (p: string) =>
|
||||||
|
`${import.meta.env.BASE_URL}${p.replace(/^\/+/, "")}`;
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { StrictMode } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { HashRouter } from "react-router-dom";
|
||||||
|
import { LangProvider } from "./i18n";
|
||||||
|
import App from "./App";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(
|
||||||
|
<StrictMode>
|
||||||
|
<HashRouter>
|
||||||
|
<LangProvider>
|
||||||
|
<App />
|
||||||
|
</LangProvider>
|
||||||
|
</HashRouter>
|
||||||
|
</StrictMode>,
|
||||||
|
);
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { useLang } from "../i18n";
|
||||||
|
|
||||||
|
export default function CTA() {
|
||||||
|
const { t } = useLang();
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
id="cta"
|
||||||
|
aria-labelledby="cta-title"
|
||||||
|
className="py-24"
|
||||||
|
>
|
||||||
|
<div className="container-page">
|
||||||
|
<div className="relative overflow-hidden rounded-xl border border-primary/25 bg-gradient-to-r from-primary/10 via-primary/5 to-violet-500/10 p-12 text-center md:p-16">
|
||||||
|
{/* 科技风光晕 */}
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute -top-24 left-1/2 h-64 w-[480px] -translate-x-1/2 rounded-full bg-primary/15 blur-3xl"
|
||||||
|
/>
|
||||||
|
<div className="relative">
|
||||||
|
<h2
|
||||||
|
id="cta-title"
|
||||||
|
className="text-3xl font-bold tracking-tight md:text-5xl"
|
||||||
|
>
|
||||||
|
{t.cta.title}
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-base text-muted-foreground md:text-lg">
|
||||||
|
{t.cta.subtitle}
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 flex flex-wrap justify-center gap-3">
|
||||||
|
<Link to="/download" className="btn-primary">
|
||||||
|
{t.cta.download}
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="https://github.com/billowliu2/KimiSwitch"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="btn-ghost"
|
||||||
|
>
|
||||||
|
{t.cta.source}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import SectionShell from "../components/SectionShell";
|
||||||
|
import { useLang } from "../i18n";
|
||||||
|
|
||||||
|
interface ChangelogEntry {
|
||||||
|
version: string;
|
||||||
|
date: string;
|
||||||
|
items: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const API_URL =
|
||||||
|
"https://api.github.com/repos/billowliu2/KimiSwitch/releases?per_page=20";
|
||||||
|
const CACHE_KEY = "kimi-switch-changelog";
|
||||||
|
const CACHE_TTL = 60 * 60 * 1000; // 1 hour
|
||||||
|
|
||||||
|
/** Parse a GitHub release body into bullet items. */
|
||||||
|
function parseBody(body: string): string[] {
|
||||||
|
return body
|
||||||
|
.split("\n")
|
||||||
|
.map((l) => l.replace(/^\s*[-*]\s+/, "").trim())
|
||||||
|
.filter((l) => l.length > 0 && l !== "---");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchReleases(): Promise<ChangelogEntry[]> {
|
||||||
|
const res = await fetch(API_URL);
|
||||||
|
if (!res.ok) throw new Error(`GitHub API ${res.status}`);
|
||||||
|
const list = (await res.json()) as Array<{
|
||||||
|
tag_name: string;
|
||||||
|
published_at: string | null;
|
||||||
|
body: string | null;
|
||||||
|
}>;
|
||||||
|
return list.map((r) => ({
|
||||||
|
version: r.tag_name,
|
||||||
|
date: (r.published_at ?? "").slice(0, 10),
|
||||||
|
items: parseBody(r.body ?? ""),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
function readCache(): ChangelogEntry[] | null {
|
||||||
|
try {
|
||||||
|
const raw = localStorage.getItem(CACHE_KEY);
|
||||||
|
if (!raw) return null;
|
||||||
|
const { ts, data } = JSON.parse(raw) as { ts: number; data: ChangelogEntry[] };
|
||||||
|
if (Date.now() - ts > CACHE_TTL) return null;
|
||||||
|
return data;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeCache(data: ChangelogEntry[]) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(
|
||||||
|
CACHE_KEY,
|
||||||
|
JSON.stringify({ ts: Date.now(), data }),
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
/* ignore quota/private-mode errors */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Changelog() {
|
||||||
|
const { t } = useLang();
|
||||||
|
const embedded = t.changelog.entries as ChangelogEntry[];
|
||||||
|
const [remote, setRemote] = useState<ChangelogEntry[] | null>(null);
|
||||||
|
const [synced, setSynced] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let cancelled = false;
|
||||||
|
const cached = readCache();
|
||||||
|
if (cached) {
|
||||||
|
setRemote(cached);
|
||||||
|
setSynced(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fetchReleases()
|
||||||
|
.then((data) => {
|
||||||
|
if (cancelled) return;
|
||||||
|
setRemote(data);
|
||||||
|
setSynced(true);
|
||||||
|
writeCache(data);
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
/* 网络不通时使用内置列表兜底 */
|
||||||
|
});
|
||||||
|
return () => {
|
||||||
|
cancelled = true;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 远端拉到的版本优先显示(含未翻译的新版本),内置列表补齐缺失版本。
|
||||||
|
const entries: ChangelogEntry[] = [
|
||||||
|
...(remote ?? []).filter(
|
||||||
|
(r) => !embedded.some((e) => e.version === r.version),
|
||||||
|
),
|
||||||
|
...embedded,
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SectionShell
|
||||||
|
id="changelog"
|
||||||
|
title={t.changelog.title}
|
||||||
|
subtitle={t.changelog.subtitle}
|
||||||
|
>
|
||||||
|
<ol className="relative space-y-8 border-l border-border pl-8">
|
||||||
|
{entries.map((e) => (
|
||||||
|
<li key={e.version} className="relative">
|
||||||
|
<span className="absolute -left-[37px] top-1.5 h-2.5 w-2.5 rounded-full bg-primary" />
|
||||||
|
<div className="flex flex-wrap items-baseline gap-3">
|
||||||
|
<h3 className="font-mono text-lg font-semibold">{e.version}</h3>
|
||||||
|
<time className="text-xs text-muted-foreground">{e.date}</time>
|
||||||
|
</div>
|
||||||
|
{e.items.length > 0 && (
|
||||||
|
<ul className="mt-2 list-disc space-y-1 pl-5 text-sm text-muted-foreground">
|
||||||
|
{e.items.map((item) => (
|
||||||
|
<li key={item}>{item}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
)}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ol>
|
||||||
|
<p className="mt-10 text-xs text-muted-foreground">
|
||||||
|
<a
|
||||||
|
href="https://github.com/billowliu2/KimiSwitch/releases"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="underline hover:text-foreground"
|
||||||
|
>
|
||||||
|
{synced ? t.changelog.syncedNote : t.changelog.fallbackNote}
|
||||||
|
</a>
|
||||||
|
{" · "}
|
||||||
|
<a
|
||||||
|
href="https://git.codingplan.site/admin/KimiCodeSwitch/releases"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="underline hover:text-foreground"
|
||||||
|
>
|
||||||
|
{t.footer.mirror}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</SectionShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import SectionShell from "../components/SectionShell";
|
||||||
|
import { WindowsLogo, AppleLogo, LinuxLogo, DownloadSimple, type Icon } from "@phosphor-icons/react";
|
||||||
|
import { useLang } from "../i18n";
|
||||||
|
|
||||||
|
const icons: Record<string, Icon> = {
|
||||||
|
windows: WindowsLogo,
|
||||||
|
macos: AppleLogo,
|
||||||
|
linux: LinuxLogo,
|
||||||
|
};
|
||||||
|
|
||||||
|
const GITHUB_RELEASES = "https://github.com/billowliu2/KimiSwitch/releases";
|
||||||
|
const MIRROR_RELEASES = "https://git.codingplan.site/admin/KimiCodeSwitch/releases";
|
||||||
|
|
||||||
|
export default function Download() {
|
||||||
|
const { t } = useLang();
|
||||||
|
return (
|
||||||
|
<SectionShell
|
||||||
|
id="download"
|
||||||
|
title={t.download.title}
|
||||||
|
subtitle={t.download.subtitle}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||||
|
{t.download.items.map((p) => {
|
||||||
|
const Icon = icons[p.id] ?? WindowsLogo;
|
||||||
|
return (
|
||||||
|
<div key={p.id} className="card flex flex-col">
|
||||||
|
<div className="mb-3 flex items-center justify-between">
|
||||||
|
<div className="flex h-10 w-10 items-center justify-center rounded-xl border border-primary/25 bg-primary/10 text-primary">
|
||||||
|
<Icon className="h-5 w-5" weight="duotone" />
|
||||||
|
</div>
|
||||||
|
<span
|
||||||
|
className={`rounded-full px-2.5 py-0.5 text-xs font-medium ${
|
||||||
|
p.ready
|
||||||
|
? "bg-primary/10 text-primary border border-primary/25"
|
||||||
|
: "bg-muted text-muted-foreground border border-border"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{p.ready ? t.download.ready : t.download.wip}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h3 className="mb-2 text-lg font-semibold">{p.name}</h3>
|
||||||
|
<p className="text-sm text-muted-foreground">{p.note}</p>
|
||||||
|
{p.ready && (
|
||||||
|
<div className="mt-4 flex flex-wrap gap-2">
|
||||||
|
<a
|
||||||
|
href={GITHUB_RELEASES}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="btn-primary text-xs"
|
||||||
|
>
|
||||||
|
{t.download.githubBtn}
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href={MIRROR_RELEASES}
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="btn-ghost text-xs"
|
||||||
|
>
|
||||||
|
{t.download.mirrorBtn}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-6 flex items-center gap-2 text-sm text-muted-foreground">
|
||||||
|
<DownloadSimple className="h-4 w-4 text-primary" weight="duotone" />
|
||||||
|
{t.download.autoUpdate}
|
||||||
|
</p>
|
||||||
|
</SectionShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import SectionShell from "../components/SectionShell";
|
||||||
|
import { useInView } from "../hooks";
|
||||||
|
import { useLang } from "../i18n";
|
||||||
|
import { asset } from "../lib/asset";
|
||||||
|
import {
|
||||||
|
Stack,
|
||||||
|
PlusCircle,
|
||||||
|
Gauge,
|
||||||
|
ChatsCircle,
|
||||||
|
Key,
|
||||||
|
SlidersHorizontal,
|
||||||
|
MagnifyingGlass,
|
||||||
|
DownloadSimple,
|
||||||
|
type Icon,
|
||||||
|
} from "@phosphor-icons/react";
|
||||||
|
|
||||||
|
const icons: Record<string, Icon> = {
|
||||||
|
"multi-provider": Stack,
|
||||||
|
"custom-provider": PlusCircle,
|
||||||
|
usage: Gauge,
|
||||||
|
sessions: ChatsCircle,
|
||||||
|
"kimi-auth": Key,
|
||||||
|
"usage-config": SlidersHorizontal,
|
||||||
|
"model-discovery": MagnifyingGlass,
|
||||||
|
"auto-update": DownloadSimple,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Features() {
|
||||||
|
const { t } = useLang();
|
||||||
|
const [gridRef, gridInView] = useInView<HTMLDivElement>();
|
||||||
|
return (
|
||||||
|
<SectionShell
|
||||||
|
id="features"
|
||||||
|
title={t.features.title}
|
||||||
|
subtitle={t.features.subtitle}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={gridRef}
|
||||||
|
className="grid grid-cols-1 gap-6 sm:grid-cols-2"
|
||||||
|
>
|
||||||
|
{t.features.items.map((f, i) => {
|
||||||
|
const Icon = icons[f.id] ?? Stack;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={f.id}
|
||||||
|
style={{ transitionDelay: `${i * 60}ms` }}
|
||||||
|
className={`card overflow-hidden p-0 transition-all duration-500 ease-out hover:border-primary/50 ${
|
||||||
|
gridInView
|
||||||
|
? "motion-safe:opacity-100 motion-safe:translate-y-0"
|
||||||
|
: "motion-safe:opacity-0 motion-safe:translate-y-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="border-b border-border bg-muted">
|
||||||
|
<img
|
||||||
|
src={asset(f.img)}
|
||||||
|
alt={`Kimi Switch - ${f.title}`}
|
||||||
|
loading="lazy"
|
||||||
|
className="aspect-[16/9] w-full object-cover object-top"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="p-6">
|
||||||
|
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-xl border border-primary/25 bg-primary/10 text-primary">
|
||||||
|
<Icon className="h-5 w-5" weight="duotone" />
|
||||||
|
</div>
|
||||||
|
<h3 className="mb-2 text-lg font-semibold">{f.title}</h3>
|
||||||
|
<p className="text-sm leading-relaxed text-muted-foreground">
|
||||||
|
{f.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</SectionShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
import { useLang } from "../i18n";
|
||||||
|
import { asset } from "../lib/asset";
|
||||||
|
|
||||||
|
export default function Hero() {
|
||||||
|
const { t } = useLang();
|
||||||
|
const [mounted, setMounted] = useState(false);
|
||||||
|
useEffect(() => setMounted(true), []);
|
||||||
|
|
||||||
|
const reveal = (delay: number, extra = "") => ({
|
||||||
|
style: { transitionDelay: `${delay}ms` },
|
||||||
|
className: `transition-all duration-700 ease-out ${
|
||||||
|
mounted
|
||||||
|
? "motion-safe:opacity-100 motion-safe:translate-y-0"
|
||||||
|
: "motion-safe:opacity-0 motion-safe:translate-y-4"
|
||||||
|
} ${extra}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
id="hero"
|
||||||
|
aria-labelledby="hero-title"
|
||||||
|
className="relative overflow-hidden pt-16 pb-20 md:pt-24"
|
||||||
|
>
|
||||||
|
{/* 科技风光晕 */}
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute -top-32 right-[-10%] h-[420px] w-[420px] rounded-full bg-primary/15 blur-3xl"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-hidden="true"
|
||||||
|
className="pointer-events-none absolute -left-40 top-1/2 h-[320px] w-[320px] rounded-full bg-violet-500/10 blur-3xl"
|
||||||
|
/>
|
||||||
|
<div className="container-page relative grid grid-cols-1 gap-10 md:grid-cols-2 md:items-center">
|
||||||
|
<div>
|
||||||
|
<div {...reveal(0)}>
|
||||||
|
<span className="inline-block rounded-full border border-primary/25 bg-primary/10 px-3 py-1 text-xs font-medium text-primary">
|
||||||
|
{t.hero.badge}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<h1
|
||||||
|
id="hero-title"
|
||||||
|
{...reveal(100, "mt-4 text-4xl font-bold tracking-tight md:text-5xl lg:text-6xl")}
|
||||||
|
>
|
||||||
|
{t.hero.titleBefore}
|
||||||
|
<br />
|
||||||
|
<span className="whitespace-nowrap bg-gradient-to-r from-blue-500 to-violet-500 bg-clip-text text-transparent">
|
||||||
|
{t.hero.titleAccent}
|
||||||
|
</span>
|
||||||
|
{t.hero.titleAfter}
|
||||||
|
</h1>
|
||||||
|
<p
|
||||||
|
{...reveal(200, "mt-5 max-w-[52ch] text-base leading-relaxed text-muted-foreground md:text-lg")}
|
||||||
|
>
|
||||||
|
{t.hero.subtitle}
|
||||||
|
</p>
|
||||||
|
<div {...reveal(300, "mt-8 flex flex-wrap gap-3")}>
|
||||||
|
<Link to="/download" className="btn-primary">
|
||||||
|
{t.hero.download}
|
||||||
|
</Link>
|
||||||
|
<a
|
||||||
|
href="https://github.com/billowliu2/KimiSwitch"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
className="btn-ghost"
|
||||||
|
>
|
||||||
|
{t.hero.source}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<dl {...reveal(400, "mt-10 flex gap-8 font-mono")}>
|
||||||
|
{t.hero.stats.map((s) => (
|
||||||
|
<div key={s.label}>
|
||||||
|
<dt className="sr-only">{s.label}</dt>
|
||||||
|
<dd className="text-2xl font-semibold tabular-nums">
|
||||||
|
{s.value}
|
||||||
|
</dd>
|
||||||
|
<dd className="mt-1 text-xs text-muted-foreground">
|
||||||
|
{s.label}
|
||||||
|
</dd>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div {...reveal(250)}>
|
||||||
|
<div className="screenshot max-h-[420px]">
|
||||||
|
<img
|
||||||
|
src={asset("screenshots/hero.png")}
|
||||||
|
alt="Kimi Switch: multi-LLM provider list with one-click switching"
|
||||||
|
loading="eager"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import SectionShell from "../components/SectionShell";
|
||||||
|
import { useInView, useCountUp } from "../hooks";
|
||||||
|
import { useLang } from "../i18n";
|
||||||
|
import { asset } from "../lib/asset";
|
||||||
|
|
||||||
|
export default function Performance() {
|
||||||
|
const { t } = useLang();
|
||||||
|
return (
|
||||||
|
<SectionShell
|
||||||
|
id="performance"
|
||||||
|
title={t.performance.title}
|
||||||
|
subtitle={t.performance.subtitle}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
|
||||||
|
{t.performance.metrics.map((m) => (
|
||||||
|
<Counter key={m.label} {...m} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 仪表盘截图示例 */}
|
||||||
|
<div className="mt-8 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
<figure>
|
||||||
|
<div className="screenshot max-h-[340px]">
|
||||||
|
<img
|
||||||
|
src={asset("screenshots/dashboard-light.png")}
|
||||||
|
alt="Kimi Switch usage dashboard (light theme)"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<figcaption className="mt-2 text-center text-xs text-muted-foreground">
|
||||||
|
{t.performance.lightCaption}
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
<figure>
|
||||||
|
<div className="screenshot max-h-[340px]">
|
||||||
|
<img
|
||||||
|
src={asset("screenshots/dashboard-dark.png")}
|
||||||
|
alt="Kimi Switch usage dashboard (dark theme)"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<figcaption className="mt-2 text-center text-xs text-muted-foreground">
|
||||||
|
{t.performance.darkCaption}
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
</div>
|
||||||
|
</SectionShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Counter({
|
||||||
|
target,
|
||||||
|
decimals = 0,
|
||||||
|
suffix,
|
||||||
|
label,
|
||||||
|
desc,
|
||||||
|
}: {
|
||||||
|
target: number;
|
||||||
|
decimals?: number;
|
||||||
|
suffix: string;
|
||||||
|
label: string;
|
||||||
|
desc: string;
|
||||||
|
}) {
|
||||||
|
const [ref, inView] = useInView<HTMLDivElement>();
|
||||||
|
const v = useCountUp(target, inView);
|
||||||
|
return (
|
||||||
|
<div ref={ref} className="card text-center">
|
||||||
|
<div className="font-mono text-4xl font-bold tracking-tight tabular-nums">
|
||||||
|
{v.toFixed(decimals)}
|
||||||
|
{suffix}
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 text-sm font-medium">{label}</div>
|
||||||
|
<div className="mt-1 text-xs text-muted-foreground">{desc}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import SectionShell from "../components/SectionShell";
|
||||||
|
import { Database, LinkSimple, BookOpen, type Icon } from "@phosphor-icons/react";
|
||||||
|
import { useLang } from "../i18n";
|
||||||
|
|
||||||
|
const icons: Record<string, Icon> = {
|
||||||
|
local: Database,
|
||||||
|
direct: LinkSimple,
|
||||||
|
open: BookOpen,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Privacy() {
|
||||||
|
const { t } = useLang();
|
||||||
|
return (
|
||||||
|
<SectionShell
|
||||||
|
id="privacy"
|
||||||
|
title={t.privacy.title}
|
||||||
|
subtitle={t.privacy.subtitle}
|
||||||
|
>
|
||||||
|
<div className="grid grid-cols-1 gap-4 md:grid-cols-3">
|
||||||
|
{t.privacy.items.map((it) => {
|
||||||
|
const Icon = icons[it.id] ?? Database;
|
||||||
|
return (
|
||||||
|
<div key={it.id} className="card">
|
||||||
|
<div className="mb-3 flex h-10 w-10 items-center justify-center rounded-xl border border-primary/25 bg-primary/10 text-primary">
|
||||||
|
<Icon className="h-5 w-5" weight="duotone" />
|
||||||
|
</div>
|
||||||
|
<h3 className="mb-2 text-lg font-semibold">{it.title}</h3>
|
||||||
|
<p className="text-sm leading-relaxed text-muted-foreground">
|
||||||
|
{it.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</SectionShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import SectionShell from "../components/SectionShell";
|
||||||
|
import { useInView } from "../hooks";
|
||||||
|
import { useLang } from "../i18n";
|
||||||
|
import { asset } from "../lib/asset";
|
||||||
|
|
||||||
|
export default function Showcase() {
|
||||||
|
const { t } = useLang();
|
||||||
|
const [gridRef, gridInView] = useInView<HTMLDivElement>();
|
||||||
|
return (
|
||||||
|
<SectionShell
|
||||||
|
id="showcase"
|
||||||
|
title={t.showcase.title}
|
||||||
|
subtitle={t.showcase.subtitle}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
ref={gridRef}
|
||||||
|
className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"
|
||||||
|
>
|
||||||
|
{t.showcase.items.map((s, i) => (
|
||||||
|
<figure
|
||||||
|
key={s.src}
|
||||||
|
style={{ transitionDelay: `${i * 60}ms` }}
|
||||||
|
className={`transition-all duration-500 ease-out ${
|
||||||
|
gridInView
|
||||||
|
? "motion-safe:opacity-100 motion-safe:translate-y-0"
|
||||||
|
: "motion-safe:opacity-0 motion-safe:translate-y-4"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="screenshot group aspect-[4/3] overflow-hidden">
|
||||||
|
<img
|
||||||
|
src={asset(s.src)}
|
||||||
|
alt={`Kimi Switch - ${s.title}`}
|
||||||
|
loading="lazy"
|
||||||
|
className="h-full w-full object-cover object-top transition-transform duration-500 group-hover:scale-[1.03]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<figcaption className="mt-3">
|
||||||
|
<div className="text-sm font-semibold">{s.title}</div>
|
||||||
|
<p className="mt-0.5 text-xs text-muted-foreground">{s.desc}</p>
|
||||||
|
</figcaption>
|
||||||
|
</figure>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</SectionShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
/// <reference types="vite/client" />
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: ["./index.html", "./src/**/*.{ts,tsx}"],
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
background: "hsl(var(--background) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--foreground) / <alpha-value>)",
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(var(--card) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--card-foreground) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(var(--muted) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--muted-foreground) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(var(--accent) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--accent-foreground) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
border: "hsl(var(--border) / <alpha-value>)",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(var(--primary) / <alpha-value>)",
|
||||||
|
foreground: "hsl(var(--primary-foreground) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
ring: "hsl(var(--ring) / <alpha-value>)",
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: [
|
||||||
|
"Inter",
|
||||||
|
"-apple-system",
|
||||||
|
"BlinkMacSystemFont",
|
||||||
|
'"Segoe UI"',
|
||||||
|
'"PingFang SC"',
|
||||||
|
'"Hiragino Sans GB"',
|
||||||
|
'"Microsoft YaHei"',
|
||||||
|
"sans-serif",
|
||||||
|
],
|
||||||
|
mono: [
|
||||||
|
'"Fira Code"',
|
||||||
|
"ui-monospace",
|
||||||
|
"SFMono-Regular",
|
||||||
|
"Consolas",
|
||||||
|
"monospace",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"paths": { "@/*": ["./src/*"] }
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"references": [{ "path": "./tsconfig.node.json" }]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"strict": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
// GitHub Pages subpath: billowliu2.github.io/KimiSwitch/
|
||||||
|
export default defineConfig({
|
||||||
|
base: "/KimiSwitch/",
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: { "@": "/src" },
|
||||||
|
},
|
||||||
|
});
|
||||||