diff --git a/README.md b/README.md index d5baa99..07961c7 100644 --- a/README.md +++ b/README.md @@ -24,61 +24,15 @@ ### Windows 用户 -从 [Releases](../../releases) 页面下载: - -- **安装版**: `CC-Switch-Setup-x.x.x.exe` - - 自动创建桌面快捷方式和开始菜单项 -- **绿色版**: `CC-Switch-x.x.x.exe` - - 无需安装,直接运行 +从 [Releases](../../releases) 页面下载最新版本的 Windows 安装包。 ### macOS 用户 -从 [Releases](../../releases) 页面下载: - -- **通用版本**: `CC Switch-x.x.x-mac.zip` - Intel 版本,兼容所有 Mac(包括 M 系列芯片) - -#### macOS 安装说明 - -通过 Rosetta 2 在 M 系列 Mac 上运行良好,兼容性最佳。 - -由于作者没有苹果开发者账号,应用使用 ad-hoc 签名(未经苹果官方认证),首次打开时可能出现"未知开发者"警告。这是正常的安全提示,处理方法: - -**方法 1 - 系统设置**: - -1. 双击应用弹出未知作者警告时选择"取消" -2. 打开"系统设置" → "隐私与安全性" -3. 在底部找到被阻止的应用,点击"仍要打开" -4. 确认后即可正常使用 - -**方法 2 - 自行编译**: - -1. Clone 代码到本地:`git clone https://github.com/farion1231/cc-switch.git` -2. 安装依赖:`pnpm install` -3. 编译代码:`pnpm run build` -4. 打包应用:`pnpm run dist` -5. 在项目 release 目录找到编译好的应用包 - -**安全保障**: - -- 应用已通过 ad-hoc 代码签名,确保文件完整性 -- 源代码完全开源,可在 GitHub 审查 -- 本地存储配置,无网络传输风险 - -**技术说明**: - -- 使用 Intel x64 架构,通过 Rosetta 2 在 M 系列芯片上运行 -- 兼容性和稳定性最佳,性能损失可接受 -- 避免了 ARM64 原生版本的签名复杂性问题 +从 [Releases](../../releases) 页面下载最新版本的 macOS 应用包。 ### Linux 用户 -- **AppImage**: `CC Switch-x.x.x.AppImage` - -下载后添加执行权限: - -```bash -chmod +x CC-Switch-x.x.x.AppImage -``` +从 [Releases](../../releases) 页面下载最新版本的 Linux 应用。 ## 使用说明 @@ -92,35 +46,34 @@ chmod +x CC-Switch-x.x.x.AppImage ```bash # 安装依赖 pnpm install -# 或 -npm install # 开发模式 pnpm run dev # 构建应用 pnpm run build - -# 打包发布 -pnpm run dist ``` ## 技术栈 -- Electron +- Tauri 2.0 - React - TypeScript - Vite +- Rust ## 项目结构 ``` -├── src/ -│ ├── main/ # 主进程代码 -│ ├── renderer/ # 渲染进程代码 -│ └── shared/ # 共享类型和工具 -├── build/ # 应用图标资源 -└── dist/ # 构建输出目录 +├── src/ # 前端代码 (React) +│ ├── components/ # React 组件 +│ ├── config/ # 配置文件 +│ ├── lib/ # 工具库 +│ └── utils/ # 工具函数 +├── src-tauri/ # Tauri 后端代码 (Rust) +│ ├── src/ # Rust 源代码 +│ └── icons/ # 应用图标资源 +└── screenshots/ # 截图资源 ``` ## License diff --git a/TAURI_MIGRATION_PLAN.md b/TAURI_MIGRATION_PLAN.md deleted file mode 100644 index 304449f..0000000 --- a/TAURI_MIGRATION_PLAN.md +++ /dev/null @@ -1,244 +0,0 @@ -# Tauri 重构计划 - -## 项目概述 - -将 CC Switch 从 Electron 框架迁移到 Tauri,以大幅减少应用体积并提升性能。 - -### 目标收益 - -- **体积优化**: 77MB → ~8MB (减少 90%) -- **内存占用**: 减少 60-70% -- **启动速度**: 提升 3-5 倍 -- **安全性**: Rust 内存安全 + 细粒度权限控制 - -## 技术栈对比 - -| 技术层 | Electron (当前) | Tauri (目标) | -| -------- | -------------------- | ------------------------- | -| 后端 | Node.js + TypeScript | Rust | -| 前端 | React + TypeScript | React + TypeScript (不变) | -| IPC | Electron IPC | Tauri Commands | -| 文件操作 | Node.js fs | Rust std::fs | -| 配置存储 | electron-store | tauri-plugin-store | -| 打包 | electron-builder | Tauri CLI | -| WebView | Chromium (内置) | 系统 WebView | - -## 迁移计划 - -### Phase 1: 环境准备 (Day 1 上午) - -- [x] 安装 Rust 开发环境 - ```bash - # Windows: 下载 rustup-init.exe - # https://www.rust-lang.org/tools/install - ``` -- [x] 安装 Tauri CLI - ```bash - pnpm add -g @tauri-apps/cli - ``` -- [x] 在现有项目中集成 Tauri - - ```bash - # 安装 Tauri CLI 作为开发依赖 - pnpm add -D @tauri-apps/cli - - # 在现有项目中初始化 Tauri - pnpm tauri init - - # 安装 Tauri API 包 - pnpm add @tauri-apps/api - ``` - -### Phase 2: 项目结构调整 (Day 1 下午) - -- [x] 创建 Tauri 项目配置 - - `src-tauri/` - Rust 后端代码 - - `src-tauri/tauri.conf.json` - Tauri 配置 - - `src-tauri/Cargo.toml` - Rust 依赖管理 -- [x] 迁移前端构建配置 - - 调整 Vite 配置适配 Tauri ✅ - - 更新 package.json scripts ✅ -- [x] 配置应用图标和元数据 - -### Phase 3: 后端功能迁移 (Day 2) - -#### 3.1 核心功能模块 (上午) - -- [x] **配置文件管理** (`src-tauri/src/config.rs`) - - 读取 ~/.claude/settings.json - - 写入配置文件 - - 备份/恢复配置 -- [x] **供应商管理** (`src-tauri/src/provider.rs`) - - 供应商列表的增删改查 - - 供应商配置切换逻辑 - - 配置文件命名规则 (settings-{name}.json) - -#### 3.2 Tauri Commands 实现 (下午) - -```rust -// 需要实现的命令列表 - 已完成 -#[tauri::command] -async fn get_providers() -> Result, String> - -#[tauri::command] -async fn get_current_provider() -> Result - -#[tauri::command] -async fn add_provider(provider: Provider) -> Result - -#[tauri::command] -async fn update_provider(provider: Provider) -> Result - -#[tauri::command] -async fn delete_provider(id: String) -> Result - -#[tauri::command] -async fn switch_provider(id: String) -> Result - -#[tauri::command] -async fn import_default_config() -> Result - -#[tauri::command] -async fn get_claude_config_status() -> Result -``` - -#### 3.3 数据存储 (`src-tauri/src/store.rs`) - -- [x] 使用 tauri-plugin-store 替代 electron-store -- [x] 迁移配置存储逻辑 (~/.cc-switch/config.json) - -### Phase 4: 前端适配 (Day 2 傍晚) - -#### 4.1 API 层重构 - -- [x] 创建 `src/lib/tauri-api.ts` - - 替换 Electron IPC 调用为 Tauri invoke - - 保持 API 接口一致,减少组件改动 - -```typescript -// 示例:迁移前后对比 -// Electron (旧) -window.electronAPI.getProviders(); - -// Tauri (新) -import { invoke } from "@tauri-apps/api/tauri"; -invoke("get_providers"); -``` - -#### 4.2 最小化前端改动 - -- [x] 更新 preload 桥接逻辑 -- [x] 调整窗口控制相关代码 -- [x] 处理文件路径差异 - -### Phase 5: 测试与优化 (Day 3 上午) - -#### 5.1 功能测试清单 - -- [ ] 供应商列表显示 -- [ ] 添加新供应商 -- [ ] 编辑供应商信息 -- [ ] 删除供应商 -- [ ] 切换供应商配置 -- [ ] 导入默认配置 -- [ ] 预设模板功能 -- [ ] API Key 快速输入 - -#### 5.2 跨平台测试 - -- [ ] Windows 10/11 测试 -- [ ] 不考虑 Windows 7/8 兼容性 -- [ ] macOS 测试 (如有条件) -- [ ] Linux 测试 (如有条件) - -#### 5.3 性能优化 - -- [ ] Rust 代码优化 (release 模式) -- [ ] 减少不必要的文件 I/O -- [ ] 优化启动加载流程 - -### Phase 6: 构建与发布 (Day 3 下午) - -#### 6.1 构建配置 - -- [ ] 配置 GitHub Actions CI/CD -- [ ] 设置代码签名 (Windows/macOS) -- [ ] 配置自动更新机制 - -#### 6.2 打包发布 - -- [ ] Windows NSIS 安装包 -- [ ] Windows 便携版 (portable) -- [ ] macOS .app 包 -- [ ] Linux AppImage - -#### 6.3 版本发布 - -- [ ] 创建 3.0.0-beta.1 预发布 -- [ ] 编写迁移说明文档 -- [ ] 更新 README.md - -## 风险与应对 - -### 技术风险 - -1. **Rust 学习曲线** - - - 风险:Rust 语法相对复杂 - - 应对:专注于基础文件 I/O,使用成熟库 - -2. **WebView2 兼容性** - -- 不需要支持旧版 Windows - -3. **跨平台差异** - - 风险:不同系统的文件路径处理 - - 应对:使用 Tauri API 统一处理路径 - -### 用户体验风险 - -1. **界面渲染差异** - - - 风险:WebView 渲染可能与 Chromium 有细微差异 - - 应对:充分测试,必要时调整 CSS - -2. **功能回归** - - 风险:迁移过程中遗漏功能 - - 应对:严格按照测试清单验证 - -## 回滚方案 - -如果 Tauri 版本出现严重问题: - -1. 立即从 electron-legacy 分支发布修复版本 -2. 在 GitHub Release 页面提供两个版本下载 -3. 明确标注版本差异和适用场景 - -## 时间线 - -- **Day 1**: 环境搭建 + 项目结构 -- **Day 2**: 后端迁移 + 前端适配 -- **Day 3**: 测试优化 + 构建发布 -- **Total**: 3 个工作日完成迁移 - -## 成功标准 - -- ✅ 应用体积 < 10MB -- ✅ 冷启动时间 < 1 秒 -- ✅ 所有现有功能正常工作 -- ✅ 通过所有测试用例 -- ✅ 成功构建三平台安装包 - -## 后续优化 (可选) - -- 添加系统托盘功能 -- 实现自动更新机制 -- 添加快捷键支持 -- 优化动画效果 -- 支持深色模式跟随系统 - ---- - -_最后更新:2024-12-23_ -_负责人:Jason Young_ -_状态:进行中 - Phase 4 已完成_ diff --git a/build/icon.icns b/build/icon.icns deleted file mode 100644 index f3dfcc0..0000000 Binary files a/build/icon.icns and /dev/null differ diff --git a/build/icon.ico b/build/icon.ico deleted file mode 100644 index c4a9ee5..0000000 Binary files a/build/icon.ico and /dev/null differ diff --git a/build/icon.png b/build/icon.png deleted file mode 100644 index 7871ac1..0000000 Binary files a/build/icon.png and /dev/null differ diff --git a/package.json b/package.json index 011a68d..93198fb 100644 --- a/package.json +++ b/package.json @@ -2,19 +2,12 @@ "name": "cc-switch", "version": "2.0.3", "description": "Claude Code 供应商切换工具", - "main": "dist/main/index.js", "scripts": { "dev": "pnpm tauri dev", "build": "pnpm tauri build", "tauri": "tauri", "dev:renderer": "vite", - "build:renderer": "vite build", - "dev:electron": "tsc -p tsconfig.main.json && electron .", - "dev:electron:watch": "tsc -p tsconfig.main.json && concurrently -k \"tsc -w -p tsconfig.main.json\" \"npm:electron\"", - "electron": "electron .", - "build:main": "tsc -p tsconfig.main.json", - "start:electron": "electron .", - "dist:electron": "electron-builder" + "build:renderer": "vite build" }, "keywords": [], "author": "Jason Young", @@ -24,9 +17,6 @@ "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.2.0", - "concurrently": "^8.2.0", - "electron": "^32.3.3", - "electron-builder": "^24.0.0", "typescript": "^5.3.0", "vite": "^5.0.0" }, @@ -35,60 +25,5 @@ "@tauri-apps/plugin-shell": "^2.3.0", "react": "^18.2.0", "react-dom": "^18.2.0" - }, - "build": { - "appId": "com.ccswitch.app", - "productName": "CC Switch", - "compression": "maximum", - "publish": null, - "directories": { - "output": "release" - }, - "icon": "build/icon.ico", - "files": [ - "dist/**/*", - "node_modules/**/*" - ], - "nsis": { - "oneClick": false, - "allowToChangeInstallationDirectory": true - }, - "mac": { - "category": "public.app-category.developer-tools", - "icon": "build/icon.icns", - "identity": "-", - "hardenedRuntime": false, - "entitlements": null, - "entitlementsInherit": null, - "target": [ - { - "target": "zip", - "arch": [ - "x64" - ] - } - ] - }, - "win": { - "target": [ - { - "target": "nsis", - "arch": [ - "x64" - ] - }, - { - "target": "portable", - "arch": [ - "x64" - ] - } - ], - "icon": "build/icon.ico" - }, - "linux": { - "target": "AppImage", - "icon": "build/icon.png" - } } } diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 9d5fa74..0000000 Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index 4a83092..0000000 Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 9d5fa74..0000000 Binary files a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 736da2b..0000000 Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 7fc95a3..0000000 Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 736da2b..0000000 Binary files a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 2074f7d..0000000 Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index a86fdbd..0000000 Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 2074f7d..0000000 Binary files a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index bc661bb..0000000 Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 7e27832..0000000 Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index bc661bb..0000000 Binary files a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index d07b197..0000000 Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 10106ef..0000000 Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index d07b197..0000000 Binary files a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png deleted file mode 100644 index d3c1be7..0000000 Binary files a/src-tauri/icons/ios/AppIcon-20x20@1x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png deleted file mode 100644 index 8947ae3..0000000 Binary files a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png deleted file mode 100644 index 8947ae3..0000000 Binary files a/src-tauri/icons/ios/AppIcon-20x20@2x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png deleted file mode 100644 index ac47e1a..0000000 Binary files a/src-tauri/icons/ios/AppIcon-20x20@3x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png deleted file mode 100644 index fbe6522..0000000 Binary files a/src-tauri/icons/ios/AppIcon-29x29@1x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png deleted file mode 100644 index 35dd6b4..0000000 Binary files a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png deleted file mode 100644 index 35dd6b4..0000000 Binary files a/src-tauri/icons/ios/AppIcon-29x29@2x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png deleted file mode 100644 index de24164..0000000 Binary files a/src-tauri/icons/ios/AppIcon-29x29@3x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png deleted file mode 100644 index 8947ae3..0000000 Binary files a/src-tauri/icons/ios/AppIcon-40x40@1x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png deleted file mode 100644 index 9b6b863..0000000 Binary files a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png deleted file mode 100644 index 9b6b863..0000000 Binary files a/src-tauri/icons/ios/AppIcon-40x40@2x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png deleted file mode 100644 index a186f1c..0000000 Binary files a/src-tauri/icons/ios/AppIcon-40x40@3x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png deleted file mode 100644 index d1d5ecf..0000000 Binary files a/src-tauri/icons/ios/AppIcon-512@2x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png deleted file mode 100644 index a186f1c..0000000 Binary files a/src-tauri/icons/ios/AppIcon-60x60@2x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png deleted file mode 100644 index 91a4c9e..0000000 Binary files a/src-tauri/icons/ios/AppIcon-60x60@3x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png deleted file mode 100644 index ad1fcb1..0000000 Binary files a/src-tauri/icons/ios/AppIcon-76x76@1x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png deleted file mode 100644 index 1922ddf..0000000 Binary files a/src-tauri/icons/ios/AppIcon-76x76@2x.png and /dev/null differ diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png deleted file mode 100644 index 02fae80..0000000 Binary files a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png and /dev/null differ diff --git a/src/renderer/App.css b/src/App.css similarity index 100% rename from src/renderer/App.css rename to src/App.css diff --git a/src/renderer/App.tsx b/src/App.tsx similarity index 100% rename from src/renderer/App.tsx rename to src/App.tsx diff --git a/src/renderer/components/AddProviderModal.css b/src/components/AddProviderModal.css similarity index 100% rename from src/renderer/components/AddProviderModal.css rename to src/components/AddProviderModal.css diff --git a/src/renderer/components/AddProviderModal.tsx b/src/components/AddProviderModal.tsx similarity index 100% rename from src/renderer/components/AddProviderModal.tsx rename to src/components/AddProviderModal.tsx diff --git a/src/renderer/components/ConfirmDialog.css b/src/components/ConfirmDialog.css similarity index 100% rename from src/renderer/components/ConfirmDialog.css rename to src/components/ConfirmDialog.css diff --git a/src/renderer/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx similarity index 100% rename from src/renderer/components/ConfirmDialog.tsx rename to src/components/ConfirmDialog.tsx diff --git a/src/renderer/components/EditProviderModal.tsx b/src/components/EditProviderModal.tsx similarity index 100% rename from src/renderer/components/EditProviderModal.tsx rename to src/components/EditProviderModal.tsx diff --git a/src/renderer/components/ProviderForm.tsx b/src/components/ProviderForm.tsx similarity index 100% rename from src/renderer/components/ProviderForm.tsx rename to src/components/ProviderForm.tsx diff --git a/src/renderer/components/ProviderList.css b/src/components/ProviderList.css similarity index 100% rename from src/renderer/components/ProviderList.css rename to src/components/ProviderList.css diff --git a/src/renderer/components/ProviderList.tsx b/src/components/ProviderList.tsx similarity index 100% rename from src/renderer/components/ProviderList.tsx rename to src/components/ProviderList.tsx diff --git a/src/renderer/config/providerPresets.ts b/src/config/providerPresets.ts similarity index 100% rename from src/renderer/config/providerPresets.ts rename to src/config/providerPresets.ts diff --git a/src/renderer/index.css b/src/index.css similarity index 100% rename from src/renderer/index.css rename to src/index.css diff --git a/src/renderer/index.html b/src/index.html similarity index 100% rename from src/renderer/index.html rename to src/index.html diff --git a/src/renderer/lib/tauri-api.ts b/src/lib/tauri-api.ts similarity index 100% rename from src/renderer/lib/tauri-api.ts rename to src/lib/tauri-api.ts diff --git a/src/renderer/main.tsx b/src/main.tsx similarity index 100% rename from src/renderer/main.tsx rename to src/main.tsx diff --git a/src/main/index.ts b/src/main/index.ts deleted file mode 100644 index beb229e..0000000 --- a/src/main/index.ts +++ /dev/null @@ -1,277 +0,0 @@ -import { app, BrowserWindow, ipcMain, dialog, shell } from "electron"; -import path from "path"; -import fs from "fs/promises"; -import { Provider } from "../shared/types"; -import { - switchProvider, - getClaudeCodeConfig, - saveProviderConfig, - deleteProviderConfig, - sanitizeProviderName, - importCurrentConfigAsDefault, - getProviderConfigPath, - fileExists, -} from "./services"; -import { store } from "./store"; - -let mainWindow: BrowserWindow | null = null; -const isMac = process.platform === "darwin"; - -function createWindow() { - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - minWidth: 600, - minHeight: 400, - webPreferences: { - preload: path.join(__dirname, "../main/preload.js"), - contextIsolation: true, - nodeIntegration: false, - }, - // 使用 macOS 隐藏式标题栏,仅在 macOS 启用 - ...(isMac ? { titleBarStyle: "hiddenInset" as const } : {}), - autoHideMenuBar: true, - }); - - if (app.isPackaged) { - mainWindow.loadFile(path.join(__dirname, "../renderer/index.html")); - } else { - mainWindow.loadURL("http://localhost:3000"); - mainWindow.webContents.openDevTools(); - } - - mainWindow.on("closed", () => { - mainWindow = null; - }); -} - -app.whenReady().then(() => { - createWindow(); - - app.on("activate", () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } - }); -}); - -app.on("window-all-closed", () => { - if (process.platform !== "darwin") { - app.quit(); - } -}); - -// IPC handlers -ipcMain.handle("getProviders", async () => { - return await store.get("providers", {} as Record); -}); - -ipcMain.handle("getCurrentProvider", async () => { - return await store.get("current", ""); -}); - -ipcMain.handle("addProvider", async (_, provider: Provider) => { - try { - // 1. 保存供应商配置到独立文件 - const saveSuccess = await saveProviderConfig(provider); - if (!saveSuccess) { - return false; - } - - // 2. 更新应用配置 - const providers = await store.get("providers", {} as Record); - providers[provider.id] = provider; - await store.set("providers", providers); - - return true; - } catch (error) { - console.error("添加供应商失败:", error); - return false; - } -}); - -ipcMain.handle("deleteProvider", async (_, id: string) => { - try { - const providers = await store.get("providers", {} as Record); - const provider = providers[id]; - - // 1. 删除供应商配置文件 - const deleteSuccess = await deleteProviderConfig(id, provider?.name); - if (!deleteSuccess) { - console.error("删除供应商配置文件失败"); - // 仍然继续删除应用配置,避免配置不同步 - } - - // 2. 更新应用配置 - delete providers[id]; - await store.set("providers", providers); - - // 3. 如果删除的是当前供应商,清空当前选择 - const currentProviderId = await store.get("current", ""); - if (currentProviderId === id) { - await store.set("current", ""); - } - - return true; - } catch (error) { - console.error("删除供应商失败:", error); - return false; - } -}); - -ipcMain.handle("updateProvider", async (_, provider: Provider) => { - try { - const providers = await store.get("providers", {} as Record); - const currentProviderId = await store.get("current", ""); - const oldProvider = providers[provider.id]; - - // 1. 如果名字发生变化,需要重命名配置文件 - if (oldProvider && oldProvider.name !== provider.name) { - const oldConfigPath = getProviderConfigPath( - provider.id, - oldProvider.name - ); - const newConfigPath = getProviderConfigPath(provider.id, provider.name); - - // 如果旧配置文件存在且路径不同,需要重命名 - if ( - (await fileExists(oldConfigPath)) && - oldConfigPath !== newConfigPath - ) { - // 如果新路径已存在文件,先删除避免冲突 - if (await fileExists(newConfigPath)) { - await fs.unlink(newConfigPath); - } - await fs.rename(oldConfigPath, newConfigPath); - console.log( - `已重命名配置文件: ${oldProvider.name} -> ${provider.name}` - ); - } - } - - // 2. 保存更新后的配置到文件 - const saveSuccess = await saveProviderConfig(provider); - if (!saveSuccess) { - return false; - } - - // 3. 更新应用配置 - providers[provider.id] = provider; - await store.set("providers", providers); - - // 4. 如果编辑的是当前激活的供应商,需要重新切换以应用更改 - if (provider.id === currentProviderId) { - const switchSuccess = await switchProvider( - provider, - currentProviderId, - providers - ); - if (!switchSuccess) { - console.error("更新当前供应商的Claude Code配置失败"); - return false; - } - } - - return true; - } catch (error) { - console.error("更新供应商失败:", error); - return false; - } -}); - -ipcMain.handle("switchProvider", async (_, providerId: string) => { - try { - const providers = await store.get("providers", {} as Record); - const provider = providers[providerId]; - const currentProviderId = await store.get("current", ""); - - if (!provider) { - console.error(`供应商不存在: ${providerId}`); - return false; - } - - // 执行切换 - const success = await switchProvider( - provider, - currentProviderId, - providers - ); - if (success) { - await store.set("current", providerId); - console.log(`成功切换到供应商: ${provider.name}`); - } - - return success; - } catch (error) { - console.error("切换供应商失败:", error); - return false; - } -}); - -ipcMain.handle("importCurrentConfigAsDefault", async () => { - try { - const result = await importCurrentConfigAsDefault(); - - if (result.success && result.provider) { - // 将默认供应商添加到store中 - const providers = await store.get("providers", {} as Record); - providers[result.provider.id] = result.provider; - await store.set("providers", providers); - - // 设置为当前选中的供应商 - await store.set("current", result.provider.id); - - return { success: true, providerId: result.provider.id }; - } - - return result; - } catch (error) { - console.error("导入默认配置失败:", error); - return { success: false }; - } -}); - -ipcMain.handle("getClaudeCodeConfigPath", () => { - return getClaudeCodeConfig().path; -}); - -ipcMain.handle("selectConfigFile", async () => { - if (!mainWindow) return null; - - const result = await dialog.showOpenDialog(mainWindow, { - properties: ["openFile"], - title: "选择 Claude Code 配置文件", - filters: [ - { name: "JSON 文件", extensions: ["json"] }, - { name: "所有文件", extensions: ["*"] }, - ], - defaultPath: "settings.json", - }); - - if (result.canceled || result.filePaths.length === 0) { - return null; - } - - return result.filePaths[0]; -}); - -ipcMain.handle("openConfigFolder", async () => { - try { - const { dir } = getClaudeCodeConfig(); - await shell.openPath(dir); - return true; - } catch (error) { - console.error("打开配置文件夹失败:", error); - return false; - } -}); - -ipcMain.handle("openExternal", async (_, url: string) => { - try { - await shell.openExternal(url); - return true; - } catch (error) { - console.error("打开外部链接失败:", error); - return false; - } -}); diff --git a/src/main/preload.ts b/src/main/preload.ts deleted file mode 100644 index 85db819..0000000 --- a/src/main/preload.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { contextBridge, ipcRenderer } from 'electron' -import { Provider } from '../shared/types' - -contextBridge.exposeInMainWorld('electronAPI', { - getProviders: () => ipcRenderer.invoke('getProviders'), - getCurrentProvider: () => ipcRenderer.invoke('getCurrentProvider'), - addProvider: (provider: Provider) => ipcRenderer.invoke('addProvider', provider), - deleteProvider: (id: string) => ipcRenderer.invoke('deleteProvider', id), - updateProvider: (provider: Provider) => ipcRenderer.invoke('updateProvider', provider), - switchProvider: (providerId: string) => ipcRenderer.invoke('switchProvider', providerId), - importCurrentConfigAsDefault: () => ipcRenderer.invoke('importCurrentConfigAsDefault'), - getClaudeCodeConfigPath: () => ipcRenderer.invoke('getClaudeCodeConfigPath'), - selectConfigFile: () => ipcRenderer.invoke('selectConfigFile'), - openConfigFolder: () => ipcRenderer.invoke('openConfigFolder'), - openExternal: (url: string) => ipcRenderer.invoke('openExternal', url) -}) - -// 暴露平台信息给渲染进程,用于平台特定样式控制 -contextBridge.exposeInMainWorld('platform', { - isMac: process.platform === 'darwin' -}) diff --git a/src/main/services.ts b/src/main/services.ts deleted file mode 100644 index 643bea0..0000000 --- a/src/main/services.ts +++ /dev/null @@ -1,181 +0,0 @@ -import fs from "fs/promises"; -import path from "path"; -import os from "os"; -import { Provider } from "../shared/types"; - -/** - * 清理供应商名称,确保文件名安全 - */ -export function sanitizeProviderName(name: string): string { - return name.replace(/[<>:"/\\|?*]/g, "-").toLowerCase(); -} - -export function getClaudeCodeConfig() { - // Claude Code 配置文件路径 - const configDir = path.join(os.homedir(), ".claude"); - const configPath = path.join(configDir, "settings.json"); - - return { path: configPath, dir: configDir }; -} - -/** - * 获取供应商配置文件路径(基于供应商名称) - */ -export function getProviderConfigPath( - providerId: string, - providerName?: string -): string { - const { dir } = getClaudeCodeConfig(); - - // 如果提供了名称,使用名称;否则使用ID(向后兼容) - const baseName = providerName - ? sanitizeProviderName(providerName) - : sanitizeProviderName(providerId); - return path.join(dir, `settings-${baseName}.json`); -} - -/** - * 保存供应商配置到独立文件 - */ -export async function saveProviderConfig(provider: Provider): Promise { - try { - const { dir } = getClaudeCodeConfig(); - const providerConfigPath = getProviderConfigPath( - provider.id, - provider.name - ); - - // 确保目录存在 - await fs.mkdir(dir, { recursive: true }); - - // 保存配置到供应商专用文件 - await fs.writeFile( - providerConfigPath, - JSON.stringify(provider.settingsConfig, null, 2), - "utf-8" - ); - - return true; - } catch (error) { - console.error("保存供应商配置失败:", error); - return false; - } -} - -/** - * 检查文件是否存在 - */ -export async function fileExists(filePath: string): Promise { - try { - await fs.access(filePath); - return true; - } catch { - return false; - } -} - -/** - * 切换供应商配置(基于文件复制) - */ -export async function switchProvider( - provider: Provider, - currentProviderId?: string, - providers?: Record -): Promise { - try { - const { path: settingsPath, dir: configDir } = getClaudeCodeConfig(); - - // 确保目录存在 - await fs.mkdir(configDir, { recursive: true }); - - const newSettingsPath = getProviderConfigPath(provider.id, provider.name); - - // 检查目标配置文件是否存在 - if (!(await fileExists(newSettingsPath))) { - console.error(`供应商配置文件不存在: ${newSettingsPath}`); - return false; - } - - // 1. 如果当前存在settings.json,先备份到当前供应商的配置文件 - if (await fileExists(settingsPath)) { - if (currentProviderId && providers && providers[currentProviderId]) { - const currentProvider = providers[currentProviderId]; - const currentProviderPath = getProviderConfigPath( - currentProviderId, - currentProvider.name - ); - // 使用复制而不是重命名,避免删除原文件 - await fs.copyFile(settingsPath, currentProviderPath); - console.log(`已备份当前供应商配置: ${currentProvider.name}`); - } - } - - // 2. 复制新配置到settings.json(保留原文件) - await fs.copyFile(newSettingsPath, settingsPath); - - console.log(`成功切换到供应商: ${provider.name}`); - return true; - } catch (error) { - console.error("切换供应商失败:", error); - return false; - } -} - -/** - * 导入当前配置为默认供应商 - */ -export async function importCurrentConfigAsDefault(): Promise<{ success: boolean; provider?: Provider }> { - try { - const { path: settingsPath } = getClaudeCodeConfig(); - - // 检查当前配置是否存在 - if (!(await fileExists(settingsPath))) { - return { success: false }; - } - - // 读取当前配置 - const configContent = await fs.readFile(settingsPath, "utf-8"); - const settingsConfig = JSON.parse(configContent); - - // 创建默认供应商对象 - const provider: Provider = { - id: "default", - name: "default", - settingsConfig: settingsConfig, - }; - - // 保存默认供应商的配置到独立文件 - const saveSuccess = await saveProviderConfig(provider); - if (!saveSuccess) { - return { success: false }; - } - - console.log(`已导入当前配置为默认供应商,配置文件:settings-default.json`); - return { success: true, provider }; - } catch (error) { - console.error("导入默认配置失败:", error); - return { success: false }; - } -} - -/** - * 删除供应商配置文件 - */ -export async function deleteProviderConfig( - providerId: string, - providerName?: string -): Promise { - try { - const providerConfigPath = getProviderConfigPath(providerId, providerName); - - if (await fileExists(providerConfigPath)) { - await fs.unlink(providerConfigPath); - console.log(`已删除供应商配置文件: ${providerConfigPath}`); - } - - return true; - } catch (error) { - console.error("删除供应商配置失败:", error); - return false; - } -} diff --git a/src/main/store.ts b/src/main/store.ts deleted file mode 100644 index 2b57c60..0000000 --- a/src/main/store.ts +++ /dev/null @@ -1,60 +0,0 @@ -import fs from 'fs/promises' -import path from 'path' -import os from 'os' -import { AppConfig } from '../shared/types' - -export class SimpleStore { - private configPath: string - private configDir: string - private data: AppConfig = { providers: {}, current: '' } - private initPromise: Promise - - constructor() { - this.configDir = path.join(os.homedir(), '.cc-switch') - this.configPath = path.join(this.configDir, 'config.json') - // 立即开始加载,但不阻塞构造函数 - this.initPromise = this.loadData() - } - - private async loadData(): Promise { - try { - const content = await fs.readFile(this.configPath, 'utf-8') - this.data = JSON.parse(content) - } catch (error) { - // 文件不存在或格式错误,使用默认数据 - this.data = { providers: {}, current: '' } - await this.saveData() - } - } - - private async saveData(): Promise { - try { - // 确保目录存在 - await fs.mkdir(this.configDir, { recursive: true }) - // 写入配置文件 - await fs.writeFile(this.configPath, JSON.stringify(this.data, null, 2), 'utf-8') - } catch (error) { - console.error('保存配置失败:', error) - } - } - - async get(key: keyof AppConfig, defaultValue?: T): Promise { - await this.initPromise // 等待初始化完成 - const value = this.data[key] as T - return value !== undefined ? value : (defaultValue as T) - } - - async set(key: K, value: AppConfig[K]): Promise { - await this.initPromise // 等待初始化完成 - this.data[key] = value - await this.saveData() - } - - // 获取配置文件路径,用于调试 - getConfigPath(): string { - return this.configPath - } -} - -// 创建单例 -export const store = new SimpleStore() \ No newline at end of file diff --git a/src/shared/types.ts b/src/types.ts similarity index 100% rename from src/shared/types.ts rename to src/types.ts diff --git a/src/renderer/utils/providerConfigUtils.ts b/src/utils/providerConfigUtils.ts similarity index 100% rename from src/renderer/utils/providerConfigUtils.ts rename to src/utils/providerConfigUtils.ts diff --git a/src/renderer/vite-env.d.ts b/src/vite-env.d.ts similarity index 100% rename from src/renderer/vite-env.d.ts rename to src/vite-env.d.ts diff --git a/tsconfig.main.json b/tsconfig.main.json deleted file mode 100644 index 7ce821b..0000000 --- a/tsconfig.main.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.node.json", - "compilerOptions": { - "outDir": "dist", - "rootDir": "src", - "types": ["node"] - }, - "include": ["src/main/**/*", "src/shared/**/*"] -} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts index 2a21cb4..750a017 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,13 +1,11 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' -import { resolve } from 'path' export default defineConfig({ plugins: [react()], - root: resolve(__dirname, 'src/renderer'), base: './', build: { - outDir: resolve(__dirname, 'build'), + outDir: 'dist', emptyOutDir: true }, server: {