diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b8cb8b79..bd8a937e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -134,6 +134,19 @@ jobs: workspaces: '. -> target' key: tauri + # frontend/ 是 Vite 项目, 其 dist 被 src-tauri include_dir! 编译期嵌入。 + # 必须在 cargo check 前 npm build 产出 frontend/dist, 否则 include_dir! panic。 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Build frontend (produce dist for include_dir!) + run: | + npm --prefix frontend ci + npm --prefix frontend run build + - name: cargo check -p codex-app-transfer run: cargo check -p codex-app-transfer --all-targets diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2d7071b..6752a6c4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -174,6 +174,21 @@ jobs: # 因为第二次 cargo tauri build --bundles dmg 时 Tauri 会重新 bundle .app, # 把手动 codesign 结果覆盖 (PR #13 review 实测验证). 必须 Tauri 自己在 # 同一次 build 里完成签名链. + # frontend/ 是 Vite 项目, dist 被 src-tauri include_dir! 编译期嵌入。 + # 必须在 cargo tauri build 前产出 frontend/dist (CI runner 无 node_modules, + # 且 tauri.conf.json 不设 beforeBuildCommand)。setup-node 跨平台。 + - uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Build frontend (produce dist for include_dir!) + shell: bash + run: | + npm --prefix frontend ci + npm --prefix frontend run build + - name: cargo tauri build working-directory: src-tauri shell: bash diff --git a/.gitignore b/.gitignore index 06dce040..2e8edf2a 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,12 @@ feedback-worker/node_modules/ src-tauri/gen/ **/*.rs.bk +# Vue/Vite frontend (Stage 1 起 frontend/ 是 Vite 项目; dist 被 src-tauri +# include_dir! 编译期嵌入, 由 npm run build 生成, 不入仓) +frontend/node_modules/ +frontend/dist/ +frontend/.vite/ + # IDE / editor noise .vscode/ .idea/ diff --git a/Makefile b/Makefile index ebf47257..9634cb50 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,8 @@ help: @echo " tag 触发: git tag v && git push --tags" mac-app: + npm --prefix frontend ci + npm --prefix frontend run build CARGO_TARGET_DIR=target cargo tauri build --bundles app mkdir -p dist/mac rm -rf "dist/mac/Codex App Transfer.app" diff --git a/ONBOARDING.md b/ONBOARDING.md index d3e36701..382f4d31 100644 --- a/ONBOARDING.md +++ b/ONBOARDING.md @@ -110,10 +110,10 @@ codex-app-transfer/ │ ├── codex_integration/ # 守护 ~/.codex/{config.toml,auth.json} 快照 / 还原 + MCP OAuth 凭据可移植保险箱 │ └── gemini_oauth/ # Gemini CLI + Antigravity OAuth flow │ -├── frontend/ # 前端 (静态文件被 include_dir! 编进二进制) -│ ├── css/{tokens,base,responsive}.css + components/.css + pages/.css -│ ├── gallery.html # 组件预览页 (不需 dev server, 直接 open) -│ └── js/api.js # 调 cas://localhost/api/* +├── frontend/ # 前端 (Vue 3 + Vite + TS; npm run build 出 dist/ 被 include_dir! 编进二进制) +│ ├── src/ # main.ts + App.vue (重构进行中: 逐步迁 components/pages/stores/api/i18n) +│ ├── vite.config.ts # base './' + modulePreload.polyfill=false (CSP script-src 'self' 合规) +│ └── public/assets/ # app + provider 图标 (vite 原样拷到 dist 根) │ ├── xtask/ # release-bundle (签名 + latest.json) + gen-fixtures ├── release/ # 内置公钥 PEM (build-time include_str!) @@ -530,7 +530,7 @@ Codex Desktop 默认隐藏 `Plugins` 选项卡(只对 ChatGPT 账号开放)。Pl | `cargo run -p xtask -- release-bundle` 报 "no platform artifacts found" | 这是预期,首次跑生成 keypair 即可 | | MiMo / Kimi 突然连不通 | 先查 `~/.codex-app-transfer/config.json` 里 `apiFormat` / `extraHeaders` 是否完整,缺失就是 healing 没生效 | | 客户端验签失败 | `rsa` / `sha2` crate 版本是否两边对齐(`src-tauri` vs `xtask`),`sha2` 必须带 `oid` feature | -| `cargo tauri dev` 改前端不刷新 | frontend 是 `include_dir!` 编进二进制的,dev 模式仍走文件系统;改 `frontend/*.html|css|js` 不需重编译,改 Rust 需 | +| `cargo tauri dev` 改前端不刷新 | 前端是 Vite 项目,dev 经 `beforeDevCommand` 拉起 vite(`localhost:1420`)+ HMR;改 `frontend/src/*.vue\|*.ts` 自动热更,改 Rust 需重编。prod 是 `npm run build` 出 `frontend/dist/` 被 `include_dir!` 嵌进二进制 | | 单实例锁死(双开打不开) | `tauri_plugin_single_instance` + `fs2::FileExt` 跨进程 file lock 双层;查 `~/.codex-app-transfer/.lock`(或类似) | --- diff --git a/README.en.md b/README.en.md index dd307341..5ae8d8b8 100644 --- a/README.en.md +++ b/README.en.md @@ -221,28 +221,15 @@ The viewer now has six tabs (switch via the "Kind" dropdown): **forward** (proto ### Tweaking the UI -`frontend/css/` is organized as a small component library — no need to grep the whole `style.css`: +The frontend is **Vue 3 + Vite + TypeScript** (`frontend/`, mid-migration from the old vanilla-JS stack). Source lives in `frontend/src/` (SFC single-file components + Pinia + vue-router). -| What to tweak | Where to edit | -|---|---| -| Theme colors / radius / shadow / spacing (design tokens) | `frontend/css/tokens.css` (129 vars + 6 themes) | -| Global reset / body font / focus ring | `frontend/css/base.css` | -| Buttons / cards / forms / badges / modals etc. | `frontend/css/components/.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: +Dev (hot reload): ```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 +cargo tauri dev # beforeDevCommand spins up vite (localhost:1420) + HMR; editing .vue/.ts auto-refreshes ``` -`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`. +Build: `npm --prefix frontend run build` emits `frontend/dist/`, embedded into the binary via `src-tauri`'s `include_dir!` (prod loads over `cas://localhost/` under a strict CSP `script-src 'self'`). The MacBook-style design system and three themes (light / dark / Chinese-ink) land across the refactor stages. ## Troubleshooting diff --git a/README.md b/README.md index b2e2a16a..77d044d4 100644 --- a/README.md +++ b/README.md @@ -220,30 +220,17 @@ viewer 现在有六个分页(顶部 `种类` 下拉切换):**forward**(协议转 forward 分页详情为调 adapter 准备了几件利器(借鉴 [`liaohch3/claude-tap`](https://github.com/liaohch3/claude-tap),MOC-184):**一键 copy-as-cURL**(把 OUTBOUND 复刻成 curl 打上游,秒分「adapter 错 vs 上游错」;凭据已脱敏需自行填回)+ 复制 INBOUND/OUTBOUND body;**INBOUND↔OUTBOUND 行级 diff**(直接看出 adapter 把 Codex 原始请求转成了啥);**单请求 token 用量分解**(输入/输出/总计/缓存命中/推理,兼容 Responses / Chat / Gemini / Anthropic 各家命名);**tools[] 与消息结构化卡片**(跨协议尽力识别,识别不了退回原文)。列表还可按 `provider` / `model` 分组,`j`/`k`(或方向键)键盘上下导航。 -### 想改 UI 样式怎么改 +### 想改 UI 怎么改 -`frontend/css/` 走"组件库"形式拆开,不需要全文 grep `style.css`: +前端是 **Vue 3 + Vite + TypeScript**(`frontend/`,正从旧 vanilla JS 逐步重构迁移)。源码在 `frontend/src/`(SFC 单文件组件 + Pinia + vue-router)。 -| 想改什么 | 改哪个文件 | -|---|---| -| 主题色 / 圆角 / 阴影 / 间距等 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 +cargo tauri dev # 经 beforeDevCommand 拉起 vite (localhost:1420) + HMR,改 .vue/.ts 自动刷新 ``` -`gallery.html` 顶部有主题切换 + 深浅色按钮,改 component css 后刷新即看。`frontend/index.html` 主入口 `` 不需要改 — `style.css` 只是 @import 入口聚合所有子文件。 - -加新组件: 在 `components/` 建 `.css` + 在 `style.css` 加一行 `@import url("components/.css");` + 在 `gallery.html` 加 section。 +打包:`npm --prefix frontend run build` 出 `frontend/dist/`,被 `src-tauri` 的 `include_dir!` 编进二进制(prod 走 `cas://localhost/` 加载,严格 CSP `script-src 'self'`)。MacBook 风设计系统与三套主题(白 / 黑 / 国风)随重构阶段落地。 ## 常见问题 diff --git a/finetune-inkwash.png b/finetune-inkwash.png new file mode 100644 index 00000000..649a7ebc Binary files /dev/null and b/finetune-inkwash.png differ diff --git a/finetune-settings.png b/finetune-settings.png new file mode 100644 index 00000000..adde8f7e Binary files /dev/null and b/finetune-settings.png differ diff --git a/frontend/env.d.ts b/frontend/env.d.ts new file mode 100644 index 00000000..fbc6664f --- /dev/null +++ b/frontend/env.d.ts @@ -0,0 +1,8 @@ +/// +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent, Record, unknown> + export default component +} diff --git a/frontend/index.html b/frontend/index.html index e6de2f90..2f92a3b7 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,1274 +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 地址。

-
-
-
- - -

auto-review 自动审批工具调用时改用的模型,从上方已配置的模型里选(通常选快/便宜模型加速审批)。留空 = 跟随主模型。

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

快捷预设

-

选择后会自动填入 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 配置;如该提供商需要转发服务,会同时启动转发。关闭则需要手动点「应用配置」。

-
-
-
- 显示灰色提供商 -
- -

⚠️ 存在封号风险,开启并使用这些提供商的后果由你自行承担。

-

默认隐藏 Grok(Web)、Gemini CLI、Antigravity 等 TOS 灰色 / 实验性提供商。开启后它们才会出现在「添加提供商」的列表中。

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

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

-

真实账号模式:用真实 ChatGPT 账号登录,避开 CDP 伪造登录带来的额外启动延迟。登录后自动长期保留,活动 auth.json 被改写也会在启动时自动恢复。

-

- 运行状态: - · - 账号状态: -

-
-
-
- Codex 内显示用量信息 -
- -

在 Codex 的固定摘要(pinned summary)弹窗底部显示用量面板:上下文占用、Tokens 速率与累计(所有 provider),以及 5 小时 / 每周额度(仅支持的 provider)。需 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 仍是你本人的原配置。

-

正在读取快照状态…

-
-
-
- MCP 授权可移植保险箱 -
- -

开启后把 Codex 的 MCP 授权改存为可移植文件(~/.codex/.credentials.json),并由本应用在 ~/.codex 之外维护一份镜像;即使切账号 / 误删 / 换机把授权清掉,也能自动恢复。注意:授权 token 将明文存盘(权限 0o600,与其它 CLI agent 一致);此功能不解决授权自然过期(过期仍需重新授权)。

-
-
-
- Codex 原配置完整性检查 -
-

扫描 ~/.codex/config.toml 与本应用历史快照里是否残留 transfer apply 字段。启动时会自动扫一次,发现污染会顶部弹提示。

-

正在扫描…

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

⚠️ 默认关闭(全权限有风险)。开启后让 Codex 走 danger-full-access 沙箱 + approval_policy=never(Codex 官方推荐的 "Full access" 配对):模型可以读写任何文件、用 curl / wget 等联网命令获取实时网页,且所有命令无审批弹窗。**等于完全信任模型**。默认(关闭)时 Codex 走 read-only 沙箱 + on-request 审批:无网络;第三方 provider 原生 web_search 已在协议层 drop(MOC-208),联网搜索改走自研 web_search / web_fetch 工具(需开启「内置联网抓取工具」)。

-
-
-
- - 内置联网抓取工具(transfer 代抓网页) - - -
-
- - - - - -
-

transfer 自己抓取网页内容返给模型(独立于上面的 Codex 沙箱联网开关)。auto=按页面难度自动 curl→wreq→headless 升级,并记住每站最佳档;curl=reqwest 静态抓取;wreq=浏览器 TLS 指纹绕 Cloudflare;headless=无头 Chrome 跑 JS 抓 JS 渲染 SPA。auto / headless 需系统代理(梯子)连通才能抓墙外站,否则自动降级 wreq;首次选 auto / headless 会检测系统 Chrome,未安装则提示下载 chrome-headless-shell(~86MB,仅一次,复用,不打包进安装包)。

-
-
-
- 诊断模式 · 协议流量查看器(开发者) -
- - -

⚠️ 开发者诊断,默认关。开启后在本机独立端口(http://127.0.0.1:18090)实时查看 Codex↔上游 协议转发全流量(原始请求 / 转换后 / 上游回包),用于排查 adapter / 协议映射问题;也可设环境变量 CAS_DIAG_TRACE=1 启动。仅本地 loopback;credential(authorization / api_key 等)落盘前脱敏,但 prompt / 代码 / 模型回复正文会完整记录,故仅供本地排查、勿外传。关闭即停止采集与查看器。

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

一键检查所有 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..f376b771 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,1938 @@ +{ + "name": "codex-app-transfer-frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "codex-app-transfer-frontend", + "version": "0.0.0", + "dependencies": { + "pinia": "^2.3.0", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@iconify-json/lucide": "^1.2.113", + "@types/node": "^22.10.5", + "@vitejs/plugin-vue": "^5.2.1", + "typescript": "^5.7.2", + "unplugin-icons": "^23.0.1", + "vite": "^6.0.7", + "vue-tsc": "^2.2.0" + } + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@iconify-json/lucide": { + "version": "1.2.113", + "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.113.tgz", + "integrity": "sha512-hfHPXZmLAsZkEfSd4kKRPMRM2HOQz1k11XWtSReEWeecOM+nLtZlfHAzv8nSsrJw2scLz3DKqosNMNXd3mP60Q==", + "dev": true, + "license": "ISC", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.3.tgz", + "integrity": "sha512-LPKOXPn/zV+zis1oOfGWogaXVpqUybF3ZS6SCZIsz8vg0ivVp9+fVqyYB7xq0aiST/VhUQYGO1qo6uoYSiEJqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "import-meta-resolve": "^4.2.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==", + "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/@rollup/rollup-android-arm-eabi": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.0.tgz", + "integrity": "sha512-IPIQ55ythEHkfEd9jMEi32OQ7SxURsGA43JI22lj01OLZNt2NUbJX8YUHxkVWyQ6daHPNn0truF5nSj3DQp6YQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.0.tgz", + "integrity": "sha512-M6s9cr10MibETyo8JsOkq+Lo1+lU6hcvb1MApnUql5qte/5hMEgzlN8/ReIKNfRV8rrqX50W1BX9zoUhC192RA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.0.tgz", + "integrity": "sha512-BqCoMoIbn0keKys+dEAdBa70EtOwV1bEsQCUgU9FdiZmmMge/Zk7LlkYGqbrdHR+Frnt0E1FOanly+rlwvvQzw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.0.tgz", + "integrity": "sha512-SIMzST3VFNXDAbeIWDWiFCNM5qncUBDWaEV7NfE7oZbDt2mgfW4MvbKdbYiGOLoM32gbTv608UMd0XktEYSD7w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.0.tgz", + "integrity": "sha512-ezjfSQMP7ArdUsbBwbQIfwAlhE84I2iVnzQNCFSveqV42q+BmKlzVpf7mxv5EchLcoWU4y6/heFzVg1F+hodUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.0.tgz", + "integrity": "sha512-9+qTWGW9AZRhnUgwtTwzNwcPlL87ngkeN0LA+q1bADvmY9aNvWaF2TFW8BZgnQPYxpDI7+rMVLivcd4V737TAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.0.tgz", + "integrity": "sha512-T1dMEQhXA/jkJ/jyMIw9IovK8bSUq7A8kLIlvZTb/6YIVsp2zLavr4F3oyllHWo7eIVJRyE5n3tUjQJEbE1IuQ==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.0.tgz", + "integrity": "sha512-2as0LgT7qQpyceQq6VUJYnumUMUrgGQCWIiDIN9DE0/tglsk6o66uCB4f3djRawAltvfCNLyZZrsqbPA6inCsA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.0.tgz", + "integrity": "sha512-bVURMg+6eNN9C/yc0aVjooZcwTTtYF4YW3xta5pP0//r3o1V8gXEHXWCndj47w/HhwsFroZrFhR+6uQP5T0n0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.0.tgz", + "integrity": "sha512-Ful8pM/2yYI83PViWdFdpZhdI8HJ5qsXANe5atypbHDf+KIBBDsZsbyy8hbXnULVvW9NsTh5DHwbcBftyLTfiw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.0.tgz", + "integrity": "sha512-9Gp/DgrkzfUBmNPVTyPTvay+4xEP7M/clXpj3efXBcm6uTIVIgDg4rqUpqKXvLEuFRVuEpSAOkhgNeecvaZ4Cg==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.0.tgz", + "integrity": "sha512-m9tsJz54LUXkSYM8+8PG81B9IKK5r+2T0clMq4QrS16xFosufU7firBDAZEsDheDs7wTlP7h3++S7lMsU955HA==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.0.tgz", + "integrity": "sha512-3UvJ5PNVU16aJf6M3tFI24pWzAl2/ynfbyRN3ICyQajK1lSkrnVYNnLz3v04J32qKa0FczJc22zeToc0lr2A3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.0.tgz", + "integrity": "sha512-vRWUAbYLGHBZS6Q8Msb2sfnf1fvJf+47t8l/TwOerM2qArzy+IeNMTHrYLHXh95h8MoatPHI5hhSZNs+mGXKPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.0.tgz", + "integrity": "sha512-c00T5SYENHAt86cfW47URaP3Us5vLC/4QO7GYud1G5VNRffCwwCuBspwqYrriuJB+5m0WFzClCn9wed0FBjKvg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.0.tgz", + "integrity": "sha512-krrCDilhXOwFkSkO3Wm9I/f9H0L92XHHwy2fwxjukxIbh0dem8gZqOW5Y8BsHrpJv5qwlRBV+Wl4ZFyRWhUpwg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.0.tgz", + "integrity": "sha512-7pfYFSTc4/rUC/FtAI0Qp6QthDBCIi6/AuP1xYqFk5vanI6KnL5dWKP60OM/05LOsbwTmIcvr6eXC4CJuJ75IA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.0.tgz", + "integrity": "sha512-7SDIalKeIpG0Ifogbbdn58HmSotYMlf23K3dCJEmiVd9Fg36Vmni82iPQec27N3wY4Bvbxftkxz6vSx9OcouTg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.0.tgz", + "integrity": "sha512-eRZevouTH2i1HeAVLqJuLnt256krQkGY0TN6WsTmsIhuzbh457HuWDMakKwmi0Cjadux983CoSr8Lim2QhUIFw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.0.tgz", + "integrity": "sha512-3oVS7FLGa4U1qcvao9ylGxrjXZyUQqR8UwxEcnUEyPX53O/C/mKDZegNXTdHCP+h3e6ta/f1EN38Yif1mmZHYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.0.tgz", + "integrity": "sha512-yTB9TgfWj5wHe5QgktAgXTLLot1gvEjl1NiPPAUiCs4oPrIWFl5V4nC3GrkNdj9LaAU4s94nVrGbGOCqUpyWsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.0.tgz", + "integrity": "sha512-5LOhoaesY3doG1c+ac/2JtgREpKoJr5bUHH8tKY0V8di7+uSV6BwLs2PlR0/yzefGOkR+wE7ZolZphHCsyG5Rw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.0.tgz", + "integrity": "sha512-yYkWHhmbhRTWTnWos5HC4GcPQfjlzzCNbM9e/+GXrLuaBXYA3qSDR9f0Vgufd5S8yX81U8jPKp7ZnAjZFMtRnw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.0.tgz", + "integrity": "sha512-SoTb6lPg25xZlA2ibwQ++ahCCnH+FP0qmEuafMJ4gznZKOlXioKEAeJLgCrqjM98ACziXM9V1amFjICVL4IFoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.0.tgz", + "integrity": "sha512-5L+T1fMX4RIEBoZzT0+sQ0PhTS36NULFmMXtl1TZo44TMAROIMHbZufSOjVWt/Y622BtxgxtaNOokbTDvfsrZA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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": "22.19.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.21.tgz", + "integrity": "sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@volar/language-core": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz", + "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/source-map": "2.4.15" + } + }, + "node_modules/@volar/source-map": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz", + "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@volar/typescript": { + "version": "2.4.15", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz", + "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "path-browserify": "^1.0.1", + "vscode-uri": "^3.0.8" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.38.tgz", + "integrity": "sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.38", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.38.tgz", + "integrity": "sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.38", + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.38.tgz", + "integrity": "sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.38", + "@vue/compiler-dom": "3.5.38", + "@vue/compiler-ssr": "3.5.38", + "@vue/shared": "3.5.38", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.15", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.38.tgz", + "integrity": "sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.38", + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/compiler-vue2": { + "version": "2.7.16", + "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz", + "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==", + "dev": true, + "license": "MIT", + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/language-core": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz", + "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/language-core": "2.4.15", + "@vue/compiler-dom": "^3.5.0", + "@vue/compiler-vue2": "^2.7.16", + "@vue/shared": "^3.5.0", + "alien-signals": "^1.0.3", + "minimatch": "^9.0.3", + "muggle-string": "^0.4.1", + "path-browserify": "^1.0.1" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.38.tgz", + "integrity": "sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.38.tgz", + "integrity": "sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.38", + "@vue/shared": "3.5.38" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.38.tgz", + "integrity": "sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.38", + "@vue/runtime-core": "3.5.38", + "@vue/shared": "3.5.38", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.38.tgz", + "integrity": "sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.38", + "@vue/shared": "3.5.38" + }, + "peerDependencies": { + "vue": "3.5.38" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.38.tgz", + "integrity": "sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==", + "license": "MIT" + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/alien-signals": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz", + "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "dev": true, + "license": "MIT" + }, + "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/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "license": "MIT", + "bin": { + "he": "bin/he" + } + }, + "node_modules/import-meta-resolve": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", + "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mlly/node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/muggle-string": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz", + "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "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.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-browserify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "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==", + "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/pinia": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz", + "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.10" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "typescript": ">=4.4.4", + "vue": "^2.7.0 || ^3.5.11" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "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/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.62.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.0.tgz", + "integrity": "sha512-nc72Wgq62I7rtDV4izT5/aaS0zxy3kttkinf9586ApknY3jZO9NYsmtc24fUckA0X7Q2v+ML4a15pdUlV5V/jA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.0", + "@rollup/rollup-android-arm64": "4.62.0", + "@rollup/rollup-darwin-arm64": "4.62.0", + "@rollup/rollup-darwin-x64": "4.62.0", + "@rollup/rollup-freebsd-arm64": "4.62.0", + "@rollup/rollup-freebsd-x64": "4.62.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.0", + "@rollup/rollup-linux-arm-musleabihf": "4.62.0", + "@rollup/rollup-linux-arm64-gnu": "4.62.0", + "@rollup/rollup-linux-arm64-musl": "4.62.0", + "@rollup/rollup-linux-loong64-gnu": "4.62.0", + "@rollup/rollup-linux-loong64-musl": "4.62.0", + "@rollup/rollup-linux-ppc64-gnu": "4.62.0", + "@rollup/rollup-linux-ppc64-musl": "4.62.0", + "@rollup/rollup-linux-riscv64-gnu": "4.62.0", + "@rollup/rollup-linux-riscv64-musl": "4.62.0", + "@rollup/rollup-linux-s390x-gnu": "4.62.0", + "@rollup/rollup-linux-x64-gnu": "4.62.0", + "@rollup/rollup-linux-x64-musl": "4.62.0", + "@rollup/rollup-openbsd-x64": "4.62.0", + "@rollup/rollup-openharmony-arm64": "4.62.0", + "@rollup/rollup-win32-arm64-msvc": "4.62.0", + "@rollup/rollup-win32-ia32-msvc": "4.62.0", + "@rollup/rollup-win32-x64-gnu": "4.62.0", + "@rollup/rollup-win32-x64-msvc": "4.62.0", + "fsevents": "~2.3.2" + } + }, + "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==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "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/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", + "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "acorn": "^8.15.0", + "picomatch": "^4.0.3", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/unplugin-icons": { + "version": "23.0.1", + "resolved": "https://registry.npmjs.org/unplugin-icons/-/unplugin-icons-23.0.1.tgz", + "integrity": "sha512-rv0XEJepajKzDLvRUWASM8K+8+/CCfZn2jtogXqg6RIp7kpatRc/aFrVJn8ANQA09e++lPEEv9yX8cC9enc+QQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/utils": "^3.1.0", + "local-pkg": "^1.1.2", + "obug": "^2.1.1", + "unplugin": "^2.3.11" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@svgr/core": ">=7.0.0", + "@svgx/core": "^1.0.1", + "@vue/compiler-sfc": "^3.0.2", + "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0" + }, + "peerDependenciesMeta": { + "@svgr/core": { + "optional": true + }, + "@svgx/core": { + "optional": true + }, + "@vue/compiler-sfc": { + "optional": true + }, + "svelte": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "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/vscode-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz", + "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vue": { + "version": "3.5.38", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.38.tgz", + "integrity": "sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.38", + "@vue/compiler-sfc": "3.5.38", + "@vue/runtime-dom": "3.5.38", + "@vue/server-renderer": "3.5.38", + "@vue/shared": "3.5.38" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/vue-tsc": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz", + "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@volar/typescript": "2.4.15", + "@vue/language-core": "2.2.12" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 00000000..d87e1112 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,26 @@ +{ + "name": "codex-app-transfer-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc --noEmit && vite build", + "build:nocheck": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "pinia": "^2.3.0", + "vue": "^3.5.13", + "vue-router": "^4.5.0" + }, + "devDependencies": { + "@iconify-json/lucide": "^1.2.113", + "@types/node": "^22.10.5", + "@vitejs/plugin-vue": "^5.2.1", + "typescript": "^5.7.2", + "unplugin-icons": "^23.0.1", + "vite": "^6.0.7", + "vue-tsc": "^2.2.0" + } +} diff --git a/frontend/assets/app-icon.ico b/frontend/public/assets/app-icon.ico similarity index 100% rename from frontend/assets/app-icon.ico rename to frontend/public/assets/app-icon.ico diff --git a/frontend/assets/app-icon.png b/frontend/public/assets/app-icon.png similarity index 100% rename from frontend/assets/app-icon.png rename to frontend/public/assets/app-icon.png diff --git a/frontend/assets/providers/aliyun.ico b/frontend/public/assets/providers/aliyun.ico similarity index 100% rename from frontend/assets/providers/aliyun.ico rename to frontend/public/assets/providers/aliyun.ico diff --git a/frontend/assets/providers/antigravity.png b/frontend/public/assets/providers/antigravity.png similarity index 100% rename from frontend/assets/providers/antigravity.png rename to frontend/public/assets/providers/antigravity.png diff --git a/frontend/assets/providers/anyrouter.png b/frontend/public/assets/providers/anyrouter.png similarity index 100% rename from frontend/assets/providers/anyrouter.png rename to frontend/public/assets/providers/anyrouter.png diff --git a/frontend/assets/providers/deepseek.ico b/frontend/public/assets/providers/deepseek.ico similarity index 100% rename from frontend/assets/providers/deepseek.ico rename to frontend/public/assets/providers/deepseek.ico diff --git a/frontend/assets/providers/gemini.svg b/frontend/public/assets/providers/gemini.svg similarity index 100% rename from frontend/assets/providers/gemini.svg rename to frontend/public/assets/providers/gemini.svg diff --git a/frontend/assets/providers/google-ai-studio.png b/frontend/public/assets/providers/google-ai-studio.png similarity index 100% rename from frontend/assets/providers/google-ai-studio.png rename to frontend/public/assets/providers/google-ai-studio.png diff --git a/frontend/assets/providers/grok.svg b/frontend/public/assets/providers/grok.svg similarity index 100% rename from frontend/assets/providers/grok.svg rename to frontend/public/assets/providers/grok.svg diff --git a/frontend/assets/providers/kimi.ico b/frontend/public/assets/providers/kimi.ico similarity index 100% rename from frontend/assets/providers/kimi.ico rename to frontend/public/assets/providers/kimi.ico diff --git a/frontend/assets/providers/minimax.ico b/frontend/public/assets/providers/minimax.ico similarity index 100% rename from frontend/assets/providers/minimax.ico rename to frontend/public/assets/providers/minimax.ico diff --git a/frontend/assets/providers/qiniu.ico b/frontend/public/assets/providers/qiniu.ico similarity index 100% rename from frontend/assets/providers/qiniu.ico rename to frontend/public/assets/providers/qiniu.ico diff --git a/frontend/assets/providers/xiaomi-mimo.png b/frontend/public/assets/providers/xiaomi-mimo.png similarity index 100% rename from frontend/assets/providers/xiaomi-mimo.png rename to frontend/public/assets/providers/xiaomi-mimo.png diff --git a/frontend/assets/providers/zhipu.png b/frontend/public/assets/providers/zhipu.png similarity index 100% rename from frontend/assets/providers/zhipu.png rename to frontend/public/assets/providers/zhipu.png diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 00000000..f56c012e --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,7 @@ + + + diff --git a/frontend/src/components/ui/AppButton.vue b/frontend/src/components/ui/AppButton.vue new file mode 100644 index 00000000..2c1219b4 --- /dev/null +++ b/frontend/src/components/ui/AppButton.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/frontend/src/components/ui/AppSwitch.vue b/frontend/src/components/ui/AppSwitch.vue new file mode 100644 index 00000000..0f03df2c --- /dev/null +++ b/frontend/src/components/ui/AppSwitch.vue @@ -0,0 +1,52 @@ + + + + + diff --git a/frontend/src/components/ui/SegmentedControl.vue b/frontend/src/components/ui/SegmentedControl.vue new file mode 100644 index 00000000..c121b3af --- /dev/null +++ b/frontend/src/components/ui/SegmentedControl.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/frontend/src/components/ui/SettingsGroup.vue b/frontend/src/components/ui/SettingsGroup.vue new file mode 100644 index 00000000..5ca76a88 --- /dev/null +++ b/frontend/src/components/ui/SettingsGroup.vue @@ -0,0 +1,35 @@ + + + + + diff --git a/frontend/src/components/ui/SettingsRow.vue b/frontend/src/components/ui/SettingsRow.vue new file mode 100644 index 00000000..ef75678c --- /dev/null +++ b/frontend/src/components/ui/SettingsRow.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/frontend/src/components/ui/ThemeSwitcher.vue b/frontend/src/components/ui/ThemeSwitcher.vue new file mode 100644 index 00000000..431e9262 --- /dev/null +++ b/frontend/src/components/ui/ThemeSwitcher.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/frontend/src/composables/useAppearance.ts b/frontend/src/composables/useAppearance.ts new file mode 100644 index 00000000..854a9b16 --- /dev/null +++ b/frontend/src/composables/useAppearance.ts @@ -0,0 +1,47 @@ +import { ref } from 'vue' + +export type Appearance = 'light' | 'dark' | 'inkwash' + +const STORAGE_KEY = 'cas:appearance' +const VALID: Appearance[] = ['light', 'dark', 'inkwash'] + +// 模块级单例: 全 app 共享当前主题 +const current = ref('light') + +function normalizeLegacy(v?: string | null): Appearance | null { + if (!v) return null + if (v === 'dark') return 'dark' + if (v === 'inkwash') return 'inkwash' + // 旧 6-palette(default/white/gray/green/orange)统一收敛到 light + if (['light', 'default', 'white', 'gray', 'green', 'orange'].includes(v)) return 'light' + return null +} + +export function useAppearance() { + function set(theme: Appearance, persist = true) { + const t = VALID.includes(theme) ? theme : 'light' + current.value = t + document.documentElement.setAttribute('data-theme', t) + if (persist) { + try { + localStorage.setItem(STORAGE_KEY, t) + } catch { + /* localStorage 不可用时忽略 */ + } + // Stage 3 接 settings store 后, 这里同步 PUT /api/settings { theme: t } + } + } + + function load(serverTheme?: string) { + let fromLocal: string | null = null + try { + fromLocal = localStorage.getItem(STORAGE_KEY) + } catch { + /* ignore */ + } + const t = normalizeLegacy(serverTheme) ?? normalizeLegacy(fromLocal) ?? 'light' + set(t, false) + } + + return { current, set, load } +} diff --git a/frontend/src/i18n/en.ts b/frontend/src/i18n/en.ts new file mode 100644 index 00000000..4690cfe7 --- /dev/null +++ b/frontend/src/i18n/en.ts @@ -0,0 +1,761 @@ +// Auto-extracted from legacy frontend/js/i18n.js (Stage 2). 勿手改, 改 key 走原 i18n 流程或后续 stage 整理。 +export default { + "pluginUnlock.disconnected": "Not running", + "pluginUnlock.connecting": "Connecting…", + "pluginUnlock.connected": "Connected, awaiting injection", + "pluginUnlock.injected": "Unlocked", + "pluginUnlock.nativeRelay": "Unlocked (real account)", + "realAccount.sectionHint": "Real-account mode: use a real ChatGPT account instead of the CDP-spoofed login to avoid Codex's extra startup latency. After logging in or importing it's kept long-term; if the active auth.json is overwritten it's auto-restored on startup.", + "realAccount.login": "Log in real account", + "realAccount.forceEnable": "Force enable (high latency)", + "realAccount.loginStarted": "Login started; complete authorization in your browser", + "realAccount.forceEnabled": "Force-enabled (high latency)", + "realAccount.forceEnableTitle": "Force enable (high latency)", + "realAccount.forceEnableConfirm": "Force enable", + "realAccount.forceEnableWarn": "Force-enable injects a spoofed login via CDP, adding a noticeable startup delay every time Codex launches (potentially tens of seconds on Windows). Prefer \"Log in real account\" to avoid it. Force enable anyway?", + "realAccount.reloginRequired": "Real ChatGPT account expired; auto-unlock turned off automatically — please log in again", + "realAccount.statusUnavailable": "Account status temporarily unavailable, please retry shortly", + "realAccount.noAccountTitle": "No valid account detected", + "realAccount.noAccountDesc": "No valid real ChatGPT account detected. Log in a valid account (recommended: Codex shows Plugins natively with no extra startup latency), or force-enable (CDP-spoofed login, noticeable latency on every launch).", + "realAccount.gateProxyTitle": "Network proxy not connected", + "realAccount.gateProxyDesc": "Your account is available, but the system proxy is not connected. Plugins and third-party models are reached through the proxy — please start your proxy service and retry, or force-enable (CDP-spoofed login, noticeable latency on every launch, and plugins may still fail to load while the proxy is down).", + "realAccount.gateBothTitle": "No proxy and no account", + "realAccount.gateBothDesc": "Please start your proxy service and log in a ChatGPT account, then retry; or force-enable (CDP-spoofed login, noticeable latency on every launch).", + "dashboard.systemProxyStatus": "Network Proxy", + "systemProxy.connected": "Connected", + "systemProxy.disconnected": "Disconnected", + "systemProxy.pac": "Auto-config (PAC)", + "systemProxy.notConfigured": "Not configured", + "status.checking": "Checking…", + "status.unknown": "Unknown", + "realAccount.runStatusLabel": "Run status", + "realAccount.acctStatusLabel": "Account status", + "realAccount.runOn": "Enabled", + "realAccount.runOff": "Disabled", + "realAccount.runOnProxyDown": "Enabled (network proxy down)", + "realAccount.acctOk": "Obtained", + "realAccount.acctFail": "Not obtained", + "realAccount.acctExpired": "Account expired", + "realAccount.acctFetching": "Obtaining…", + "realAccount.import": "Import file", + "realAccount.imported": "Imported and persisted real account", + "realAccount.importExpired": "Imported, but this account's login has expired — re-export a fresh file or use \"Log in real account\"", + "realAccount.importBadJson": "File is not valid JSON", + "realAccount.importPickTitle": "Pick chatgpt-mode auth.json (import real account)", + "realAccount.forget": "Clear real account", + "realAccount.forgetConfirm": "Clear the real account? Switches back to apikey mode (Codex stops showing Plugins); your login is kept and auto-restored when you quit transfer.", + "realAccount.modeEnabled": "Real-account mode enabled", + "realAccount.modeDisabled": "Real-account mode disabled (switched to apikey, login kept)", + "realAccount.forgetApplyFailed": "Mirror cleared, but switching to apikey failed — Plugins may still be unlocked; retry or restart Codex", + "realAccount.forgotten": "Cleared the real account", + "realAccount.kept": "kept long-term", + "nav.dashboard": "Dashboard", + "nav.providers": "Providers", + "nav.models": "Model Mapping", + "nav.desktop": "Desktop", + "nav.proxy": "Forwarding", + "nav.usage": "Usage", + "nav.settings": "Settings", + "usage.title": "Token usage report", + "usage.subtitle": "Parsed from ~/.codex/sessions/ rollout JSONL — parser vendored from ryoppippi/ccusage", + "usage.refresh": "Refresh", + "usage.viewDaily": "Daily", + "usage.viewModel": "By model", + "usage.viewConversation": "By conversation", + "usage.empty": "No usage data yet — start forwarding conversations to populate", + "usage.loading": "Scanning rollout…", + "usage.loadError": "Load failed", + "usage.kpi.totalInput": "Total input", + "usage.kpi.totalOutput": "Total output", + "usage.kpi.totalTokens": "Total tokens", + "usage.kpi.conversations": "Conversations", + "usage.col.date": "Date", + "usage.col.model": "Model", + "usage.col.input": "Input", + "usage.col.output": "Output", + "usage.col.cacheCreate": "Cache Create", + "usage.col.cacheRead": "Cache Read", + "usage.col.reasoning": "Reasoning", + "usage.col.total": "Total", + "usage.col.turns": "Turns", + "usage.col.lastActivity": "Last", + "usage.col.conversation": "Conversation", + "usage.col.cacheHit": "Cache hit", + "usage.cacheModal.title": "Cache hit distribution", + "usage.cacheModal.overall": "Overall hit rate", + "usage.cacheModal.empty": "No cache data for this conversation", + "usage.cacheModal.loading": "Loading…", + "usage.cacheModal.turn": "turn", + "usage.cacheModal.hitInput": "Cached input", + "usage.cacheModal.totalInput": "Total input", + "usage.cacheModal.output": "Output", + "nav.codex": "Codex Docs", + "nav.theme": "Theme", + "nav.guide": "Guide", + "theme.title": "Codex Desktop Theme", + "theme.subtitle": "Injects CSS / DOM into the running Codex Desktop via CDP to override the native look. Off by default; once enabled pick a built-in theme. Codex Desktop full-page reload re-applies automatically; restarting Codex.app from this tool also auto-applies the selected theme.", + "theme.toggleLabel": "Enable Codex Desktop Theme", + "theme.toggleHint": "A persistent preference: turning on saves it and best-effort applies immediately (if Codex wasn't launched via this tool, you'll be prompted to restart to take effect); turning off only clears the preference — any injected theme stays until the next Codex restart. Independent from Plugin Unlock — the two do not affect each other.", + "theme.applyBtn": "Apply", + "theme.clearBtn": "Clear", + "theme.restartCodexBtn": "Restart Codex", + "theme.restartToast": "Restart Codex requested", + "theme.restartFailed": "Restart failed", + "theme.applied": "Applied", + "theme.failed": "Failed", + "theme.disabled": "Disabled", + "theme.pickFirst": "Pick a theme first", + "theme.appliedToast": "Theme applied", + "theme.clearedToast": "Theme cleared", + "theme.applyFailed": "Apply failed", + "theme.clearFailed": "Clear failed", + "theme.loadFailed": "Failed to load theme list", + "theme.disabledPendingRestart": "Disabled. The applied theme will be removed after Codex restarts.", + "theme.savedTitle": "Theme preference saved", + "theme.savedPendingRestart": "Codex isn't running via this tool (or its debug port is unavailable); the theme will take effect after Codex restarts.", + "theme.savedPendingRestartToast": "Saved. The theme will take effect after Codex restarts.", + "theme.restartNow": "Restart now", + "theme.restartLater": "Restart later", + "theme.saveFailed": "Save failed", + "theme.pendingRestart": "Pending restart", + "theme.uploadOk": "Custom theme saved", + "theme.uploadFailed": "Upload failed", + "theme.uploadTooLarge": "Image too large (>20MB)", + "theme.restoreHidden": "Restore hidden", + "codex.title": "Codex Doc Management", + "codex.subtitle": "", + "codex.assetType": "Asset type", + "codex.tabAgents": "Agents", + "codex.tabMcp": "MCP", + "codex.tabMemories": "Memories", + "codex.tabSkills": "Skills", + "codex.tabConversations": "Sessions", + "codex.conv.searchPlaceholder": "Search (title / cwd / id)", + "codex.conv.kindAll": "All", + "codex.conv.kindActive": "Active", + "codex.conv.kindArchived": "Archived", + "codex.conv.cwdAll": "All projects", + "codex.conv.refresh": "Refresh", + "codex.conv.selectAll": "Select all (filtered)", + "codex.conv.formatJsonl": "Raw JSONL", + "codex.conv.options": "Options", + "codex.conv.exportSelected": "Export selected", + "codex.conv.exportSelectedN": "Export selected ({count})", + "codex.conv.warnRedact": "Secrets (sk- / cas_ / JWT / Bearer) are redacted by default. Reasoning hidden; tool calls included; output > 2KB truncated. Adjust in \"Options\".", + "codex.conv.summary": "{count} sessions", + "codex.conv.loading": "Loading…", + "codex.conv.noResults": "No matching sessions", + "codex.conv.selectPrompt": "Select a session to see details.", + "codex.conv.turns": "turns", + "codex.conv.roleUser": "User", + "codex.conv.roleAssistant": "Assistant", + "codex.conv.reasoning": "Reasoning", + "codex.conv.compacted": "Autocompact checkpoint", + "codex.conv.toastExported": "Exported {count} sessions", + "codex.conv.toastExportedTo": "Exported {count} sessions → {path}", + "codex.conv.exportFailed": "Export failed", + "codex.conv.saveDialogSingle": "Save conversation", + "codex.conv.saveDialogMulti": "Save conversations zip", + "codex.conv.deleteSelected": "Delete selected", + "codex.conv.deleteSelectedN": "Delete selected ({count})", + "codex.conv.confirmDelete": "Move {count} sessions to system Trash? You can restore them from Finder Trash later.", + "codex.conv.toastDeleted": "Moved {count} sessions to Trash", + "codex.conv.toastDeletedPartial": "Moved {moved} sessions, {failed} failed", + "codex.conv.deleteFailed": "Delete failed", + "codex.conv.deleteFailureDetail": "Some deletes failed", + "codex.conv.defaultDirPlaceholder": "Set default export folder", + "codex.conv.defaultDirPickTitle": "Pick default export folder", + "codex.conv.defaultDirSet": "Default export folder set to: {path}", + "codex.conv.defaultDirCleared": "Default export folder cleared", + "codex.conv.defaultDirPickFailed": "Pick folder failed", + "codex.conv.optionsTitle": "Export options", + "codex.conv.optIncludeReasoning": "Include reasoning blocks", + "codex.conv.optIncludeToolCalls": "Include tool calls + outputs", + "codex.conv.optIncludeSystem": "Include developer / system messages", + "codex.conv.optRedact": "Redact secrets (sk- / cas_ / JWT / Bearer)", + "codex.conv.optToolMax": "Truncate tool output at N chars", + "codex.conv.optionsSaved": "Options saved", + "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\" or paste an absolute path to a non-sensitive AGENTS.md under HOME; system and credential directories are rejected", + "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.memoriesPathAddTitle": "Add MEMORY.md path", + "codex.memoriesPathAddPrompt": "Choose a non-sensitive project directory or MEMORY.md under HOME; system and credential directories are rejected", + "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.saveConfirmStdio": "Save MCP server \"{name}\"?\n\nThis writes to ~/.codex/config.toml and applies to all Codex sessions.\n\n⚠️ This server will run the following command on your machine with your account's permissions:\n\n {cmdline}{extra}\n\nMake sure the command, working directory and environment variables are correct before continuing.", + "codex.mcp.saveConfirmHttp": "Save MCP server \"{name}\"?\n\nThis writes to ~/.codex/config.toml and applies to all Codex sessions.\n\nTransport: streamable_http\nConnects to: {url}{extra}\n\nMake sure the address and the credentials / headers sent to the remote are correct before continuing.", + "codex.mcp.rawApplyConfirm": "Overwrite ~/.codex/config.toml with the edited raw TOML?\n\nThis replaces the entire file and applies to all Codex sessions; any [mcp_servers.*] command runs on your machine with your account's permissions.\n\nMake sure the content is correct before continuing.", + "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.docApplyConfirm": "Apply your changes to {doc}?\n\nThis overwrites {doc} on disk (the previous version goes to History) and affects the AI's behavior.\n\nMake sure the content is correct before continuing.", + "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 snapshot 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.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.droppedToolsTitle": "transfer silently dropped Responses API tool types", + "dashboard.droppedToolsExpand": "Show per-type counts", + "dashboard.droppedToolsHint": "These are Responses API tool types unknown to the transfer adapter (likely Codex upstream introduced new types, or a third-party client sent types Codex does not use). Counted per process, reset on restart. If a type's count keeps growing, add explicit handling in transfer.", + "dashboard.droppedToolsCalls": "calls", + "dashboard.droppedToolsTypes": "types", + "dashboard.restartCodex": "Restart Codex", + "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", + "common.recommended": "Recommended", + "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": "Models shown in Codex", + "providersAdd.mappingSubtitle": "List the models you want to see in the Codex model picker (up to 5). The first one is the default — new conversations use it directly; mapping to Codex slots is handled automatically.", + "providersAdd.defaultModelBadge": "Default", + "providersAdd.addModel": "Add model", + "providersAdd.reviewModel": "Auto-review Model", + "providersAdd.reviewModelFollow": "Follow main model (default)", + "providersAdd.reviewModelHint": "Model used when auto-review auto-approves tool calls; pick one of the models configured above (usually a fast / cheap model to speed up approvals). Leave empty to follow the main model.", + "providersAdd.providerModel": "Provider Model", + "providersAdd.providerModelPlaceholder": "Type or choose model", + "providersAdd.removeMapping": "Remove", + "providersAdd.optionApplied": "Model option applied", + "providersAdd.applyToDesktop": "Generate Codex CLI config", + "providersAdd.applyHint": "Apply saves the provider and model list, makes it the default, and connects Codex CLI to this app. Messages from Codex CLI will then be forwarded to your API URL.", + "providersAdd.gemini1mLabel": "Enable Gemini 1M context", + "providersAdd.gemini1mHint": "When off, Gemini context is capped at ~600K (auto-compaction triggers earlier) for better instruction-following on long contexts; turn on for the full 1M. Off by default.", + "providersAdd.apiFormatLabel": "API protocol", + "providersAdd.mimoLogin.label": "Plan usage quota", + "providersAdd.mimoLogin.button": "Sign in with Xiaomi", + "providersAdd.mimoLogin.hint": "MiMo plan usage requires a Xiaomi account sign-in (the usage API does not accept the API key). Click to sign in via an embedded window; once done it's used to show plan quota in the Codex usage panel.", + "providersAdd.mimoLogin.statusLoggedIn": "Signed in", + "providersAdd.mimoLogin.statusNotLoggedIn": "Not signed in", + "providersAdd.mimoLogin.statusLoggingIn": "Signing in…", + "providersAdd.mimoLogin.success": "Xiaomi sign-in succeeded; plan session captured", + "providersAdd.mimoLogin.cancelled": "Sign-in not completed (window closed); no session saved", + "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.loginCancelled": "Login cancelled", + "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.loginCancelled": "Login cancelled", + "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)", + "zaiOauth.title": "GLM Account Login (Z.ai)", + "zaiOauth.statusLoading": "Loading...", + "zaiOauth.statusNotLoggedIn": "Not logged in — click the button below to log in with your z.ai account (no API key needed)", + "zaiOauth.statusLoggedIn": "Logged in: {email}", + "zaiOauth.loginBtn": "Log in with z.ai account", + "zaiOauth.loginBtnInProgress": "Waiting for browser authorization (up to 5 minutes)...", + "zaiOauth.logoutBtn": "Log out and delete local credentials", + "zaiOauth.hint": "After login the browser window can be closed. No API key needed — uses your GLM Coding Plan subscription quota. Token is persisted to ~/.codex-app-transfer/zai-oauth.json and auto-refreshes before expiry.", + "zaiOauth.loginSuccess": "Login successful! email: {email}", + "zaiOauth.loginCancelled": "Login cancelled", + "zaiOauth.loginFailed": "Login failed: {error}", + "zaiOauth.loginRequired": "Switching to GLM (Z.ai) requires logging in first — click the \"Log in with z.ai account\" button on the form", + "zaiOauth.logoutConfirmed": "Logged out, local credentials deleted", + "zaiOauth.logoutFailedManual": "Logout failed: {error}. Token file may still be at ~/.codex-app-transfer/zai-oauth.json — delete manually to fully sign out", + "zaiOauth.statusFetchFailed": "Status fetch failed: {error}", + "zaiOauth.switchAccountBtn": "Switch account (re-login)", + "bigmodelOauth.title": "GLM Account Login (BigModel)", + "bigmodelOauth.statusLoading": "Loading...", + "bigmodelOauth.statusNotLoggedIn": "Not logged in — click the button below to log in with your Zhipu BigModel account (no API key needed)", + "bigmodelOauth.statusLoggedIn": "Logged in: {email}", + "bigmodelOauth.loginBtn": "Log in with BigModel account", + "bigmodelOauth.loginBtnInProgress": "Waiting for browser authorization (up to 5 minutes)...", + "bigmodelOauth.logoutBtn": "Log out and delete local credentials", + "bigmodelOauth.hint": "After login the browser window can be closed. No API key needed — uses your GLM Coding Plan subscription quota. Token is persisted to ~/.codex-app-transfer/bigmodel-oauth.json and auto-refreshes before expiry.", + "bigmodelOauth.loginSuccess": "Login successful! email: {email}", + "bigmodelOauth.loginCancelled": "Login cancelled", + "bigmodelOauth.loginFailed": "Login failed: {error}", + "bigmodelOauth.loginRequired": "Switching to GLM (BigModel) requires logging in first — click the \"Log in with BigModel account\" button on the form", + "bigmodelOauth.logoutConfirmed": "Logged out, local credentials deleted", + "bigmodelOauth.logoutFailedManual": "Logout failed: {error}. Token file may still be at ~/.codex-app-transfer/bigmodel-oauth.json — delete manually to fully sign out", + "bigmodelOauth.statusFetchFailed": "Status fetch failed: {error}", + "bigmodelOauth.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.showGrayProviders": "Show gray-area providers", + "settings.showGrayProvidersWarn": "⚠️ Account-ban risk: enabling and using these providers is entirely at your own risk.", + "settings.showGrayProvidersHint": "By default, TOS-gray / experimental providers such as Grok (Web), Gemini CLI and Antigravity are hidden. Turn this on to make them appear in the \"Add provider\" list.", + "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.codexQuotaEnabled": "Show usage in Codex", + "settings.codexQuotaEnabledHint": "Shows a usage panel at the bottom of Codex's pinned summary popup: context usage and token rate/total (all providers), plus 5-hour / weekly quota (supported providers only). Codex must be launched from this app; if Codex is already running, restart Codex after toggling.", + "settings.autoUnlockRestartCodex": "Restart Codex", + "settings.pluginUnlockRuntimeStatus": "Runtime status: not detected", + "settings.pluginUnlockRuntimeStatusPrefix": "Runtime status: ", + "settings.autoWakeCodexPet": "Auto-wake Codex desktop pet", + "settings.autoWakeCodexPetHint": "Launching Codex Desktop syncs electron-avatar-overlay-open in ~/.codex/.codex-global-state.json: enabled writes true so the pet wakes up with Codex, disabled writes false to override any leftover true so the pet stops auto-opening.", + "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.mcpCredentialsPortableStore": "Portable MCP auth vault", + "settings.mcpCredentialsPortableStoreHint": "When enabled, Codex MCP authorizations are stored in a portable file (~/.codex/.credentials.json) and this app keeps a mirror outside ~/.codex; if the whole file is wiped by an account switch / accidental delete / new machine, the next launch prompts you to restore from backup (an intentional logout of an individual server is respected and never resurrected). Note: auth tokens are stored in plaintext (mode 0o600, consistent with other CLI agents); this does not fix natural OAuth expiry (expired tokens still need re-authorization).", + "mcp.restorePromptTitle": "Restore MCP authorizations?", + "mcp.restorePromptBody": "~/.codex/.credentials.json is gone, but this app has a backup with {count} MCP authorization(s). Restore from backup? (If you just intentionally logged out of all MCP servers, choose No — the backup is cleared and you won't be asked again.)", + "mcp.restoreDone": "Restored {count} MCP authorization(s) from backup", + "mcp.restoreDismissed": "Ignored and cleared the MCP authorization backup", + "settings.webFetchBackend": "Built-in web fetch tool (transfer fetches pages)", + "settings.newBadge": "NEW", + "settings.webFetchBackendHint": "transfer fetches web page content and returns it to the model (independent of the Codex sandbox network toggle below). auto = automatically escalates curl→wreq→headless by page difficulty and remembers the best tier per site; curl = static reqwest fetch; wreq = browser TLS fingerprint to bypass Cloudflare; headless = headless Chrome that runs JS to scrape JS-rendered SPAs. auto / headless need a connected system proxy (VPN) to reach blocked sites, otherwise they auto-downgrade to wreq; the first time you pick auto / headless it detects system Chrome and offers to download chrome-headless-shell (~86MB, once, reused, not bundled).", + "settings.webFetchBackend.off": "Off", + "settings.webFetchBackend.auto": "auto", + "settings.webFetchAutoNeedsProxy": "The auto / headless tiers need a connected system proxy (VPN) to reach blocked sites. The system proxy is not reachable, so it auto-downgraded to wreq. Please enable your system proxy before selecting auto.", + "settings.headlessChromeTitle": "Chrome not found", + "settings.headlessChromeDesc": "Headless fetching needs a Chromium browser, but no Chrome / Edge / Chromium was detected on the system. Download chrome-headless-shell (~86MB, downloaded once and reused, not bundled)? Cancel reverts to the previous option.", + "settings.headlessChromeConfirm": "Download & enable", + "settings.headlessChromeSystemFound": "System Chrome detected, headless fetching enabled", + "settings.headlessChromeDownloading": "Downloading chrome-headless-shell (~86MB)…", + "settings.headlessChromeDownloaded": "Downloaded, headless fetching enabled", + "settings.headlessChromeFailed": "Download failed, reverted to the previous option", + "settings.webFetchSaveFailed": "Failed to save the setting, reverted to the last saved option", + "settings.webFetchSyncWarning": "Setting saved, but registering the fetch tool with Codex failed (transfer will retry automatically on next start). If it keeps failing, check write permissions on ~/.codex/config.toml.", + "settings.codexNetworkAccess": "Allow Codex network tools (full-access mode)", + "settings.codexNetworkAccessHint": "⚠️ Disabled by default (full access is risky). When enabled, 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**. By default (disabled) Codex uses the read-only sandbox + on-request approval: no network access. Provider-native web_search is now dropped at the protocol layer (MOC-208); use the built-in web_search / web_fetch tools instead (enable the built-in web tools in Settings).", + "settings.traceViewerEnabled": "Diagnostic mode · protocol traffic viewer (developer)", + "settings.openTraceViewer": "Open viewer", + "settings.traceViewerEnabledHint": "⚠️ Developer diagnostic, off by default. When on, a read-only web viewer is served on a dedicated local port (http://127.0.0.1:18090) showing the full Codex↔upstream protocol-forwarding traffic (original request / transformed / upstream reply) in real time, for debugging adapter / protocol-mapping issues; can also be started via the CAS_DIAG_TRACE=1 env var. Loopback-only; credentials (authorization / api_key, etc.) are redacted before being written, but request/response payloads (prompts, code, model replies) are recorded in full — keep it local and never share. Turning it off stops capture and the viewer. Note: MCP / OAuth traffic capture additionally requires the plugin-unlocker daemon to be running (i.e. \"auto-unlock Codex Plugins\" on, with Codex launched via this tool with a debug port); without it only forward-trace is captured.", + "settings.residualScanTitle": "Codex original config integrity check", + "settings.residualScanHint": "Scans ~/.codex/config.toml and this app's historical snapshots for fields written by transfer apply (model_catalog_json / openai_base_url, etc.). Runs once at startup; a banner pops up if pollution is found.", + "settings.residualScanStatusUnknown": "Scanning…", + "settings.residualScanStatusClean": "✅ Clean: ~/.codex/config.toml and all snapshots are free of transfer apply residue.", + "settings.residualScanStatusCleanWhileApplied": "✅ Transfer apply is currently in effect; all snapshots are clean (live config containing transfer fields is normal).", + "settings.residualScanStatusDirty": "⚠️ Found {count} files with transfer fields. Click \"Targeted cleanup\" to preview and strip.", + "settings.residualScanStatusError": "Scan failed: {error}", + "settings.residualScanRefresh": "Rescan", + "settings.residualScanRepair": "Targeted cleanup", + "settings.residualScanShowFields": "Show residual fields", + "settings.residualScanShowFieldsClean": "✅ No residual fields; nothing to clean.", + "settings.residualScanPreviewTitle": "Fields to be stripped:", + "settings.residualScanConfirm": "This tool will delete the listed transfer fields from the following files (all other content including model / personality / [projects.*] / mcp_servers is preserved):\n\n{preview}\n\nContinue?", + "settings.residualScanToastCleaned": "Cleaned transfer residue from {count} files.", + "settings.residualScanStartupToast": "Startup scan: found {count} files with transfer fields. Open Settings to review / clean.", + "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": "Apply Once", + "guide.step2Text": "Click Generate Codex CLI config. This app generates the environment variable commands.", + "guide.step2Link": "Manage →", + "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.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": "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?" +} as Record diff --git a/frontend/src/i18n/index.ts b/frontend/src/i18n/index.ts new file mode 100644 index 00000000..dbdf8b50 --- /dev/null +++ b/frontend/src/i18n/index.ts @@ -0,0 +1,37 @@ +import { reactive } from 'vue' +import zh from './zh' +import en from './en' + +export type Locale = 'zh' | 'en' + +const dicts: Record> = { zh, en } + +// reactive locale: 切换语言后所有用 t() 的模板自动重渲染 +export const i18nState = reactive<{ locale: Locale }>({ locale: 'zh' }) + +// 三级 fallback(移植 i18n.js): 当前语言 → 中文 → 键本身 +export function t(key: string): string { + return dicts[i18nState.locale][key] ?? dicts.zh[key] ?? key +} + +export function setLocale(l: Locale) { + i18nState.locale = dicts[l] ? l : 'zh' + document.documentElement.lang = i18nState.locale === 'zh' ? 'zh-CN' : 'en' + try { + localStorage.setItem('cas:lang', i18nState.locale) + } catch { + /* ignore */ + } + // Stage 3 接 settings store 后同步 PUT /api/settings { language } +} + +export function cachedLocale(): Locale { + try { + const v = localStorage.getItem('cas:lang') + if (v === 'zh' || v === 'en') return v + } catch { + /* ignore */ + } + const nav = (navigator.language || 'zh').toLowerCase() + return nav.startsWith('en') ? 'en' : 'zh' +} diff --git a/frontend/src/i18n/zh.ts b/frontend/src/i18n/zh.ts new file mode 100644 index 00000000..c0afd4c8 --- /dev/null +++ b/frontend/src/i18n/zh.ts @@ -0,0 +1,762 @@ +// Auto-extracted from legacy frontend/js/i18n.js (Stage 2). 勿手改, 改 key 走原 i18n 流程或后续 stage 整理。 +export default { + "pluginUnlock.disconnected": "未运行", + "pluginUnlock.connecting": "连接中…", + "pluginUnlock.connected": "已连接,等待注入", + "pluginUnlock.injected": "已解锁", + "pluginUnlock.nativeRelay": "已解锁(真实账号)", + "realAccount.sectionHint": "真实账号模式:用真实 ChatGPT 账号取代 CDP 伪造登录,避开 Codex 启动的额外延迟。登录或导入后会长期保留,活动 auth.json 被改也会在启动时自动恢复。", + "realAccount.login": "登录真实账号", + "realAccount.forceEnable": "强制开启(高延迟)", + "realAccount.loginStarted": "已启动登录,请在浏览器完成授权", + "realAccount.forceEnabled": "已强制开启(高延迟)", + "realAccount.forceEnableTitle": "强制开启(高延迟)", + "realAccount.forceEnableConfirm": "确定强制开启", + "realAccount.forceEnableWarn": "强制开启会用 CDP 注入伪造登录态,Codex 每次启动会多一段明显的加载延迟(Windows 上可能长达数十秒)。建议改用「登录真实账号」避免该延迟。确定要强制开启吗?", + "realAccount.reloginRequired": "真实 ChatGPT 账号已失效,已自动关闭自动解锁,请重新登录", + "realAccount.statusUnavailable": "暂时无法获取账号状态,请稍后重试", + "realAccount.noAccountTitle": "未检测到有效账号", + "realAccount.noAccountDesc": "未检测到有效的真实 ChatGPT 账号。请登录一个有效账号(推荐:Codex 原生显示 Plugins、无额外启动延迟),或强制开启(CDP 注入伪造登录,每次启动有明显延迟)。", + "realAccount.gateProxyTitle": "网络代理未连接", + "realAccount.gateProxyDesc": "检测到账号可用,但系统代理(梯子)未连接。Plugins 与第三方模型都需经代理访问,请先开启代理服务后重试;或强制开启(CDP 注入伪造登录,每次启动有明显延迟,且代理不通时插件仍可能无法加载)。", + "realAccount.gateBothTitle": "未连接代理且未登录账号", + "realAccount.gateBothDesc": "请开启代理服务(梯子)并登录 GPT 账号后重试;或强制开启(CDP 注入伪造登录,每次启动有明显延迟)。", + "dashboard.systemProxyStatus": "网络代理", + "systemProxy.connected": "已连接", + "systemProxy.disconnected": "未连接", + "systemProxy.pac": "自动配置(PAC)", + "systemProxy.notConfigured": "未配置", + "status.checking": "检测中", + "status.unknown": "未知", + "realAccount.runStatusLabel": "运行状态", + "realAccount.acctStatusLabel": "账号状态", + "realAccount.runOn": "已开启", + "realAccount.runOff": "未开启", + "realAccount.runOnProxyDown": "已开启(网络代理未连接)", + "realAccount.acctOk": "获取成功", + "realAccount.acctFail": "获取失败", + "realAccount.acctExpired": "账号已失效", + "realAccount.acctFetching": "获取中…", + "realAccount.import": "导入文件", + "realAccount.imported": "已导入并持久保留真实账号", + "realAccount.importExpired": "已导入,但该账号登录态已失效,请重新导出最新文件或改用「登录真实账号」", + "realAccount.importBadJson": "文件不是合法 JSON", + "realAccount.importPickTitle": "选择 chatgpt 模式 auth.json(导入真实账号)", + "realAccount.forget": "清除真实账号", + "realAccount.forgetConfirm": "清除真实账号?将切回 apikey 模式(Codex 不再显示 Plugins);你的登录态会保留,退出 transfer 时自动恢复。", + "realAccount.modeEnabled": "已开启真实账号模式", + "realAccount.modeDisabled": "已关闭真实账号模式(切 apikey,登录态保留)", + "realAccount.forgetApplyFailed": "已清除镜像,但切 apikey 失败 —— Plugins 可能未关,请重试或重启 Codex", + "realAccount.forgotten": "已清除真实账号", + "realAccount.kept": "已长期保留", + "nav.dashboard": "仪表盘", + "nav.providers": "提供商", + "nav.models": "模型映射", + "nav.desktop": "桌面版", + "nav.proxy": "转发", + "nav.usage": "用量", + "nav.settings": "设置", + "usage.title": "Token 用量统计", + "usage.subtitle": "基于 ~/.codex/sessions/ 的 rollout JSONL,解析层 vendor 自 ryoppippi/ccusage", + "usage.refresh": "刷新", + "usage.viewDaily": "按日", + "usage.viewModel": "按模型", + "usage.viewConversation": "按对话", + "usage.empty": "暂无用量数据 — 通过本应用转发对话后会自动出现", + "usage.loading": "扫描 rollout 中…", + "usage.loadError": "加载失败", + "usage.kpi.totalInput": "总输入", + "usage.kpi.totalOutput": "总输出", + "usage.kpi.totalTokens": "总 tokens", + "usage.kpi.conversations": "对话数", + "usage.col.date": "日期", + "usage.col.model": "模型", + "usage.col.input": "Input", + "usage.col.output": "Output", + "usage.col.cacheCreate": "Cache Create", + "usage.col.cacheRead": "Cache Read", + "usage.col.reasoning": "Reasoning", + "usage.col.total": "Total", + "usage.col.turns": "Turns", + "usage.col.lastActivity": "Last", + "usage.col.conversation": "对话", + "usage.col.cacheHit": "命中率", + "usage.cacheModal.title": "缓存命中分布", + "usage.cacheModal.overall": "整体命中率", + "usage.cacheModal.empty": "该对话无缓存数据", + "usage.cacheModal.loading": "加载中…", + "usage.cacheModal.turn": "轮", + "usage.cacheModal.hitInput": "命中输入", + "usage.cacheModal.totalInput": "总计输入", + "usage.cacheModal.output": "输出", + "nav.codex": "Codex 文档", + "nav.theme": "主题", + "nav.guide": "引导", + "theme.title": "Codex Desktop 主题", + "theme.subtitle": "向运行中的 Codex Desktop 通过 CDP 注入 CSS / DOM,覆盖原生外观。默认关闭;启用后选一个内置主题。改动后 Codex Desktop 整页 reload 会自动重应用;从本工具重启 Codex.app 也会自动应用已选主题。", + "theme.toggleLabel": "启用 Codex Desktop 主题", + "theme.toggleHint": "开关为持久化偏好:开启后保存并即时应用(若 Codex 未经本工具启动则提示重启生效);关闭只清除偏好,已注入主题保留至 Codex 下次重启。状态独立于 Plugin Unlock,互不影响。", + "theme.applyBtn": "应用", + "theme.clearBtn": "清除", + "theme.restartCodexBtn": "重启 Codex", + "theme.restartToast": "已请求重启 Codex", + "theme.restartFailed": "重启失败", + "theme.applied": "已应用", + "theme.failed": "失败", + "theme.disabled": "未启用", + "theme.pickFirst": "请先选一个主题", + "theme.appliedToast": "主题已应用", + "theme.clearedToast": "主题已清除", + "theme.applyFailed": "应用失败", + "theme.clearFailed": "清除失败", + "theme.loadFailed": "主题列表加载失败", + "theme.disabledPendingRestart": "已关闭,主题将在 Codex 重启后移除", + "theme.savedTitle": "主题偏好已保存", + "theme.savedPendingRestart": "当前 Codex 未通过本工具启动或调试端口不可用,主题将在 Codex 重启后生效。", + "theme.savedPendingRestartToast": "主题已保存,Codex 重启后生效", + "theme.restartNow": "立即重启", + "theme.restartLater": "稍后重启", + "theme.saveFailed": "保存失败", + "theme.pendingRestart": "待重启生效", + "theme.uploadOk": "自定义主题已保存", + "theme.uploadFailed": "上传失败", + "theme.uploadTooLarge": "图片过大(>20MB)", + "theme.restoreHidden": "恢复隐藏", + "codex.title": "Codex 文档管理", + "codex.subtitle": "", + "codex.assetType": "资产类型", + "codex.tabAgents": "Agents", + "codex.tabMcp": "MCP", + "codex.tabMemories": "Memories", + "codex.tabSkills": "Skills", + "codex.tabConversations": "Sessions", + "codex.conv.searchPlaceholder": "搜索 (title / cwd / id)", + "codex.conv.kindAll": "全部", + "codex.conv.kindActive": "Active", + "codex.conv.kindArchived": "Archived", + "codex.conv.cwdAll": "所有项目", + "codex.conv.refresh": "刷新", + "codex.conv.selectAll": "全选(过滤后)", + "codex.conv.formatJsonl": "原始 JSONL", + "codex.conv.options": "选项", + "codex.conv.exportSelected": "导出选中", + "codex.conv.exportSelectedN": "导出选中 ({count})", + "codex.conv.warnRedact": "默认已 redact 密钥(sk- / cas_ / JWT / Bearer)。Reasoning 默认隐藏,Tool calls 默认含,output > 2KB 截断。点「选项」调整。", + "codex.conv.summary": "共 {count} 个 session", + "codex.conv.loading": "加载中…", + "codex.conv.noResults": "无匹配 session", + "codex.conv.selectPrompt": "选中一条 session 看详情。", + "codex.conv.turns": "turns", + "codex.conv.roleUser": "用户", + "codex.conv.roleAssistant": "助手", + "codex.conv.reasoning": "Reasoning", + "codex.conv.compacted": "Autocompact 切点", + "codex.conv.toastExported": "已导出 {count} 个 session", + "codex.conv.toastExportedTo": "已导出 {count} 个 session → {path}", + "codex.conv.exportFailed": "导出失败", + "codex.conv.saveDialogSingle": "保存对话文件", + "codex.conv.saveDialogMulti": "保存对话 zip", + "codex.conv.deleteSelected": "删除选中", + "codex.conv.deleteSelectedN": "删除选中 ({count})", + "codex.conv.confirmDelete": "确认把 {count} 个 session 移到系统回收站?此操作可在 Finder Trash 中恢复。", + "codex.conv.toastDeleted": "已移到回收站 {count} 个 session", + "codex.conv.toastDeletedPartial": "移到回收站 {moved} 个,失败 {failed} 个", + "codex.conv.deleteFailed": "删除失败", + "codex.conv.deleteFailureDetail": "部分删除失败", + "codex.conv.defaultDirPlaceholder": "设置默认导出文件夹", + "codex.conv.defaultDirPickTitle": "选择默认导出文件夹", + "codex.conv.defaultDirSet": "默认导出文件夹已设为: {path}", + "codex.conv.defaultDirCleared": "已清除默认导出文件夹", + "codex.conv.defaultDirPickFailed": "选择目录失败", + "codex.conv.optionsTitle": "导出选项", + "codex.conv.optIncludeReasoning": "包含 reasoning 块", + "codex.conv.optIncludeToolCalls": "包含 tool calls + outputs", + "codex.conv.optIncludeSystem": "包含 developer / system 消息", + "codex.conv.optRedact": "Redact 密钥 (sk- / cas_ / JWT / Bearer)", + "codex.conv.optToolMax": "Tool output 截断字符数", + "codex.conv.optionsSaved": "选项已保存", + "codex.agentsPath.global": "全局", + "codex.agentsPath.projectRoot": "项目根目录", + "codex.agentsPath.subdir": "子目录", + "codex.agentsPathEmpty": "未检测到 AGENTS.md,请通过右侧\"添加\"按钮指定", + "codex.agentsPathAdd": "添加", + "codex.agentsPathAddTitle": "添加自定义 AGENTS.md 路径", + "codex.agentsPathAddPrompt": "点击\"浏览\"打开系统文件选择,或直接粘贴 HOME 下非敏感 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.memoriesPathAddTitle": "添加 MEMORY.md 路径", + "codex.memoriesPathAddPrompt": "选择 HOME 下非敏感项目目录或 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.saveConfirmStdio": "确认保存 MCP server \"{name}\"?\n\n保存后会写入 ~/.codex/config.toml,对所有 Codex 会话全局生效。\n\n⚠️ 该 server 会以你的账户权限在本机执行下面的命令:\n\n {cmdline}{extra}\n\n请确认命令、工作目录与环境变量无误再继续。", + "codex.mcp.saveConfirmHttp": "确认保存 MCP server \"{name}\"?\n\n保存后会写入 ~/.codex/config.toml,对所有 Codex 会话全局生效。\n\n传输:streamable_http\n连接到:{url}{extra}\n\n请确认地址与发往远端的凭据 / header 无误再继续。", + "codex.mcp.rawApplyConfirm": "确认用编辑后的原始 TOML 覆盖 ~/.codex/config.toml?\n\n这会整体覆盖该文件并对所有 Codex 会话全局生效;其中 [mcp_servers.*] 的 command 会以你账户权限在本机执行。\n\n请确认内容无误再继续。", + "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.docApplyConfirm": "确认应用对 {doc} 的修改?\n\n保存后会覆盖磁盘上的 {doc}(旧版本自动进 History),并会影响 AI 的后续行为。\n\n请确认内容无误再继续。", + "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.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.droppedToolsTitle": "transfer 静默丢弃了 Responses API 工具", + "dashboard.droppedToolsExpand": "查看每个类型计数", + "dashboard.droppedToolsHint": "这是 transfer adapter 不识别的 Responses API 工具类型(可能 Codex 升级引入了新 type,或第三方客户端发了非 Codex 用的 type)。本进程累计计数,重启归零。如果某 type 计数持续上升,需要在 transfer 加 explicit 适配。", + "dashboard.droppedToolsCalls": "次", + "dashboard.droppedToolsTypes": "种", + "dashboard.restartCodex": "重启 Codex", + "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": "仅保存", + "common.recommended": "推荐", + "providersAdd.title": "添加提供商", + "providersAdd.editTitle": "编辑提供商", + "providersAdd.subtitle": "添加新的 API 提供商或选择预设", + "providersAdd.presets": "快捷预设", + "providersAdd.presetsHint": "选择后会自动填入 API 地址和推荐模型,API Key 仍由你自己填写。", + "providersAdd.mappingTitle": "在 Codex 中显示的模型", + "providersAdd.mappingSubtitle": "直接列出你想在 Codex 模型选择器里看到的模型(最多 5 个)。第一个是默认模型,新对话直接用它;后端自动完成到 Codex 槽位的映射,无需手动指定。", + "providersAdd.defaultModelBadge": "默认", + "providersAdd.addModel": "添加模型", + "providersAdd.reviewModel": "auto-review 审查模型", + "providersAdd.reviewModelFollow": "跟随主模型(默认)", + "providersAdd.reviewModelHint": "auto-review 自动审批工具调用时改用的模型,从上方已配置的模型里选(通常选快 / 便宜模型加速审批)。留空 = 跟随主模型。", + "providersAdd.providerModel": "用户 API 所含模型", + "providersAdd.providerModelPlaceholder": "输入或选择模型", + "providersAdd.removeMapping": "移除", + "providersAdd.optionApplied": "已切换模型配置", + "providersAdd.applyToDesktop": "一键生成 Codex CLI 配置", + "providersAdd.applyHint": "一键应用会保存供应商和模型列表,把它设为默认,并让 Codex CLI 连接到本工具。之后你在 Codex CLI 里发消息,本工具会自动转发到你填写的 API 地址。", + "providersAdd.gemini1mLabel": "启用 Gemini 1M 上下文", + "providersAdd.gemini1mHint": "关闭时 Gemini 上下文限制在约 600K(更早触发自动压缩),提升长上下文下的指令遵循;开启使用完整 1M。默认关闭。", + "providersAdd.apiFormatLabel": "协议类型", + "providersAdd.mimoLogin.label": "套餐用量额度", + "providersAdd.mimoLogin.button": "登录小米账号", + "providersAdd.mimoLogin.hint": "MiMo 套餐用量需登录小米账号(用量接口不认 API Key)。点击在内嵌窗口登录,完成后用于在 Codex 用量面板显示套餐余量。", + "providersAdd.mimoLogin.statusLoggedIn": "已登录", + "providersAdd.mimoLogin.statusNotLoggedIn": "未登录", + "providersAdd.mimoLogin.statusLoggingIn": "登录中…", + "providersAdd.mimoLogin.success": "小米账号登录成功,已记录套餐 session", + "providersAdd.mimoLogin.cancelled": "登录未完成(窗口已关),未记录 session", + "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.loginCancelled": "已取消登录", + "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.loginCancelled": "已取消登录", + "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": "切换账号(重新登录)", + "zaiOauth.title": "GLM 账号登录(Z.ai)", + "zaiOauth.statusLoading": "加载中...", + "zaiOauth.statusNotLoggedIn": "未登录 — 请点下方按钮用 z.ai 账号登录(免 API key)", + "zaiOauth.statusLoggedIn": "已登录:{email}", + "zaiOauth.loginBtn": "用 z.ai 账号登录", + "zaiOauth.loginBtnInProgress": "等待浏览器授权(最多 5 分钟)...", + "zaiOauth.logoutBtn": "注销并删除本地凭证", + "zaiOauth.hint": "登录后浏览器窗口可关闭。免 API key,走你的 GLM Coding Plan 订阅额度。Token 持久化到 ~/.codex-app-transfer/zai-oauth.json,过期前自动刷新。", + "zaiOauth.loginSuccess": "登录成功!email: {email}", + "zaiOauth.loginCancelled": "已取消登录", + "zaiOauth.loginFailed": "登录失败:{error}", + "zaiOauth.loginRequired": "切到 GLM(Z.ai)需先登录 z.ai 账号 — 点表单上的「用 z.ai 账号登录」按钮", + "zaiOauth.logoutConfirmed": "已注销,本地凭证已删除", + "zaiOauth.logoutFailedManual": "注销失败:{error}。Token 文件可能仍在 ~/.codex-app-transfer/zai-oauth.json,需手动删除才能彻底登出", + "zaiOauth.statusFetchFailed": "状态加载失败:{error}", + "zaiOauth.switchAccountBtn": "切换账号(重新登录)", + "bigmodelOauth.title": "GLM 账号登录(BigModel)", + "bigmodelOauth.statusLoading": "加载中...", + "bigmodelOauth.statusNotLoggedIn": "未登录 — 请点下方按钮用智谱 bigmodel 账号登录(免 API key)", + "bigmodelOauth.statusLoggedIn": "已登录:{email}", + "bigmodelOauth.loginBtn": "用 bigmodel 账号登录", + "bigmodelOauth.loginBtnInProgress": "等待浏览器授权(最多 5 分钟)...", + "bigmodelOauth.logoutBtn": "注销并删除本地凭证", + "bigmodelOauth.hint": "登录后浏览器窗口可关闭。免 API key,走你的 GLM Coding Plan 订阅额度。Token 持久化到 ~/.codex-app-transfer/bigmodel-oauth.json,过期前自动刷新。", + "bigmodelOauth.loginSuccess": "登录成功!email: {email}", + "bigmodelOauth.loginCancelled": "已取消登录", + "bigmodelOauth.loginFailed": "登录失败:{error}", + "bigmodelOauth.loginRequired": "切到 GLM(BigModel)需先登录 bigmodel 账号 — 点表单上的「用 bigmodel 账号登录」按钮", + "bigmodelOauth.logoutConfirmed": "已注销,本地凭证已删除", + "bigmodelOauth.logoutFailedManual": "注销失败:{error}。Token 文件可能仍在 ~/.codex-app-transfer/bigmodel-oauth.json,需手动删除才能彻底登出", + "bigmodelOauth.statusFetchFailed": "状态加载失败:{error}", + "bigmodelOauth.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.showGrayProviders": "显示灰色提供商", + "settings.showGrayProvidersWarn": "⚠️ 存在封号风险,开启并使用这些提供商的后果由你自行承担。", + "settings.showGrayProvidersHint": "默认隐藏 Grok(Web)、Gemini CLI、Antigravity 等 TOS 灰色 / 实验性提供商。开启后它们才会出现在「添加提供商」的列表中。", + "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.codexQuotaEnabled": "Codex 内显示用量信息", + "settings.codexQuotaEnabledHint": "在 Codex 的固定摘要(pinned summary)弹窗底部显示用量面板:上下文占用、Tokens 速率与累计(所有 provider),以及 5 小时 / 每周额度(仅支持的 provider)。需 Codex 通过本应用启动;若 Codex 已在运行,改动开关后需重启 Codex 生效。", + "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 唤醒,关闭写 false 覆盖之前残留的 true 让宠物不再自动出现。", + "settings.exposeAllModels": "OpenAI 模型菜单", + "settings.restoreCodexOnExit": "退出时还原 Codex 原配置", + "settings.restoreCodexOnExitHint": "开启后,应用退出或下次启动时会自动把 ~/.codex/config.toml 与 auth.json 还原至 apply 之前的状态;保证不开应用时 Codex CLI 仍是你本人的原配置。", + "settings.mcpCredentialsPortableStore": "MCP 授权可移植保险箱", + "settings.mcpCredentialsPortableStoreHint": "开启后把 Codex 的 MCP 授权改存为可移植文件(~/.codex/.credentials.json),并由本应用在 ~/.codex 之外维护一份镜像;若整个授权文件被切账号 / 误删 / 换机清掉,下次启动会弹确认让你从备份恢复(单个 server 的主动登出会被尊重、不复活)。注意:授权 token 将明文存盘(权限 0o600,与其它 CLI agent 一致);此功能不解决授权自然过期(过期仍需重新授权)。", + "mcp.restorePromptTitle": "恢复 MCP 授权?", + "mcp.restorePromptBody": "检测到 ~/.codex/.credentials.json 不见了,但本应用备份里有 {count} 条 MCP 授权。是否从备份恢复?(若你刚主动登出了全部 MCP,请选「否」——会清掉备份不再提示。)", + "mcp.restoreDone": "已从备份恢复 {count} 条 MCP 授权", + "mcp.restoreDismissed": "已忽略并清除 MCP 授权备份", + "settings.webFetchBackend": "内置联网抓取工具(transfer 代抓网页)", + "settings.newBadge": "新功能", + "settings.webFetchBackendHint": "transfer 自己抓取网页内容返给模型(独立于下面的 Codex 沙箱联网开关)。auto=按页面难度自动 curl→wreq→headless 升级,并记住每站最佳档;curl=reqwest 静态抓取;wreq=浏览器 TLS 指纹绕 Cloudflare;headless=无头 Chrome 跑 JS 抓 JS 渲染 SPA。auto / headless 需系统代理(梯子)连通才能抓墙外站,否则自动降级 wreq;首次选 auto / headless 会检测系统 Chrome,未安装则提示下载 chrome-headless-shell(~86MB,仅一次,复用,不打包进安装包)。", + "settings.webFetchBackend.off": "关闭", + "settings.webFetchBackend.auto": "auto", + "settings.webFetchAutoNeedsProxy": "auto / headless 档需系统代理(梯子)连通才能抓墙外站,当前系统代理未连通,已自动降级为 wreq 档。请先开启系统代理再选 auto。", + "settings.headlessChromeTitle": "未检测到 Chrome", + "settings.headlessChromeDesc": "启用 headless 抓取需要一个 Chromium 浏览器,但未在系统中检测到 Chrome / Edge / Chromium。是否下载 chrome-headless-shell(约 86MB,仅下载一次并复用,不打包进安装包)?取消则回退到上一个选项。", + "settings.headlessChromeConfirm": "下载并启用", + "settings.headlessChromeSystemFound": "已检测到系统 Chrome,已启用 headless 抓取", + "settings.headlessChromeDownloading": "正在下载 chrome-headless-shell(~86MB)…", + "settings.headlessChromeDownloaded": "已下载并启用 headless 抓取", + "settings.headlessChromeFailed": "下载失败,已回退到上一个选项", + "settings.webFetchSaveFailed": "设置保存失败,已回退到上次保存的选项", + "settings.webFetchSyncWarning": "设置已保存,但向 Codex 注册抓取工具失败(下次启动 transfer 会自动重试)。如持续失败请检查 ~/.codex/config.toml 写入权限。", + "settings.codexNetworkAccess": "允许 Codex 联网工具(全权限模式)", + "settings.codexNetworkAccessHint": "⚠️ 默认关闭(全权限有风险)。开启后让 Codex 走 danger-full-access 沙箱 + approval_policy=never(Codex 官方推荐的 \"Full access\" 配对):模型可以读写任何文件、用 curl / wget 等联网命令获取实时网页,且所有命令无审批弹窗。**等于完全信任模型**。默认(关闭)时 Codex 走 read-only 沙箱 + on-request 审批:无网络;第三方 provider 原生 web_search 已在协议层 drop(MOC-208),联网搜索改走自研 web_search / web_fetch 工具(需开启「内置联网抓取工具」)。", + "settings.traceViewerEnabled": "诊断模式 · 协议流量查看器(开发者)", + "settings.openTraceViewer": "打开查看器", + "settings.traceViewerEnabledHint": "⚠️ 开发者诊断,默认关。开启后在本机独立端口(http://127.0.0.1:18090)实时查看 Codex↔上游 协议转发全流量(原始请求 / 转换后 / 上游回包),用于排查 adapter / 协议映射问题;也可设环境变量 CAS_DIAG_TRACE=1 启动。仅本地 loopback;credential(authorization / api_key 等)落盘前脱敏,但 prompt / 代码 / 模型回复正文会完整记录,故仅供本地排查、勿外传。关闭即停止采集与查看器。注:MCP / OAuth 流量采集额外依赖插件解锁器 daemon 运行(即「自动解锁 Codex Plugins」开、Codex 经本工具带调试端口启动);未运行时仅有 forward-trace。", + "settings.residualScanTitle": "Codex 原配置完整性检查", + "settings.residualScanHint": "扫描 ~/.codex/config.toml 与本应用历史快照里是否残留 transfer apply 写过的字段(model_catalog_json / openai_base_url 等)。启动时会自动扫一次,发现污染会顶部弹提示。", + "settings.residualScanStatusUnknown": "正在扫描…", + "settings.residualScanStatusClean": "✅ 干净:~/.codex/config.toml 与所有快照均未含 transfer apply 残留字段。", + "settings.residualScanStatusCleanWhileApplied": "✅ 当前 transfer apply 生效中,所有快照均未含残留字段(live config 含 transfer 字段属正常)。", + "settings.residualScanStatusDirty": "⚠️ 发现 {count} 个文件含 transfer 字段。点「针对性清除」预览要 strip 的字段并清理。", + "settings.residualScanStatusError": "扫描失败:{error}", + "settings.residualScanRefresh": "重新扫描", + "settings.residualScanRepair": "针对性清除", + "settings.residualScanShowFields": "显示残留字段", + "settings.residualScanShowFieldsClean": "✅ 无残留字段,无需清除。", + "settings.residualScanPreviewTitle": "即将 strip 的字段:", + "settings.residualScanConfirm": "本工具会从下列文件中删除标注的 transfer 字段(其它内容包括 model / personality / [projects.*] / mcp_servers 等全部保留):\n\n{preview}\n\n确认继续?", + "settings.residualScanToastCleaned": "已清除 {count} 个文件的 transfer 残留字段。", + "settings.residualScanStartupToast": "启动扫描:发现 {count} 个文件含 transfer 字段,请到设置页查看 / 清除。", + "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} 的官方文档页面。继续?" +} as Record diff --git a/frontend/src/layout/AppLayout.vue b/frontend/src/layout/AppLayout.vue new file mode 100644 index 00000000..5dd8b1cb --- /dev/null +++ b/frontend/src/layout/AppLayout.vue @@ -0,0 +1,34 @@ + + + + + diff --git a/frontend/src/layout/TopTabBar.vue b/frontend/src/layout/TopTabBar.vue new file mode 100644 index 00000000..ba3e4fff --- /dev/null +++ b/frontend/src/layout/TopTabBar.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 00000000..b9932854 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,14 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import App from './App.vue' +import { router } from './router' +import { useAppearance } from './composables/useAppearance' +import { setLocale, cachedLocale } from './i18n' +import './styles/index.css' + +// 启动即应用缓存的主题 + 语言(Vue 挂载前 #app 为空, 天然无首屏闪烁)。 +// Stage 3 接 settings store 后, 再用后端 settings.theme/language 覆盖缓存值。 +useAppearance().load() +setLocale(cachedLocale()) + +createApp(App).use(createPinia()).use(router).mount('#app') diff --git a/frontend/src/pages/CodexPage.vue b/frontend/src/pages/CodexPage.vue new file mode 100644 index 00000000..54ac7257 --- /dev/null +++ b/frontend/src/pages/CodexPage.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/pages/CodexSkinPage.vue b/frontend/src/pages/CodexSkinPage.vue new file mode 100644 index 00000000..e5ef9495 --- /dev/null +++ b/frontend/src/pages/CodexSkinPage.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/pages/DashboardPage.vue b/frontend/src/pages/DashboardPage.vue new file mode 100644 index 00000000..d46998f3 --- /dev/null +++ b/frontend/src/pages/DashboardPage.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/pages/DesktopPage.vue b/frontend/src/pages/DesktopPage.vue new file mode 100644 index 00000000..be48e405 --- /dev/null +++ b/frontend/src/pages/DesktopPage.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/pages/GuidePage.vue b/frontend/src/pages/GuidePage.vue new file mode 100644 index 00000000..7e44380f --- /dev/null +++ b/frontend/src/pages/GuidePage.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/pages/ProviderFormPage.vue b/frontend/src/pages/ProviderFormPage.vue new file mode 100644 index 00000000..fe0a47f4 --- /dev/null +++ b/frontend/src/pages/ProviderFormPage.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/pages/ProvidersPage.vue b/frontend/src/pages/ProvidersPage.vue new file mode 100644 index 00000000..1228a277 --- /dev/null +++ b/frontend/src/pages/ProvidersPage.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/pages/ProxyPage.vue b/frontend/src/pages/ProxyPage.vue new file mode 100644 index 00000000..a0f1c38d --- /dev/null +++ b/frontend/src/pages/ProxyPage.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/pages/SettingsPage.vue b/frontend/src/pages/SettingsPage.vue new file mode 100644 index 00000000..b63b4395 --- /dev/null +++ b/frontend/src/pages/SettingsPage.vue @@ -0,0 +1,55 @@ + + + diff --git a/frontend/src/pages/UsagePage.vue b/frontend/src/pages/UsagePage.vue new file mode 100644 index 00000000..fb7311f4 --- /dev/null +++ b/frontend/src/pages/UsagePage.vue @@ -0,0 +1,21 @@ + + + + + diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts new file mode 100644 index 00000000..172fce8b --- /dev/null +++ b/frontend/src/router/index.ts @@ -0,0 +1,24 @@ +import { createRouter, createWebHashHistory, type RouteRecordRaw } from 'vue-router' + +// 路由表映射旧 SPA 的 9 个页面 + providers/add 子路由 + 隐藏 desktop。 +// 原 #theme(Codex Desktop 皮肤注入)改名 /codex-skin, 与"本应用三主题"区分。 +const routes: RouteRecordRaw[] = [ + // FineTune 风顶部 tab 无 dashboard, 默认进主页 providers(dashboard 路由保留, 仅不在 tab) + { path: '/', redirect: '/providers' }, + { path: '/dashboard', name: 'dashboard', component: () => import('@/pages/DashboardPage.vue'), meta: { navKey: 'nav.dashboard', icon: 'gauge', hidden: true } }, + { path: '/providers', name: 'providers', component: () => import('@/pages/ProvidersPage.vue'), meta: { navKey: 'nav.providers', icon: 'plug' } }, + { path: '/providers/add', name: 'provider-form', component: () => import('@/pages/ProviderFormPage.vue') }, + { path: '/proxy', name: 'proxy', component: () => import('@/pages/ProxyPage.vue'), meta: { navKey: 'nav.proxy', icon: 'radio' } }, + { path: '/usage', name: 'usage', component: () => import('@/pages/UsagePage.vue'), meta: { navKey: 'nav.usage', icon: 'chart' } }, + { path: '/settings', name: 'settings', component: () => import('@/pages/SettingsPage.vue'), meta: { navKey: 'nav.settings', icon: 'settings' } }, + { path: '/codex', name: 'codex', component: () => import('@/pages/CodexPage.vue'), meta: { navKey: 'nav.codex', icon: 'bookmark' } }, + { path: '/codex-skin', name: 'codex-skin', component: () => import('@/pages/CodexSkinPage.vue'), meta: { navKey: 'nav.theme', icon: 'palette' } }, + { path: '/guide', name: 'guide', component: () => import('@/pages/GuidePage.vue'), meta: { navKey: 'nav.guide', icon: 'book' } }, + { path: '/desktop', name: 'desktop', component: () => import('@/pages/DesktopPage.vue'), meta: { hidden: true } }, + { path: '/:pathMatch(.*)*', redirect: '/providers' }, +] + +export const router = createRouter({ + history: createWebHashHistory(), + routes, +}) diff --git a/frontend/src/styles/base.css b/frontend/src/styles/base.css new file mode 100644 index 00000000..6cf941fc --- /dev/null +++ b/frontend/src/styles/base.css @@ -0,0 +1,104 @@ +/* ============================================================ + base — reset + 全局排版 + macOS 质感细节 + ============================================================ */ + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + height: 100%; +} + +body { + font-family: var(--font-sans); + font-size: var(--fs-base); + line-height: 1.5; + color: var(--text); + background: var(--bg); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + overflow: hidden; /* app 外壳不滚动, 由内容区滚动 */ +} + +#app { + height: 100vh; +} + +h1, +h2, +h3, +h4 { + margin: 0; + font-weight: 600; + color: var(--text); + letter-spacing: -0.01em; +} + +p { + margin: 0; +} + +a { + color: var(--accent); + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +button { + font-family: inherit; + font-size: inherit; + cursor: pointer; +} + +code, +pre, +.mono { + font-family: var(--font-mono); +} + +:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + border-radius: var(--radius-sm); +} + +/* macOS 风细滚动条 */ +::-webkit-scrollbar { + width: 9px; + height: 9px; +} +::-webkit-scrollbar-thumb { + background: color-mix(in srgb, var(--text-muted) 36%, transparent); + border-radius: var(--radius-full); + border: 2px solid transparent; + background-clip: padding-box; +} +::-webkit-scrollbar-thumb:hover { + background: color-mix(in srgb, var(--text-muted) 55%, transparent); + background-clip: padding-box; +} +::-webkit-scrollbar-track { + background: transparent; +} + +/* 国风标题用衬线做毛笔意境(正文仍系统字保可读) */ +html[data-theme="inkwash"] h1, +html[data-theme="inkwash"] h2 { + font-family: var(--font-serif); + letter-spacing: 0.01em; +} + +/* 主题切换的平滑过渡(背景/边框/文字) */ +body, +.theme-transition { + transition: background-color var(--transition-slow), color var(--transition-slow); +} diff --git a/frontend/src/styles/index.css b/frontend/src/styles/index.css new file mode 100644 index 00000000..833e0ff4 --- /dev/null +++ b/frontend/src/styles/index.css @@ -0,0 +1,2 @@ +@import './tokens.css'; +@import './base.css'; diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css new file mode 100644 index 00000000..89b1c932 --- /dev/null +++ b/frontend/src/styles/tokens.css @@ -0,0 +1,125 @@ +/* ============================================================ + Design tokens — 三套主题: 白(light) / 黑(dark) / 国风(inkwash 宣纸水墨) + 切换: html[data-theme="light|dark|inkwash"] (useAppearance 写入, 持久化 /api/settings) + ============================================================ */ + +:root { + /* 与主题无关的尺度 token */ + --radius-sm: 6px; + --radius: 10px; + --radius-lg: 14px; + --radius-full: 9999px; + + --space-1: 4px; + --space-2: 8px; + --space-3: 12px; + --space-4: 16px; + --space-5: 24px; + --space-6: 32px; + --space-8: 48px; + + --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", + "Segoe UI", "Microsoft YaHei UI", "PingFang SC", system-ui, sans-serif; + --font-mono: ui-monospace, "SF Mono", "Menlo", "Cascadia Code", "JetBrains Mono", monospace; + --font-serif: "Songti SC", "STSong", "SimSun", serif; + + --fs-xs: 11px; + --fs-sm: 12px; + --fs-base: 13px; + --fs-md: 14px; + --fs-lg: 16px; + --fs-xl: 20px; + --fs-2xl: 26px; + + --transition: 0.18s ease; + --transition-slow: 0.28s ease; + --sidebar-w: 220px; +} + +/* ---------- 白 · light (macOS 浅色) ---------- */ +:root, +html[data-theme="light"] { + --bg: #f5f5f7; + --surface: #ffffff; + --surface-2: #f0f0f3; + --surface-hover: #f5f5f7; + --border: #e3e3e8; + --border-strong: #d2d2d7; + --text: #1d1d1f; + --text-secondary: #424245; + --text-muted: #6e6e73; + --accent: #0a84ff; + --accent-hover: #0070e0; + --accent-soft: #e8f1ff; + --accent-text: #ffffff; + --success: #1d9d4f; + --success-soft: #e6f6ec; + --danger: #ff3b30; + --danger-soft: #ffeceb; + --warning: #c77f0a; + --warning-soft: #fff3e0; + --seal: #0a84ff; /* light 无朱砂, 退回 accent */ + --sidebar-bg: rgba(246, 246, 248, 0.72); + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.06); + --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.16); + color-scheme: light; +} + +/* ---------- 黑 · dark (macOS 深色) ---------- */ +html[data-theme="dark"] { + --bg: #1c1c1e; + --surface: #2c2c2e; + --surface-2: #3a3a3c; + --surface-hover: #3a3a3c; + --border: #38383a; + --border-strong: #48484a; + --text: #f5f5f7; + --text-secondary: #d1d1d6; + --text-muted: #98989d; + --accent: #0a84ff; + --accent-hover: #409cff; + --accent-soft: rgba(10, 132, 255, 0.18); + --accent-text: #ffffff; + --success: #30d158; + --success-soft: rgba(48, 209, 88, 0.16); + --danger: #ff453a; + --danger-soft: rgba(255, 69, 58, 0.16); + --warning: #ff9f0a; + --warning-soft: rgba(255, 159, 10, 0.16); + --seal: #0a84ff; + --sidebar-bg: rgba(30, 30, 32, 0.72); + --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-md: 0 6px 24px rgba(0, 0, 0, 0.45); + --shadow-lg: 0 16px 48px rgba(0, 0, 0, 0.55); + color-scheme: dark; +} + +/* ---------- 国风 · inkwash (宣纸 + 水墨 + 朱砂印) ---------- */ +html[data-theme="inkwash"] { + --bg: #f3ede1; /* 宣纸米黄 */ + --surface: #faf6ec; /* 宣纸本色 */ + --surface-2: #efe7d6; + --surface-hover: #f0e9da; + --border: #d8ccb4; /* 淡赭线 */ + --border-strong: #c4b596; + --text: #2b2620; /* 松烟墨(带暖) */ + --text-secondary: #4a4035; + --text-muted: #6b5d49; /* 淡墨 */ + --accent: #8a5a44; /* 赭石 */ + --accent-hover: #744a37; + --accent-soft: #ece0cf; + --accent-text: #faf6ec; + --success: #5b7a52; /* 苔绿 */ + --success-soft: #e7ecdf; + --danger: #b23b32; /* 朱砂 */ + --danger-soft: #f3ddd6; + --warning: #c89b3c; /* 赭黄 */ + --warning-soft: #f6eed8; + --seal: #b23b32; /* 朱砂印红, 点缀/激活态 */ + --sidebar-bg: rgba(243, 237, 225, 0.8); + --shadow-sm: 0 1px 3px rgba(60, 50, 35, 0.1); + --shadow-md: 0 6px 20px rgba(80, 65, 45, 0.16); + --shadow-lg: 0 14px 40px rgba(80, 65, 45, 0.22); + color-scheme: light; +} diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..ca00c211 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "noEmit": true, + "baseUrl": ".", + "paths": { "@/*": ["./src/*"] } + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue", "env.d.ts"] +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 00000000..e79b9e0e --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,45 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import Icons from 'unplugin-icons/vite' +import { fileURLToPath, URL } from 'node:url' + +// Tauri webview 在 prod 通过 cas://localhost/ 加载,由后端 axum static_files.rs +// 吐 include_dir! 嵌入的 dist 字节,并附严格 CSP(script-src 'self',禁 inline/eval)。 +// 因此构建产物必须:① 纯外链 ES module、零 inline