chore: initialize Tauri v2 project scaffold
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
src-tauri/target/
|
||||||
|
.vscode/
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
# KimiSwitch
|
||||||
|
|
||||||
|
A Windows x64 desktop config manager for Kimi Code CLI, built with Tauri v2, React, TypeScript, and Tailwind CSS.
|
||||||
|
|
||||||
|
KimiSwitch helps you manage multiple LLM provider configurations and switch between configuration profiles for Kimi Code CLI.
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,420 @@
|
|||||||
|
# KimiSwitch 设计文档
|
||||||
|
|
||||||
|
## 1. 项目概述
|
||||||
|
|
||||||
|
KimiSwitch 是一个 Windows 桌面配置管理工具,用于简化 Kimi Code CLI 的多供应商配置维护。它通过图形界面让用户增删改查 `[providers]` 与 `[models]`,支持多套配置**配置文件(Profile)**的保存与一键切换,最终生成符合 Kimi Code CLI 规范的 `~/.kimi-code/config.toml`。
|
||||||
|
|
||||||
|
## 2. 问题与目标
|
||||||
|
|
||||||
|
### 2.1 现状痛点
|
||||||
|
|
||||||
|
- Kimi Code CLI 的配置文件是 TOML 格式,需要手动编辑。
|
||||||
|
- 维护多个供应商(Kimi、OpenAI、Anthropic、Gemini、Vertex 等)时,`base_url` 与 `api_key` 容易写错或重复。
|
||||||
|
- 对 TOML 语法不熟的用户(例如 `[models."gpt-4.1"]` 需要引号)上手成本高。
|
||||||
|
- 工作、个人、测试等场景需要多套 API 配置时,只能手动备份/替换 `config.toml`,切换麻烦且容易出错。
|
||||||
|
|
||||||
|
### 2.2 项目目标
|
||||||
|
|
||||||
|
- **优先做好 Windows 版本**:安装包、路径处理、UI 风格、权限行为均以 Windows 为首要目标平台。
|
||||||
|
- 提供图形化界面,零 TOML 语法负担地维护供应商与模型别名。
|
||||||
|
- **支持一键切换当前使用的供应商及其对应模型**:选择供应商后自动筛选关联模型,确认后立即将 `default_model` 写入 `config.toml`。
|
||||||
|
- **支持多配置文件(Profile)管理**:保存多份独立配置,一键切换激活某一份到 `~/.kimi-code/config.toml`。
|
||||||
|
- 支持 Kimi Code CLI 官方支持的全部供应商类型。
|
||||||
|
- 自动处理凭证存储方式(直接字段 vs `[providers.<name>.env]` 子表)。
|
||||||
|
- 以 Windows MSI 安装包形式分发给 Windows 用户。
|
||||||
|
|
||||||
|
## 3. 功能范围
|
||||||
|
|
||||||
|
### 3.1 In Scope
|
||||||
|
|
||||||
|
- 供应商(providers)管理:增删改查。
|
||||||
|
- 模型别名(models)管理:增删改查。
|
||||||
|
- **供应商与模型快速切换**:选择供应商后只显示使用该供应商的模型,一键设为 `default_model`。
|
||||||
|
- **Profile(配置快照)管理**:新建、重命名、复制、删除、切换 Profile;Profile 保存在 `~/.kimi-code/profiles/` 下,切换时写入 `~/.kimi-code/config.toml`。
|
||||||
|
- 读取现有 `~/.kimi-code/config.toml`。
|
||||||
|
- 保存时自动备份原配置并写入新配置。
|
||||||
|
- 基础校验:必填项、重复别名、缺失的供应商引用、缺失凭证。
|
||||||
|
- 生成 Windows MSI 安装包。
|
||||||
|
|
||||||
|
### 3.2 Out of Scope
|
||||||
|
|
||||||
|
- 不实现加密存储:API Key 按 Kimi Code CLI 官方方式保存在 `config.toml` 中。
|
||||||
|
- 不修改 `tui.toml`、`mcp.json`、OAuth 凭证等其他文件。
|
||||||
|
- 不实现远程同步或云存储。
|
||||||
|
- 不实现网络连通性测试或 API 调用验证。
|
||||||
|
- 第一版不支持 macOS / Linux 安装包,仅输出 Windows x64 MSI;其他平台待 Windows x64 版本完成后再规划。
|
||||||
|
|
||||||
|
## 4. 技术方案
|
||||||
|
|
||||||
|
### 4.1 推荐方案
|
||||||
|
|
||||||
|
**Tauri(Rust + TypeScript)**
|
||||||
|
|
||||||
|
- **Tauri v2**:Rust 编写的跨平台桌面框架,内置 Windows MSI 打包,产物体积小、启动快。本设计基于 Tauri v2 的权限与命令体系。
|
||||||
|
- **Rust**:负责文件 I/O、配置解析、Profile 管理、校验逻辑;类型安全、性能高。
|
||||||
|
- **TypeScript + React + Tailwind CSS**:负责前端界面;Tailwind 提供基础样式,无需引入重型 UI 组件库。
|
||||||
|
- **toml_edit**:Rust 生态中支持 round-trip 的 TOML 编辑库,保留注释与格式。
|
||||||
|
- **IndexMap**:替代 `HashMap` 保留 Provider / Model 的插入顺序。
|
||||||
|
- **Tauri Bundler**:一键生成 `.msi` 安装包,无需额外 WiX 手动编写。
|
||||||
|
|
||||||
|
### 4.2 备选方案
|
||||||
|
|
||||||
|
| 方案 | 优点 | 缺点 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Tauri + Rust + Vanilla TS | 依赖更少,构建更快 | 大型表单状态管理需手写 |
|
||||||
|
| Tauri + Rust + Vue/Svelte | 体积与 Vanilla 相近 | 团队需熟悉对应框架 |
|
||||||
|
| egui / Iced(纯 Rust) | 无前端构建链 | UI 灵活度与组件生态不如 Web |
|
||||||
|
|
||||||
|
选择 **Tauri + Rust + React** 的原因是:Tauri 内置 MSI 打包且产物体积小;Rust 适合处理文件与配置逻辑;React 表单/表格生态成熟,能快速实现三栏配置界面。
|
||||||
|
|
||||||
|
## 5. 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
D:/AIGC/KimiSwitch
|
||||||
|
├── src-tauri/
|
||||||
|
│ ├── Cargo.toml
|
||||||
|
│ ├── tauri.conf.json # Tauri 应用配置与打包选项
|
||||||
|
│ └── src/
|
||||||
|
│ ├── main.rs # 入口
|
||||||
|
│ ├── lib.rs # 模块导出
|
||||||
|
│ ├── models.rs # Provider / Model / Config / Profile 结构体
|
||||||
|
│ ├── config_io.rs # 读写 config.toml 与 Profile 文件
|
||||||
|
│ ├── profile_manager.rs # Profile 生命周期管理
|
||||||
|
│ ├── validators.rs # 配置校验
|
||||||
|
│ └── commands.rs # Tauri 暴露给前端的命令
|
||||||
|
├── src/ # 前端源码
|
||||||
|
│ ├── App.tsx
|
||||||
|
│ ├── components/
|
||||||
|
│ │ ├── TopBar.tsx # Profile / 供应商 / 模型 / 设为默认
|
||||||
|
│ │ ├── ProviderList.tsx
|
||||||
|
│ │ ├── ProviderForm.tsx
|
||||||
|
│ │ ├── ModelList.tsx
|
||||||
|
│ │ ├── ModelForm.tsx
|
||||||
|
│ │ └── BottomToolbar.tsx
|
||||||
|
│ ├── hooks/
|
||||||
|
│ │ └── useConfig.ts # 配置状态与 Tauri 命令封装
|
||||||
|
│ └── types/
|
||||||
|
│ └── index.ts # 前端类型定义
|
||||||
|
├── tests/
|
||||||
|
│ ├── config_io.rs
|
||||||
|
│ ├── profile_manager.rs
|
||||||
|
│ └── validators.rs
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── vite.config.ts
|
||||||
|
├── Cargo.lock
|
||||||
|
├── README.md
|
||||||
|
└── .gitignore
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. 数据模型
|
||||||
|
|
||||||
|
### 6.1 ProviderType 枚举
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum ProviderType {
|
||||||
|
Kimi,
|
||||||
|
Anthropic,
|
||||||
|
Openai,
|
||||||
|
#[serde(rename = "openai_responses")]
|
||||||
|
OpenaiResponses,
|
||||||
|
#[serde(rename = "google-genai")]
|
||||||
|
GoogleGenai,
|
||||||
|
Vertexai,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> 注意:`openai_responses` 与 `google-genai` 包含连字符,无法直接用 `rename_all = "snake_case"`,需单独 `#[serde(rename)]`。
|
||||||
|
|
||||||
|
### 6.2 Provider
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Provider {
|
||||||
|
pub name: String, // 供应商唯一标识
|
||||||
|
pub provider_type: ProviderType, // 枚举
|
||||||
|
pub base_url: Option<String>, // 直接字段
|
||||||
|
pub api_key: Option<String>, // 直接字段
|
||||||
|
pub env: IndexMap<String, String>, // [providers.<name>.env] 子表,保留顺序
|
||||||
|
pub raw_other: Table, // 保留本工具不直接编辑的其他字段(如 custom_headers)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.3 Model
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Model {
|
||||||
|
pub alias: String, // 模型别名
|
||||||
|
pub provider: String, // 引用的供应商 name
|
||||||
|
pub model: String, // 实际模型 ID
|
||||||
|
pub max_context_size: u64,
|
||||||
|
pub display_name: Option<String>,
|
||||||
|
pub raw_other: Table, // 保留 capabilities 等本工具不直接编辑的字段
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.4 Config
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Config {
|
||||||
|
pub default_model: Option<String>,
|
||||||
|
pub providers: IndexMap<String, Provider>, // 保留顺序
|
||||||
|
pub models: IndexMap<String, Model>, // 保留顺序
|
||||||
|
pub raw_other: Table, // 保留 config.toml 中与本工具无关的其他字段
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6.5 Profile
|
||||||
|
|
||||||
|
```rust
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Profile {
|
||||||
|
pub name: String, // Profile 显示名称,可包含中文/空格
|
||||||
|
pub filename: String, // 安全化的存储文件名,如 "my-profile.toml"
|
||||||
|
pub config: Config, // 该 Profile 对应的完整配置内容
|
||||||
|
pub is_active: bool, // 是否当前已激活
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Profile 文件统一存放在 `~/.kimi-code/profiles/` 目录下。`default` Profile 特殊处理:它直接对应 `~/.kimi-code/config.toml`,不额外在 `profiles/` 下生成文件。
|
||||||
|
|
||||||
|
**文件名安全化**:Profile 显示名可包含中文、空格、大小写;存储时自动转译为合法 Windows 文件名。转译规则:保留 ASCII 字母数字,其他字符替换为 `-`,连续 `-` 合并,小写化;若结果为空(如纯中文名),则回退为基于名称哈希的短文件名(如 `profile-48291.toml`)。
|
||||||
|
|
||||||
|
## 7. 配置读写行为
|
||||||
|
|
||||||
|
### 7.1 读取流程
|
||||||
|
|
||||||
|
1. 定位 `~/.kimi-code/config.toml`(可通过 `KIMI_CODE_HOME` 环境变量覆盖;使用 Rust `std::path::PathBuf` 处理 Windows 路径)。
|
||||||
|
2. 使用 `toml_edit` 解析 TOML(保留注释、空行与字段顺序)。
|
||||||
|
3. 提取 `[providers.*]` 与 `[models.*]` 到 Rust 结构体。
|
||||||
|
4. 保留文件中其他字段(如 `[thinking]`、`[loop_control]` 等),保存时原样写回。
|
||||||
|
|
||||||
|
### 7.2 写入流程
|
||||||
|
|
||||||
|
1. 若目标文件已存在,先复制一份带时间戳的备份:`config.toml.bak.YYYYMMDD_HHMMSS_<毫秒>`(如 1 秒内多次保存,追加自增序号)。
|
||||||
|
2. 使用 `toml_edit` 加载并修改 `[providers]`、`[models]` 与 `default_model`,保留原文件中的注释、空行与字段顺序。
|
||||||
|
3. 合并保留的其他字段。
|
||||||
|
4. 写回文件。
|
||||||
|
5. 写操作使用临时文件 + 原子重命名(`write to tmp → fsync → rename`),降低写损风险。
|
||||||
|
|
||||||
|
> 第一版即使用 `toml_edit` 而非 `toml`/`serde_toml`,避免用户的既有注释和格式在保存后被清空。
|
||||||
|
|
||||||
|
### 7.3 Tauri 权限配置
|
||||||
|
|
||||||
|
Tauri 默认禁止前端访问文件系统,需在 `tauri.conf.json`(或 `capabilities/` 目录)中显式声明:
|
||||||
|
|
||||||
|
- `fs:allow-read-file`:`~/.kimi-code/config.toml`、Profile 文件。
|
||||||
|
- `fs:allow-write-file`:`~/.kimi-code/config.toml`、Profile 文件。
|
||||||
|
- `fs:allow-read-dir`:`~/.kimi-code/`、`~/.kimi-code/profiles/`。
|
||||||
|
- `fs:allow-create-dir`:首次运行时创建 `~/.kimi-code/profiles/`。
|
||||||
|
- `core:default`:基础窗口与事件权限。
|
||||||
|
|
||||||
|
所有路径使用 Tauri 的 `BaseDirectory::Home` 解析,避免硬编码绝对路径。
|
||||||
|
|
||||||
|
### 7.4 Tauri 命令清单
|
||||||
|
|
||||||
|
Rust 后端通过 `commands.rs` 暴露以下命令给前端调用:
|
||||||
|
|
||||||
|
| 命令 | 输入 | 输出 | 说明 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| `load_config` | 无 | `Config` | 读取 `~/.kimi-code/config.toml`,若不存在则初始化空配置。 |
|
||||||
|
| `save_config` | `Config` | `Result<(), String>` | 校验并写入 `config.toml`,失败返回错误信息。 |
|
||||||
|
| `list_profiles` | 无 | `Vec<ProfileSummary>` | 列出 `profiles/` 下所有 Profile 及其激活状态。 |
|
||||||
|
| `load_profile` | `filename: String` | `Config` | 读取指定 Profile 文件内容。 |
|
||||||
|
| `save_profile` | `filename: String, config: Config` | `Result<(), String>` | 保存或覆盖指定 Profile 文件。 |
|
||||||
|
| `switch_profile` | `filename: String` | `Result<Config, String>` | 将指定 Profile 内容写入 `config.toml`(带备份)并返回新配置。 |
|
||||||
|
| `rename_profile` | `old_filename, new_name` | `Result<String, String>` | 重命名 Profile,返回新的安全文件名。 |
|
||||||
|
| `delete_profile` | `filename: String` | `Result<(), String>` | 删除指定 Profile 文件。 |
|
||||||
|
| `open_config_dir` | 无 | `Result<(), String>` | 调用系统文件管理器打开配置目录。 |
|
||||||
|
| `get_app_version` | 无 | `String` | 返回当前应用版本号。 |
|
||||||
|
|
||||||
|
### 7.5 凭证存储策略
|
||||||
|
|
||||||
|
根据 [Kimi Code CLI 配置覆盖文档](https://www.kimi.com/code/docs/kimi-code-cli/configuration/overrides.html),凭证解析优先级为:
|
||||||
|
|
||||||
|
1. `[providers.<name>].api_key` / `.base_url`
|
||||||
|
2. `[providers.<name>.env]` 中对应键
|
||||||
|
3. 两者都缺失 → CLI 启动报错
|
||||||
|
|
||||||
|
KimiSwitch 默认将凭证写入 `[providers.<name>.env]` 子表,使用官方约定键名,同时提供「使用直接字段」开关,允许用户将 `api_key` / `base_url` 写到 `[providers.<name>]` 顶层。UI 中两种方式互斥:
|
||||||
|
|
||||||
|
- **env 子表模式**:`api_key` / `base_url` 输入框的值映射到 `env` 中的约定键名;保存时顶层 `api_key` / `base_url` 字段被移除。
|
||||||
|
- **直接字段模式**:`api_key` / `base_url` 写入顶层字段;保存时 `env` 中的对应约定键名被移除,其他自定义 `env` 键保留。
|
||||||
|
|
||||||
|
避免直接字段与 `env` 同时存在导致优先级迷惑。
|
||||||
|
|
||||||
|
各供应商的默认 Base URL 与 `env` 键名如下(参考 [Kimi Code CLI 平台与模型](https://www.kimi.com/code/docs/kimi-code-cli/configuration/providers.html)):
|
||||||
|
|
||||||
|
| 供应商类型 | 默认 Base URL | API Key 键名 | Base URL 键名 | 特殊说明 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `kimi` | `https://api.moonshot.ai/v1` | `KIMI_API_KEY` | `KIMI_BASE_URL` | 支持视频上传 |
|
||||||
|
| `anthropic` | Anthropic SDK 默认 | `ANTHROPIC_API_KEY` | `ANTHROPIC_BASE_URL` | Claude 模型自动识别 thinking / 视觉 / 工具调用 |
|
||||||
|
| `openai` | `https://api.openai.com/v1` | `OPENAI_API_KEY` | `OPENAI_BASE_URL` | 兼容 DeepSeek、Qwen 等第三方网关 |
|
||||||
|
| `openai_responses` | `https://api.openai.com/v1` | `OPENAI_API_KEY` | `OPENAI_BASE_URL` | 使用 OpenAI Responses API |
|
||||||
|
| `google-genai` | `https://generativelanguage.googleapis.com` | `GOOGLE_API_KEY` | `GOOGLE_GEMINI_BASE_URL` | 只填主机根地址,不要带 `/v1beta` |
|
||||||
|
| `vertexai` | 区域化 `*-aiplatform.googleapis.com` | 通常不需要(走 ADC) | `GOOGLE_VERTEX_BASE_URL` | 必须提供 `GOOGLE_CLOUD_PROJECT` 与 `GOOGLE_CLOUD_LOCATION`;只填主机根地址 |
|
||||||
|
|
||||||
|
创建供应商时,UI 根据所选 `type` 自动填入默认 Base URL 与推荐 `env` 键名,用户可手动覆盖。
|
||||||
|
|
||||||
|
### 7.6 Profile 生命周期
|
||||||
|
|
||||||
|
Profile 是 `~/.kimi-code/config.toml` 的命名快照,存放在 `~/.kimi-code/profiles/` 目录下。
|
||||||
|
|
||||||
|
#### 7.6.1 初始化
|
||||||
|
|
||||||
|
- 工具首次启动时,若 `~/.kimi-code/config.toml` 已存在,自动生成一个名为 `default` 的 Profile,其内容即为当前 `config.toml`。
|
||||||
|
- 若 `config.toml` 不存在,创建一个空 `default` Profile。
|
||||||
|
- `default` Profile 始终直接对应 `~/.kimi-code/config.toml`,不在 `profiles/` 下额外生成文件。
|
||||||
|
|
||||||
|
**文件名安全化**:Profile 显示名可包含中文、空格、大小写;存储时自动转译为合法 Windows 文件名。转译规则:保留 ASCII 字母数字,其他字符替换为 `-`,连续 `-` 合并,小写化;若结果为空(如纯中文名),则回退为基于名称哈希的短文件名(如 `profile-48291.toml`)。
|
||||||
|
|
||||||
|
#### 7.6.2 操作
|
||||||
|
|
||||||
|
| 操作 | 行为 |
|
||||||
|
| --- | --- |
|
||||||
|
| 新建 Profile | 用户输入显示名,自动生成安全文件名;可选择「从当前配置复制」或「空白」;保存到 `profiles/<safe-name>.toml`。 |
|
||||||
|
| 重命名 Profile | 仅重命名非 `default` 的 Profile,修改显示名并重新生成安全文件名;原文件删除,新文件写入。 |
|
||||||
|
| 复制 Profile | 以 `<原显示名> Copy` 创建副本,自动生成新的安全文件名。 |
|
||||||
|
| 删除 Profile | 删除 `profiles/<safe-name>.toml`;`default` 不可删除。 |
|
||||||
|
| 切换 Profile | 将选中的 Profile 内容写入 `~/.kimi-code/config.toml`(先备份当前 `config.toml`),然后重新加载界面。 |
|
||||||
|
| 保存当前到 Profile | 把当前编辑内容导出为新的 Profile 文件,不影响当前激活状态。 |
|
||||||
|
|
||||||
|
#### 7.6.3 激活状态判定
|
||||||
|
|
||||||
|
- 工具启动时将 `config.toml` 解析为 `Config`,再与各 Profile 的 `Config` 做深度比较(而非字符串比较),语义一致即标记为 `is_active`。
|
||||||
|
- 若没有任何 Profile 与当前 `config.toml` 语义一致,则 `default` Profile 标记为 `is_active`,并在界面上提示「当前 config.toml 与所有 Profile 不完全匹配」。
|
||||||
|
|
||||||
|
#### 7.6.4 未保存变更处理
|
||||||
|
|
||||||
|
- 切换 Profile 或退出前若存在未保存的编辑,弹出对话框:「保存当前修改 / 丢弃 / 取消」。
|
||||||
|
- 标题栏或状态栏显示「已修改」指示器。
|
||||||
|
|
||||||
|
## 8. 界面设计
|
||||||
|
|
||||||
|
前端使用 React + TypeScript + Tailwind CSS 实现,主窗口采用三栏布局,顶部增加「快速切换」工具条。前端通过 Tauri `invoke` 调用 Rust 命令完成所有文件操作。
|
||||||
|
|
||||||
|
- **窗口约束**:最小宽度 1000px,最小高度 700px;标题栏显示 `[未保存] Profile 名 - KimiSwitch`。
|
||||||
|
- **快捷键**:`Ctrl + S` 保存当前配置,`Ctrl + R` 重新读取,`Ctrl + O` 打开配置目录。第一版不单独做主题切换,跟随系统亮/暗模式。
|
||||||
|
|
||||||
|
### 8.1 顶部工具条
|
||||||
|
|
||||||
|
从左到右依次为:
|
||||||
|
|
||||||
|
- **Profile 下拉框**:列出所有 Profile(含 `default`),当前激活项高亮显示。
|
||||||
|
- **Profile 操作按钮**:「新建」「复制」「重命名」「删除」「切换」。
|
||||||
|
- **当前供应商** 下拉框:列出当前 Profile 中已配置供应商。
|
||||||
|
- **当前模型** 下拉框:根据所选供应商,只列出引用该供应商的模型别名;若该供应商下没有模型,则提示「未绑定模型,请先在右侧添加」。
|
||||||
|
- **设为默认** 按钮:将当前选中的模型写入 `default_model` 字段,并立即保存到当前 Profile 与 `config.toml`。
|
||||||
|
- 启动时自动高亮当前 `default_model` 对应的供应商和模型。
|
||||||
|
|
||||||
|
### 8.2 左栏:供应商列表
|
||||||
|
|
||||||
|
- 显示所有 provider name 与 type。
|
||||||
|
- 支持「添加」与「删除」按钮。
|
||||||
|
- 切换左栏选中项时,顶部「当前供应商」下拉框同步联动,右栏模型列表按该供应商过滤。
|
||||||
|
|
||||||
|
### 8.3 中栏:供应商详情表单
|
||||||
|
|
||||||
|
- `type` 下拉选择框;切换时自动填充默认 `base_url` 与推荐 `env` 键名。
|
||||||
|
- `base_url` 输入框。
|
||||||
|
- `api_key` 输入框(默认掩码,可切换明文)。
|
||||||
|
- 凭证存储方式单选:直接字段 / env 子表;两种方式互斥,保存时只写入被选中的那一种。
|
||||||
|
- `env` 键值对表格(可增删改行)。
|
||||||
|
|
||||||
|
### 8.4 右栏:模型别名列表与详情
|
||||||
|
|
||||||
|
- 显示当前选中供应商下的 model alias(也可切换显示全部)。
|
||||||
|
- 表单包含 `alias`、`provider` 下拉选择、`model`、`max_context_size`、`display_name`。
|
||||||
|
- 提供「设为默认模型」按钮,等同于顶部快速切换区的「设为默认」。
|
||||||
|
|
||||||
|
### 8.5 状态与错误处理
|
||||||
|
|
||||||
|
- **未保存指示**:标题栏在配置有未保存变更时显示 `*` 前缀;状态栏显示「已修改」或「已保存」。
|
||||||
|
- **切换/退出前检查**:存在未保存变更时,弹出「保存 / 丢弃 / 取消」三选一对话框。
|
||||||
|
- **文件不存在**:首次启动或配置目录不存在时,自动创建 `~/.kimi-code/` 目录与空 `config.toml`,不报错。
|
||||||
|
- **文件只读/权限不足**:保存失败时给出明确提示,并建议检查目录权限或以管理员身份运行。
|
||||||
|
- **校验失败**:保存前校验不通过时,弹窗列出所有错误项,禁止写入。
|
||||||
|
- **备份失败**:若备份步骤失败(如磁盘满),禁止覆盖原文件。
|
||||||
|
- **多实例冲突**:启动时检测 `~/.kimi-code/.kimiswitch.lock`;若已存在且进程仍在运行,提示用户「KimiSwitch 已在运行,请勿多开」;若进程已消失则清理旧锁。保存时不强制全局锁,但建议单次会话内顺序执行写操作。
|
||||||
|
|
||||||
|
### 8.6 底部工具栏
|
||||||
|
|
||||||
|
- **读取配置**:从 `~/.kimi-code/config.toml` 重新加载。
|
||||||
|
- **保存配置**:校验并写入当前 Profile 与 `config.toml`,失败时弹出错误提示。
|
||||||
|
- **保存为新 Profile**:把当前编辑内容导出为新的 Profile 文件。
|
||||||
|
- **打开配置目录**:调用系统文件管理器定位到数据目录。
|
||||||
|
|
||||||
|
## 9. 校验规则
|
||||||
|
|
||||||
|
保存前必须校验通过,否则禁止写入:
|
||||||
|
|
||||||
|
1. 每个 Provider 的 `name` 非空、不重复,且为合法 TOML 表键(必要时生成带引号的键)。
|
||||||
|
2. 每个 Provider 的 `type` 必须是支持的类型之一。
|
||||||
|
3. 每个 Provider 必须至少提供一种凭证来源,但 `vertexai` 例外(走 Google Cloud ADC,无需静态 API Key):
|
||||||
|
- `api_key` 直接字段非空,或
|
||||||
|
- `env` 中包含对应供应商的 API Key 键名。
|
||||||
|
4. 凭证存储方式在直接字段与 `env` 子表之间互斥,保存时只写入选中的那一种。
|
||||||
|
5. `vertexai` 类型的 Provider 必须在 `env` 中提供 `GOOGLE_CLOUD_PROJECT` 与 `GOOGLE_CLOUD_LOCATION`,不强制要求 API Key。
|
||||||
|
6. 每个 Model 的 `alias` 非空、不重复,且为合法 TOML 表键(含 `.` 等字符时自动加引号)。
|
||||||
|
7. 每个 Model 的 `provider` 必须引用一个已存在的 Provider。
|
||||||
|
8. 每个 Model 的 `model` 非空,`max_context_size` 为正整数。
|
||||||
|
|
||||||
|
## 10. MSI 打包
|
||||||
|
|
||||||
|
### 10.1 打包流程
|
||||||
|
|
||||||
|
1. 安装依赖:`npm install` 与 `cargo fetch`
|
||||||
|
2. 开发/构建前端:`npm run build`
|
||||||
|
3. 构建并打包:`npm run tauri build`
|
||||||
|
- Tauri 自动调用 `cargo build --release` 与 Windows bundler
|
||||||
|
- 输出:`src-tauri/target/release/bundle/msi/KimiSwitch_<version>_x64_en-US.msi`
|
||||||
|
|
||||||
|
### 10.2 MSI 行为
|
||||||
|
|
||||||
|
- 默认安装目录:`C:\Program Files\KimiSwitch`( per-machine 安装,需管理员权限)。
|
||||||
|
- 创建开始菜单快捷方式:`KimiSwitch`。
|
||||||
|
- 安装程序不自动写入 `config.toml`,仅部署工具本身。
|
||||||
|
- 支持标准卸载。
|
||||||
|
- 可在 `tauri.conf.json` 中配置签名、版本、厂商信息、安装目录等。
|
||||||
|
- 第一版仅构建 x64 架构 MSI,不构建 arm64 或 32 位版本。
|
||||||
|
- 第一版**不启用 Tauri Updater**,后续版本通过重新安装 MSI 升级,降低第一版复杂度。
|
||||||
|
|
||||||
|
### 10.3 Windows 专项处理
|
||||||
|
|
||||||
|
- **路径解析**:使用 Rust `dirs::home_dir()` 获取用户主目录,拼接 `.kimi-code`,避免依赖 `$HOME` 环境变量(Windows 下可能不存在)。
|
||||||
|
- **配置文件位置**:默认 `C:\Users\<用户名>\.kimi-code\config.toml`;若用户设置 `KIMI_CODE_HOME`,按该变量解析。
|
||||||
|
- **权限**:工具运行时只读写用户目录下的文件,无需以管理员身份运行;仅 MSI 安装本身需要管理员权限。
|
||||||
|
- **编码与换行**:TOML 文件统一使用 UTF-8;Windows 下换行保持为 `\r\n` 或 `\n` 均可,`toml_edit` 会保留原文件换行风格。
|
||||||
|
- **代码签名(可选)**:如后续分发,建议为 MSI 配置代码签名证书,减少 Windows Defender SmartScreen 提示。
|
||||||
|
|
||||||
|
## 11. 测试策略
|
||||||
|
|
||||||
|
- **单元测试**(Rust):
|
||||||
|
- `config_io.rs`:解析与生成 TOML 的 round-trip 测试。
|
||||||
|
- `profile_manager.rs`:Profile 新建、复制、切换、激活状态判定测试。
|
||||||
|
- `validators.rs`:校验规则的边界测试。
|
||||||
|
- **手动测试**:
|
||||||
|
- 启动 GUI,增删改供应商与模型。
|
||||||
|
- 创建多个 Profile 并相互切换,验证 `config.toml` 内容正确变化。
|
||||||
|
- 保存后使用 Kimi Code CLI 读取并验证可用性。
|
||||||
|
- 在 Windows 上执行 MSI 安装与卸载。
|
||||||
|
|
||||||
|
## 12. 假设与约束
|
||||||
|
|
||||||
|
- **第一版目标平台仅为 Windows x64**,macOS / Linux 跨平台支持不在第一版范围内,待 Windows x64 版本完成后再规划。
|
||||||
|
- API Key 按 Kimi Code CLI 官方方式明文存储在 `config.toml` 中,不引入额外加密。
|
||||||
|
- 开发者已安装 Rust 工具链、Node.js 与 Tauri CLI(仅开发与打包时需要,最终用户不需要)。
|
||||||
|
- 不覆盖 Kimi Code CLI 的自动更新机制或 OAuth 登录流程。
|
||||||
|
- 最终用户运行 KimiSwitch 只需标准 Windows 用户权限,安装 MSI 时才需要管理员权限。
|
||||||
|
|
||||||
|
## 13. 参考文档
|
||||||
|
|
||||||
|
- [Kimi Code CLI 配置文件](https://www.kimi.com/code/docs/kimi-code-cli/configuration/config-files.html)
|
||||||
|
- [Kimi Code CLI 平台与模型](https://www.kimi.com/code/docs/kimi-code-cli/configuration/providers.html)
|
||||||
|
- [Kimi Code CLI 数据路径](https://www.kimi.com/code/docs/kimi-code-cli/configuration/data-locations.html)
|
||||||
|
- [Kimi Code CLI 环境变量](https://www.kimi.com/code/docs/kimi-code-cli/configuration/env-vars.html)
|
||||||
|
- [Kimi Code CLI 配置覆盖](https://www.kimi.com/code/docs/kimi-code-cli/configuration/overrides.html)
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>KimiSwitch</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Generated
+2973
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"name": "kimiswitch",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "tsc && vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"tauri": "tauri",
|
||||||
|
"tauri-dev": "tauri dev",
|
||||||
|
"tauri-build": "tauri build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@tauri-apps/api": "^2.0.0",
|
||||||
|
"@tauri-apps/plugin-shell": "^2.0.0",
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@tauri-apps/cli": "^2.0.0",
|
||||||
|
"@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"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+4729
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
|||||||
|
[package]
|
||||||
|
name = "kimiswitch"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Kimi Code CLI config manager"
|
||||||
|
authors = ["you"]
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
tauri-build = { version = "2.0.0", features = [] }
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tauri = { version = "2.0.0", features = [] }
|
||||||
|
tauri-plugin-shell = "2.0.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
toml_edit = "0.22"
|
||||||
|
indexmap = { version = "2.2", features = ["serde"] }
|
||||||
|
dirs = "5.0"
|
||||||
|
thiserror = "1.0"
|
||||||
|
chrono = "0.4"
|
||||||
|
anyhow = "1.0"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["custom-protocol"]
|
||||||
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
kimiswitch_lib::run();
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"productName": "KimiSwitch",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"identifier": "com.kimiswitch.app",
|
||||||
|
"build": {
|
||||||
|
"beforeDevCommand": "npm run dev",
|
||||||
|
"beforeBuildCommand": "npm run build",
|
||||||
|
"devUrl": "http://localhost:1420",
|
||||||
|
"frontendDist": "../dist"
|
||||||
|
},
|
||||||
|
"app": {
|
||||||
|
"windows": [
|
||||||
|
{
|
||||||
|
"title": "KimiSwitch",
|
||||||
|
"width": 1200,
|
||||||
|
"height": 800,
|
||||||
|
"minWidth": 1000,
|
||||||
|
"minHeight": 700,
|
||||||
|
"resizable": true,
|
||||||
|
"fullscreen": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"security": {
|
||||||
|
"csp": null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bundle": {
|
||||||
|
"active": true,
|
||||||
|
"targets": ["msi"],
|
||||||
|
"icon": [
|
||||||
|
"icons/32x32.png",
|
||||||
|
"icons/128x128.png",
|
||||||
|
"icons/128x128@2x.png",
|
||||||
|
"icons/icon.ico"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
html, body, #root {
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import React from "react";
|
||||||
|
import ReactDOM from "react-dom/client";
|
||||||
|
import App from "./App";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"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,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"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,13 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
|
||||||
|
export default defineConfig(async () => ({
|
||||||
|
plugins: [react()],
|
||||||
|
clearScreen: false,
|
||||||
|
server: {
|
||||||
|
port: 1420,
|
||||||
|
strictPort: true,
|
||||||
|
watch: { ignored: ["**/src-tauri/**"] },
|
||||||
|
},
|
||||||
|
envPrefix: ["VITE_", "TAURI_"],
|
||||||
|
}));
|
||||||
Reference in New Issue
Block a user