diff --git a/Cargo.lock b/Cargo.lock index 6d3ac58e..dc765a1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -696,6 +696,7 @@ dependencies = [ "tower", "tracing", "tracing-subscriber", + "window-vibrancy 0.7.1", "windows 0.59.0", ] @@ -4719,7 +4720,7 @@ dependencies = [ "url", "webkit2gtk", "webview2-com", - "window-vibrancy", + "window-vibrancy 0.6.0", "windows 0.61.3", ] @@ -6056,6 +6057,21 @@ dependencies = [ "windows-version", ] +[[package]] +name = "window-vibrancy" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "010797bd7c40396fbc59d3105089fed0885fe267a0ef4a0a4646df54e28647f6" +dependencies = [ + "objc2 0.6.4", + "objc2-app-kit 0.3.2", + "objc2-core-foundation", + "objc2-foundation 0.3.2", + "raw-window-handle", + "windows-sys 0.60.2", + "windows-version", +] + [[package]] name = "windows" version = "0.59.0" diff --git a/README.en.md b/README.en.md index 7d0499ef..4d61fa62 100644 --- a/README.en.md +++ b/README.en.md @@ -58,7 +58,7 @@ With any provider enabled, Codex App's model picker shows ` / .css` | -| Page-specific styles for dashboard / providers / proxy / settings / guide | `frontend/css/pages/.css` | -| Responsive breakpoints (1100px / 720px) | `frontend/css/responsive.css` | - -Preview every component + variant + theme switching: - -```bash -# Open directly in your browser (no dev server needed) -open frontend/gallery.html # macOS -xdg-open frontend/gallery.html # Linux -start frontend/gallery.html # Windows -``` - -`gallery.html` has a theme picker + dark/light toggle at the top, refresh after editing component css to see changes. `frontend/index.html`'s `` does not need to change — `style.css` is just an `@import` entry that aggregates every sub-file. - -To add a new component: create `components/.css` + add a line `@import url("components/.css");` to `style.css` + add a section in `gallery.html`. +- **Global Styles & Design Tokens**: `frontend/src/app.css` defines all global CSS variables (including glassmorphism backgrounds, backdrop blurs, typography, gradients, border radius, etc.) and macOS system-level themes. +- **Component Styles**: Styles specific to individual components (e.g. `Titlebar.svelte`, `Sidebar.svelte`) are declared directly inside their corresponding `.svelte` files. +- **Page Styles**: Individual page styles (e.g. Dashboard, Providers, etc.) are located in their respective Svelte pages inside `frontend/src/pages/`. +- **Local Development**: + ```bash + cd frontend + npm run dev # Start Vite dev server for frontend previewing + # Or in the root directory + cargo tauri dev # Start Tauri app window with hot reloading enabled + ``` ## Troubleshooting @@ -220,7 +210,7 @@ Design intent: the client trusts only the build-time embedded public key and nev - **Backend / forwarding**: Rust 1.80+ · axum 0.8 · reqwest 0.12 (rustls-tls) · tokio - **Protocol adapters**: `crates/adapters/` — Responses ↔ Chat / Gemini Native / Gemini CLI OAuth / Anthropic Messages / Grok Web (request body + streaming response state machine + reasoning_content + tool_calls) -- **Frontend**: HTML + CSS + vanilla JavaScript + Bootstrap 5.3.3 (localized, no CDN dependency) +- **Frontend**: Svelte 5 + TypeScript + Vite + custom Vanilla CSS glassmorphism UI - **Desktop shell**: Tauri 2 + tray-icon 0.23; the `cas://` URI scheme glues frontend/ and axum in-process, no TCP loopback - **Storage**: `~/.codex-app-transfer/config.json` (config, compatible with v1.x), `~/.codex-app-transfer/sessions.db` (L2 sqlite session persistence), `~/.codex/{config.toml,auth.json}` (Codex App integration) - **Packaging**: `cargo tauri build` single command produces dmg/AppImage/deb/exe/msi; `xtask release-bundle` finalizes sha256 + RSA-3072 sig + latest.json + draft GitHub release diff --git a/README.md b/README.md index 6c6573ca..48919c38 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ Codex App Transfer 是一个面向 **OpenAI Codex APP** 的轻量桌面配置 + - **MCP**:结构化 JSON 编辑 `~/.codex/config.toml` 的 `[mcp_servers.*]` 节(`toml_edit` round-trip 保留注释 + 其他配置节)+ Plugins 子页扫 `~/.codex/plugins/cache/` 列已安装 plugin(enable toggle / uninstall);所有改动 atomic write + 独立 history 互不交叉(SHA-256 hash 路径) - 实时日志面板,2 秒自动刷新;统一 `tracing::warn!(error_id, detail)` + 稳定 token,operator 可 grep / 聚合 - 反馈弹窗附带诊断材料(环境信息、脱敏配置、最近错误快照及完整请求 / 响应),减少手工补材料 -- 中文 / 英文界面,浅色 / 深色 / 绿色 / 橙色 / 灰色 / 白色多种主题 +- 中文 / 英文界面, 适配 macOS 经典玻璃质感设计 (Frosted Glass Vibrancy) + 自动深浅色切换 - 跨平台单实例锁定(双击启动自动唤起已有窗口)+ 跨进程 file lock 防多实例同时写 config 丢更新 - Windows / macOS / Linux 系统托盘 @@ -137,28 +137,18 @@ cargo tauri build --bundles deb,appimage # Linux x86_64 ### 想改 UI 样式怎么改 -`frontend/css/` 走"组件库"形式拆开,不需要全文 grep `style.css`: +本项目前端已重构为 **Svelte 5 + TypeScript + Vite**,采用 scoped styles 进行组件化样式管理,并遵循 macOS 经典玻璃质感设计规范: -| 想改什么 | 改哪个文件 | -|---|---| -| 主题色 / 圆角 / 阴影 / 间距等 design tokens | `frontend/css/tokens.css`(129 vars + 6 套主题) | -| 全局 reset / body 字体 / focus 描边 | `frontend/css/base.css` | -| 按钮 / 卡片 / 表单 / 徽章 / 模态等组件 | `frontend/css/components/.css` | -| 仪表盘 / 提供商 / 转发 / 设置 / 引导某一页专属样式 | `frontend/css/pages/.css` | -| 响应式断点 / 1100px / 720px | `frontend/css/responsive.css` | - -预览所有组件 + 各状态 + 主题切换: - -```bash -# 浏览器直接打开(不需 dev server) -open frontend/gallery.html # macOS -xdg-open frontend/gallery.html # Linux -start frontend/gallery.html # Windows -``` - -`gallery.html` 顶部有主题切换 + 深浅色按钮,改 component css 后刷新即看。`frontend/index.html` 主入口 `` 不需要改 — `style.css` 只是 @import 入口聚合所有子文件。 - -加新组件: 在 `components/` 建 `.css` + 在 `style.css` 加一行 `@import url("components/.css");` + 在 `gallery.html` 加 section。 +- **全局样式与 Design Tokens**: `frontend/src/app.css` 定义了所有全局 CSS 变量(如玻璃质感的背景色、毛玻璃模糊度、字体、渐变及圆角等)和 macOS 系统级的主题配置。 +- **组件样式**: 各组件的专属样式均直接声明在对应的 `.svelte` 文件内(如 `Titlebar.svelte`、`Sidebar.svelte`)。 +- **页面样式**: 各页面(如 Dashboard、Providers 等)的样式声明在 `frontend/src/pages/` 下的对应 Svelte 页面中。 +- **本地开发**: + ```bash + cd frontend + npm run dev # 启动 Vite 开发服务器进行前端预览 + # 或者在根目录下 + cargo tauri dev # 启动 Tauri 窗口并启用热重载 + ``` ## 常见问题 @@ -219,7 +209,7 @@ v2.1.12+ 的客户端 **强制** RSA-3072 PKCS#1-v1.5-SHA256 验签 `latest.json - **后端 / 转发**:Rust 1.80+ · axum 0.8 · reqwest 0.12(rustls-tls)· tokio - **协议适配**:`crates/adapters/` — Responses ↔ Chat / Gemini Native / Gemini CLI OAuth / Anthropic Messages / Grok Web 互转(请求 body + 流式响应状态机 + reasoning_content + tool_calls) -- **前端**:HTML + CSS + 原生 JavaScript + Bootstrap 5.3.3(本地化,无 CDN 依赖) +- **前端**: Svelte 5 + TypeScript + Vite + custom Vanilla CSS glassmorphism UI - **桌面壳**:Tauri 2 + tray-icon 0.23,通过 `cas://` URI scheme 把 frontend/ 与 axum 同进程串起来,无 TCP loopback - **存储**:`~/.codex-app-transfer/config.json`(配置,与 v1.x 互通)、`~/.codex-app-transfer/sessions.db`(L2 sqlite 会话持久化)、`~/.codex/{config.toml,auth.json}`(Codex APP 集成) - **打包**:`cargo tauri build` 单命令出 dmg/AppImage/deb/exe/msi;`xtask release-bundle` 收口出 sha256 + RSA-3072 sig + latest.json + draft GitHub release diff --git a/frontend/assets/app-icon.ico b/frontend-old/assets/app-icon.ico similarity index 100% rename from frontend/assets/app-icon.ico rename to frontend-old/assets/app-icon.ico diff --git a/frontend/assets/app-icon.png b/frontend-old/assets/app-icon.png similarity index 100% rename from frontend/assets/app-icon.png rename to frontend-old/assets/app-icon.png diff --git a/frontend/assets/providers/aliyun.ico b/frontend-old/assets/providers/aliyun.ico similarity index 100% rename from frontend/assets/providers/aliyun.ico rename to frontend-old/assets/providers/aliyun.ico diff --git a/frontend/assets/providers/antigravity.png b/frontend-old/assets/providers/antigravity.png similarity index 100% rename from frontend/assets/providers/antigravity.png rename to frontend-old/assets/providers/antigravity.png diff --git a/frontend/assets/providers/anyrouter.png b/frontend-old/assets/providers/anyrouter.png similarity index 100% rename from frontend/assets/providers/anyrouter.png rename to frontend-old/assets/providers/anyrouter.png diff --git a/frontend/assets/providers/deepseek.ico b/frontend-old/assets/providers/deepseek.ico similarity index 100% rename from frontend/assets/providers/deepseek.ico rename to frontend-old/assets/providers/deepseek.ico diff --git a/frontend/assets/providers/gemini.svg b/frontend-old/assets/providers/gemini.svg similarity index 100% rename from frontend/assets/providers/gemini.svg rename to frontend-old/assets/providers/gemini.svg diff --git a/frontend/assets/providers/google-ai-studio.png b/frontend-old/assets/providers/google-ai-studio.png similarity index 100% rename from frontend/assets/providers/google-ai-studio.png rename to frontend-old/assets/providers/google-ai-studio.png diff --git a/frontend/assets/providers/grok.svg b/frontend-old/assets/providers/grok.svg similarity index 100% rename from frontend/assets/providers/grok.svg rename to frontend-old/assets/providers/grok.svg diff --git a/frontend/assets/providers/kimi.ico b/frontend-old/assets/providers/kimi.ico similarity index 100% rename from frontend/assets/providers/kimi.ico rename to frontend-old/assets/providers/kimi.ico diff --git a/frontend/assets/providers/minimax.ico b/frontend-old/assets/providers/minimax.ico similarity index 100% rename from frontend/assets/providers/minimax.ico rename to frontend-old/assets/providers/minimax.ico diff --git a/frontend/assets/providers/qiniu.ico b/frontend-old/assets/providers/qiniu.ico similarity index 100% rename from frontend/assets/providers/qiniu.ico rename to frontend-old/assets/providers/qiniu.ico diff --git a/frontend/assets/providers/xiaomi-mimo.png b/frontend-old/assets/providers/xiaomi-mimo.png similarity index 100% rename from frontend/assets/providers/xiaomi-mimo.png rename to frontend-old/assets/providers/xiaomi-mimo.png diff --git a/frontend/assets/providers/zhipu.png b/frontend-old/assets/providers/zhipu.png similarity index 100% rename from frontend/assets/providers/zhipu.png rename to frontend-old/assets/providers/zhipu.png diff --git a/frontend/css/base.css b/frontend-old/css/base.css similarity index 100% rename from frontend/css/base.css rename to frontend-old/css/base.css diff --git a/frontend/css/components/activity.css b/frontend-old/css/components/activity.css similarity index 100% rename from frontend/css/components/activity.css rename to frontend-old/css/components/activity.css diff --git a/frontend/css/components/badge.css b/frontend-old/css/components/badge.css similarity index 100% rename from frontend/css/components/badge.css rename to frontend-old/css/components/badge.css diff --git a/frontend/css/components/baseurl-menu.css b/frontend-old/css/components/baseurl-menu.css similarity index 100% rename from frontend/css/components/baseurl-menu.css rename to frontend-old/css/components/baseurl-menu.css diff --git a/frontend/css/components/button.css b/frontend-old/css/components/button.css similarity index 100% rename from frontend/css/components/button.css rename to frontend-old/css/components/button.css diff --git a/frontend/css/components/card.css b/frontend-old/css/components/card.css similarity index 100% rename from frontend/css/components/card.css rename to frontend-old/css/components/card.css diff --git a/frontend/css/components/codex-mcp.css b/frontend-old/css/components/codex-mcp.css similarity index 100% rename from frontend/css/components/codex-mcp.css rename to frontend-old/css/components/codex-mcp.css diff --git a/frontend/css/components/codex-path-picker.css b/frontend-old/css/components/codex-path-picker.css similarity index 100% rename from frontend/css/components/codex-path-picker.css rename to frontend-old/css/components/codex-path-picker.css diff --git a/frontend/css/components/codex-sidebar.css b/frontend-old/css/components/codex-sidebar.css similarity index 100% rename from frontend/css/components/codex-sidebar.css rename to frontend-old/css/components/codex-sidebar.css diff --git a/frontend/css/components/desktop-warning.css b/frontend-old/css/components/desktop-warning.css similarity index 100% rename from frontend/css/components/desktop-warning.css rename to frontend-old/css/components/desktop-warning.css diff --git a/frontend/css/components/feedback.css b/frontend-old/css/components/feedback.css similarity index 100% rename from frontend/css/components/feedback.css rename to frontend-old/css/components/feedback.css diff --git a/frontend/css/components/form.css b/frontend-old/css/components/form.css similarity index 100% rename from frontend/css/components/form.css rename to frontend-old/css/components/form.css diff --git a/frontend/css/components/header.css b/frontend-old/css/components/header.css similarity index 100% rename from frontend/css/components/header.css rename to frontend-old/css/components/header.css diff --git a/frontend/css/components/info-box.css b/frontend-old/css/components/info-box.css similarity index 100% rename from frontend/css/components/info-box.css rename to frontend-old/css/components/info-box.css diff --git a/frontend/css/components/modal.css b/frontend-old/css/components/modal.css similarity index 100% rename from frontend/css/components/modal.css rename to frontend-old/css/components/modal.css diff --git a/frontend/css/components/nav.css b/frontend-old/css/components/nav.css similarity index 100% rename from frontend/css/components/nav.css rename to frontend-old/css/components/nav.css diff --git a/frontend/css/components/page.css b/frontend-old/css/components/page.css similarity index 100% rename from frontend/css/components/page.css rename to frontend-old/css/components/page.css diff --git a/frontend/css/components/segmented.css b/frontend-old/css/components/segmented.css similarity index 100% rename from frontend/css/components/segmented.css rename to frontend-old/css/components/segmented.css diff --git a/frontend/css/components/step.css b/frontend-old/css/components/step.css similarity index 100% rename from frontend/css/components/step.css rename to frontend-old/css/components/step.css diff --git a/frontend/css/components/switch.css b/frontend-old/css/components/switch.css similarity index 100% rename from frontend/css/components/switch.css rename to frontend-old/css/components/switch.css diff --git a/frontend/css/components/update-badge.css b/frontend-old/css/components/update-badge.css similarity index 100% rename from frontend/css/components/update-badge.css rename to frontend-old/css/components/update-badge.css diff --git a/frontend/css/pages/dashboard.css b/frontend-old/css/pages/dashboard.css similarity index 100% rename from frontend/css/pages/dashboard.css rename to frontend-old/css/pages/dashboard.css diff --git a/frontend/css/pages/guide.css b/frontend-old/css/pages/guide.css similarity index 100% rename from frontend/css/pages/guide.css rename to frontend-old/css/pages/guide.css diff --git a/frontend/css/pages/providers.css b/frontend-old/css/pages/providers.css similarity index 100% rename from frontend/css/pages/providers.css rename to frontend-old/css/pages/providers.css diff --git a/frontend/css/pages/proxy.css b/frontend-old/css/pages/proxy.css similarity index 100% rename from frontend/css/pages/proxy.css rename to frontend-old/css/pages/proxy.css diff --git a/frontend/css/pages/settings.css b/frontend-old/css/pages/settings.css similarity index 100% rename from frontend/css/pages/settings.css rename to frontend-old/css/pages/settings.css diff --git a/frontend/css/responsive.css b/frontend-old/css/responsive.css similarity index 100% rename from frontend/css/responsive.css rename to frontend-old/css/responsive.css diff --git a/frontend/css/style.css b/frontend-old/css/style.css similarity index 100% rename from frontend/css/style.css rename to frontend-old/css/style.css diff --git a/frontend/css/tokens.css b/frontend-old/css/tokens.css similarity index 100% rename from frontend/css/tokens.css rename to frontend-old/css/tokens.css diff --git a/frontend/gallery.html b/frontend-old/gallery.html similarity index 100% rename from frontend/gallery.html rename to frontend-old/gallery.html diff --git a/frontend-old/index.html b/frontend-old/index.html new file mode 100644 index 00000000..9e85a4cb --- /dev/null +++ b/frontend-old/index.html @@ -0,0 +1,1008 @@ + + + + + + Codex App Transfer + + + + + + + + +
+
+ + +
+ +
+
+ + / + +
+ + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+

桌面版状态

+
+ 未配置 +
+
+

代理状态

+
+ 运行中 :18080 +
+
+

当前提供商

+ + DeepSeek +
+
+

Plugins 解锁

+
+ 未运行 + +
+
+ + +
+ 添加并生成配置 + + 切换提供商 +
+ +
+
+
+ +

最近操作

+
+ +
+
+
+
+ +
+
+

添加提供商

+

添加新的 API 提供商或选择预设

+
+ +
+
+
+
+ + +
+
+
+ + +
+
+ + +
+
+ +
+
+
+ +
+ + +
+
+ + + +
+ +
+ + +
+ +
+ + + +
+
+
+

模型映射

+

把 OpenAI 模型名(gpt-5.5 / gpt-5.4 / gpt-5.4-mini / gpt-5.3-codex / gpt-5.2)映射到这个供应商真实支持的模型。未设置的模型自动使用 Default 映射。

+
+ +
+
+ +

+
+ +

一键应用会保存供应商和模型映射,把它设为默认,并让 Codex CLI 连接到本工具。之后你在 Codex CLI 里发消息,本工具会自动转发到你填写的 API 地址。

+
+
+
+ + + 取消 +
+
+
+ +
+

快捷预设

+

选择后会自动填入 API 地址和推荐模型,API Key 仍由你自己填写。

+
+
+
+
+ +
+
+
+

提供商

+

管理已配置的 API 提供商

+
+ 添加提供商 +
+ + + +
+
+ + 提供商名称 + API Base URL + 模型映射 + 状态 + 操作 +
+
+
+
+ +
+
+ +
+

Codex CLI

+

一键让 Codex CLI 使用当前供应商

+
+
+ +
+
+
+

Codex CLI 配置

+
+ + 已配置 +
+ +
+
+ +

原理很简单:Codex CLI 先连接到本工具;本工具再把模型名翻译成你选择的供应商模型,并转发到对应 API。你的上游 API Key 只保存在本机配置里,不直接写进 Codex CLI。

+
+ +
+
+
+

配置详情

+ +
+

+            
+ +
+ +
+

快速引导

+
+
1复制命令

把 Codex CLI 连接到本工具

+
2设置环境变量

在终端执行复制的命令

+
3开始使用

之后在终端使用 codex 命令即可

+
+
+
+
+ +
+
+
+ +
+ 运行中 +

本机监听

+
+
+
+ + +
+ +
+ +
+
+ + + +
+
+
+ +
+
+ +
+
+
+ 主题 +
+ + + + + + +
+
+
+ 语言 +
+ + +
+
+
+ 转发端口 + +
+
+ 管理端口 + +
+
+ 启动时自动应用配置 +
+ +

启动时自动应用当前提供商的 Codex 配置;如该提供商需要转发服务,会同时启动转发。关闭则需要手动点「应用配置」。

+
+
+
+ 自动解锁 Codex Plugins +
+
+ + +
+

此功能需要通过本应用内启动 Codex 才能正常使用。点击右侧「重启 Codex」可直接重启 Codex 并触发自动注入。

+

运行时状态:未检测

+
+
+
+ 自动唤醒 Codex 桌面宠物 +
+ +

开启后,启动 Codex Desktop 时会自动将 ~/.codex/.codex-global-state.json 中的 electron-avatar-overlay-open 置为 true,让桌面宠物随 Codex 一起唤醒。

+
+
+
+ 退出时还原 Codex 原配置 +
+ +

开启后,应用退出或下次启动时会自动把 ~/.codex/config.toml 与 auth.json 还原至 apply 之前的状态;保证不开应用时 Codex CLI 仍是你本人的原配置。

+

正在读取快照状态…

+
+
+
+ 允许 Codex 联网工具(全权限模式) +
+ +

⚠️ 默认开启。让 Codex 走 danger-full-access 沙箱 + approval_policy=never(Codex 官方推荐的 "Full access" 配对):模型可以读写任何文件、用 curl / wget 等联网命令获取实时网页,且所有命令无审批弹窗。**等于完全信任模型**。关闭后 Codex 回 read-only 沙箱 + on-request 审批:无网络,仅能用所选模型自带的 web_search 能力;若模型不支持 web_search,则完全无法联网搜索。

+
+
+ +
+ 更新地址 + +
+
+ 第三方兼容 +
+
+ +
+
+

一键检查所有 provider 实际可用性。自定义 provider 可选择 OpenAI Chat 本地转换、原生 Responses 透传、Anthropic Messages 本地转换等协议路由。

+
+
+
+ 配置备份 +
+
+ + + + +
+
+

导出的配置会包含 API Key,请只保存在可信设备上。

+
+
+
+ 反馈与建议 +
+
+ +
+

遇到问题或想提建议?支持文本 + 截图 + 日志,匿名提交,无需登录。诊断信息(应用版本 / OS / 当前 provider 名称 / 最近 200 行代理日志)默认附上,不含 API Key 等敏感数据。

+
+
+
+ +
+

关于

+
版本...
+
许可证MIT License
+
检查更新
+

+
GitHub
+
+
+ +
+
+ + + +
+ +
+ +
+
+ + +
+ + +
+ + +
+ + AGENTS.md 会严重影响 AI 的行为,请谨慎修改 +
+ + +

+
+            
+            
+
+            
+            
+ + + + + + + + +
+ +
+ + + + + + + + + + + + + + + + + + +
+
+ + + + + + +
+ +
+
+ +
+

使用引导

+

让 OpenAI Codex CLI 接入 Kimi、DeepSeek、智谱 GLM、阿里云百炼、Xiaomi MiMo 等供应商,无需改动 CLI 本身。

+
+
+ +
+

开始之前

+
+ +
+

需要已安装 OpenAI Codex CLI 0.126+。终端跑 codex --version 看版本号;没装的话先去 github.com/openai/codex

+
+
+
+ +
+

快速开始

+
+
+ 1 +
+

添加提供商

+

在「提供商」页右上角「+」选预设(Kimi / Kimi Code / DeepSeek / 智谱 GLM / 阿里云百炼 / Xiaomi MiMo),粘贴 API Key。模型映射按官方文档已预填。

+ 去添加 → +
+
+
+ 2 +
+

设为默认

+

「提供商」列表点你要用的那个,确认带上「默认」标记。可以同时保存多个,以后随时切。

+ 去管理 → +
+
+
+ 3 +
+

应用配置(自动)

+

应用启动时自动写入 ~/.codex/config.tomlauth.json,按需启动本地转发服务。第一次会先快照备份你原来的 ~/.codex 配置,退出时按 key 智能合并还原。

+
+
+
+ 4 +
+

在终端跑 codex

+

打开新终端,直接跑 codex。模型选单会显示当前 provider 的映射(Sonnet / Haiku / Opus 对应真实模型)。

+
+
+
+ 5 +
+

切换 / 退出

+

右键系统托盘图标可以一键切换 provider。退出应用时,~/.codex/ 自动回到你原来的配置 —— 不开应用 = 用你自己的原配置。

+
+
+
+
+ +
+

进阶用法

+ +
+ +
+

遇到问题

+
+
+ +

提交反馈

+

Dashboard 顶栏「反馈」按钮匿名提交问题描述 + 截图 + 日志,默认附诊断信息(应用版本 / OS / 当前 provider 名,不含 API Key)。无需登录。

+
+
+ +

查看日志

+

Settings 页底部「查看日志」按钮直接打开 ~/.codex-app-transfer/logs/。或在「转发」页内置实时日志面板,2 秒自动刷新。

+
+
+ +

模型菜单不刷新?

+

Codex CLI 只在启动时读 ~/.codex/ 配置。切换 provider 后必须 关闭并重新打开终端,新终端才会用新映射。

+
+
+ +

测速 / 兼容性失败?

+

如果测速失败,先确认 baseUrl 与 API Key 对得上。v2.x 起所有 provider 的协议类型由系统统一管理:内置预设按 baseUrl 自动命中,自定义 provider 默认 chat/completions;仅当上游明确实现 OpenAI Responses API 时才选 Responses,Claude / Anthropic 上游请选择 Anthropic Messages 本地转换。

+
+
+
+ + 添加你的第一个提供商 +
+
+ + + + + + + +
+
+
+
+
+ + + + + + + diff --git a/frontend/js/api.js b/frontend-old/js/api.js similarity index 100% rename from frontend/js/api.js rename to frontend-old/js/api.js diff --git a/frontend/js/app.js b/frontend-old/js/app.js similarity index 100% rename from frontend/js/app.js rename to frontend-old/js/app.js diff --git a/frontend/js/i18n.js b/frontend-old/js/i18n.js similarity index 100% rename from frontend/js/i18n.js rename to frontend-old/js/i18n.js diff --git a/frontend/vendor/bootstrap-icons@1.11.3/bootstrap-icons.css b/frontend-old/vendor/bootstrap-icons@1.11.3/bootstrap-icons.css similarity index 100% rename from frontend/vendor/bootstrap-icons@1.11.3/bootstrap-icons.css rename to frontend-old/vendor/bootstrap-icons@1.11.3/bootstrap-icons.css diff --git a/frontend/vendor/bootstrap-icons@1.11.3/fonts/bootstrap-icons.woff b/frontend-old/vendor/bootstrap-icons@1.11.3/fonts/bootstrap-icons.woff similarity index 100% rename from frontend/vendor/bootstrap-icons@1.11.3/fonts/bootstrap-icons.woff rename to frontend-old/vendor/bootstrap-icons@1.11.3/fonts/bootstrap-icons.woff diff --git a/frontend/vendor/bootstrap-icons@1.11.3/fonts/bootstrap-icons.woff2 b/frontend-old/vendor/bootstrap-icons@1.11.3/fonts/bootstrap-icons.woff2 similarity index 100% rename from frontend/vendor/bootstrap-icons@1.11.3/fonts/bootstrap-icons.woff2 rename to frontend-old/vendor/bootstrap-icons@1.11.3/fonts/bootstrap-icons.woff2 diff --git a/frontend/vendor/bootstrap@5.3.3/bootstrap.bundle.min.js b/frontend-old/vendor/bootstrap@5.3.3/bootstrap.bundle.min.js similarity index 100% rename from frontend/vendor/bootstrap@5.3.3/bootstrap.bundle.min.js rename to frontend-old/vendor/bootstrap@5.3.3/bootstrap.bundle.min.js diff --git a/frontend/vendor/bootstrap@5.3.3/bootstrap.min.css b/frontend-old/vendor/bootstrap@5.3.3/bootstrap.min.css similarity index 100% rename from frontend/vendor/bootstrap@5.3.3/bootstrap.min.css rename to frontend-old/vendor/bootstrap@5.3.3/bootstrap.min.css diff --git a/frontend/vendor/fira/fira.css b/frontend-old/vendor/fira/fira.css similarity index 100% rename from frontend/vendor/fira/fira.css rename to frontend-old/vendor/fira/fira.css diff --git a/frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh09SDqFGedCMX.woff2 b/frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh09SDqFGedCMX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh09SDqFGedCMX.woff2 rename to frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh09SDqFGedCMX.woff2 diff --git a/frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh0NSDqFGedCMX.woff2 b/frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh0NSDqFGedCMX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh0NSDqFGedCMX.woff2 rename to frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh0NSDqFGedCMX.woff2 diff --git a/frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh0dSDqFGedCMX.woff2 b/frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh0dSDqFGedCMX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh0dSDqFGedCMX.woff2 rename to frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh0dSDqFGedCMX.woff2 diff --git a/frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh2dSDqFGedCMX.woff2 b/frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh2dSDqFGedCMX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh2dSDqFGedCMX.woff2 rename to frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh2dSDqFGedCMX.woff2 diff --git a/frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh3dSDqFGedA.woff2 b/frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh3dSDqFGedA.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh3dSDqFGedA.woff2 rename to frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh3dSDqFGedA.woff2 diff --git a/frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh3tSDqFGedCMX.woff2 b/frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh3tSDqFGedCMX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh3tSDqFGedCMX.woff2 rename to frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bh3tSDqFGedCMX.woff2 diff --git a/frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bhZ_Wmh3mUfBsu_Q.woff2 b/frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bhZ_Wmh3mUfBsu_Q.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bhZ_Wmh3mUfBsu_Q.woff2 rename to frontend-old/vendor/fira/fonts/uU9NCBsR6Z2vfE9aq3bhZ_Wmh3mUfBsu_Q.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eQhf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eQhf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eQhf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eQhf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eRRf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eRRf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eRRf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eRRf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eRhf6Xl7Glw.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eRhf6Xl7Glw.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eRhf6Xl7Glw.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eRhf6Xl7Glw.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSBf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSBf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSBf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSBf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSRf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSRf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSRf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSRf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eShf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eShf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eShf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eShf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSxf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSxf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSxf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnLK3eSxf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eQhf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eQhf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eQhf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eQhf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eRRf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eRRf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eRRf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eRRf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eRhf6Xl7Glw.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eRhf6Xl7Glw.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eRhf6Xl7Glw.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eRhf6Xl7Glw.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSBf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSBf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSBf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSBf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSRf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSRf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSRf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSRf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eShf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eShf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eShf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eShf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSxf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSxf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSxf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnMK7eSxf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeQhf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeQhf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeQhf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeQhf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeRRf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeRRf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeRRf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeRRf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeRhf6Xl7Glw.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeRhf6Xl7Glw.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeRhf6Xl7Glw.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeRhf6Xl7Glw.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSBf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSBf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSBf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSBf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSRf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSRf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSRf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSRf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeShf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeShf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeShf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeShf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSxf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSxf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSxf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnSKzeSxf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveQhf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveQhf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveQhf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveQhf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveRRf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveRRf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveRRf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveRRf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveRhf6Xl7Glw.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveRhf6Xl7Glw.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveRhf6Xl7Glw.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveRhf6Xl7Glw.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSBf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSBf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSBf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSBf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSRf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSRf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSRf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSRf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveShf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveShf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveShf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveShf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSxf6Xl7Gl3LX.woff2 b/frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSxf6Xl7Gl3LX.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSxf6Xl7Gl3LX.woff2 rename to frontend-old/vendor/fira/fonts/va9B4kDNxMZdWfMOD5VnZKveSxf6Xl7Gl3LX.woff2 diff --git a/frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvk4jLazX3dGTP.woff2 b/frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvk4jLazX3dGTP.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvk4jLazX3dGTP.woff2 rename to frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvk4jLazX3dGTP.woff2 diff --git a/frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvl4jLazX3dA.woff2 b/frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvl4jLazX3dA.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvl4jLazX3dA.woff2 rename to frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvl4jLazX3dA.woff2 diff --git a/frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvlIjLazX3dGTP.woff2 b/frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvlIjLazX3dGTP.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvlIjLazX3dGTP.woff2 rename to frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvlIjLazX3dGTP.woff2 diff --git a/frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvm4jLazX3dGTP.woff2 b/frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvm4jLazX3dGTP.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvm4jLazX3dGTP.woff2 rename to frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5Vvm4jLazX3dGTP.woff2 diff --git a/frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmIjLazX3dGTP.woff2 b/frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmIjLazX3dGTP.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmIjLazX3dGTP.woff2 rename to frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmIjLazX3dGTP.woff2 diff --git a/frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmYjLazX3dGTP.woff2 b/frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmYjLazX3dGTP.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmYjLazX3dGTP.woff2 rename to frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmYjLazX3dGTP.woff2 diff --git a/frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmojLazX3dGTP.woff2 b/frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmojLazX3dGTP.woff2 similarity index 100% rename from frontend/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmojLazX3dGTP.woff2 rename to frontend-old/vendor/fira/fonts/va9E4kDNxMZdWfMOD5VvmojLazX3dGTP.woff2 diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 00000000..a547bf36 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/frontend/README.md b/frontend/README.md new file mode 100644 index 00000000..e6cd94fc --- /dev/null +++ b/frontend/README.md @@ -0,0 +1,47 @@ +# Svelte + TS + Vite + +This template should help get you started developing with Svelte and TypeScript in Vite. + +## Recommended IDE Setup + +[VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). + +## Need an official Svelte framework? + +Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. + +## Technical considerations + +**Why use this over SvelteKit?** + +- It brings its own routing solution which might not be preferable for some users. +- It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. + +This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. + +Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. + +**Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** + +Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. + +**Why include `.vscode/extensions.json`?** + +Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. + +**Why enable `allowJs` in the TS template?** + +While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. + +**Why is HMR not preserving my local component state?** + +HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). + +If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. + +```ts +// store.ts +// An extremely simple external store +import { writable } from 'svelte/store' +export default writable(0) +``` diff --git a/frontend/index.html b/frontend/index.html index 9e85a4cb..7bbda10b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,1008 +1,13 @@ - + - - + + + Codex App Transfer - - - - - - -
-
- - -
- -
-
- - / - -
- - - - - - - - -
-
- -
-
-
-
-
- -
-
-

桌面版状态

-
- 未配置 -
-
-

代理状态

-
- 运行中 :18080 -
-
-

当前提供商

- - DeepSeek -
-
-

Plugins 解锁

-
- 未运行 - -
-
- - -
- 添加并生成配置 - - 切换提供商 -
- -
-
-
- -

最近操作

-
- -
-
-
-
- -
-
-

添加提供商

-

添加新的 API 提供商或选择预设

-
- -
-
-
-
- - -
-
-
- - -
-
- - -
-
- -
-
-
- -
- - -
-
- - - -
- -
- - -
- -
- - - -
-
-
-

模型映射

-

把 OpenAI 模型名(gpt-5.5 / gpt-5.4 / gpt-5.4-mini / gpt-5.3-codex / gpt-5.2)映射到这个供应商真实支持的模型。未设置的模型自动使用 Default 映射。

-
- -
-
- -

-
- -

一键应用会保存供应商和模型映射,把它设为默认,并让 Codex CLI 连接到本工具。之后你在 Codex CLI 里发消息,本工具会自动转发到你填写的 API 地址。

-
-
-
- - - 取消 -
-
-
- -
-

快捷预设

-

选择后会自动填入 API 地址和推荐模型,API Key 仍由你自己填写。

-
-
-
-
- -
-
-
-

提供商

-

管理已配置的 API 提供商

-
- 添加提供商 -
- - - -
-
- - 提供商名称 - API Base URL - 模型映射 - 状态 - 操作 -
-
-
-
- -
-
- -
-

Codex CLI

-

一键让 Codex CLI 使用当前供应商

-
-
- -
-
-
-

Codex CLI 配置

-
- - 已配置 -
- -
-
- -

原理很简单:Codex CLI 先连接到本工具;本工具再把模型名翻译成你选择的供应商模型,并转发到对应 API。你的上游 API Key 只保存在本机配置里,不直接写进 Codex CLI。

-
- -
-
-
-

配置详情

- -
-

-            
- -
- -
-

快速引导

-
-
1复制命令

把 Codex CLI 连接到本工具

-
2设置环境变量

在终端执行复制的命令

-
3开始使用

之后在终端使用 codex 命令即可

-
-
-
-
- -
-
-
- -
- 运行中 -

本机监听

-
-
-
- - -
- -
- -
-
- - - -
-
-
- -
-
- -
-
-
- 主题 -
- - - - - - -
-
-
- 语言 -
- - -
-
-
- 转发端口 - -
-
- 管理端口 - -
-
- 启动时自动应用配置 -
- -

启动时自动应用当前提供商的 Codex 配置;如该提供商需要转发服务,会同时启动转发。关闭则需要手动点「应用配置」。

-
-
-
- 自动解锁 Codex Plugins -
-
- - -
-

此功能需要通过本应用内启动 Codex 才能正常使用。点击右侧「重启 Codex」可直接重启 Codex 并触发自动注入。

-

运行时状态:未检测

-
-
-
- 自动唤醒 Codex 桌面宠物 -
- -

开启后,启动 Codex Desktop 时会自动将 ~/.codex/.codex-global-state.json 中的 electron-avatar-overlay-open 置为 true,让桌面宠物随 Codex 一起唤醒。

-
-
-
- 退出时还原 Codex 原配置 -
- -

开启后,应用退出或下次启动时会自动把 ~/.codex/config.toml 与 auth.json 还原至 apply 之前的状态;保证不开应用时 Codex CLI 仍是你本人的原配置。

-

正在读取快照状态…

-
-
-
- 允许 Codex 联网工具(全权限模式) -
- -

⚠️ 默认开启。让 Codex 走 danger-full-access 沙箱 + approval_policy=never(Codex 官方推荐的 "Full access" 配对):模型可以读写任何文件、用 curl / wget 等联网命令获取实时网页,且所有命令无审批弹窗。**等于完全信任模型**。关闭后 Codex 回 read-only 沙箱 + on-request 审批:无网络,仅能用所选模型自带的 web_search 能力;若模型不支持 web_search,则完全无法联网搜索。

-
-
- -
- 更新地址 - -
-
- 第三方兼容 -
-
- -
-
-

一键检查所有 provider 实际可用性。自定义 provider 可选择 OpenAI Chat 本地转换、原生 Responses 透传、Anthropic Messages 本地转换等协议路由。

-
-
-
- 配置备份 -
-
- - - - -
-
-

导出的配置会包含 API Key,请只保存在可信设备上。

-
-
-
- 反馈与建议 -
-
- -
-

遇到问题或想提建议?支持文本 + 截图 + 日志,匿名提交,无需登录。诊断信息(应用版本 / OS / 当前 provider 名称 / 最近 200 行代理日志)默认附上,不含 API Key 等敏感数据。

-
-
-
- -
-

关于

-
版本...
-
许可证MIT License
-
检查更新
-

-
GitHub
-
-
- -
-
- - - -
- -
- -
-
- - -
- - -
- - -
- - AGENTS.md 会严重影响 AI 的行为,请谨慎修改 -
- - -

-
-            
-            
-
-            
-            
- - - - - - - - -
- -
- - - - - - - - - - - - - - - - - - -
-
- - - - - - -
- -
-
- -
-

使用引导

-

让 OpenAI Codex CLI 接入 Kimi、DeepSeek、智谱 GLM、阿里云百炼、Xiaomi MiMo 等供应商,无需改动 CLI 本身。

-
-
- -
-

开始之前

-
- -
-

需要已安装 OpenAI Codex CLI 0.126+。终端跑 codex --version 看版本号;没装的话先去 github.com/openai/codex

-
-
-
- -
-

快速开始

-
-
- 1 -
-

添加提供商

-

在「提供商」页右上角「+」选预设(Kimi / Kimi Code / DeepSeek / 智谱 GLM / 阿里云百炼 / Xiaomi MiMo),粘贴 API Key。模型映射按官方文档已预填。

- 去添加 → -
-
-
- 2 -
-

设为默认

-

「提供商」列表点你要用的那个,确认带上「默认」标记。可以同时保存多个,以后随时切。

- 去管理 → -
-
-
- 3 -
-

应用配置(自动)

-

应用启动时自动写入 ~/.codex/config.tomlauth.json,按需启动本地转发服务。第一次会先快照备份你原来的 ~/.codex 配置,退出时按 key 智能合并还原。

-
-
-
- 4 -
-

在终端跑 codex

-

打开新终端,直接跑 codex。模型选单会显示当前 provider 的映射(Sonnet / Haiku / Opus 对应真实模型)。

-
-
-
- 5 -
-

切换 / 退出

-

右键系统托盘图标可以一键切换 provider。退出应用时,~/.codex/ 自动回到你原来的配置 —— 不开应用 = 用你自己的原配置。

-
-
-
-
- -
-

进阶用法

- -
- -
-

遇到问题

-
-
- -

提交反馈

-

Dashboard 顶栏「反馈」按钮匿名提交问题描述 + 截图 + 日志,默认附诊断信息(应用版本 / OS / 当前 provider 名,不含 API Key)。无需登录。

-
-
- -

查看日志

-

Settings 页底部「查看日志」按钮直接打开 ~/.codex-app-transfer/logs/。或在「转发」页内置实时日志面板,2 秒自动刷新。

-
-
- -

模型菜单不刷新?

-

Codex CLI 只在启动时读 ~/.codex/ 配置。切换 provider 后必须 关闭并重新打开终端,新终端才会用新映射。

-
-
- -

测速 / 兼容性失败?

-

如果测速失败,先确认 baseUrl 与 API Key 对得上。v2.x 起所有 provider 的协议类型由系统统一管理:内置预设按 baseUrl 自动命中,自定义 provider 默认 chat/completions;仅当上游明确实现 OpenAI Responses API 时才选 Responses,Claude / Anthropic 上游请选择 Anthropic Messages 本地转换。

-
-
-
- - 添加你的第一个提供商 -
-
- - - - - - - -
-
-
-
-
- - - - - +
+ diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 00000000..3ab25363 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1314 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "bootstrap-icons": "^1.13.1" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tsconfig/svelte": "^5.0.8", + "@types/node": "^24.12.3", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "~6.0.2", + "vite": "^8.0.12" + } + }, + "node_modules/@emnapi/core": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", + "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", + "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.132.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.132.0.tgz", + "integrity": "sha512-FESMOxil5Se014ui/Eq8fT5uHJo6nIRwH0PfJrZJXs6Gek3ZVFOrpUv3YIZT20m+extU98Hg1Ym72U58rlsxUQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.2.tgz", + "integrity": "sha512-ZS4D1JPGn/MYQN/SYDWftIE/nVsM8j/AFOYEzAoOE2O3NktQOZru+/vYXGbR/qtdLdIfGCP0lcoJiYVzsEz+iQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.2.tgz", + "integrity": "sha512-vdFA9+C/rekyGce7WqHs/xoT0ioZEWaOFyZLIV1mEeNFaFDUQrPIo8Vs2GvJ6eetb3rzDUtUBgzto3ExpXJB3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.2.tgz", + "integrity": "sha512-BewSOwTHazv77DTYiAZXSqqKZ4KP/KonFisDMVU7PImxoWfB2aepnPhd2E4SWz3zDzYgDNbs6jBmTdgNnF02GA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.2.tgz", + "integrity": "sha512-m41o7M0YWtUdqk61Tb+jnKb2rN++iRdIASlExkUoKfIAH30DOHCB8fVLzSUpbWHHU8esmEioY62PxzexE8MBuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.2.tgz", + "integrity": "sha512-jcojB9H7W/jS29pMKWAK1N+fU99vXodHDTatS3b3y/XSOCiHo0kkA74pL3jJmkoQtYpOCxDvaKs1fo2Ij/1X5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.2.tgz", + "integrity": "sha512-1jn6qDU5iiOgFgygDzKUuKP0maTi0/f1+sBLgvij/76C77Nm3ts6ufz9Bjg5q5dduxiUIxtq86JIoBvo1xQ4Ig==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.2.tgz", + "integrity": "sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.2.tgz", + "integrity": "sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.2.tgz", + "integrity": "sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.2.tgz", + "integrity": "sha512-0+bOkiQ779+r1WpoHOWHqncvyySci0vKph+myNDYb+im6meJAzHQXay6oEgnkHuUGouM1LKTZwqKpBow6Kj7CQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.2.tgz", + "integrity": "sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.2.tgz", + "integrity": "sha512-1v5vHasdfQAZoEHakBV72LIFAC9JjnymsiKxp+GEr/ma3+NJCPSaYK+qavInOovJkgwFrs7GccX2d6IgDA3Z5w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.2.tgz", + "integrity": "sha512-mb1VobWn6NheziTk5/WEaR6AKVbrwT5sOi6C7zk3gy/pD1qtJfU1j4PgTo2NJnOtbL9Dl3Aeei8w9jJ7qC2jZQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.10.0", + "@emnapi/runtime": "1.10.0", + "@napi-rs/wasm-runtime": "^1.1.4" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.2.tgz", + "integrity": "sha512-SqKonF56vA/L2yHwHYcEp2P34URpOZ7d1fS635cTkpDnUtEGdUbhI6NzsPdqeSWvAAeGDrxjWjNmibDIdFf9/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.2.tgz", + "integrity": "sha512-v7qRI7gXLRINcOGXt+7YmAZ6iFuyZVMIoXAxhd8oP+DR9dLfL9GfNIx7PLMxmhZdvq8waUJBQiWN9EKNy+TRBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sveltejs/acorn-typescript": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.10.tgz", + "integrity": "sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^8.9.0" + } + }, + "node_modules/@sveltejs/vite-plugin-svelte": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-7.1.2.tgz", + "integrity": "sha512-DrUBA2UXRfDmUX/ZTiEopd3X40yavsJF1FX2RygcuIScHL7o5YX1fMvoYnDhjeJQC4weCOklirpNWlcb2NiSeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "deepmerge": "^4.3.1", + "magic-string": "^0.30.21", + "obug": "^2.1.0", + "vitefu": "^1.1.2" + }, + "engines": { + "node": "^20.19 || ^22.12 || >=24" + }, + "peerDependencies": { + "svelte": "^5.46.4", + "vite": "^8.0.0-beta.7 || ^8.0.0" + } + }, + "node_modules/@tsconfig/svelte": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@tsconfig/svelte/-/svelte-5.0.8.tgz", + "integrity": "sha512-UkNnw1/oFEfecR8ypyHIQuWYdkPvHiwcQ78sh+ymIiYoF+uc5H1UBetbjyqT+vgGJ3qQN6nhucJviX6HesWtKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.12.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.4.tgz", + "integrity": "sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/aria-query": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz", + "integrity": "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/bootstrap-icons": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/bootstrap-icons/-/bootstrap-icons-1.13.1.tgz", + "integrity": "sha512-ijombt4v6bv5CLeXvRWKy7CuM3TRTuPEuGaGKvTV5cz65rQSY8RQ2JcHt6b90cBBAC7s8fsf2EkQDldzCoXUjw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devalue": { + "version": "5.8.1", + "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz", + "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==", + "dev": true, + "license": "MIT" + }, + "node_modules/esm-env": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", + "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esrap": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.2.9.tgz", + "integrity": "sha512-4KijP+NxCWthMCUC3qHbE6n4vCjqgJS1uAYKhuT/GWfFTf1Qyive2TgOjep+gzbSzRfnNyaN/UU9YmdOt8Eg0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "peerDependencies": { + "@typescript-eslint/types": "^8.2.0" + }, + "peerDependenciesMeta": { + "@typescript-eslint/types": { + "optional": true + } + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/is-reference": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", + "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.6" + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-character": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", + "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/rolldown": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.2.tgz", + "integrity": "sha512-oZx5zVDtVB44AW3eaifgDml1gWRDZGvjcfdxonE4swNPG98PrrXjaO/KrnUjzlMnztCCRVlUueA1kCXhARGk6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.132.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.0.2", + "@rolldown/binding-darwin-arm64": "1.0.2", + "@rolldown/binding-darwin-x64": "1.0.2", + "@rolldown/binding-freebsd-x64": "1.0.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.2", + "@rolldown/binding-linux-arm64-gnu": "1.0.2", + "@rolldown/binding-linux-arm64-musl": "1.0.2", + "@rolldown/binding-linux-ppc64-gnu": "1.0.2", + "@rolldown/binding-linux-s390x-gnu": "1.0.2", + "@rolldown/binding-linux-x64-gnu": "1.0.2", + "@rolldown/binding-linux-x64-musl": "1.0.2", + "@rolldown/binding-openharmony-arm64": "1.0.2", + "@rolldown/binding-wasm32-wasi": "1.0.2", + "@rolldown/binding-win32-arm64-msvc": "1.0.2", + "@rolldown/binding-win32-x64-msvc": "1.0.2" + } + }, + "node_modules/sade": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz", + "integrity": "sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "mri": "^1.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/svelte": { + "version": "5.55.9", + "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.9.tgz", + "integrity": "sha512-fTjjT8cHLDwigcu2j3pv7Jq04LklXevPB8uBgyHNiTXv+RMNvVnrjS4UEYrLMkhuq1vpCodHjiW+z/95SDs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "@jridgewell/sourcemap-codec": "^1.5.0", + "@sveltejs/acorn-typescript": "^1.0.10", + "@types/estree": "^1.0.5", + "@types/trusted-types": "^2.0.7", + "acorn": "^8.12.1", + "aria-query": "5.3.1", + "axobject-query": "^4.1.0", + "clsx": "^2.1.1", + "devalue": "^5.8.1", + "esm-env": "^1.2.1", + "esrap": "^2.2.9", + "is-reference": "^3.0.3", + "locate-character": "^3.0.0", + "magic-string": "^0.30.11", + "zimmerframe": "^1.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/svelte-check": { + "version": "4.4.8", + "resolved": "https://registry.npmjs.org/svelte-check/-/svelte-check-4.4.8.tgz", + "integrity": "sha512-67adfgBox5eNSNIvIIwgFizKGdcRrGpiMoNO2obHcYuLz7iTa8Xgm/NGU3ntMFnNm8K1grFOIG6HhMLX/vcN8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.25", + "chokidar": "^4.0.1", + "fdir": "^6.2.0", + "picocolors": "^1.0.0", + "sade": "^1.7.4" + }, + "bin": { + "svelte-check": "bin/svelte-check" + }, + "engines": { + "node": ">= 18.0.0" + }, + "peerDependencies": { + "svelte": "^4.0.0 || ^5.0.0-next.0", + "typescript": ">=5.0.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.0.14", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.14.tgz", + "integrity": "sha512-s4BJJ+5y1pYL6Otw51FHhVJQhPnuRinKig64g/1+EUNaJsd3gCKdD31IPFvswUgW9/60QT9oFHbZHbQK5imcxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.15", + "rolldown": "1.0.2", + "tinyglobby": "^0.2.16" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.18", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitefu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/vitefu/-/vitefu-1.1.3.tgz", + "integrity": "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg==", + "dev": true, + "license": "MIT", + "workspaces": [ + "tests/deps/*", + "tests/projects/*", + "tests/projects/workspace/packages/*" + ], + "peerDependencies": { + "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/zimmerframe": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", + "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..9ecdbfae --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,24 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview", + "check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^7.1.2", + "@tsconfig/svelte": "^5.0.8", + "@types/node": "^24.12.3", + "svelte": "^5.55.5", + "svelte-check": "^4.4.8", + "typescript": "~6.0.2", + "vite": "^8.0.12" + }, + "dependencies": { + "bootstrap-icons": "^1.13.1" + } +} diff --git a/frontend/public/favicon.svg b/frontend/public/favicon.svg new file mode 100644 index 00000000..6893eb13 --- /dev/null +++ b/frontend/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/icons.svg b/frontend/public/icons.svg new file mode 100644 index 00000000..e9522193 --- /dev/null +++ b/frontend/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/App.svelte b/frontend/src/App.svelte new file mode 100644 index 00000000..8a11e137 --- /dev/null +++ b/frontend/src/App.svelte @@ -0,0 +1,123 @@ + + +
+ + + + +
+ + + + +
+ {#if $activeTab === 'dashboard'} + + {:else if $activeTab === 'providers'} + + {:else if $activeTab === 'providers/add' || $activeTab === 'providers/edit'} + + {:else if $activeTab === 'proxy'} + + {:else if $activeTab === 'codex'} + + {:else if $activeTab === 'guide'} + + {:else if $activeTab === 'settings'} + + {:else} +
+

404 Not Found

+

The requested view {$activeTab} could not be resolved.

+
+ {/if} +
+
+
+ + diff --git a/frontend/src/app.css b/frontend/src/app.css new file mode 100644 index 00000000..c93bb13e --- /dev/null +++ b/frontend/src/app.css @@ -0,0 +1,484 @@ +@import url('https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap'); +@import 'bootstrap-icons/font/bootstrap-icons.css'; + +:root { + /* Fonts */ + --font-sans: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, SF Pro Text, Menlo, Monaco, Consolas, monospace; + + /* Classic macOS Glassmorphism / Vibrancy Variables */ + --mac-bg-window: rgba(255, 255, 255, 0.4); + --mac-bg-sidebar: rgba(246, 246, 246, 0.5); + --mac-bg-panel: rgba(255, 255, 255, 0.5); + --mac-bg-card: rgba(255, 255, 255, 0.6); + --mac-bg-input: rgba(255, 255, 255, 0.7); + --mac-bg-button: rgba(255, 255, 255, 0.85); + --mac-bg-button-hover: rgba(255, 255, 255, 1); + --mac-bg-button-active: rgba(240, 240, 240, 0.9); + + --mac-border-window: 1px solid rgba(255, 255, 255, 0.4); + --mac-border-highlight: 1px solid rgba(255, 255, 255, 0.3); + --mac-border-separator: rgba(0, 0, 0, 0.08); + + --mac-text-primary: #1c1c1e; + --mac-text-secondary: #8e8e93; + --mac-text-tertiary: #c7c7cc; + --mac-text-danger: #ff3b30; + + --mac-accent: #007aff; + --mac-accent-soft: rgba(0, 122, 255, 0.15); + --mac-success: #34c759; + --mac-success-soft: rgba(52, 199, 89, 0.15); + --mac-warning: #ff9500; + --mac-danger: #ff3b30; + --mac-danger-soft: rgba(255, 59, 48, 0.15); + + --mac-blur: 20px; + --mac-saturate: 180%; + --mac-shadow-window: 0 20px 60px rgba(0, 0, 0, 0.12); + --mac-shadow-card: 0 4px 12px rgba(0, 0, 0, 0.04); + --mac-shadow-popover: 0 10px 30px rgba(0, 0, 0, 0.15); + + --radius-window: 12px; + --radius-panel: 10px; + --radius-card: 8px; + --radius-button: 6px; + --radius-input: 6px; + + /* Transition timings */ + --transition-fast: 0.15s cubic-bezier(0.16, 1, 0.3, 1); + --transition-normal: 0.25s cubic-bezier(0.16, 1, 0.3, 1); +} + +/* Dark Mode Overrides */ +@media (prefers-color-scheme: dark) { + :root { + --mac-bg-window: rgba(25, 25, 25, 0.45); + --mac-bg-sidebar: rgba(18, 18, 18, 0.55); + --mac-bg-panel: rgba(30, 30, 30, 0.5); + --mac-bg-card: rgba(35, 35, 35, 0.6); + --mac-bg-input: rgba(40, 40, 40, 0.65); + --mac-bg-button: rgba(50, 50, 50, 0.7); + --mac-bg-button-hover: rgba(65, 65, 65, 0.85); + --mac-bg-button-active: rgba(45, 45, 45, 0.8); + + --mac-border-window: 1px solid rgba(255, 255, 255, 0.15); + --mac-border-highlight: 1px solid rgba(255, 255, 255, 0.12); + --mac-border-separator: rgba(255, 255, 255, 0.08); + + --mac-text-primary: #f5f5f7; + --mac-text-secondary: #98989d; + --mac-text-tertiary: #48484a; + + --mac-accent: #0a84ff; + --mac-accent-soft: rgba(10, 132, 255, 0.2); + --mac-success: #30d158; + --mac-success-soft: rgba(48, 209, 88, 0.2); + --mac-warning: #ffd60a; + --mac-danger: #ff453a; + --mac-danger-soft: rgba(255, 69, 58, 0.2); + + --mac-shadow-window: 0 30px 80px rgba(0, 0, 0, 0.4); + --mac-shadow-card: 0 4px 16px rgba(0, 0, 0, 0.2); + --mac-shadow-popover: 0 10px 40px rgba(0, 0, 0, 0.4); + } +} + +/* Palette Themes (mapped from dashboard palette selection) */ +[data-theme-palette="green"] { + --mac-accent: #34c759; + --mac-accent-soft: rgba(52, 199, 89, 0.15); +} +@media (prefers-color-scheme: dark) { + [data-theme-palette="green"] { + --mac-accent: #30d158; + --mac-accent-soft: rgba(48, 209, 88, 0.2); + } +} + +[data-theme-palette="orange"] { + --mac-accent: #ff9500; + --mac-accent-soft: rgba(255, 149, 0, 0.15); +} +@media (prefers-color-scheme: dark) { + [data-theme-palette="orange"] { + --mac-accent: #ff9f0a; + --mac-accent-soft: rgba(255, 159, 10, 0.2); + } +} + +[data-theme-palette="gray"] { + --mac-accent: #8e8e93; + --mac-accent-soft: rgba(142, 142, 147, 0.15); +} +@media (prefers-color-scheme: dark) { + [data-theme-palette="gray"] { + --mac-accent: #98989d; + --mac-accent-soft: rgba(152, 152, 157, 0.2); + } +} + +[data-theme-palette="white"] { + --mac-accent: #8e8e93; + --mac-accent-soft: rgba(142, 142, 147, 0.1); +} + +[data-theme-palette="dark"] { + --mac-accent: #64d2ff; + --mac-accent-soft: rgba(100, 210, 255, 0.2); +} + +/* Base resets & layouts */ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, body, #app { + background: transparent !important; + background-color: transparent !important; +} + +body { + font-family: var(--font-sans); + color: var(--mac-text-primary); + overflow: hidden; + height: 100vh; + user-select: none; + -webkit-font-smoothing: antialiased; +} + +/* App Container Layout */ +#app-root { + display: flex; + flex-direction: column; + height: 100vh; + width: 100vw; + background-color: var(--mac-bg-window); + backdrop-filter: blur(var(--mac-blur)) saturate(var(--mac-saturate)); + -webkit-backdrop-filter: blur(var(--mac-blur)) saturate(var(--mac-saturate)); + border: var(--mac-border-window); + border-radius: var(--radius-window); + box-shadow: var(--mac-shadow-window); + overflow: hidden; + position: relative; +} + +/* Custom Drag Titlebar Region */ +.window-titlebar { + height: 52px; + min-height: 52px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 16px; + border-bottom: 1px solid var(--mac-border-separator); + background-color: rgba(0, 0, 0, 0.01); + position: relative; + z-index: 100; +} + +/* Drag area helper class */ +.drag-region { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: -1; +} + +/* Standard Header Title */ +.titlebar-center { + font-weight: 600; + font-size: 14px; + letter-spacing: -0.1px; + color: var(--mac-text-primary); + pointer-events: none; +} + +/* Window Control Lights - Shifted right when decorations: false is NOT used, but since we use titleBarStyle: "Transparent" on macOS, Tauri draws the native lights. We just leave space on the top-left */ +.titlebar-left-placeholder { + width: 80px; + height: 100%; + pointer-events: none; +} + +/* Right Header actions */ +.titlebar-right { + display: flex; + align-items: center; + gap: 8px; +} + +/* Main Application Area (Sidebar + Content Viewport) */ +.app-workspace { + display: flex; + flex: 1; + overflow: hidden; + position: relative; +} + +/* Sidebar Styling */ +.app-sidebar { + width: 220px; + background-color: var(--mac-bg-sidebar); + border-right: 1px solid var(--mac-border-separator); + display: flex; + flex-direction: column; + padding: 16px 8px; + overflow-y: auto; + gap: 4px; +} + +/* Main Content Page Viewport */ +.app-viewport { + flex: 1; + background-color: transparent; + overflow-y: auto; + padding: 24px; + position: relative; +} + +/* Custom Scrollbar to mimic macOS look */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} +::-webkit-scrollbar-track { + background: transparent; +} +::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.15); + border-radius: 9999px; + border: 2px solid transparent; + background-clip: padding-box; +} +::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.35); + border: 2px solid transparent; + background-clip: padding-box; +} +@media (prefers-color-scheme: dark) { + ::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.15); + } + ::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.35); + } +} + +/* CSS Utilities & Standard Elements */ +h1, h2, h3, h4 { + font-family: var(--font-sans); + color: var(--mac-text-primary); + font-weight: 600; +} + +h1 { + font-size: 24px; + letter-spacing: -0.5px; + margin-bottom: 4px; +} + +.subtitle-text { + font-size: 13px; + color: var(--mac-text-secondary); + margin-bottom: 24px; +} + +/* Glassmorphic Panel/Card */ +.mac-card { + background-color: var(--mac-bg-card); + border: var(--mac-border-highlight); + border-radius: var(--radius-card); + padding: 16px; + box-shadow: var(--mac-shadow-card); + margin-bottom: 16px; + transition: transform var(--transition-fast), box-shadow var(--transition-fast), background-color var(--transition-normal); +} + +.mac-card.interactive:hover { + transform: translateY(-1px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.06); + background-color: rgba(255, 255, 255, 0.7); +} +@media (prefers-color-scheme: dark) { + .mac-card.interactive:hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + background-color: rgba(45, 45, 45, 0.7); + } +} + +/* Premium Inputs */ +.mac-input { + width: 100%; + font-family: var(--font-sans); + font-size: 13px; + color: var(--mac-text-primary); + background-color: var(--mac-bg-input); + border: var(--mac-border-highlight); + border-radius: var(--radius-input); + padding: 8px 12px; + outline: none; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.05); + transition: border-color var(--transition-fast), box-shadow var(--transition-fast); +} + +.mac-input:focus { + border-color: var(--mac-accent); + box-shadow: 0 0 0 3px var(--mac-accent-soft); +} + +/* Premium Buttons */ +.mac-btn { + font-family: var(--font-sans); + font-size: 13px; + font-weight: 500; + color: var(--mac-text-primary); + background-color: var(--mac-bg-button); + border: var(--mac-border-highlight); + border-radius: var(--radius-button); + padding: 6px 12px; + cursor: pointer; + outline: none; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 6px; + transition: all var(--transition-fast); +} + +.mac-btn:hover { + background-color: var(--mac-bg-button-hover); + border-color: rgba(0, 0, 0, 0.15); +} +.mac-btn:active { + background-color: var(--mac-bg-button-active); +} + +.mac-btn.primary { + background-color: var(--mac-accent); + color: #ffffff; + border-color: transparent; +} +.mac-btn.primary:hover { + filter: brightness(1.08); +} +.mac-btn.primary:active { + filter: brightness(0.92); +} + +.mac-btn.danger { + background-color: var(--mac-danger-soft); + color: var(--mac-text-danger); + border-color: transparent; +} +.mac-btn.danger:hover { + background-color: var(--mac-danger); + color: #ffffff; +} + +/* Labels and forms */ +.form-group { + margin-bottom: 16px; +} +.form-label { + display: block; + font-size: 12px; + font-weight: 600; + color: var(--mac-text-primary); + margin-bottom: 6px; +} +.form-label.required::after { + content: " *"; + color: var(--mac-text-danger); +} +.field-hint { + font-size: 11px; + color: var(--mac-text-secondary); + margin-top: 4px; + line-height: 1.4; +} + +/* Custom toggle switch */ +.mac-switch { + display: inline-flex; + align-items: center; + cursor: pointer; +} +.mac-switch input { + display: none; +} +.mac-switch-slider { + width: 36px; + height: 20px; + background-color: rgba(120, 120, 128, 0.16); + border-radius: 9999px; + position: relative; + transition: background-color var(--transition-normal); + margin-right: 8px; +} +.mac-switch-slider::before { + content: ""; + position: absolute; + width: 16px; + height: 16px; + border-radius: 50%; + background-color: #ffffff; + top: 2px; + left: 2px; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); + transition: transform var(--transition-normal); +} +.mac-switch input:checked + .mac-switch-slider { + background-color: var(--mac-success); +} +.mac-switch input:checked + .mac-switch-slider::before { + transform: translateX(16px); +} + +/* Overlay & Modals */ +.mac-overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background-color: rgba(0, 0, 0, 0.25); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} +@media (prefers-color-scheme: dark) { + .mac-overlay { + background-color: rgba(0, 0, 0, 0.45); + } +} + +.mac-modal { + width: 460px; + max-width: 90vw; + background-color: var(--mac-bg-window); + backdrop-filter: blur(30px) saturate(190%); + -webkit-backdrop-filter: blur(30px) saturate(190%); + border: var(--mac-border-window); + border-radius: 12px; + box-shadow: var(--mac-shadow-popover); + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; + transform: scale(0.97); + opacity: 0; + animation: modalEnter 0.22s cubic-bezier(0.16, 1, 0.3, 1) forwards; +} + +@keyframes modalEnter { + to { + transform: scale(1); + opacity: 1; + } +} diff --git a/frontend/src/assets/hero.png b/frontend/src/assets/hero.png new file mode 100644 index 00000000..02251f4b Binary files /dev/null and b/frontend/src/assets/hero.png differ diff --git a/frontend/src/assets/svelte.svg b/frontend/src/assets/svelte.svg new file mode 100644 index 00000000..c5e08481 --- /dev/null +++ b/frontend/src/assets/svelte.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/vite.svg b/frontend/src/assets/vite.svg new file mode 100644 index 00000000..5101b674 --- /dev/null +++ b/frontend/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/frontend/src/components/Sidebar.svelte b/frontend/src/components/Sidebar.svelte new file mode 100644 index 00000000..bdd5271a --- /dev/null +++ b/frontend/src/components/Sidebar.svelte @@ -0,0 +1,124 @@ + + + + + diff --git a/frontend/src/components/Titlebar.svelte b/frontend/src/components/Titlebar.svelte new file mode 100644 index 00000000..048b67d0 --- /dev/null +++ b/frontend/src/components/Titlebar.svelte @@ -0,0 +1,84 @@ + + +
+ +
+ + +
+ {$t('nav.' + $activeTab, { defaultValue: 'Codex App Transfer' })} +
+ + +
+ + + + + + + + + + + +
+
+ + diff --git a/frontend/src/lib/Counter.svelte b/frontend/src/lib/Counter.svelte new file mode 100644 index 00000000..5f046bd0 --- /dev/null +++ b/frontend/src/lib/Counter.svelte @@ -0,0 +1,10 @@ + + + diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts new file mode 100644 index 00000000..06cb52a5 --- /dev/null +++ b/frontend/src/lib/api.ts @@ -0,0 +1,527 @@ +const BASE = ''; + +async function api(method: string, path: string, body?: any): Promise { + const opts: RequestInit = { method, headers: { 'X-CAS-Request': '1' } }; + if (body !== undefined) { + (opts.headers as Record)['Content-Type'] = 'application/json'; + opts.body = JSON.stringify(body); + } + const resp = await fetch(BASE + path, opts); + const data = await resp.json(); + if (!resp.ok || data.success === false) { + const baseMessage = data.message || `Request failed: ${method} ${path}`; + const error = new Error(baseMessage) as any; + error.errors = Array.isArray(data.errors) ? data.errors : []; + error.responseData = data; + throw error; + } + return data; +} + +// ── Types ── +export interface Provider { + id?: string; + name: string; + baseUrl: string; + apiFormat: string; + authScheme: string; + hasApiKey?: boolean; + apiKey?: string; + extraHeaders?: Record; + modelCapabilities?: Record; + requestOptions?: Record; + default?: boolean; + isBuiltin?: boolean; + icon?: string; + logo?: string; + mappings?: Record; + models?: Record; + supportsWebSearch?: boolean; + grokWeb?: { + sso?: string; + cookieString?: string; + cfClearance?: string; + ssoRw?: string; + statsigId?: string; + userAgent?: string; + }; +} + +export interface AppStatus { + desktopConfigured: boolean; + proxyRunning: boolean; + proxyPort: number; + activeProvider: { name: string; id: string | null }; + activeProviderId: string | null; + desktopHealth: { needsApply: boolean; issues: string[] }; + exposeAllProviderModels: boolean; +} + +export interface Preset { + id: string; + name: string; + baseUrl: string; + apiFormat: string; + authScheme: string; + models: Record; + modelOptions: Record; + baseUrlOptions: string[]; + baseUrlHint: string; + requestOptionPresets: Record; + extraHeaders: Record; + modelCapabilities: Record; + requestOptions: Record; + supportsWebSearch: boolean; + icon?: string; + logo?: string; +} + +export interface DesktopStatus { + configured: boolean; + health: { needsApply: boolean; issues: string[] }; + config: { + inferenceProvider: string; + inferenceGatewayBaseUrl: string; + inferenceGatewayApiKey: string; + inferenceGatewayAuthScheme: string; + inferenceModels: string; + }; +} + +export interface ProxyStatus { + running: boolean; + port: number; + stats: { total: number; success: number; failed: number; today: number }; +} + +export interface ProxyLog { + at: string; + level: string; + message: string; +} + +export interface Activity { + time: string; + text: string; +} + +// ── Icon Mapping ── +const ICON_MAP: Record = { + deepseek: { logo: 'assets/providers/deepseek.ico' }, + kimi: { logo: 'assets/providers/kimi.ico' }, + moonshot: { logo: 'assets/providers/kimi.ico' }, + xiaomi: { logo: 'assets/providers/xiaomi-mimo.png' }, + mimo: { logo: 'assets/providers/xiaomi-mimo.png' }, + qiniu: { logo: 'assets/providers/qiniu.ico' }, + qnaigc: { logo: 'assets/providers/qiniu.ico' }, + zhipu: { logo: 'assets/providers/zhipu.png' }, + bigmodel: { logo: 'assets/providers/zhipu.png' }, + glm: { logo: 'assets/providers/zhipu.png' }, + siliconflow: { icon: 'bi-diagram-3-fill' }, + bailian: { logo: 'assets/providers/aliyun.ico' }, + dashscope: { logo: 'assets/providers/aliyun.ico' }, + aliyun: { logo: 'assets/providers/aliyun.ico' }, + minimax: { logo: 'assets/providers/minimax.ico' }, + minimaxi: { logo: 'assets/providers/minimax.ico' }, + 'gemini-cli': { logo: 'assets/providers/gemini.svg' }, + 'antigravity-oauth': { logo: 'assets/providers/antigravity.png' }, + google: { logo: 'assets/providers/google-ai-studio.png' }, + gemini: { logo: 'assets/providers/google-ai-studio.png' }, + aistudio: { logo: 'assets/providers/google-ai-studio.png' }, + generativelanguage: { logo: 'assets/providers/google-ai-studio.png' }, + 'grok-web': { logo: 'assets/providers/grok.svg' }, + anyrouter: { logo: 'assets/providers/anyrouter.png' }, +}; + +function buildCustomThirdPartyPreset(): Preset { + return { + id: 'custom-third-party', + name: '自定义第三方', + baseUrl: '', + apiFormat: 'OpenAI', + authScheme: 'bearer', + models: {}, + modelOptions: {}, + baseUrlOptions: [], + baseUrlHint: '', + requestOptionPresets: {}, + extraHeaders: {}, + modelCapabilities: {}, + requestOptions: {}, + icon: 'bi-puzzle', + supportsWebSearch: true, + }; +} + +function computeIcon(provider: { id?: string; name: string; baseUrl: string; apiFormat: string }) { + const raw = `${provider.id || ''} ${provider.name || ''} ${provider.baseUrl || ''} ${provider.apiFormat || ''}`.toLowerCase(); + const lookup = raw.replace(/[_\s]+/g, '-'); + for (const [key, val] of Object.entries(ICON_MAP)) { + if (lookup.includes(key)) return val; + } + return { icon: 'bi-plug-fill' }; +} + +function mapProvider(provider: any, activeId: string | null): Provider { + const models = provider.models || {}; + return { + id: provider.id, + name: provider.name, + baseUrl: provider.baseUrl, + apiFormat: ['openai', 'openai_chat'].includes(provider.apiFormat) ? 'openai_chat' : (provider.apiFormat || 'openai_chat'), + authScheme: provider.authScheme || 'bearer', + hasApiKey: !!provider.hasApiKey, + extraHeaders: provider.extraHeaders || {}, + modelCapabilities: provider.modelCapabilities || {}, + requestOptions: provider.requestOptions || {}, + default: provider.id === activeId, + isBuiltin: !!provider.isBuiltin, + mappings: { + default: models.default || '', + gpt_5_5: models.gpt_5_5 || '', + gpt_5_4: models.gpt_5_4 || '', + gpt_5_4_mini: models.gpt_5_4_mini || '', + gpt_5_3_codex: models.gpt_5_3_codex || '', + gpt_5_2: models.gpt_5_2 || '', + }, + ...computeIcon(provider), + }; +} + +function providerBody(payload: Provider, includeModels = true) { + const body: any = { + name: payload.name, + baseUrl: payload.baseUrl, + authScheme: payload.authScheme || 'bearer', + apiFormat: (() => { + const v = (payload.apiFormat || '').toLowerCase().replace(/-/g, '_'); + if (['responses', 'openai_responses'].includes(v)) return 'responses'; + if (['anthropic_messages', 'anthropic', 'claude', 'messages', 'claude_messages'].includes(v)) return 'anthropic_messages'; + if (['gemini_native', 'google_ai_studio', 'gemini'].includes(v)) return 'gemini_native'; + if (['gemini_cli_oauth', 'gemini_oauth', 'google_oauth_cloud_code'].includes(v)) return 'gemini_cli_oauth'; + if (['antigravity_oauth', 'google_oauth_antigravity'].includes(v)) return 'antigravity_oauth'; + if (['grok_web', 'grok', 'grok_com'].includes(v)) return 'grok_web'; + return 'openai_chat'; + })(), + extraHeaders: payload.extraHeaders || {}, + modelCapabilities: payload.modelCapabilities || {}, + requestOptions: payload.requestOptions || {}, + }; + if (payload.apiKey) { + body.apiKey = payload.apiKey; + } + if (includeModels) { + body.models = payload.models || {}; + } + if (payload.grokWeb) { + body.grokWeb = payload.grokWeb; + } + return body; +} + +function mapLog(log: any): ProxyLog { + return { + at: log.time, + level: log.level.toLowerCase(), + message: log.message, + }; +} + +// ── Public API Export ── +export const CCApi = { + async getStatus(): Promise { + const data = await api('GET', '/api/status'); + const active = data.activeProvider; + return { + desktopConfigured: !!data.desktopConfigured, + proxyRunning: !!data.proxyRunning, + proxyPort: data.proxyPort || 18080, + activeProvider: active ? { name: active.name, id: active.id } : { name: '-', id: null }, + activeProviderId: data.activeProviderId, + desktopHealth: data.desktopHealth || { needsApply: false, issues: [] }, + exposeAllProviderModels: !!data.exposeAllProviderModels, + }; + }, + + async getProviders(): Promise { + const data = await api('GET', '/api/providers'); + return (data.providers || []).map((p: any) => mapProvider(p, data.activeId)); + }, + + async getProviderSecret(id: string): Promise { + return api('GET', `/api/providers/${encodeURIComponent(id)}/secret`); + }, + + async getPresets(): Promise { + const data = await api('GET', '/api/presets'); + const builtin = (data.presets || []).map((p: any) => ({ + id: p.id, + name: p.name, + baseUrl: p.baseUrl, + apiFormat: p.apiFormat || 'openai_chat', + authScheme: p.authScheme || 'bearer', + models: p.models || {}, + modelOptions: p.modelOptions || {}, + baseUrlOptions: p.baseUrlOptions || [], + baseUrlHint: p.baseUrlHint || '', + requestOptionPresets: p.requestOptionPresets || {}, + extraHeaders: p.extraHeaders || {}, + modelCapabilities: p.modelCapabilities || {}, + requestOptions: p.requestOptions || {}, + supportsWebSearch: !!p.supportsWebSearch, + ...computeIcon(p), + })); + return [...builtin, buildCustomThirdPartyPreset()]; + }, + + async addProvider(payload: Provider): Promise { + const data = await api('POST', '/api/providers', providerBody(payload)); + return data.provider || data; + }, + + async updateProvider(id: string, payload: Provider): Promise { + const data = await api('PUT', `/api/providers/${encodeURIComponent(id)}`, providerBody(payload)); + return data.provider || data; + }, + + async deleteProvider(id: string): Promise { + return api('DELETE', `/api/providers/${encodeURIComponent(id)}`); + }, + + async setDefaultProvider(id: string): Promise { + return api('PUT', `/api/providers/${encodeURIComponent(id)}/default`); + }, + + async saveDraft(id: string, payload: Provider): Promise { + return api('POST', `/api/providers/${encodeURIComponent(id)}/draft`, providerBody(payload, true)); + }, + + async activateProvider(id: string): Promise { + return api('POST', `/api/providers/${encodeURIComponent(id)}/activate`); + }, + + async reorderProviders(providerIds: string[]): Promise { + return api('PUT', '/api/providers/reorder', { providerIds }); + }, + + async testProvider(id: string): Promise { + return api('POST', `/api/providers/${encodeURIComponent(id)}/test`); + }, + + async queryProviderUsage(id: string): Promise { + return api('POST', `/api/providers/${encodeURIComponent(id)}/usage`); + }, + + async getProviderCompatibility(): Promise { + return api('GET', '/api/providers/compatibility'); + }, + + async testProviderPayload(payload: Provider): Promise { + return api('POST', '/api/providers/test', providerBody(payload, true)); + }, + + async saveModelMappings(id: string, mappings: Record): Promise { + return api('PUT', `/api/providers/${encodeURIComponent(id)}/models`, { models: mappings }); + }, + + async fetchProviderModels(id: string): Promise { + return api('GET', `/api/providers/${encodeURIComponent(id)}/models/available`); + }, + + async fetchProviderModelsPayload(payload: Provider): Promise { + return api('POST', '/api/providers/models/available', providerBody(payload, false)); + }, + + async autofillProviderModels(id: string): Promise { + return api('POST', `/api/providers/${encodeURIComponent(id)}/models/autofill`); + }, + + async getDesktopStatus(): Promise { + const data = await api('GET', '/api/desktop/status'); + const status = await api('GET', '/api/status'); + const proxyPort = status.proxyPort || 18080; + const registryConfig = data.keys || {}; + return { + configured: !!data.configured, + health: data.health || { needsApply: false, issues: [] }, + config: { + inferenceProvider: registryConfig.inferenceProvider || 'gateway', + inferenceGatewayBaseUrl: registryConfig.inferenceGatewayBaseUrl || `http://127.0.0.1:${proxyPort}`, + inferenceGatewayApiKey: registryConfig.inferenceGatewayApiKey ? '******' : '', + inferenceGatewayAuthScheme: registryConfig.inferenceGatewayAuthScheme || 'bearer', + inferenceModels: registryConfig.inferenceModels || '[]', + }, + }; + }, + + async configureDesktop(): Promise { + return api('POST', '/api/desktop/configure'); + }, + + async clearDesktop(): Promise { + return api('POST', '/api/desktop/clear'); + }, + + async getDesktopSnapshots(): Promise { + const data = await api('GET', '/api/desktop/snapshots'); + return data.snapshots || []; + }, + + async restoreDesktopSnapshot(snapshotId: string): Promise { + return api('POST', '/api/desktop/restore', { + snapshotId, + cleanupAll: true, + }); + }, + + async startProxy(port?: number): Promise { + if (port) { + await this.saveSettings({ proxyPort: Number(port) }); + } + await api('POST', '/api/proxy/start', port ? { port: Number(port) } : undefined); + const status = await api('GET', '/api/status'); + return { + running: !!status.proxyRunning, + port: status.proxyPort || port || 18080, + stats: { total: 0, success: 0, failed: 0, today: 0 } // Default stats structure + }; + }, + + async stopProxy(): Promise { + await api('POST', '/api/proxy/stop'); + const status = await api('GET', '/api/status'); + return { + running: !!status.proxyRunning, + port: status.proxyPort || 18080, + }; + }, + + async getProxyLogs(): Promise { + const data = await api('GET', '/api/proxy/logs'); + return (data.logs || []).map(mapLog); + }, + + async getProxyStatus(): Promise { + const data = await api('GET', '/api/proxy/status'); + return { + running: !!data.running, + port: data.port || 18080, + stats: data.stats || { total: 0, success: 0, failed: 0, today: 0 }, + }; + }, + + async clearLogs(): Promise { + return api('POST', '/api/proxy/logs/clear'); + }, + + async openLogDir(): Promise { + return api('POST', '/api/proxy/logs/open-dir'); + }, + + async getSettings(): Promise { + return api('GET', '/api/settings'); + }, + + async getVersion(): Promise { + return api('GET', '/api/version'); + }, + + async saveSettings(settings: any): Promise { + const data = await api('PUT', '/api/settings', settings); + return data.settings || data; + }, + + async checkUpdate(updateUrl?: string): Promise { + const params = new URLSearchParams(); + if (updateUrl) params.set('url', updateUrl); + return api('GET', `/api/update/check?${params.toString()}`); + }, + + async installUpdate(updateUrl?: string): Promise { + return api('POST', '/api/update/install', updateUrl ? { url: updateUrl } : {}); + }, + + async createBackup(): Promise { + return api('POST', '/api/config/backup'); + }, + + async listBackups(): Promise { + const data = await api('GET', '/api/config/backups'); + return data.backups || []; + }, + + async exportConfig(): Promise { + return api('GET', '/api/config/export'); + }, + + async importConfig(configData: any): Promise { + return api('POST', '/api/config/import', configData); + }, + + async getDesktopSnapshotStatus(): Promise { + return api('GET', '/api/desktop/snapshot-status'); + }, + + async restartCodexApp(): Promise { + return api('POST', '/api/desktop/restart-codex-app'); + }, + + async submitFeedback(payload: any): Promise { + return api('POST', '/api/feedback', payload); + }, + + async getActivities(): Promise { + const data = await api('GET', '/api/proxy/logs'); + const logs = data.logs || []; + return logs.slice(-5).reverse().map((log: any) => ({ + time: log.time, + text: log.message, + })); + }, + + // ── Gemini CLI OAuth ── + async getGeminiOauthStatus(): Promise { + return api('GET', '/api/gemini-oauth/status'); + }, + + async loginGeminiOauth(): Promise { + return api('POST', '/api/gemini-oauth/login', {}); + }, + + async logoutGeminiOauth(): Promise { + return api('DELETE', '/api/gemini-oauth/logout'); + }, + + // ── Antigravity OAuth ── + async getAntigravityOauthStatus(): Promise { + return api('GET', '/api/antigravity-oauth/status'); + }, + + async loginAntigravityOauth(): Promise { + return api('POST', '/api/antigravity-oauth/login', {}); + }, + + async logoutAntigravityOauth(): Promise { + return api('DELETE', '/api/antigravity-oauth/logout'); + }, + + async getAntigravityOauthModels(): Promise { + return api('GET', '/api/antigravity-oauth/models'); + }, +}; + +// ── Plugin Unlock API ── +export const PluginUnlockApi = { + async status(): Promise { + return api('GET', '/api/desktop/plugin-unlock/status'); + }, + async start(): Promise { + return api('POST', '/api/desktop/plugin-unlock/start'); + }, + async stop(): Promise { + return api('POST', '/api/desktop/plugin-unlock/stop'); + }, + async reinject(): Promise { + return api('POST', '/api/desktop/plugin-unlock/reinject'); + }, +}; diff --git a/frontend/src/lib/i18n.ts b/frontend/src/lib/i18n.ts new file mode 100644 index 00000000..5445de53 --- /dev/null +++ b/frontend/src/lib/i18n.ts @@ -0,0 +1,1065 @@ +import { writable, derived } from 'svelte/store'; + +const dictionaries = { + zh: { + "nav.dashboard": "仪表盘", + "nav.providers": "提供商", + "nav.models": "模型映射", + "nav.desktop": "桌面版", + "nav.proxy": "转发", + "nav.settings": "设置", + "nav.codex": "Codex 文档", + "nav.guide": "引导", + "codex.title": "Codex 文档管理", + "codex.subtitle": "", + "codex.assetType": "资产类型", + "codex.tabAgents": "Agents", + "codex.tabMcp": "MCP", + "codex.tabMemories": "Memories", + "codex.tabSkills": "Skills", + "codex.agentsPath.global": "全局", + "codex.agentsPath.projectRoot": "项目根目录", + "codex.agentsPath.subdir": "子目录", + "codex.agentsPathEmpty": "未检测到 AGENTS.md,请通过右侧\"添加\"按钮指定", + "codex.agentsPathAdd": "添加", + "codex.agentsPathAddTitle": "添加自定义 AGENTS.md 路径", + "codex.agentsPathAddPrompt": "点击\"浏览\"打开系统文件选择,或直接粘贴 AGENTS.md 的绝对路径(可同时使用)", + "codex.agentsPathBrowse": "浏览", + "codex.agentsPathAddOk": "已添加自定义 AGENTS.md 路径", + "codex.agentsPathAddOkBtn": "添加", + "codex.agentsPathAddEmpty": "请粘贴路径", + "codex.agentsPathRemoveConfirm": "确认从下拉列表删除当前路径?(只移除引用,不删文件)", + "codex.agentsPathRemoveOk": "已删除路径引用", + "codex.agentsWarn": "AGENTS.md 会严重影响 AI 的行为,请谨慎修改", + "codex.memoriesPathEmpty": "未添加项目 MEMORY.md,请通过右侧\"添加\"按钮指定", + "codex.memoriesWarn": "MEMORY.md 跟 memory_summary.md 是 AI 长期记忆 user-editable 索引,请谨慎修改", + "codex.memoriesLoading": "加载 codex 全局 memories…", + "codex.memoriesPath.index": "主索引", + "codex.memoriesPath.summary": "摘要", + "codex.mcp.servers": "Servers", + "codex.mcp.plugins": "Plugins", + "codex.mcp.marketplace": "Marketplace", + "codex.mcp.serversWarn": "修改 MCP servers 会改 ~/.codex/config.toml 全局生效", + "codex.mcp.pluginsWarn": "Plugin 来自 marketplace 安装到 ~/.codex/plugins/cache/,卸载将删除整个 cache 目录", + "codex.mcp.serversEmpty": "尚未配置 MCP server,点底部新增", + "codex.mcp.pluginsEmpty": "尚未安装 plugin,去 Marketplace 安装", + "codex.mcp.marketEmpty": "无匹配条目,试着切源 / 搜索 / 刷新", + "codex.mcp.formEmpty": "选 server 编辑,或新增一个", + "codex.mcp.formName": "Server ID(TOML key,如 vercel)", + "codex.mcp.serverNew": "新增 server", + "codex.mcp.rawToml": "Edit raw TOML", + "codex.mcp.rawWarn": "Raw 模式直改 ~/.codex/config.toml,parse 失败拒绝;通过校验后 atomic 写盘,pre-backup", + "codex.mcp.saveOk": "已保存", + "codex.mcp.installServerOk": "已添加到 Servers", + "codex.mcp.installPluginOk": "Plugin 安装成功", + "codex.mcp.uninstallOk": "Plugin 已卸载", + "codex.mcp.pluginEnabled": "Enabled", + "codex.mcp.pluginDisabled": "Disabled", + "codex.mcp.refresh": "刷新", + "codex.mcp.sourceAdd": "添加源", + "codex.mcp.sourceAddTitle": "添加 marketplace 源", + "codex.mcp.sourceAddPrompt": "输入 https registry.json 的完整 URL,跟一个友好名字", + "codex.mcp.sourceAddConfirm": "添加", + "codex.mcp.serverPresets": "Server 预设(一键加进 Servers tab)", + "codex.mcp.pluginBundles": "Plugin 安装包(一键安装)", + "codex.mcp.searchPlaceholder": "搜索 server / plugin", + "codex.mcp.deeplinkConfirmTitle": "确认导入(外部链接)", + "codex.mcp.deeplinkConfirmDesc": "外部 URL 请求导入以下条目,请确认后再继续", + "codex.mcp.deeplinkConfirmYes": "确认导入", + "codex.mcp.deeplinkInstallOk": "Deeplink 导入完成", + "codex.skillsLoading": "扫描 ~/.codex/skills/…", + "codex.skillsEmpty": "未检测到 skill,~/.codex/skills/ 为空", + "codex.skillsReveal": "打开文件夹", + "codex.skillsWarn": "SKILL.md 决定 AI 是否找到 / 用对工具,请谨慎修改", + "codex.agentsEdit": "Edit", + "codex.agentsBackup": "Backup", + "codex.agentsCancel": "Cancel", + "codex.agentsApplyOk": "已保存 AGENTS.md(旧版本已备份到 History)", + "codex.agentsBackupOk": "已备份当前 AGENTS.md 到 History", + "codex.agentsRestoreConfirm": "确认还原此版本?当前 AGENTS.md 会先备份到 History", + "codex.agentsRestoreOk": "已还原 AGENTS.md", + "codex.historyEmpty": "暂无 History snapshot", + "codex.historyApply": "应用", + "codex.historyWarn": "应用后当前 AGENTS.md 会被替换,旧版本会自动加入 History", + "codex.historyDiffEmpty": "选择 History 条目查看 diff", + "codex.skillsList": "已安装 skill", + "codex.skillsBackup": "Backup now", + "codex.skillsRefresh": "Refresh", + "codex.skillsBackups": "已有备份(tar.gz, 最新在上)", + "codex.statusBlockState": "受管块状态", + "codex.statusManaged": "已注入", + "codex.statusEmpty": "未注入", + "codex.statusUserBytes": "用户区(marker 外)", + "codex.statusHistoryCount": "历史快照", + "codex.statusHistoryCountSuffix": "条(上限 10)", + "codex.statusLastApply": "上次 apply", + "codex.statusTargetFile": "目标文件", + "codex.statusNone": "无", + "codex.statusBytesSuffix": "bytes", + "codex.confirmClear": "确认 Clear 受管块?会删除 marker + 内容(进 history 可 rollback)", + "codex.confirmRollback": "确认 Rollback {type} 到 history[{idx}]?", + "codex.confirmSkillsRestore": "确认从 {filename} 还原 skills/?会覆盖当前 ~/.codex/skills 内容!", + "codex.toastApplied": "{type} 受管块已 apply", + "codex.toastCleared": "{type} 受管块已 clear(进 history)", + "codex.toastRollbacked": "已 rollback {type} 到 history[{idx}]", + "codex.toastSkillsBackedUp": "备份完成: {name}", + "codex.toastSkillsRestored": "已从 {filename} 还原", + "codex.historyEmpty": "暂无 history snapshot", + "codex.skillsListEmpty": "暂无 skill", + "codex.backupsListEmpty": "暂无备份(点 Backup now 创建)", + "codex.skillsHasSkillMd": "✓ SKILL.md", + "codex.skillsNoSkillMd": "✗ no SKILL.md", + "codex.skillsFilesSuffix": "files", + "codex.skillsDirLabel": "Skills 目录", + "codex.skillsListLabel": "已安装 skill", + "codex.skillsBackupDirLabel": "备份目录", + "codex.skillsInstalledLabel": "已安装 skill", + "codex.skillsBackupsLabel": "已有备份", + "codex.skillsCountSuffix": "个", + "codex.skillsBackupsCountSuffix": "份", + "codex.statusLoading": "状态加载中…", + "codex.managedContent": "受管块内容(start/end marker 之间的内容)", + "codex.placeholder": "贴入 app 受管 AGENTS.md 段(留空 = Apply 创建空块或 Preview 看 marker 注入位置)", + "codex.preview": "Preview", + "codex.apply": "Apply", + "codex.history": "History", + "codex.clear": "Clear", + "codex.historyTitle": "History(最多 10 条 snapshot)", + "dashboard.title": "提供商", + "dashboard.subtitle": "选择一个提供商,启动转发后给 Codex CLI 使用。", + "dashboard.desktopStatus": "Codex CLI 状态", + "dashboard.proxyStatus": "转发状态", + "dashboard.activeProvider": "当前提供商", + "dashboard.configureDesktop": "添加并生成配置", + "dashboard.switchProvider": "切换提供商", + "dashboard.clearDesktopConfig": "还原 Codex 原配置", + "dashboard.feedback": "反馈", + "settings.feedback": "反馈与建议", + "settings.feedbackOpen": "打开反馈窗口", + "settings.feedbackHint": "遇到问题或想提建议?支持文本 + 截图 + 日志,匿名提交,无需登录。诊断信息默认自动附上:应用版本、环境信息、脱敏配置、最近错误快照(完整请求/响应)。不含 API Key 等敏感数据。", + "feedback.title": "反馈与建议", + "feedback.intro": "欢迎反馈使用问题或改进建议。提交内容会发送到开发者维护的私有存储,不会公开。", + "feedback.titleLabel": "标题(选填)", + "feedback.titlePlaceholder": "一句话概括问题", + "feedback.contactEmailLabel": "联系邮箱(选填)", + "feedback.contactEmailPlaceholder": "name@example.com", + "feedback.contactEmailHint": "选填。填写后作者可通过该邮箱联系你反馈处理进展。", + "feedback.bodyLabel": "详细描述 *", + "feedback.bodyPlaceholder": "请尽量描述清楚:发生了什么,你期望的是什么,如何复现", + "feedback.attachmentsLabel": "附件(选填,可多个,单文件最大 5MB)", + "feedback.attachmentsHint": "点击选择文件 / 直接拖拽到此处 / Cmd+V 粘贴截图", + "feedback.includeDiagnostics": "附加诊断信息(默认开启,推荐)", + "feedback.includeDiagnosticsHint": "自动附加应用版本、环境信息、脱敏配置和最近错误快照(含完整请求/响应)。不含 API Key 等敏感数据。", + "feedback.privacyWarning": "⚠ 截图请检查是否含 API Key、个人信息等敏感内容。", + "feedback.submit": "提交反馈", + "feedback.submitting": "提交中...", + "feedback.bodyRequired": "请填写描述", + "feedback.successToast": "✓ 反馈已收到!ID: {id}", + "feedback.failToast": "提交失败:{message}", + "feedback.tooLargeFile": "文件 {name} 超过 5MB,已跳过", + "dashboard.recentActivity": "最近操作", + "dashboard.updateAvailable": "有新版本", + "dashboard.availablePresets": "继续添加提供商", + "dashboard.availablePresetsHint": "这里会一直保留还没添加的厂商,点一个就能带着预设进入添加页。", + "status.configured": "已配置", + "status.notConfigured": "未配置", + "status.needsApply": "需重新应用", + "status.running": "运行中", + "status.stopped": "已停止", + "status.default": "默认", + "status.active": "正在应用", + "status.standby": "备用", + "common.viewAll": "查看全部", + "common.save": "保存", + "common.cancel": "取消", + "common.reset": "重置", + "common.delete": "删除", + "common.edit": "编辑", + "common.copy": "复制", + "common.saveOnly": "仅保存", + "providersAdd.title": "添加提供商", + "providersAdd.editTitle": "编辑提供商", + "providersAdd.subtitle": "添加新的 API 提供商或选择预设", + "providersAdd.presets": "快捷预设", + "providersAdd.presetsHint": "选择后会自动填入 API 地址和推荐模型,API Key 仍由你自己填写。", + "providersAdd.mappingTitle": "模型映射", + "providersAdd.mappingSubtitle": "左侧选择 OpenAI 模型,右侧填写或选择这个供应商真实支持的模型。未单独配置的模型会回退到 Default。", + "providersAdd.claudeModel": "OpenAI 模型", + "providersAdd.providerModel": "用户 API 所含模型", + "providersAdd.providerModelPlaceholder": "输入或选择模型", + "providersAdd.addMapping": "添加", + "providersAdd.removeMapping": "移除", + "providersAdd.customModelPlaceholder": "输入客户端发送的模型名", + "providersAdd.optionApplied": "已切换模型配置", + "providersAdd.applyToDesktop": "一键生成 Codex CLI 配置", + "providersAdd.applyHint": "一键应用会保存供应商和模型映射,把它设为默认,并让 Codex CLI 连接到本工具。之后你在 Codex CLI 里发消息,本工具会自动转发到你填写的 API 地址。", + "providersAdd.apiFormatLabel": "协议类型", + "providersAdd.webSearchEnabled": "联网搜索", + "providersAdd.webSearchEnabledToggle": "启用 web_search 工具", + "providersAdd.webSearchEnabledHint.default": "启用后代理在出站请求注入 web_search 工具,让模型联网搜索。计费 / 限制取决于具体 provider。", + "providersAdd.webSearchEnabledHint.kimi": "启用后代理把 Kimi `$web_search` builtin_function 注入出站请求。Kimi 计费策略:**每次搜索调用独立计费 $0.005**(独立于 token),搜索结果计入 prompt_tokens。", + "providersAdd.webSearchEnabledHint.kimi-code": "启用后代理把 Kimi `$web_search` builtin_function 注入出站请求(Kimi Code 跟 Kimi 同款联网搜索机制,$0.005/次独立计费)。", + "providersAdd.webSearchEnabledHint.xiaomi-mimo-payg": "启用后代理把 web_search 工具(MiMo 私有形态)注入出站请求。MiMo PAYG 走按 token 计费,联网搜索结果直接计入 prompt_tokens,无独立搜索费。", + "providersAdd.webSearchEnabledHint.xiaomi-mimo-token-plan": "启用后代理把 web_search 工具(MiMo 私有形态)注入出站请求。注意:Token Plan 套餐用户必须先在 MiMo 控制台(platform.xiaomimimo.com/console/plan-manage)打开 Web Search Plugin,否则上游会返 400(代理已实现 4xx fallback 自动降级)。", + "providersAdd.customThirdPartyName": "自定义第三方", + "providersAdd.customThirdPartyHint": "用户自填上游地址,协议类型可选", + "toast.directModeBaseUrlRequired": "Responses 透传协议必须填写 baseUrl", + "toast.directModeApiKeyRequired": "Responses 透传协议必须填写 API Key", + "apiFormatDisplay.openaiChat.name": "OpenAI Chat", + "apiFormatDisplay.openaiChat.detail": "Responses ↔ Chat 本地协议转换", + "apiFormatDisplay.responses.name": "Responses", + "apiFormatDisplay.responses.detail": "原生透传上游", + "apiFormatDisplay.anthropic.name": "Anthropic Messages", + "apiFormatDisplay.anthropic.detail": "Responses ↔ Anthropic Messages 本地协议转换", + "apiFormatDisplay.geminiNative.name": "Gemini Native", + "apiFormatDisplay.geminiNative.detail": "Responses ↔ Gemini generateContent 直转", + "apiFormatDisplay.geminiCliOauth.name": "Gemini CLI(OAuth)", + "apiFormatDisplay.geminiCliOauth.detail": "浏览器登录 Google → Cloud Code Assist 直转", + "geminiOauth.title": "Google OAuth 登录", + "geminiOauth.statusLoading": "加载中...", + "geminiOauth.statusNotLoggedIn": "未登录 — 请点下方按钮通过浏览器登录 Google 账号", + "geminiOauth.statusLoggedIn": "已登录:{email} — 项目 {projectId}(过期:{expiresAt})", + "geminiOauth.statusLoggedInNoProject": "已登录但 project 未 provision:{email}(请重新登录修复)", + "geminiOauth.loginBtn": "浏览器登录 Google 账号", + "geminiOauth.loginBtnInProgress": "等待浏览器授权(最多 5 分钟)...", + "geminiOauth.logoutBtn": "注销并删除本地凭证", + "geminiOauth.hint": "登录后浏览器窗口可关闭。Token 持久化到 ~/.codex-app-transfer/gemini-oauth.json,过期前自动刷新。", + "geminiOauth.tosWarning": "⚠️ 通过 impersonate gemini-cli 接 Google Cloud Code 内部端点,TOS 灰色,Google 可随时改协议。降级路径:用 Google AI Studio(API key)provider。", + "geminiOauth.loginSuccess": "登录成功!email: {email}, project: {projectId}", + "geminiOauth.loginFailed": "登录失败:{error}", + "geminiOauth.loginPartial": "OAuth 通过但 Cloud Code project 未 provision(token 未持久化,请重新登录)", + "geminiOauth.loginRequired": "切到 Gemini CLI(OAuth)需先登录 Google 账号 — 点表单上的「浏览器登录」按钮", + "geminiOauth.logoutConfirmed": "已注销,本地凭证已删除", + "geminiOauth.logoutFailedManual": "注销失败:{error}。Token 文件可能仍在 ~/.codex-app-transfer/gemini-oauth.json,需手动删除才能彻底登出", + "geminiOauth.statusFetchFailed": "状态加载失败:{error}", + "geminiOauth.switchAccountBtn": "切换账号(重新登录)", + "apiFormatDisplay.antigravityOauth.name": "Antigravity(OAuth)", + "apiFormatDisplay.antigravityOauth.detail": "浏览器登录 Google → Antigravity IDE 凭证 → Cloud Code Assist", + "antigravityOauth.title": "Google OAuth 登录", + "antigravityOauth.statusLoading": "加载中...", + "antigravityOauth.statusNotLoggedIn": "未登录 — 请点下方按钮通过浏览器登录 Google 账号", + "antigravityOauth.statusLoggedIn": "已登录:{email} — 项目 {projectId}(过期:{expiresAt})", + "antigravityOauth.statusLoggedInNoProject": "已登录但 project 未 provision:{email}(请重新登录修复)", + "antigravityOauth.loginBtn": "浏览器登录 Google 账号", + "antigravityOauth.loginBtnInProgress": "等待浏览器授权(最多 5 分钟)...", + "antigravityOauth.logoutBtn": "注销并删除本地凭证", + "antigravityOauth.hint": "登录后浏览器窗口可关闭。Token 持久化到 ~/.codex-app-transfer/antigravity-oauth.json,过期前自动刷新。可与 Gemini CLI(OAuth)同时登录互不影响。", + "antigravityOauth.tosWarning": "⚠️ 通过 impersonate Antigravity IDE 客户端接 Google Cloud Code 内部端点,TOS 灰色,Google 可随时改协议。降级路径:用 Google AI Studio(API key)或 Gemini CLI(OAuth)。", + "antigravityOauth.loginSuccess": "登录成功!email: {email}, project: {projectId}", + "antigravityOauth.loginFailed": "登录失败:{error}", + "antigravityOauth.loginPartial": "OAuth 通过但 Cloud Code project 未 provision(token 未持久化,请重新登录)", + "antigravityOauth.loginRequired": "切到 Antigravity(OAuth)需先登录 Google 账号 — 点表单上的「浏览器登录」按钮", + "antigravityOauth.logoutConfirmed": "已注销,本地凭证已删除", + "antigravityOauth.logoutFailedManual": "注销失败:{error}。Token 文件可能仍在 ~/.codex-app-transfer/antigravity-oauth.json,需手动删除才能彻底登出", + "antigravityOauth.statusFetchFailed": "状态加载失败:{error}", + "antigravityOauth.switchAccountBtn": "切换账号(重新登录)", + "apiFormatDisplay.grokWeb.name": "Grok Web(实验性)", + "apiFormatDisplay.grokWeb.detail": "反代 grok.com Web 后端,SuperGrok / X Premium+ cookie 鉴权", + "grokWeb.title": "Grok Web Cookie 鉴权", + "grokWeb.hint": "浏览器登录 grok.com → F12 → Application → Cookies → 复制 sso 一个值即可,其他自动处理(x-statsig-id 每次动态生成、sso-rw 复用 sso、UA 默认)。", + "grokWeb.ssoLabel": "sso(JWT,必填)", + "grokWeb.ssoRwLabel": "sso-rw(可选,默认复用 sso)", + "grokWeb.cookieStringLabel": "完整 Cookie 字符串(CF challenge 时必填)", + "grokWeb.cookieStringHint": "浏览器 DevTools → Network → grok.com 任一请求 → Request Headers → 整行 Cookie value 复制粘贴(可带 \"Cookie: \" 前缀,自动 strip)。grok.com Cloudflare 401 \"No credentials presented\" 即缺这一段。对齐 chenyme/grok2api `proxy.clearance.cf_cookies` 设计。", + "grokWeb.cfClearanceLabel": "cf_clearance(可选,单 token)", + "grokWeb.cfClearanceHint": "如已 paste 完整 Cookie 字符串无需填。单独填只发 `cf_clearance=` segment(可能不够);整段 paste 更稳。", + "grokWeb.advancedSummary": "高级选项(401 / CF challenge 时展开补充)", + "grokWeb.statsigIdLabel": "x-statsig-id(可选 override,默认动态生成)", + "grokWeb.statsigIdHint": "默认每次请求后端动态生成伪 Statsig blob(参考 chenyme/grok2api),无需手动维护;仅高级用户精确控制 statsig 内容时填写。", + "grokWeb.userAgentLabel": "User-Agent override(可选)", + "grokWeb.tosWarning": "⚠️ 反代 grok.com Web 端,grok TOS 灰色,仅限个人使用本机 SuperGrok 账号,不应作为对外服务发布。", + "grokWeb.savedPlaceholder": "已保存,留空则保持不变", + "providers.title": "提供商", + "providers.subtitle": "管理已配置的 API 提供商", + "providers.add": "添加提供商", + "providers.name": "提供商名称", + "providers.baseUrl": "API Base URL", + "providers.mapping": "模型映射", + "providers.status": "状态", + "providers.actions": "操作", + "providers.deleteTitle": "删除提供商", + "providers.deleteMessage": "确认删除这个提供商吗?此操作会更新当前配置文件。", + "providers.setDefault": "设为默认", + "providers.enable": "启用", + "providers.added": "已添加", + "providers.manageAndTest": "管理与测速", + "providers.testSpeed": "测速", + "providers.testing": "测速中...", + "providers.testDone": "测速完成", + "providers.usage": "余额/用量", + "providers.usageQuerying": "查询中...", + "providers.usageUnavailable": "暂未识别到余额/用量", + "providers.openDocsHint": "点击打开官方文档", + "providers.empty": "还没有提供商,先从预设添加一个。", + "providers.keyPlaceholder": "sk-...", + "providers.keySavedPlaceholder": "已填入,可点眼睛查看", + "providers.modelMenuTitle": "OpenAI 模型菜单", + "providers.modelMenuSingleHint": "当前只把默认提供商的模型显示到 Codex CLI。新增或改名模型后,需要重新一键应用并重启终端。", + "providers.modelMenuAllHint": "已开启全部模型。下次一键应用后,Codex CLI 会显示所有已配置提供商的模型;同步过的模型之间切换不用再回本工具切换。", + "providers.showAllModels": "显示全部模型", + "providers.showSingleModel": "只显示当前模型", + "models.title": "模型映射", + "models.subtitle": "为每个提供商配置模型别名", + "models.provider": "提供商", + "models.defaultModel": "默认模型", + "models.fetch": "获取模型", + "models.fetching": "正在获取模型列表...", + "models.fetched": "已获取模型数:", + "models.fetchSuccess": "模型获取成功", + "models.fetchFailedManual": "模型获取失败,请手动输入可用模型", + "models.fetchFailed": "无法自动获取模型列表", + "models.upstreamError.api_key_invalid": "API key 无效,请检查 key 格式或重新生成", + "models.upstreamError.invalid_argument": "请求参数错误 (HTTP 400),请检查 baseUrl 是否对应 Codex 兼容端点", + "models.upstreamError.bad_request": "请求格式错误 (HTTP 400)", + "models.upstreamError.unauthenticated_oauth": "API key 缺失或无效,请填写正确的 API key 后重试", + "models.upstreamError.unauthenticated_expired": "API key 已过期,请重新生成", + "models.upstreamError.unauthenticated": "鉴权失败 (HTTP 401),请检查 API key", + "models.upstreamError.permission_denied": "API key 无权访问此功能 (HTTP 403),请确认账号已开通对应 API", + "models.upstreamError.billing_required": "未启用计费,请到上游控制台启用 billing (HTTP 403)", + "models.upstreamError.forbidden": "上游拒绝访问 (HTTP 403)", + "models.upstreamError.not_found": "端点不存在 (HTTP 404),请检查 baseUrl 是否填写正确", + "models.upstreamError.method_not_allowed": "端点不支持此方法 (HTTP 405),baseUrl 可能填错", + "models.upstreamError.timeout": "上游响应超时,稍后重试", + "models.upstreamError.quota_exceeded": "上游配额已用完 (HTTP 429),请检查计费 / 等待配额刷新", + "models.upstreamError.rate_limited": "请求过于频繁 (HTTP 429),稍后重试", + "models.upstreamError.server_error": "上游服务异常 (HTTP {status}),稍后重试", + "models.upstreamError.unknown": "上游返回错误 (HTTP {status})", + "models.upstreamError.network_timeout": "请求超时", + "models.upstreamError.network_connect": "无法连接上游", + "models.upstreamError.network_redirect": "重定向异常", + "models.upstreamError.network_decode": "响应解码失败", + "models.upstreamError.network_body": "响应体读取失败", + "models.upstreamError.network_request": "请求构造失败", + "models.upstreamError.network_other": "网络错误", + "models.upstreamError.non_json_response": "上游返回非 JSON 响应", + "models.upstreamError.models_not_found": "响应中未找到模型列表", + "models.upstreamError.client_init_failure": "无法创建 HTTP 客户端", + "models.upstreamError.invalid_base_url": "API 地址无效", + "models.hint": "Codex CLI 发送 OpenAI 模型名,本工具会映射到你选择的提供商模型。", + "desktop.title": "Codex CLI", + "desktop.subtitle": "一键让 Codex CLI 使用当前供应商", + "desktop.configTitle": "Codex CLI 配置", + "desktop.apply": "复制环境变量命令", + "desktop.details": "配置详情", + "desktop.clear": "还原 Codex 原配置", + "desktop.quickGuide": "快速引导", + "desktop.explainText": "原理很简单:Codex CLI 先连接到本工具;本工具再把模型名翻译成你选择的供应商模型,并转发到对应 API。你的上游 API Key 只保存在本机配置里,不直接写进 Codex CLI。", + "desktop.step1Title": "复制命令", + "desktop.step1Text": "把 Codex CLI 连接到本工具", + "desktop.step2Title": "设置环境变量", + "desktop.step2Text": "在终端执行复制的命令", + "desktop.step3Title": "开始使用", + "desktop.step3Text": "之后在终端使用 codex 命令即可", + "proxy.title": "转发状态", + "proxy.subtitle": "查看本机转发服务状态和请求日志", + "proxy.localhost": "本机监听", + "proxy.start": "启动转发", + "proxy.stop": "停止转发", + "proxy.viewLog": "查看日志", + "proxy.clearLog": "清除日志", + "proxy.autoScroll": "自动滚动", + "proxy.stats.total": "总请求", + "proxy.stats.success": "成功", + "proxy.stats.failed": "失败", + "proxy.stats.today": "今日", + "settings.title": "设置", + "settings.subtitle": "全局偏好和应用信息", + "settings.theme": "主题", + "settings.language": "语言", + "settings.proxyPort": "转发端口", + "settings.adminPort": "管理端口", + "settings.autoStart": "开机自启", + "settings.autoApplyOnStart": "启动时自动应用配置", + "settings.autoApplyOnStartHint": "启动时自动应用当前提供商的 Codex 配置;如该提供商需要转发服务,会同时启动转发。关闭则需要手动点「应用配置」。", + "settings.autoUnlockCodexPlugins": "自动解锁 Codex Plugins", + "settings.autoUnlockCodexPluginsHint": "此功能需要通过本应用内启动 Codex 才能正常使用。点击右侧「重启 Codex」可直接重启 Codex 并触发自动注入。注意:Plugins / MCP 工具仅在协议转发路径(`apiFormat=openai_chat / anthropic_messages / gemini_native` 等)生效;Responses 直连 provider(`apiFormat=responses`,如 OpenAI 官方)走字节级 passthrough,MCP `namespace` 工具包不展平,部分上游会忽略导致工具列表静默丢失。", + "settings.autoUnlockRestartCodex": "重启 Codex", + "settings.pluginUnlockRuntimeStatus": "运行时状态:未检测", + "settings.pluginUnlockRuntimeStatusPrefix": "运行时状态:", + "settings.autoWakeCodexPet": "自动唤醒 Codex 桌面宠物", + "settings.autoWakeCodexPetHint": "开启后,启动 Codex Desktop 时会自动将 ~/.codex/.codex-global-state.json 中的 electron-avatar-overlay-open 置为 true,让桌面宠物随 Codex 一起唤醒。", + "settings.exposeAllModels": "OpenAI 模型菜单", + "settings.restoreCodexOnExit": "退出时还原 Codex 原配置", + "settings.restoreCodexOnExitHint": "开启后,应用退出或下次启动时会自动把 ~/.codex/config.toml 与 auth.json 还原至 apply 之前的状态;保证不开应用时 Codex CLI 仍是你本人的原配置。", + "settings.codexNetworkAccess": "允许 Codex 联网工具(全权限模式)", + "settings.codexNetworkAccessHint": "⚠️ 默认开启。让 Codex 走 danger-full-access 沙箱 + approval_policy=never(Codex 官方推荐的 \"Full access\" 配对):模型可以读写任何文件、用 curl / wget 等联网命令获取实时网页,且所有命令无审批弹窗。**等于完全信任模型**。关闭后 Codex 回 read-only 沙箱 + on-request 审批:无网络,仅能用所选模型自带的 web_search 能力;若模型不支持 web_search,则完全无法联网搜索。", + "settings.codexSnapshotStatusActive": "已保存 apply 之前的原配置快照({time});退出或点「还原 Codex 原配置」会自动回滚。", + "settings.codexSnapshotStatusRecovery": "存在 {count} 个可人工恢复的 Codex 配置备份。点「还原 Codex 原配置」可选择其中一个恢复。", + "settings.codexSnapshotStatusEmpty": "暂未生成 Codex 配置快照。第一次点「应用配置」时会自动备份 ~/.codex/config.toml 与 auth.json。", + "settings.codexSnapshotKind.active": "当前会话", + "settings.codexSnapshotKind.recovery": "恢复备份", + "settings.codexSnapshotKind.legacy": "旧版备份", + "settings.codexSnapshotKind.unknown": "未知备份", + "settings.codexSnapshotProviderUnknown": "未知供应商", + "settings.codexSnapshotTimeUnknown": "未知时间", + "settings.codexSnapshotVersionUnknown": "未知版本", + "settings.codexSnapshotFilesNone": "无原始文件", + "settings.updateUrl": "更新地址", + "settings.configBackup": "配置备份", + "settings.backupNow": "立即备份", + "settings.exportConfig": "导出配置", + "settings.importConfig": "导入配置", + "settings.noBackups": "暂无备份", + "settings.backupLoadFailed": "备份列表读取失败", + "settings.configBackupHint": "导出的配置会包含 API Key,请只保存在可信设备上。", + "settings.thirdPartyCompat": "第三方兼容", + "settings.checkCompatibility": "检查兼容性", + "settings.thirdPartyCompatHint": "一键检查所有 provider 实际可用性。自定义 provider 可选择 OpenAI Chat 本地转换、原生 Responses 透传、Anthropic Messages 本地转换等协议路由。", + "settings.compatibilityEmpty": "还没有已保存的提供商。", + "settings.checkUpdate": "检查更新", + "settings.installUpdate": "下载并安装", + "settings.downloadingUpdate": "下载中…", + "settings.installingUpdate": "准备安装…", + "settings.about": "关于", + "settings.version": "版本", + "settings.license": "许可证", + "theme.default": "默认", + "theme.green": "绿色", + "theme.orange": "橙色", + "theme.gray": "灰色", + "theme.dark": "暗色", + "theme.white": "白色", + "guide.title": "使用引导", + "guide.subtitle": "让 OpenAI Codex CLI 接入 Kimi、DeepSeek、智谱 GLM、阿里云百炼、Xiaomi MiMo 等供应商,无需改动 CLI 本身。", + "guide.prereqTitle": "开始之前", + "guide.prereqBody": "需要已安装 OpenAI Codex CLI 0.126+。终端跑 codex --version 看版本号;没装的话先去 github.com/openai/codex。", + "guide.quickStartTitle": "快速开始", + "guide.step1": "添加提供商", + "guide.step1Text": "在「提供商」页右上角「+」选预设(Kimi / Kimi Code / DeepSeek / 智谱 GLM / 阿里云百炼 / Xiaomi MiMo),粘贴 API Key。模型映射按官方文档已预填。", + "guide.step1Link": "去添加 →", + "guide.step2": "设为默认", + "guide.step2Text": "「提供商」列表点你要用的那个,确认带上「默认」标记。可以同时保存多个,以后随时切。", + "guide.step2Link": "去管理 →", + "guide.step3": "应用配置(自动)", + "guide.step3Text": "应用启动时自动写入 ~/.codex/config.tomlauth.json,按需启动本地转发服务。第一次会先快照备份你原来的 ~/.codex 配置,退出时按 key 智能合并还原。", + "guide.step4": "在终端跑 codex", + "guide.step4Text": "打开新终端,直接跑 codex。模型选单会显示当前 provider 的映射(Sonnet / Haiku / Opus 对应真实模型)。", + "guide.step5": "切换 / 退出", + "guide.step5Text": "右键系统托盘图标可以一键切换 provider。退出应用时,~/.codex/ 自动回到你原来的配置 —— 不开应用 = 用你自己的原配置。", + "guide.advancedTitle": "进阶用法", + "guide.advThinkingTitle": "DeepSeek 思维模式", + "guide.advThinkingText": "编辑 DeepSeek provider 时打开「Max 思维」开关,自动按官方 chat/completions 思维协议发送 reasoning_effort + thinking 字段。", + "guide.advCompatTitle": "兼容性测试", + "guide.advCompatText": "Settings 页「检查兼容性」按钮一键测试所有 provider 的实际响应,标记哪些直连、哪些需要转发、哪些有问题。", + "guide.advBackupTitle": "配置备份", + "guide.advBackupText": "Settings 页可立即备份当前配置,或导出/导入完整 JSON。备份包含 API Key,只在可信设备上保存。", + "guide.advRestoreTitle": "还原 Codex 原配置", + "guide.advRestoreText": "Dashboard 顶栏红色按钮按 key 智能合并 —— 仅回滚我们写过的 auth_mode / OPENAI_API_KEY / openai_base_url,你的 model_reasoning_effort 等其他配置原样保留。", + "guide.troubleshootTitle": "遇到问题", + "guide.tsFeedbackTitle": "提交反馈", + "guide.tsFeedbackText": "Dashboard 顶栏「反馈」按钮匿名提交问题描述 + 截图 + 日志,默认附诊断信息(应用版本 / OS / 当前 provider 名,不含 API Key)。无需登录。", + "guide.tsLogsTitle": "查看日志", + "guide.tsLogsText": "Settings 页底部「查看日志」按钮直接打开 ~/.codex-app-transfer/logs/。或在「转发」页内置实时日志面板,2 秒自动刷新。", + "guide.tsRestartTitle": "模型菜单不刷新?", + "guide.tsRestartText": "Codex CLI 只在启动时读 ~/.codex/ 配置。切换 provider 后必须 关闭并重新打开终端,新终端才会用新映射。", + "guide.tsSpeedTitle": "测速 / 兼容性失败?", + "guide.tsSpeedText": "如果测速失败,先确认 baseUrl 与 API Key 对得上。v2.x 起所有 provider 的协议类型由系统统一管理:内置预设按 baseUrl 自动命中,自定义 provider 默认 chat/completions;仅当上游明确实现 OpenAI Responses API 时才选 Responses,Claude / Anthropic 上游请选择 Anthropic Messages 本地转换。", + "guide.start": "添加你的第一个提供商", + "restartReminder.title": "是否立即重启 Codex App?", + "restartReminder.body": "已切换供应商并同步模型列表,需要重启 Codex App 才能生效。", + "restartReminder.later": "取消,稍后重启", + "restartReminder.now": "立即重启", + "restartReminder.restarting": "正在重启…", + "toast.defaultUpdated": "默认提供商已更新", + "toast.defaultUpdatedDesktop": "已启用并同步到 Codex CLI,请重启 Codex App", + "toast.defaultUpdatedDesktopFailed": "已启用,但桌面版同步失败,请重新一键应用", + "toast.codexAppRestartRequested": "已请求重启 Codex App", + "toast.codexAppRestartFailed": "无法重启 Codex App", + "toast.modelsSaved": "模型映射已保存", + "toast.modelsReset": "已恢复当前模型映射", + "toast.modelsAutofilled": "模型获取成功", + "toast.desktopApplied": "环境变量命令已复制到剪贴板", + "toast.desktopCleared": "已还原 ~/.codex/ 至 apply 之前的状态", + "toast.desktopClearedLegacy": "未发现快照,已按旧逻辑清除我们写入的字段(升级前的原 base_url / API Key 无法恢复)", + "toast.desktopSnapshotInvalid": "未选择有效的 Codex 配置备份", + "toast.proxyStarted": "转发服务已启动", + "toast.proxyStopped": "转发服务已停止", + "toast.logsCleared": "日志已清除", + "toast.logDirOpened": "已打开日志目录", + "toast.logDirOpenFailed": "无法打开日志目录", + "toast.presetFilled": "预设已填入", + "toast.providerSaved": "提供商已保存", + "toast.providerUpdated": "提供商已更新", + "toast.providerAppliedDesktop": "已保存并生成 Codex CLI 配置", + "toast.providerDeleted": "提供商已删除", + "toast.providersReordered": "提供商顺序已更新", + "toast.copied": "已复制", + "toast.updateAvailable": "发现新版本", + "toast.noUpdate": "当前已是最新版本", + "toast.updateDownloading": "正在下载更新包…", + "toast.updateInstallerStarted": "安装包已下载并启动,请按提示完成安装", + "toast.configBackedUp": "配置已备份", + "toast.configExported": "配置已导出", + "toast.configImported": "配置已导入", + "toast.configImportFailed": "配置导入失败", + "toast.allModelsEnabled": "已开启全部模型显示,请重新一键应用并重启终端", + "toast.singleModelEnabled": "已切回只显示当前模型,请重新一键应用并重启终端", + "toast.compatibilityChecked": "兼容性检查完成", + "toast.requestFailed": "操作失败,请查看后端日志", + "confirm.desktopApply": "即将生成 Codex CLI 环境变量配置命令并复制到剪贴板。确认继续?", + "confirm.desktopClear": "这会把 ~/.codex/config.toml 与 auth.json 还原至 apply 之前的状态(智能合并,仅回滚我们写过的字段)。本工具内保存的提供商与 API Key 不受影响。确认继续?", + "confirm.desktopClearFallback": "未发现可用快照。继续后会清除本工具写入 Codex 的接管字段,包括代理地址、模型目录和模型选择残留;会保留 Codex 账号 token。确认继续?", + "confirm.desktopSnapshotRestoreSingle": "将恢复以下 Codex 配置备份,并保留 Codex 账号 token:\n\n{summary}\n\n恢复成功后会清理剩余备份。确认继续?", + "confirm.desktopSnapshotSelect": "检测到多个 Codex 配置备份。请输入要恢复的序号:\n\n{list}", + "confirm.desktopSnapshotRestoreSelected": "将恢复以下 Codex 配置备份,并在成功后清理所有剩余备份:\n\n{summary}\n\n确认继续?", + "confirm.configImport": "导入配置会覆盖当前提供商和设置,系统会先自动备份现有配置。确认继续?", + "confirm.providerApplyDesktop": "即将保存当前供应商和模型映射,并生成 Codex CLI 环境变量配置,同时启动转发服务。确认继续?", + "confirm.installUpdate": "将自动下载安装包。下载完成后,当前应用会自动退出并启动安装器。确认继续?", + "confirm.openDocs": "即将在浏览器打开 {provider} 的官方文档页面。继续?", + }, + en: { + "nav.dashboard": "Dashboard", + "nav.providers": "Providers", + "nav.models": "Model Mapping", + "nav.desktop": "Desktop", + "nav.proxy": "Forwarding", + "nav.settings": "Settings", + "nav.codex": "Codex Docs", + "nav.guide": "Guide", + "codex.title": "Codex Doc Management", + "codex.subtitle": "", + "codex.assetType": "Asset type", + "codex.tabAgents": "Agents", + "codex.tabMcp": "MCP", + "codex.tabMemories": "Memories", + "codex.tabSkills": "Skills", + "codex.agentsPath.global": "global", + "codex.agentsPath.projectRoot": "project root", + "codex.agentsPath.subdir": "subdir", + "codex.agentsPathEmpty": "No AGENTS.md detected — click \"Add\" on the right to specify one", + "codex.agentsPathAdd": "Add", + "codex.agentsPathAddTitle": "Add a custom AGENTS.md path", + "codex.agentsPathAddPrompt": "Click \"Browse\" to open the system file picker, or paste an absolute path to AGENTS.md (both can be combined)", + "codex.agentsPathBrowse": "Browse", + "codex.agentsPathAddOk": "Custom AGENTS.md path added", + "codex.agentsPathAddOkBtn": "Add", + "codex.agentsPathAddEmpty": "Please paste a path", + "codex.agentsPathRemoveConfirm": "Remove the current path from the dropdown? (reference only — the file is not deleted)", + "codex.agentsPathRemoveOk": "Path reference removed", + "codex.agentsWarn": "Edits to AGENTS.md materially change AI behavior — modify with care", + "codex.memoriesPathEmpty": "No project MEMORY.md added — click \"Add\" on the right to specify one", + "codex.memoriesWarn": "MEMORY.md and memory_summary.md are user-editable AI long-term memory indexes — modify with care", + "codex.memoriesLoading": "Loading codex global memories…", + "codex.memoriesPath.index": "MEMORY", + "codex.memoriesPath.summary": "Summary", + "codex.mcp.servers": "Servers", + "codex.mcp.plugins": "Plugins", + "codex.mcp.marketplace": "Marketplace", + "codex.mcp.serversWarn": "Editing MCP servers writes to ~/.codex/config.toml — affects all codex sessions", + "codex.mcp.pluginsWarn": "Plugins are installed under ~/.codex/plugins/cache/ from a marketplace; uninstall removes the entire cache directory", + "codex.mcp.serversEmpty": "No MCP server configured — click + to add one", + "codex.mcp.pluginsEmpty": "No plugin installed — go to Marketplace to install", + "codex.mcp.marketEmpty": "Nothing matches — try switching sources / refining search / refresh", + "codex.mcp.formEmpty": "Select a server to edit, or add a new one", + "codex.mcp.formName": "Server ID (TOML key, e.g. vercel)", + "codex.mcp.serverNew": "New server", + "codex.mcp.rawToml": "Edit raw TOML", + "codex.mcp.rawWarn": "Raw mode edits ~/.codex/config.toml directly; parse failure is rejected; atomic write with pre-backup on success", + "codex.mcp.saveOk": "Saved", + "codex.mcp.installServerOk": "Added to Servers", + "codex.mcp.installPluginOk": "Plugin installed", + "codex.mcp.uninstallOk": "Plugin uninstalled", + "codex.mcp.pluginEnabled": "Enabled", + "codex.mcp.pluginDisabled": "Disabled", + "codex.mcp.refresh": "Refresh", + "codex.mcp.sourceAdd": "Add source", + "codex.mcp.sourceAddTitle": "Add marketplace source", + "codex.mcp.sourceAddPrompt": "Enter the full HTTPS URL of registry.json and a friendly name", + "codex.mcp.sourceAddConfirm": "Add", + "codex.mcp.serverPresets": "Server presets (one-click add to Servers tab)", + "codex.mcp.pluginBundles": "Plugin bundles (one-click install)", + "codex.mcp.searchPlaceholder": "Search server / plugin", + "codex.mcp.deeplinkConfirmTitle": "Confirm import (external link)", + "codex.mcp.deeplinkConfirmDesc": "An external URL is asking to import the following — confirm before continuing", + "codex.mcp.deeplinkConfirmYes": "Confirm import", + "codex.mcp.deeplinkInstallOk": "Deeplink import complete", + "codex.skillsLoading": "Scanning ~/.codex/skills/…", + "codex.skillsEmpty": "No skills detected — ~/.codex/skills/ is empty", + "codex.skillsReveal": "Open folder", + "codex.skillsWarn": "SKILL.md governs whether the AI finds and uses the tool correctly — modify with care", + "codex.agentsEdit": "Edit", + "codex.agentsBackup": "Backup", + "codex.agentsCancel": "Cancel", + "codex.agentsApplyOk": "AGENTS.md saved (previous version backed up to History)", + "codex.agentsBackupOk": "Current AGENTS.md backed up to History", + "codex.agentsRestoreConfirm": "Restore this version? Current AGENTS.md will be backed up to History first", + "codex.agentsRestoreOk": "AGENTS.md restored", + "codex.historyEmpty": "No history snapshots yet", + "codex.historyApply": "Apply", + "codex.historyWarn": "Applying replaces the current AGENTS.md; the current version is auto-added to History", + "codex.historyDiffEmpty": "Select a History entry to view the diff", + "codex.skillsList": "Installed skills", + "codex.skillsBackup": "Backup now", + "codex.skillsRefresh": "Refresh", + "codex.skillsBackups": "Existing backups (tar.gz, newest first)", + "codex.statusBlockState": "Managed block", + "codex.statusManaged": "Injected", + "codex.statusEmpty": "Not injected", + "codex.statusUserBytes": "User region (outside markers)", + "codex.statusHistoryCount": "History snapshots", + "codex.statusHistoryCountSuffix": "entries (cap 10)", + "codex.statusLastApply": "Last apply", + "codex.statusTargetFile": "Target file", + "codex.statusNone": "none", + "codex.statusBytesSuffix": "bytes", + "codex.confirmClear": "Clear the managed block? Marker + content will be removed (snapshot saved to history, can rollback).", + "codex.confirmRollback": "Rollback {type} to history[{idx}]?", + "codex.confirmSkillsRestore": "Restore skills/ from {filename}? This overwrites current ~/.codex/skills content!", + "codex.toastApplied": "{type} managed block applied", + "codex.toastCleared": "{type} managed block cleared (snapshot saved to history)", + "codex.toastRollbacked": "Rolled back {type} to history[{idx}]", + "codex.toastSkillsBackedUp": "Backup done: {name}", + "codex.toastSkillsRestored": "Restored from {filename}", + "codex.historyEmpty": "no history snapshot yet", + "codex.skillsListEmpty": "no skill installed yet", + "codex.backupsListEmpty": "no backup yet (click Backup now to create)", + "codex.skillsHasSkillMd": "✓ SKILL.md", + "codex.skillsNoSkillMd": "✗ no SKILL.md", + "codex.skillsFilesSuffix": "files", + "codex.skillsDirLabel": "Skills directory", + "codex.skillsListLabel": "Installed skills", + "codex.skillsBackupDirLabel": "Backup directory", + "codex.skillsInstalledLabel": "Installed skills", + "codex.skillsBackupsLabel": "Existing backups", + "codex.skillsCountSuffix": "", + "codex.skillsBackupsCountSuffix": "", + "codex.statusLoading": "Loading status…", + "codex.managedContent": "Managed block content (between start/end markers)", + "codex.placeholder": "Paste app-managed AGENTS.md content (empty = Apply creates empty block or Preview shows marker insertion)", + "codex.preview": "Preview", + "codex.apply": "Apply", + "codex.history": "History", + "codex.clear": "Clear", + "codex.historyTitle": "History (up to 10 snapshots)", + "dashboard.title": "Provider", + "dashboard.subtitle": "Choose a provider, then start forwarding for Codex CLI.", + "dashboard.desktopStatus": "Codex CLI Status", + "dashboard.proxyStatus": "Forwarding Status", + "dashboard.activeProvider": "Active Provider", + "dashboard.configureDesktop": "Add and Generate Config", + "dashboard.switchProvider": "Switch Provider", + "dashboard.clearDesktopConfig": "Restore original Codex config", + "dashboard.feedback": "Feedback", + "settings.feedback": "Feedback & Suggestions", + "settings.feedbackOpen": "Open feedback window", + "settings.feedbackHint": "Have a problem or suggestion? Submit text + screenshots + logs anonymously, no account needed. Diagnostics are attached automatically by default: app/environment info, redacted configs, and recent error snapshots with full request/response payloads. No API keys are included.", + "feedback.title": "Feedback & Suggestions", + "feedback.intro": "Your feedback goes to a private storage maintained by the developer, not public.", + "feedback.titleLabel": "Title (optional)", + "feedback.titlePlaceholder": "Briefly describe the issue", + "feedback.contactEmailLabel": "Contact email (optional)", + "feedback.contactEmailPlaceholder": "name@example.com", + "feedback.contactEmailHint": "Optional. If provided, the maintainer can reply with updates on your feedback.", + "feedback.bodyLabel": "Description *", + "feedback.bodyPlaceholder": "Please describe: what happened, what you expected, how to reproduce", + "feedback.attachmentsLabel": "Attachments (optional, max 5MB each)", + "feedback.attachmentsHint": "Click to select / drag here / Cmd+V to paste screenshots", + "feedback.includeDiagnostics": "Include diagnostics (recommended, default on)", + "feedback.includeDiagnosticsHint": "Automatically attaches app/environment info, redacted configs, and recent error snapshots with full request/response payloads. No API keys or sensitive secrets.", + "feedback.privacyWarning": "⚠ Please check screenshots for API keys / personal info before submitting.", + "feedback.submit": "Submit Feedback", + "feedback.submitting": "Submitting...", + "feedback.bodyRequired": "Please fill in the description", + "feedback.successToast": "✓ Feedback received! ID: {id}", + "feedback.failToast": "Submission failed: {message}", + "feedback.tooLargeFile": "File {name} exceeds 5MB, skipped", + "dashboard.recentActivity": "Recent Activity", + "dashboard.updateAvailable": "Update available", + "dashboard.availablePresets": "Add another provider", + "dashboard.availablePresetsHint": "Presets that are not yet added stay visible here. Pick one to open the add form with recommended settings.", + "status.configured": "Configured", + "status.notConfigured": "Not configured", + "status.needsApply": "Needs reapply", + "status.running": "Running", + "status.stopped": "Stopped", + "status.default": "Default", + "status.active": "Active", + "status.standby": "Standby", + "common.viewAll": "View All", + "common.save": "Save", + "common.cancel": "Cancel", + "common.reset": "Reset", + "common.delete": "Delete", + "common.edit": "Edit", + "common.copy": "Copy", + "common.saveOnly": "Save Only", + "providersAdd.title": "Add Provider", + "providersAdd.editTitle": "Edit Provider", + "providersAdd.subtitle": "Add a new API provider or choose a preset", + "providersAdd.presets": "Quick Presets", + "providersAdd.presetsHint": "Choosing one fills the API URL and suggested models. You still enter your own API key.", + "providersAdd.mappingTitle": "Model Mapping", + "providersAdd.mappingSubtitle": "Choose the OpenAI model on the left and map it to the real upstream model on the right. Unmapped models fall back to Default.", + "providersAdd.claudeModel": "OpenAI Model", + "providersAdd.providerModel": "Provider Model", + "providersAdd.providerModelPlaceholder": "Type or choose model", + "providersAdd.addMapping": "Add Mapping", + "providersAdd.removeMapping": "Remove", + "providersAdd.customModelPlaceholder": "Model name sent by client", + "providersAdd.optionApplied": "Model option applied", + "providersAdd.applyToDesktop": "Generate Codex CLI config", + "providersAdd.applyHint": "Apply saves the provider and model mapping, makes it the default, and connects Codex CLI to this app. Messages from Codex CLI will then be forwarded to your API URL.", + "providersAdd.apiFormatLabel": "API protocol", + "providersAdd.webSearchEnabled": "Web Search", + "providersAdd.webSearchEnabledToggle": "Enable web_search tool", + "providersAdd.webSearchEnabledHint.default": "When enabled, the proxy injects a web_search tool into outbound requests so the model can search the web. Cost / limits depend on the specific provider.", + "providersAdd.webSearchEnabledHint.kimi": "When enabled, the proxy injects Kimi's `$web_search` builtin_function into outbound requests. Kimi billing: **each search call is charged separately at $0.005** (independent of tokens); search results are counted into prompt_tokens.", + "providersAdd.webSearchEnabledHint.kimi-code": "When enabled, the proxy injects Kimi's `$web_search` builtin_function (Kimi Code shares Kimi's web-search mechanism, $0.005/call separate billing).", + "providersAdd.webSearchEnabledHint.xiaomi-mimo-payg": "When enabled, the proxy injects MiMo's proprietary web_search tool into outbound requests. MiMo PAYG bills per token; search results are counted into prompt_tokens with no separate search fee.", + "providersAdd.webSearchEnabledHint.xiaomi-mimo-token-plan": "When enabled, the proxy injects MiMo's proprietary web_search tool. Note: Token Plan users must first enable Web Search Plugin on MiMo console (platform.xiaomimimo.com/console/plan-manage), otherwise the upstream returns 400 (the proxy implements a 4xx fallback to auto-degrade).", + "providersAdd.customThirdPartyName": "Custom Third-Party", + "providersAdd.customThirdPartyHint": "User-defined upstream URL, API protocol selectable", + "toast.directModeBaseUrlRequired": "Responses passthrough requires a baseUrl", + "toast.directModeApiKeyRequired": "Responses passthrough requires an API key", + "apiFormatDisplay.openaiChat.name": "OpenAI Chat", + "apiFormatDisplay.openaiChat.detail": "Local Responses ↔ Chat conversion", + "apiFormatDisplay.responses.name": "Responses", + "apiFormatDisplay.responses.detail": "Native passthrough to upstream", + "apiFormatDisplay.anthropic.name": "Anthropic Messages", + "apiFormatDisplay.anthropic.detail": "Local Responses ↔ Anthropic Messages conversion", + "apiFormatDisplay.geminiNative.name": "Gemini Native", + "apiFormatDisplay.geminiNative.detail": "Local Responses ↔ Gemini generateContent conversion", + "apiFormatDisplay.geminiCliOauth.name": "Gemini CLI (OAuth)", + "apiFormatDisplay.geminiCliOauth.detail": "Browser login to Google → Cloud Code Assist direct", + "geminiOauth.title": "Google OAuth Login", + "geminiOauth.statusLoading": "Loading...", + "geminiOauth.statusNotLoggedIn": "Not logged in — click the button below to log in with Google in your browser", + "geminiOauth.statusLoggedIn": "Logged in: {email} — project {projectId} (expires: {expiresAt})", + "geminiOauth.statusLoggedInNoProject": "Logged in but project not provisioned: {email} (please re-login to fix)", + "geminiOauth.loginBtn": "Log in with Google in browser", + "geminiOauth.loginBtnInProgress": "Waiting for browser authorization (up to 5 minutes)...", + "geminiOauth.logoutBtn": "Log out and delete local credentials", + "geminiOauth.hint": "After login the browser window can be closed. Token is persisted to ~/.codex-app-transfer/gemini-oauth.json and auto-refreshes before expiry.", + "geminiOauth.tosWarning": "⚠️ Impersonates gemini-cli to reach Google's internal Cloud Code endpoint. TOS gray area; Google may change the protocol at any time. Fallback: use the Google AI Studio (API key) provider.", + "geminiOauth.loginSuccess": "Login successful! email: {email}, project: {projectId}", + "geminiOauth.loginFailed": "Login failed: {error}", + "geminiOauth.loginPartial": "OAuth succeeded but Cloud Code project provisioning failed (token NOT persisted; please retry login)", + "geminiOauth.loginRequired": "Switching to Gemini CLI (OAuth) requires logging in first — click the \"Log in with Google\" button on the form", + "geminiOauth.logoutConfirmed": "Logged out, local credentials deleted", + "geminiOauth.logoutFailedManual": "Logout failed: {error}. Token file may still be at ~/.codex-app-transfer/gemini-oauth.json — delete manually to fully sign out", + "geminiOauth.statusFetchFailed": "Status fetch failed: {error}", + "geminiOauth.switchAccountBtn": "Switch account (re-login)", + "apiFormatDisplay.antigravityOauth.name": "Antigravity (OAuth)", + "apiFormatDisplay.antigravityOauth.detail": "Browser login to Google → Antigravity IDE creds → Cloud Code Assist", + "antigravityOauth.title": "Google OAuth Login", + "antigravityOauth.statusLoading": "Loading...", + "antigravityOauth.statusNotLoggedIn": "Not logged in — click the button below to log in with Google in your browser", + "antigravityOauth.statusLoggedIn": "Logged in: {email} — project {projectId} (expires: {expiresAt})", + "antigravityOauth.statusLoggedInNoProject": "Logged in but project not provisioned: {email} (please re-login to fix)", + "antigravityOauth.loginBtn": "Log in with Google in browser", + "antigravityOauth.loginBtnInProgress": "Waiting for browser authorization (up to 5 minutes)...", + "antigravityOauth.logoutBtn": "Log out and delete local credentials", + "antigravityOauth.hint": "After login the browser window can be closed. Token is persisted to ~/.codex-app-transfer/antigravity-oauth.json and auto-refreshes before expiry. Can be logged in alongside Gemini CLI (OAuth) without conflict.", + "antigravityOauth.tosWarning": "⚠️ Impersonates the Antigravity IDE client to reach Google's internal Cloud Code endpoint. TOS gray area; Google may change the protocol at any time. Fallback: use the Google AI Studio (API key) provider or Gemini CLI (OAuth).", + "antigravityOauth.loginSuccess": "Login successful! email: {email}, project: {projectId}", + "antigravityOauth.loginFailed": "Login failed: {error}", + "antigravityOauth.loginPartial": "OAuth succeeded but Cloud Code project provisioning failed (token NOT persisted; please retry login)", + "antigravityOauth.loginRequired": "Switching to Antigravity (OAuth) requires logging in first — click the \"Log in with Google\" button on the form", + "antigravityOauth.logoutConfirmed": "Logged out, local credentials deleted", + "antigravityOauth.logoutFailedManual": "Logout failed: {error}. Token file may still be at ~/.codex-app-transfer/antigravity-oauth.json — delete manually to fully sign out", + "antigravityOauth.statusFetchFailed": "Status fetch failed: {error}", + "antigravityOauth.switchAccountBtn": "Switch account (re-login)", + "apiFormatDisplay.grokWeb.name": "Grok Web (experimental)", + "apiFormatDisplay.grokWeb.detail": "Reverse-proxy grok.com Web backend, SuperGrok / X Premium+ cookie auth", + "grokWeb.title": "Grok Web Cookie Authentication", + "grokWeb.hint": "Log into grok.com → F12 → Application → Cookies → copy the `sso` value. That's all you need; everything else is auto-handled (x-statsig-id is regenerated per request, sso-rw reuses sso, UA defaults).", + "grokWeb.ssoLabel": "sso (JWT, required)", + "grokWeb.advancedSummary": "Advanced (expand when 401 / CF challenge)", + "grokWeb.ssoRwLabel": "sso-rw (optional; defaults to sso)", + "grokWeb.cookieStringLabel": "Full Cookie string (required under CF challenge)", + "grokWeb.cookieStringHint": "Open DevTools → Network → pick any grok.com request → Request Headers → copy the full Cookie value and paste here (the leading \"Cookie: \" prefix is auto-stripped). A `No credentials presented` 401 from grok.com Cloudflare usually means this segment is missing. Mirrors chenyme/grok2api `proxy.clearance.cf_cookies`.", + "grokWeb.cfClearanceLabel": "cf_clearance (optional, single token)", + "grokWeb.cfClearanceHint": "Leave empty if you have pasted the full Cookie string. Filling this alone only injects a `cf_clearance=` segment which may be insufficient; pasting the full Cookie string is more reliable.", + "grokWeb.statsigIdLabel": "x-statsig-id (optional override; auto-generated by default)", + "grokWeb.statsigIdHint": "By default the backend dynamically generates a forged Statsig blob per request (port of chenyme/grok2api); no manual maintenance required. Only fill this if you need precise control.", + "grokWeb.userAgentLabel": "User-Agent override (optional)", + "grokWeb.tosWarning": "⚠️ Reverse-proxying grok.com Web; grok TOS gray area. Personal use of your own SuperGrok account only; do not deploy as a public service.", + "grokWeb.savedPlaceholder": "Saved — leave empty to keep current value", + "providers.title": "Providers", + "providers.subtitle": "Manage configured API providers", + "providers.add": "Add Provider", + "providers.name": "Provider Name", + "providers.mapping": "Model Mapping", + "providers.status": "Status", + "providers.actions": "Actions", + "providers.deleteTitle": "Delete Provider", + "providers.deleteMessage": "Delete this provider? This will update the current configuration file.", + "providers.setDefault": "Set Default", + "providers.enable": "Enable", + "providers.added": "Added", + "providers.manageAndTest": "Manage & Test", + "providers.testSpeed": "Speed Test", + "providers.testing": "Testing...", + "providers.testDone": "Speed test completed", + "providers.usage": "Balance / Usage", + "providers.usageQuerying": "Querying...", + "providers.usageUnavailable": "Balance or usage not found", + "providers.openDocsHint": "Click to open official docs", + "providers.empty": "No provider yet. Add one from presets.", + "providers.keyPlaceholder": "sk-...", + "providers.keySavedPlaceholder": "Saved. Use the eye button to view it.", + "providers.modelMenuTitle": "OpenAI Model Menu", + "providers.modelMenuSingleHint": "Only the default provider is shown in Codex CLI. Re-apply and restart your terminal after adding or renaming models.", + "providers.modelMenuAllHint": "All-model mode is on. After applying once, Codex CLI can show every configured provider model; switching synced models does not require switching providers in this app.", + "providers.showAllModels": "Show All Models", + "providers.showSingleModel": "Show Current Only", + "models.title": "Model Mapping", + "models.subtitle": "Configure model aliases for each provider", + "models.provider": "Provider", + "models.defaultModel": "Default Model", + "models.fetch": "Fetch Models", + "models.fetching": "Fetching model list...", + "models.fetched": "Fetched models:", + "models.fetchSuccess": "Models fetched successfully", + "models.fetchFailedManual": "Model fetch failed. Enter an available model name manually.", + "models.fetchFailed": "Could not auto-fetch model list", + "models.upstreamError.api_key_invalid": "API key is invalid; check the key format or regenerate it", + "models.upstreamError.invalid_argument": "Invalid request (HTTP 400); verify the baseUrl points to a Codex-compatible endpoint", + "models.upstreamError.bad_request": "Malformed request (HTTP 400)", + "models.upstreamError.unauthenticated_oauth": "API key missing or invalid; please enter a valid key and retry", + "models.upstreamError.unauthenticated_expired": "API key has expired; please regenerate it", + "models.upstreamError.unauthenticated": "Authentication failed (HTTP 401); check the API key", + "models.upstreamError.permission_denied": "API key lacks permission for this resource (HTTP 403); verify the account has the API enabled", + "models.upstreamError.billing_required": "Billing not enabled; activate billing in the upstream console (HTTP 403)", + "models.upstreamError.forbidden": "Upstream denied access (HTTP 403)", + "models.upstreamError.not_found": "Endpoint not found (HTTP 404); verify the baseUrl", + "models.upstreamError.method_not_allowed": "Endpoint does not accept this method (HTTP 405); baseUrl may be wrong", + "models.upstreamError.timeout": "Upstream timed out; please retry", + "models.upstreamError.quota_exceeded": "Upstream quota exhausted (HTTP 429); check billing or wait for reset", + "models.upstreamError.rate_limited": "Too many requests (HTTP 429); please retry later", + "models.upstreamError.server_error": "Upstream server error (HTTP {status}); please retry", + "models.upstreamError.unknown": "Upstream error (HTTP {status})", + "models.upstreamError.network_timeout": "Request timed out", + "models.upstreamError.network_connect": "Could not connect to upstream", + "models.upstreamError.network_redirect": "Redirect error", + "models.upstreamError.network_decode": "Response decode failed", + "models.upstreamError.network_body": "Response body read failed", + "models.upstreamError.network_request": "Request construction failed", + "models.upstreamError.network_other": "Network error", + "models.upstreamError.non_json_response": "Upstream returned non-JSON response", + "models.upstreamError.models_not_found": "No model list found in response", + "models.upstreamError.client_init_failure": "Failed to initialize HTTP client", + "models.upstreamError.invalid_base_url": "Invalid base URL", + "models.hint": "Desktop sends OpenAI model names. This app maps them to provider models.", + "desktop.title": "Codex CLI", + "desktop.subtitle": "Make Codex CLI use the active provider", + "desktop.configTitle": "Codex CLI Configuration", + "desktop.apply": "Copy env commands", + "desktop.details": "Configuration Details", + "desktop.clear": "Restore original Codex config", + "desktop.quickGuide": "Quick Guide", + "desktop.explainText": "Codex CLI connects to this app first. This app translates model names to the selected provider models, then forwards requests to the API. Your upstream API key stays in local config and is not written directly into Codex CLI.", + "desktop.step1Title": "Copy commands", + "desktop.step1Text": "Connect Codex CLI to this app", + "desktop.step2Title": "Set environment variables", + "desktop.step2Text": "Run the copied commands in your terminal", + "desktop.step3Title": "Start using it", + "desktop.step3Text": "Use the codex command in your terminal as usual", + "proxy.title": "Forwarding Status", + "proxy.subtitle": "View local forwarding status and request logs", + "proxy.localhost": "Listening locally", + "proxy.start": "Start", + "proxy.stop": "Stop", + "proxy.viewLog": "View Log", + "proxy.clearLog": "Clear Log", + "proxy.autoScroll": "Auto-scroll", + "proxy.stats.total": "Request", + "proxy.stats.success": "Success", + "proxy.stats.failed": "Failed", + "proxy.stats.today": "Today", + "settings.title": "Settings", + "settings.subtitle": "Global preferences and app information", + "settings.theme": "Theme", + "settings.language": "Language", + "settings.proxyPort": "Forwarding Port", + "settings.adminPort": "Admin Port", + "settings.autoStart": "Auto-start on boot", + "settings.autoApplyOnStart": "Auto-apply config on start", + "settings.autoApplyOnStartHint": "When the app launches, automatically apply the active provider's Codex config; if that provider needs forwarding, the proxy is started too. Turn off to require manual \"Apply\" each time.", + "settings.autoUnlockCodexPlugins": "Auto-unlock Codex plugins", + "settings.autoUnlockCodexPluginsHint": "Codex must be launched from this app for plugin unlock to work. Click \"Restart Codex\" on the right to relaunch Codex now and trigger injection. Note: Plugins / MCP tools only take effect on protocol-translation routes (`apiFormat=openai_chat / anthropic_messages / gemini_native` etc.); Responses-direct providers (`apiFormat=responses`, e.g. OpenAI official) use byte-level passthrough and do NOT flatten the MCP `namespace` tool bundle — some upstreams silently drop the tool list.", + "settings.autoUnlockRestartCodex": "Restart Codex", + "settings.pluginUnlockRuntimeStatus": "Runtime status: not detected", + "settings.pluginUnlockRuntimeStatusPrefix": "Runtime status: ", + "settings.autoWakeCodexPet": "Auto-wake Codex desktop pet", + "settings.autoWakeCodexPetHint": "When enabled, launching Codex Desktop sets electron-avatar-overlay-open to true in ~/.codex/.codex-global-state.json so the desktop pet wakes up alongside Codex.", + "settings.exposeAllModels": "OpenAI model menu", + "settings.restoreCodexOnExit": "Restore original Codex config on exit", + "settings.restoreCodexOnExitHint": "When enabled, ~/.codex/config.toml and auth.json are restored to their pre-apply state on exit or next start, so Codex CLI keeps using your own original config when the app isn't running.", + "settings.codexNetworkAccess": "Allow Codex network tools (full-access mode)", + "settings.codexNetworkAccessHint": "⚠️ Enabled by default. Puts Codex in the danger-full-access sandbox + approval_policy=never (Codex's recommended \"Full access\" pairing): the model can read/write any file, run network commands like curl / wget for live web content, and execute commands without approval prompts. **Equivalent to fully trusting the model**. When disabled Codex falls back to the read-only sandbox + on-request approval: no network access, only the model's built-in web_search capability works; if the selected model doesn't support web_search, no live web search is possible.", + "settings.codexSnapshotStatusActive": "Pre-apply Codex config snapshot saved at {time}. Exiting the app or clicking \"Restore original Codex config\" will roll back automatically.", + "settings.codexSnapshotStatusRecovery": "{count} Codex config recovery snapshots are available. Click \"Restore original Codex config\" to choose one.", + "settings.codexSnapshotStatusEmpty": "No Codex config snapshot yet. The first \"Apply\" will back up ~/.codex/config.toml and auth.json.", + "settings.codexSnapshotKind.active": "Current session", + "settings.codexSnapshotKind.recovery": "Recovery backup", + "settings.codexSnapshotKind.legacy": "Legacy backup", + "settings.codexSnapshotKind.unknown": "Unknown backup", + "settings.codexSnapshotProviderUnknown": "Unknown provider", + "settings.codexSnapshotTimeUnknown": "Unknown time", + "settings.codexSnapshotVersionUnknown": "Unknown version", + "settings.codexSnapshotFilesNone": "No original file", + "settings.updateUrl": "Update URL", + "settings.configBackup": "Config Backup", + "settings.backupNow": "Backup Now", + "settings.exportConfig": "Export Config", + "settings.importConfig": "Import Config", + "settings.noBackups": "No backups yet", + "settings.backupLoadFailed": "Could not load backups", + "settings.configBackupHint": "Exported config includes API keys. Keep it only on trusted devices.", + "settings.thirdPartyCompat": "Third-party compatibility", + "settings.checkCompatibility": "Check Compatibility", + "settings.thirdPartyCompatHint": "One-click check of every provider's real-world availability. Custom providers can choose local OpenAI Chat conversion, native Responses passthrough, Anthropic Messages conversion, and other supported protocol routes.", + "settings.compatibilityEmpty": "No saved providers yet.", + "settings.checkUpdate": "Check for Updates", + "settings.installUpdate": "Download and Install", + "settings.downloadingUpdate": "Downloading...", + "settings.installingUpdate": "Preparing Installer...", + "settings.about": "About", + "settings.version": "Version", + "settings.license": "License", + "theme.default": "Default", + "theme.green": "Green", + "theme.orange": "Orange", + "theme.gray": "Gray", + "theme.dark": "Dark", + "theme.white": "White", + "guide.title": "Getting Started", + "guide.subtitle": "Make OpenAI Codex CLI work with Kimi, DeepSeek, Zhipu GLM, Alibaba Bailian, Xiaomi MiMo and other providers — no changes to the CLI itself.", + "guide.prereqTitle": "Before You Start", + "guide.prereqBody": "You need OpenAI Codex CLI 0.126+. Run codex --version in a terminal. Not installed yet? Get it at github.com/openai/codex.", + "guide.quickStartTitle": "Quick Start", + "guide.step1": "Add a Provider", + "guide.step1Text": "On the Providers page, click the top-right \"+\" and pick a preset (Kimi / Kimi Code / DeepSeek / Zhipu GLM / Alibaba Bailian / Xiaomi MiMo). Paste the API key. Model mapping is pre-filled per official docs.", + "guide.step1Link": "Add →", + "guide.step2": "Set Default", + "guide.step2Text": "On the Providers list, click the one you want to use; confirm the \"Default\" tag. Multiple providers can be saved at once and switched anytime.", + "guide.step2Link": "Manage →", + "guide.step3": "Apply (Automatic)", + "guide.step3Text": "When the app launches, it auto-writes ~/.codex/config.toml and auth.json, and starts the local proxy if needed. The first time, it snapshots your original ~/.codex/ config and smart-merges it back on exit.", + "guide.step4": "Run codex in a Terminal", + "guide.step4Text": "Open a fresh terminal and run codex. The model picker shows the current provider's mapping (Sonnet / Haiku / Opus → real model).", + "guide.step5": "Switch / Exit", + "guide.step5Text": "Right-click the system tray icon to switch provider in one click. When you exit the app, ~/.codex/ reverts to your original config — app off = your own config back.", + "guide.advancedTitle": "Advanced", + "guide.advThinkingTitle": "DeepSeek Thinking Mode", + "guide.advThinkingText": "When editing a DeepSeek provider, toggle \"Max Thinking\" — the request body is auto-aligned to the official chat/completions thinking schema (reasoning_effort + thinking).", + "guide.advCompatTitle": "Compatibility Check", + "guide.advCompatText": "On Settings, the \"Check Compatibility\" button tests all providers' actual responses, flagging which work direct, which need proxy, and which have issues.", + "guide.advBackupTitle": "Config Backup", + "guide.advBackupText": "On Settings, back up your current config instantly, or export/import full JSON. Backups include API keys — keep them on trusted devices only.", + "guide.advRestoreTitle": "Restore Original Codex Config", + "guide.advRestoreText": "The red button on the Dashboard top bar performs a key-level smart merge — only rolls back fields we wrote (auth_mode / OPENAI_API_KEY / openai_base_url). Your model_reasoning_effort and other settings stay intact.", + "guide.troubleshootTitle": "Troubleshooting", + "guide.tsFeedbackTitle": "Submit Feedback", + "guide.tsFeedbackText": "The \"Feedback\" button on the Dashboard top bar lets you submit a description + screenshots + logs anonymously. Diagnostics (app version / OS / current provider name — no API keys) are attached by default. No login required.", + "guide.tsLogsTitle": "View Logs", + "guide.tsLogsText": "On Settings, \"View Logs\" opens ~/.codex-app-transfer/logs/. The Forwarding page also has an inline log panel that refreshes every 2 seconds.", + "guide.tsRestartTitle": "Model Picker Not Refreshing?", + "guide.tsRestartText": "Codex CLI only reads ~/.codex/ at startup. After switching providers, you must close and reopen the terminal for the new mapping to take effect.", + "guide.tsSpeedTitle": "Speed / Compatibility Test Failing?", + "guide.tsSpeedText": "If a speed test fails, first verify the baseUrl and API key are correct. Starting in v2.x every provider's API protocol is system-managed: builtin presets are auto-rewritten on baseUrl match, and custom providers default to chat/completions. Choose Responses only for upstreams that implement the OpenAI Responses API; choose Anthropic Messages for Claude or Anthropic-compatible upstreams.", + "guide.start": "Add Your First Provider", + "guide.step2": "Apply Once", + "guide.step2Text": "Click Generate Codex CLI config. This app generates the environment variable commands.", + "guide.step3": "Set environment variables", + "guide.step3Text": "After restart, ask as usual. This app forwards messages to the active provider.", + "guide.step4": "Start using Codex CLI", + "guide.step4Text": "Select 3P mode after restart.", + "guide.start": "Start", + "restartReminder.title": "Restart Codex App now?", + "restartReminder.body": "The provider and model list were synced. Restart Codex App for the change to take effect.", + "restartReminder.later": "Cancel, restart later", + "restartReminder.now": "Restart now", + "restartReminder.restarting": "Restarting…", + "toast.defaultUpdated": "Default provider updated", + "toast.defaultUpdatedDesktop": "Enabled and synced to Codex CLI. Restart Codex App.", + "toast.defaultUpdatedDesktopFailed": "Enabled, but sync failed. Generate Codex CLI config again.", + "toast.codexAppRestartRequested": "Codex App restart requested.", + "toast.codexAppRestartFailed": "Unable to restart Codex App.", + "toast.modelsSaved": "Model mappings saved", + "toast.modelsReset": "Current model mappings restored", + "toast.modelsAutofilled": "Models fetched successfully", + "toast.desktopApplied": "Environment variable commands copied to clipboard.", + "toast.desktopCleared": "Restored ~/.codex/ to its pre-apply state", + "toast.desktopClearedLegacy": "No snapshot found; cleared only the fields we wrote (the original base_url / API key from before the upgrade can't be recovered)", + "toast.desktopSnapshotInvalid": "No valid Codex config snapshot was selected", + "toast.proxyStarted": "Forwarding service started", + "toast.proxyStopped": "Forwarding service stopped", + "toast.logsCleared": "Logs cleared", + "toast.logDirOpened": "Opened log directory", + "toast.logDirOpenFailed": "Failed to open log directory", + "toast.presetFilled": "preset filled", + "toast.providerSaved": "Provider saved", + "toast.providerUpdated": "Provider updated", + "toast.providerAppliedDesktop": "Saved and generated Codex CLI config.", + "toast.providerDeleted": "Provider deleted", + "toast.providersReordered": "Provider order updated", + "toast.copied": "Copied", + "toast.updateAvailable": "Update available", + "toast.noUpdate": "You are up to date", + "toast.updateDownloading": "Downloading the update package...", + "toast.updateInstallerStarted": "Installer downloaded and started. Follow the installer prompts.", + "toast.configBackedUp": "Config backed up", + "toast.configExported": "Config exported", + "toast.configImported": "Config imported", + "toast.configImportFailed": "Config import failed", + "toast.allModelsEnabled": "All-model display enabled. Re-apply and restart your terminal.", + "toast.singleModelEnabled": "Switched back to current-provider display. Re-apply and restart your terminal.", + "toast.compatibilityChecked": "Compatibility check completed", + "toast.requestFailed": "Operation failed. Check backend logs.", + "confirm.desktopApply": "This will generate Codex CLI environment variable commands and copy them to clipboard. Continue?", + "confirm.desktopClear": "This will restore ~/.codex/config.toml and auth.json to their pre-apply state (smart merge — only the fields we wrote are reverted). Providers and API keys saved in this app are untouched. Continue?", + "confirm.desktopClearFallback": "No usable snapshot was found. Continuing will clear this app's Codex takeover fields, including proxy URL, model catalog, and model selection residue, while preserving Codex account tokens. Continue?", + "confirm.desktopSnapshotRestoreSingle": "This will restore the following Codex config backup while preserving Codex account tokens:\n\n{summary}\n\nRemaining backups will be cleaned after a successful restore. Continue?", + "confirm.desktopSnapshotSelect": "Multiple Codex config backups were found. Enter the number to restore:\n\n{list}", + "confirm.desktopSnapshotRestoreSelected": "This will restore the following Codex config backup and clean all remaining backups after success:\n\n{summary}\n\nContinue?", + "confirm.configImport": "Importing config will replace current providers and settings. A backup will be created first. Continue?", + "confirm.providerApplyDesktop": "This will save the provider and model mapping, generate Codex CLI environment config, and start the forwarding service. Continue?", + "confirm.installUpdate": "This will download the installer. After the download finishes, the app will quit automatically and start the installer. Continue?", + "confirm.openDocs": "Open {provider}'s official docs in the browser?", + }, + } + +function cachedLanguage(): string { + try { + const stored = window.localStorage.getItem("cas:lang"); + if (stored && ['zh', 'en'].includes(stored)) return stored; + } catch (_) {} + const nav = (navigator.language || '').toLowerCase(); + if (nav.startsWith("zh")) return "zh"; + if (nav.startsWith("en")) return "en"; + return "zh"; +} + +export const locale = writable(cachedLanguage()); + +// Apply system language initially +if (typeof document !== 'undefined') { + const initialLocale = cachedLanguage(); + document.documentElement.lang = initialLocale === "zh" ? "zh-CN" : "en"; +} + +export const t = derived(locale, ($locale) => { + return (key: string, vars?: Record) => { + let text = dictionaries[$locale]?.[key] || dictionaries['zh']?.[key] || key; + if (vars) { + Object.entries(vars).forEach(([k, v]) => { + text = text.replace(new RegExp(`\\{${k}\\}`, 'g'), v); + }); + } + return text; + }; +}); + +export function setLocale(lang: string) { + if (['zh', 'en'].includes(lang)) { + locale.set(lang); + if (typeof document !== 'undefined') { + document.documentElement.lang = lang === "zh" ? "zh-CN" : "en"; + } + try { + window.localStorage.setItem("cas:lang", lang); + } catch (_) {} + } +} diff --git a/frontend/src/lib/store.ts b/frontend/src/lib/store.ts new file mode 100644 index 00000000..dceba91d --- /dev/null +++ b/frontend/src/lib/store.ts @@ -0,0 +1,67 @@ +import { writable } from 'svelte/store'; +import type { AppStatus, Provider, ProxyStatus, ProxyLog } from './api'; +import { CCApi } from './api'; + +// Active Tab +export const activeTab = writable('dashboard'); + +// Global App Status +export const appStatus = writable({ + desktopConfigured: false, + proxyRunning: false, + proxyPort: 18080, + activeProvider: { name: '-', id: null }, + activeProviderId: null, + desktopHealth: { needsApply: false, issues: [] }, + exposeAllProviderModels: false, +}); + +// Providers List +export const providers = writable([]); + +// Settings +export const settings = writable({}); + +// Proxy Status & Logs +export const proxyStatus = writable({ + running: false, + port: 18080, + stats: { total: 0, success: 0, failed: 0, today: 0 } +}); +export const proxyLogs = writable([]); + +// OAuth Statuses +export const geminiOauthStatus = writable(null); +export const antigravityOauthStatus = writable(null); + +// Functions to refresh state +export async function refreshAll() { + try { + const [statusData, providersData, settingsData] = await Promise.all([ + CCApi.getStatus(), + CCApi.getProviders(), + CCApi.getSettings(), + ]); + + appStatus.set(statusData); + providers.set(providersData); + settings.set(settingsData); + + // Also fetch proxy details if running + if (statusData.proxyRunning) { + const proxyState = await CCApi.getProxyStatus(); + proxyStatus.set(proxyState); + } + } catch (err) { + console.error('Failed to refresh global state:', err); + } +} + +export async function refreshLogs() { + try { + const logs = await CCApi.getProxyLogs(); + proxyLogs.set(logs); + } catch (err) { + console.error('Failed to fetch proxy logs:', err); + } +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 00000000..664a057a --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,9 @@ +import { mount } from 'svelte' +import './app.css' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app')!, +}) + +export default app diff --git a/frontend/src/pages/Codex.svelte b/frontend/src/pages/Codex.svelte new file mode 100644 index 00000000..82b3a78d --- /dev/null +++ b/frontend/src/pages/Codex.svelte @@ -0,0 +1,1059 @@ + + +{#if showToastBanner} +
+ {toastMsg} +
+{/if} + +
+
+

{$t('codex.title')}

+

{$t('codex.subtitle')}

+
+ +
+ + + + +
+ + {#if currentTab === 'agents' || currentTab === 'memories'} +
+
+ +
+ +
+ + + {currentTab === 'agents' ? $t('codex.agentsWarn') : $t('codex.memoriesWarn')} + +
+ + +
+ {#if isEditing} + + {:else} +
{mdContent || 'Document is empty.'}
+ {/if} +
+ + +
+ {#if !isEditing} + + + + {:else} + + + {/if} +
+
+ {/if} + + + {#if currentTab === 'mcp'} +
+ +
+ + +
+ + + {#if subMcpTab === 'servers'} +
+
+ {#each Object.keys(mcpServers) as serverId} + + {/each} + +
+ + +
+ {#if selectedMcpServerId} +

Server ID: {selectedMcpServerId}

+ +
+ + +
+ + +
+ +
+ {#each mcpArgs as arg, idx} +
+ {arg} + +
+ {/each} +
+
+ + +
+
+ + +
+ +
+ {#each Object.entries(mcpEnvs) as [envKey, envVal]} +
+ {envKey} = {envVal} + +
+ {/each} +
+
+ + + +
+
+ +
+ + +
+ {:else} +

Select or add an MCP server on the left sidebar.

+ {/if} +
+
+ {/if} + + + {#if subMcpTab === 'plugins'} +
+ + +
+ {#each mcpPlugins.filter(p => p.id.toLowerCase().includes(pluginSearchQuery.toLowerCase())) as plugin} +
+
+ {plugin.name || plugin.id} + {plugin.description || 'No description provided.'} +
+
+ + +
+
+ {:else} +

No plugins installed yet.

+ {/each} +
+
+ {/if} +
+ {/if} + + + {#if currentTab === 'skills'} +
+
+

Installed Skills

+
+ + +
+
+ +
+
+ {#each skills as skill} +
+ {skill.name || 'Unnamed Skill'} + {#if skill.hasSkillMd} + ✓ SKILL.md + {:else} + ✗ no SKILL.md + {/if} +
+ {:else} +

No skills detected. Click Open Folder to add skill scripts.

+ {/each} +
+ +
+

Existing Backups

+ {#each skillBackups as backup} +
+ {backup.filename} + +
+ {:else} +

No backups created yet.

+ {/each} +
+
+ + +
+
+

SKILL.md documentation

+ {#if !skillMdEditing} + + {:else} +
+ + +
+ {/if} +
+ +
+ {#if skillMdEditing} + + {:else} +
{skillMdContent || 'SKILL.md is empty.'}
+ {/if} +
+
+
+ {/if} +
+
+
+ + +{#if showHistoryModal} +
+
+

History snapshots (Cap 10)

+ +
+ {#each historyList as entry} +
+
+ Index: {entry.index} + Time: {new Date(entry.time || Date.now()).toLocaleString()} +
+ +
+ {:else} +

No history snapshots for this path.

+ {/each} +
+ +
+ +
+
+
+{/if} + + +{#if showRawTomlEditor} +
+
+

Edit Raw MCP config.toml

+

{$t('codex.mcp.rawWarn')}

+ + + +
+ + +
+
+
+{/if} + + diff --git a/frontend/src/pages/Dashboard.svelte b/frontend/src/pages/Dashboard.svelte new file mode 100644 index 00000000..b8c72c90 --- /dev/null +++ b/frontend/src/pages/Dashboard.svelte @@ -0,0 +1,873 @@ + + + +{#if showToastBanner} +
+ {toastMsg} +
+{/if} + +
+ +
+

{$t('dashboard.title')}

+

{$t('dashboard.subtitle')}

+ + +
+ {#each $providers as provider, idx (provider.id)} + +
dragStart(e, idx)} + on:dragover|preventDefault + on:drop|preventDefault={(e) => drop(e, idx)} + > + + + + {provider.name} + {provider.baseUrl} + + + {Object.values(provider.mappings || {}).filter(Boolean).slice(0, 2).join(' / ') || provider.apiFormat} + + + + {#if provider.default} + + + {$t('status.active')} + + {:else} + + {/if} + + + + + + + + + {#if speedTesting[provider.id || '']} + Testing... + {:else if speedResults[provider.id || '']} + {speedResults[provider.id || '']} + {/if} + + {#if usageQuerying[provider.id || '']} + Querying... + {:else if usageResults[provider.id || '']} + {usageResults[provider.id || '']} + {/if} + +
+ {:else} + +
+

{$t('providers.empty')}

+
+ {/each} +
+ + + {#if presets.filter(p => !$providers.some(configured => configured.id === p.id || configured.name === p.name)).length > 0} +
+
+

{$t('dashboard.availablePresets')}

+

{$t('dashboard.availablePresetsHint')}

+
+
+ {#each presets.filter(p => !$providers.some(configured => configured.id === p.id || configured.name === p.name)) as preset} + + {/each} +
+
+ {/if} +
+ + +
+ +
+

{$t('dashboard.desktopStatus')}

+
+ +
+ + {#if $appStatus.desktopHealth.needsApply} + {$t('status.needsApply')} + {:else if $appStatus.desktopConfigured} + {$t('status.configured')} + {:else} + {$t('status.notConfigured')} + {/if} + +
+ + +
+

{$t('dashboard.proxyStatus')}

+
+ + {#if $appStatus.proxyRunning} + + {/if} +
+ + {#if $appStatus.proxyRunning} + {$t('status.running')}: {$appStatus.proxyPort} + {:else} + {$t('status.stopped')} + {/if} + +
+ + +
+

{$t('dashboard.activeProvider')}

+ + {$appStatus.activeProvider.name} +
+ + +
+

{$t('dashboard.pluginUnlockStatus')}

+
+ +
+ {getPluginUnlockText(pluginUnlockState.status)} + + {#if pluginUnlockState.status === 'injected' || pluginUnlockState.status === 'connected'} +
+ + + +
+ {/if} +
+
+ + + {#if $appStatus.desktopHealth.needsApply && $appStatus.desktopHealth.issues.length > 0} +
+ + {$appStatus.desktopHealth.issues.join('; ')} +
+ {/if} + + +
+ + + +
+ + +
+
+
+ +

{$t('dashboard.recentActivity')}

+
+ +
+ +
+ {#each activities as item} +
+ + {item.text} +
+ {:else} +
+

{$t('codex.historyEmpty') || '暂无最近操作'}

+
+ {/each} +
+
+
+ + diff --git a/frontend/src/pages/Guide.svelte b/frontend/src/pages/Guide.svelte new file mode 100644 index 00000000..9c849c17 --- /dev/null +++ b/frontend/src/pages/Guide.svelte @@ -0,0 +1,263 @@ + + +
+
+

{$t('guide.title')}

+

{$t('guide.subtitle')}

+
+ +
+ +
+

{$t('guide.prereqTitle')}

+

{@html $t('guide.prereqBody')}

+
+ + +
+

{$t('guide.quickStartTitle')}

+ +
+ +
+
1
+

{$t('guide.step1')}

+

{$t('guide.step1Text')}

+ +
+ + +
+
2
+

{$t('guide.step2')}

+

{$t('guide.step2Text')}

+ +
+ + +
+
3
+

{$t('guide.step3')}

+

{$t('guide.step3Text')}

+
+ + +
+
4
+

{$t('guide.step4')}

+

{@html $t('guide.step4Text')}

+
+ + +
+
5
+

{$t('guide.step5')}

+

{@html $t('guide.step5Text')}

+
+
+
+ + +
+

{$t('guide.advancedTitle')}

+ +
+
+

{$t('guide.advThinkingTitle')}

+

{$t('guide.advThinkingText')}

+
+ +
+

{$t('guide.advCompatTitle')}

+

{$t('guide.advCompatText')}

+
+ +
+

{$t('guide.advBackupTitle')}

+

{$t('guide.advBackupText')}

+
+ +
+

{$t('guide.advRestoreTitle')}

+

{$t('guide.advRestoreText')}

+
+
+
+ + +
+

{$t('guide.troubleshootTitle')}

+ +
+
+

{$t('guide.tsFeedbackTitle')}

+

{$t('guide.tsFeedbackText')}

+
+ +
+

{$t('guide.tsLogsTitle')}

+

{$t('guide.tsLogsText')}

+
+ +
+

{$t('guide.tsRestartTitle')}

+

{@html $t('guide.tsRestartText')}

+
+ +
+

{$t('guide.tsSpeedTitle')}

+

{@html $t('guide.tsSpeedText')}

+
+
+
+
+
+ + diff --git a/frontend/src/pages/Providers.svelte b/frontend/src/pages/Providers.svelte new file mode 100644 index 00000000..b83c13d5 --- /dev/null +++ b/frontend/src/pages/Providers.svelte @@ -0,0 +1,414 @@ + + +{#if showToastBanner} +
+ {toastMsg} +
+{/if} + +
+
+
+

{$t('providers.title')}

+

{$t('providers.subtitle')}

+
+ +
+ + +
+
+

{$t('providers.modelMenuTitle')}

+

+ {currentExposeAll ? $t('providers.modelMenuAllHint') : $t('providers.modelMenuSingleHint')} +

+
+ +
+ + +
+
+ + {$t('providers.name')} + {$t('providers.baseUrl')} + {$t('providers.mapping')} + {$t('providers.status')} + {$t('providers.actions')} +
+ +
+ {#each $providers as provider} +
+ + + {#if provider.logo} + {provider.name} + {:else} + + {/if} + + + + + {provider.name} + {#if provider.isBuiltin} + Built-in + {/if} + + + + {provider.baseUrl} + + + + {Object.values(provider.mappings || {}).filter(Boolean).slice(0, 2).join(' / ') || provider.apiFormat} + + + + + {#if provider.default} + + + {$t('status.active')} + + {:else} + + + {$t('status.standby')} + + {/if} + + + + + {#if !provider.default} + + {/if} + + + +
+ {:else} +
+

{$t('providers.empty')}

+
+ {/each} +
+
+
+ + diff --git a/frontend/src/pages/ProvidersAdd.svelte b/frontend/src/pages/ProvidersAdd.svelte new file mode 100644 index 00000000..a64c0103 --- /dev/null +++ b/frontend/src/pages/ProvidersAdd.svelte @@ -0,0 +1,968 @@ + + +{#if showToastBanner} +
+ {toastMsg} +
+{/if} + +
+
+

{isEditMode ? $t('providersAdd.editTitle') : $t('providersAdd.title')}

+

{isEditMode ? $t('providersAdd.editTitle') : $t('providersAdd.subtitle')}

+
+ +
+ +
+
handleSave(false)}> + +
+ + +
+ + + {#if !isOauth} +
+
+ + +
+ + {#if speedTesting} +

Testing latency...

+ {:else if speedResult} +

{speedResult}

+ {/if} +
+ {/if} + + + {#if !isOauth && apiFormat !== 'grok_web'} +
+ +
+ + +
+
+ {/if} + + + {#if isOauth} +
+ + {#if oauthLoading} +

Waiting for Google browser authentication...

+ {:else if oauthStatus} + {#if oauthStatus.loggedIn} +

+ + Logged in: {oauthStatus.email || '?'} (Project: {oauthStatus.projectId || '?'}) +

+
+ + +
+ {:else} +

+ + {$t(oauthPrefix + '.statusNotLoggedIn')} +

+ + {/if} + {:else} +

Loading authentication status...

+ {/if} +

{oauthStatus?.expiresAt ? `Token expires: ${new Date(oauthStatus.expiresAt).toLocaleString()}` : ''}

+

{$t(oauthPrefix + '.tosWarning')}

+
+ {/if} + + + {#if apiFormat === 'grok_web'} +
+ +

{$t('grokWeb.hint')}

+ +
+ + +
+ +
+ + +
+ +
+ Advanced Custom Options +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+

{$t('grokWeb.tosWarning')}

+
+ {/if} + + + {#if selectedPreset?.supportsWebSearch} +
+ +

+ {#if selectedPreset.id === 'kimi'} + {$t('providersAdd.webSearchEnabledHint.kimi')} + {:else if selectedPreset.id === 'mimo'} + {$t('providersAdd.webSearchEnabledHint.xiaomi-mimo-payg')} + {:else} + {$t('providersAdd.webSearchEnabledHint.default')} + {/if} +

+
+ {/if} + + +
+ + {#if selectedPreset?.id !== 'custom-third-party'} + + {:else} + + {/if} +
+ + +
+
+
+

{$t('providersAdd.mappingTitle')}

+

{$t('providersAdd.mappingSubtitle')}

+
+ +
+ + {#if modelFetching} +

Fetching upstream models...

+ {:else if modelFetchResult} +

{modelFetchResult}

+ {/if} + +
+ {#each Object.keys(mappings) as mapKey} +
+ + +
+ + + + {#if fetchedModels.length > 0} + + + {#if showModelDropdown[mapKey]} + + + {/if} + {/if} +
+
+ {/each} +
+
+ + +
+ + + +
+
+
+ + +
+

{$t('providersAdd.presets')}

+

{$t('providersAdd.presetsHint')}

+ +
+ {#each presets as preset} + + {/each} +
+
+
+
+ + diff --git a/frontend/src/pages/Proxy.svelte b/frontend/src/pages/Proxy.svelte new file mode 100644 index 00000000..8bf8752a --- /dev/null +++ b/frontend/src/pages/Proxy.svelte @@ -0,0 +1,450 @@ + + +{#if showToastBanner} +
+ {toastMsg} +
+{/if} + +
+
+

{$t('proxy.title')}

+

{$t('proxy.subtitle')}

+
+ + +
+
+ +
+ {$appStatus.proxyRunning ? $t('status.running') : $t('status.stopped')} + {$t('proxy.localhost')} +
+
+ + +
+ + +
+ + + +
+ + +
+
+ {$t('proxy.stats.total')} + {$proxyStatus.stats.total} +
+
+ {$t('proxy.stats.success')} + {$proxyStatus.stats.success} +
+
+ {$t('proxy.stats.failed')} + {$proxyStatus.stats.failed} +
+
+ {$t('proxy.stats.today')} + {$proxyStatus.stats.today} +
+
+ + +
+
+
+ + +
+ + +
+ + +
+ {#each $proxyLogs as log} +
+ {log.at} + [{log.level}] + {log.message} +
+ {:else} +
+ +

No active logs in proxy forwarding service.

+
+ {/each} +
+
+
+ + diff --git a/frontend/src/pages/Settings.svelte b/frontend/src/pages/Settings.svelte new file mode 100644 index 00000000..61f05fdd --- /dev/null +++ b/frontend/src/pages/Settings.svelte @@ -0,0 +1,913 @@ + + +{#if showToastBanner} +
+ {toastMsg} +
+{/if} + +
+
+

{$t('settings.title')}

+

{$t('settings.subtitle')}

+
+ +
+ +
+ +
+

Appearance & Language

+ + +
+
+ {$t('settings.theme')} +
+
+ {#each ['default', 'green', 'orange', 'gray', 'dark', 'white'] as color} + + {/each} +
+
+ + +
+
+ {$t('settings.language')} +
+
+ + +
+
+
+ + +
+

Network Configuration

+ +
+
+ {$t('settings.proxyPort')} +
+ updateSettingField('proxyPort', proxyPort)} + /> +
+ +
+
+ {$t('settings.adminPort')} +
+ updateSettingField('adminPort', adminPort)} + /> +
+
+ + +
+

Automation Settings

+ + +
+
+ {$t('settings.autoApplyOnStart')} + +
+

{$t('settings.autoApplyOnStartHint')}

+
+ + +
+
+ {$t('settings.autoUnlockCodexPlugins')} +
+ + +
+
+

{$t('settings.autoUnlockCodexPluginsHint')}

+
+ + +
+
+ {$t('settings.autoWakeCodexPet')} + +
+

{$t('settings.autoWakeCodexPetHint')}

+
+ + +
+
+ {$t('settings.restoreCodexOnExit')} + +
+

{$t('settings.restoreCodexOnExitHint')}

+
+ + +
+
+ {$t('settings.codexNetworkAccess')} + +
+

{$t('settings.codexNetworkAccessHint')}

+
+
+ + +
+
+

{$t('settings.configBackup')}

+
+ + + + + +
+
+

{$t('settings.configBackupHint')}

+ + +
+ {#each backups as backup} +
+ {backup.filename || backup.time || 'Snapshot'} + +
+ {:else} +

{$t('settings.noBackups')}

+ {/each} +
+
+ + +
+
+

{$t('settings.thirdPartyCompat')}

+ +
+

{$t('settings.thirdPartyCompatHint')}

+ + + {#if compatList.length > 0} +
+ {#each compatList as item} +
+ {item.name} + + {item.ok ? '✓ OK' : '✗ Failed'} + + {item.message} +
+ {/each} +
+ {/if} +
+
+ + +
+ +
+

{$t('settings.about')}

+ +
+ {$t('settings.version')} + {appVersion} +
+ +
+ {$t('settings.license')} + MIT License +
+ +
+ GitHub + + + +
+ + +
+
+ Check for Updates + +
+

{updateStatusText}

+ {#if installBtnVisible} + + {/if} +
+
+ + + +
+
+
+ + diff --git a/frontend/svelte.config.js b/frontend/svelte.config.js new file mode 100644 index 00000000..0cf7db32 --- /dev/null +++ b/frontend/svelte.config.js @@ -0,0 +1,2 @@ +/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ +export default {} diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json new file mode 100644 index 00000000..d774b201 --- /dev/null +++ b/frontend/tsconfig.app.json @@ -0,0 +1,20 @@ +{ + "extends": "@tsconfig/svelte/tsconfig.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "module": "esnext", + "types": ["svelte", "vite/client"], + "noEmit": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable checkJs if you'd like to use dynamic types in JS. + * Note that setting allowJs false does not prevent the use + * of JS in `.svelte` files. + */ + "allowJs": true, + "checkJs": true, + "moduleDetection": "force" + }, + "include": ["src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..1ffef600 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 00000000..d3c52ea6 --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "module": "esnext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 00000000..d32eba1d --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [svelte()], +}) diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a8c20892..9ee4fbdc 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -17,11 +17,12 @@ path = "src/main.rs" tauri-build = { version = "2", features = [] } [dependencies] -tauri = { version = "2", features = ["tray-icon"] } +tauri = { version = "2", features = ["tray-icon", "macos-private-api"] } tauri-plugin-shell = "2" tauri-plugin-single-instance = "2" tauri-plugin-dialog = "2" tauri-plugin-deep-link = "2" +window-vibrancy = "0.7.1" serde = { version = "1", features = ["derive"] } serde_json = "1" codex-app-transfer-registry = { path = "../crates/registry" } diff --git a/src-tauri/src/admin/static_files.rs b/src-tauri/src/admin/static_files.rs index 92c8ac36..289a3132 100644 --- a/src-tauri/src/admin/static_files.rs +++ b/src-tauri/src/admin/static_files.rs @@ -10,7 +10,7 @@ use axum::{ use include_dir::{include_dir, Dir}; /// frontend/ 目录在编译期被嵌入。路径相对 src-tauri/Cargo.toml 所在目录。 -static FRONTEND: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../frontend"); +static FRONTEND: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/../frontend/dist"); pub async fn serve_static(req: Request) -> Response { let path = req.uri().path(); diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 569ad72c..1147e9e4 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -61,6 +61,16 @@ fn main() { }); }) .setup(|app| { + #[cfg(target_os = "macos")] + if let Some(window) = app.get_webview_window("main") { + let _ = window_vibrancy::apply_vibrancy( + &window, + window_vibrancy::NSVisualEffectMaterial::Sidebar, + None, + None, + ); + } + let startup_proxy_manager = app.state::>().inner().clone(); let _ = handlers::desktop::restore_codex_if_enabled("startup"); diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 05fba383..dbc476b0 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -4,9 +4,10 @@ "version": "2.1.14", "identifier": "store.alyse.codex-app-transfer", "build": { - "frontendDist": "../frontend" + "frontendDist": "../frontend/dist" }, "app": { + "macOSPrivateApi": true, "withGlobalTauri": true, "windows": [ { @@ -18,7 +19,8 @@ "minWidth": 900, "minHeight": 600, "resizable": true, - "fullscreen": false + "fullscreen": false, + "transparent": true } ], "security": {