Skip to content

Commit 6984933

Browse files
王超claude
authored andcommitted
ui(cc-switch-import): side-by-side ProxyModal footer buttons, drop import hint line
Replace the two stacked full-width dashed buttons with a flex row — "Import from cc-switch" on the left, "+ Add proxy" on the right, each half-width. Remove the explanatory hint line under the import button and its now-unused ui.proxy.ccswitchImportHint i18n key (x18 locales). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent ad9ec77 commit 6984933

3 files changed

Lines changed: 9 additions & 8 deletions

File tree

history.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
- feat(proxy): **Import providers from cc-switch** — reads AI provider credentials (baseURL/authToken/model mappings) from the local [cc-switch](https://github.com/farion1231/cc-switch) Tauri app's SQLite database and auto-generates cc-viewer proxy profiles. Cross-platform path detection probes `~/.cc-switch/cc-switch.db` **first on every platform** (cc-switch hardcodes this path via `get_app_config_dir()` in its `config.rs` on mac/linux/windows; the Tauri identifier does not affect the DB path), with platform-specific Tauri app-data paths (`~/Library/Application Support/cc-switch/`, `%APPDATA%\cc-switch\`, `~/.local/share/cc-switch/`) kept only as low-priority legacy fallbacks so a stale leftover there can never shadow the real DB. Opens `cc-switch.db` in **read-only** mode (no SQLITE_BUSY lock when cc-switch is running), queries the `providers` table for `app_type='claude'` rows, and maps `settings_config.env` → cc-viewer profile fields (`ANTHROPIC_BASE_URL`→`baseURL`, `ANTHROPIC_AUTH_TOKEN`/`ANTHROPIC_API_KEY`→`apiKey`, `ANTHROPIC_MODEL` + the three family-model fields, ignoring the `_NAME` suffixed variants; `CLAUDE_CODE_EFFORT_LEVEL`→`effort` so a user's effort toggle is not silently dropped on import). Codex providers are skipped (incompatible auth format). Imported profiles get a `ccs_` id prefix and `source: 'cc-switch'` marker; a `mergeImportedProfiles` pure function updates existing `ccs_` entries (credential refresh) and appends new ones while **leaving user-created `proxy_` profiles untouched** — deleted-from-cc-switch entries are pruned. New `server/lib/ccswitch-import.js` (pure functions, fully unit-tested against the real db); `GET /api/ccswitch-providers` (preview, masked off-host) + `POST /api/ccswitch-import` (local-only merge + SSE `proxy_profile` broadcast); a "从 cc-switch 导入" button in ProxyModal. 4 new `ui.proxy.ccswitch*` i18n keys × 18 locales; `test/ccswitch-import.test.js` (25 cases incl. live-db integration + cross-platform path-priority injection).
1515
- fix(cc-switch-import): **the import reported a misleading `providers table not found`** whenever the DB was unreadable. The `sqlite_master` existence query's inner `catch {}` swallowed the real error (e.g. `file is not a database` for a corrupt/non-SQLite `cc-switch.db`) and collapsed every failure into the generic message, so the user could not tell a corrupt file from a genuine schema mismatch. The catch is removed — a throw now propagates to the existing outer catch, which surfaces `query failed: <real message>`; the genuine table-missing case reports `providers table not found in <resolved path>` (naming the file so a stale leftover at a probed path is distinguishable from the real cc-switch DB). Tests: a non-SQLite file asserts the error is *not* the masked string and exposes the real cause; a valid-but-tableless DB asserts the distinct `providers table not found in <path>` message.
1616
- fix(cc-switch-import): **five P1 findings from a 6-role review adopted.** (1) A corrupt/unparseable existing `profile.json` no longer wipes user-created proxies: the import previously swallowed the parse error, merged into an empty base, and overwrote the file with only `ccs_` entries — it now ABORTS with `ok:false` (`reportSwallowed('ccswitch-import.read-existing')`), and a parse to a non-object shape aborts the same way; the on-disk file is left byte-identical. (2) The client no longer misreports 403/400 as success: those response shapes carry no `imported`/`updated` counters, so the old `data.imported === 0` heuristic fell through to the green "Imported 0, updated 0" toast — success is now decided strictly on `resp.ok && data.ok === true`, and every server error path (403 gate, 400 exception) carries `ok:false`. (3) `mergeImportedProfiles` now upholds the same `max`-invariant as `proxyProfilesPost`: the built-in Default is seeded at the front when missing (a fresh install importing before ever saving a proxy used to write a list with no Default option), and existing id-less entries are preserved instead of silently dropped. (4) The Node floor mismatch is surfaced instead of silent: `node:sqlite` needs Node ≥ 22.5 (`--experimental-sqlite`) / ≥ 23.4 unflagged while the project floor is 20.14 — the unavailable-runtime error now names the requirement and ProxyModal maps it to a dedicated localized message (`ui.proxy.ccswitchNodeUnsupported` × 18 locales) instead of the misleading "cc-switch not detected"; the degradation contract is pinned by a test via the new `_setDatabaseSyncForTest` hook (CI runs Node 24, so the sqlite suites are exercised there). (5) The two HTTP endpoints — previously untested — get route coverage in `test/api-ccswitch-import.test.js` (fixture SQLite db under an injected HOME, no real cc-switch needed): local-only 403 gate writes nothing, `ok:false` contract on 400, first-import max seeding + `0o600` + credential-free SSE `refresh` broadcast, re-import preserve/prune semantics, corrupt/non-object `profile.json` abort leaves the file untouched, and off-host GET masking never leaks the plaintext key.
17+
- ui(cc-switch-import): ProxyModal footer buttons are now side-by-side — "从 cc-switch 导入" on the left, "+ 添加代理" on the right (each half-width) instead of stacked full-width rows; the explanatory hint line under the import button is removed together with its now-unused `ui.proxy.ccswitchImportHint` i18n key (×18 locales).
1718
- fix(cc-switch-import): **a stale/empty leftover `cc-switch.db` at a platform-specific probe path could shadow the real DB**. `candidateDbPaths()` probed `~/Library/Application Support/cc-switch/`, `%APPDATA%\cc-switch\`, and `~/.local/share/cc-switch/` *before* `~/.cc-switch/cc-switch.db`, but cc-switch never writes to those Tauri app-data paths — a leftover file there was matched first and yielded `providers table not found`. `~/.cc-switch/cc-switch.db` is now the primary probe on all platforms; the platform paths are demoted to legacy fallbacks. `candidateDbPaths()` was refactored to accept an injectable `{plat, home, env}` and exported as `_candidateDbPathsForTest` so the win32/darwin priority ordering is exercised on any test host (previously zero coverage on non-linux branches); new cross-platform tests assert the primary path wins and a stale legacy file cannot shadow it, using a neutral home value (no hardcoded username).
1819

1920
## 1.7.3 (2026-07-17)

src/components/settings/ProxyModal.jsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -242,13 +242,14 @@ export default function ProxyModal({
242242
))}
243243
</div>
244244

245-
<Button block type="dashed" icon={<PlusOutlined />} style={{ marginTop: 12 }} onClick={handleStartNew}>
246-
{t('ui.proxy.addProxy')}
247-
</Button>
248-
<Button block type="dashed" icon={<ImportOutlined />} style={{ marginTop: 8 }} loading={importing} onClick={handleImportFromCcSwitch}>
249-
{t('ui.proxy.ccswitchImport')}
250-
</Button>
251-
<div className={styles.proxyEditHint}>{t('ui.proxy.ccswitchImportHint')}</div>
245+
<div style={{ display: 'flex', gap: 8, marginTop: 12 }}>
246+
<Button type="dashed" icon={<ImportOutlined />} style={{ flex: 1 }} loading={importing} onClick={handleImportFromCcSwitch}>
247+
{t('ui.proxy.ccswitchImport')}
248+
</Button>
249+
<Button type="dashed" icon={<PlusOutlined />} style={{ flex: 1 }} onClick={handleStartNew}>
250+
{t('ui.proxy.addProxy')}
251+
</Button>
252+
</div>
252253
</div>
253254
);
254255

src/i18n.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11517,7 +11517,6 @@ const i18nData = {
1151711517
"uk": "Додати проксі"
1151811518
},
1151911519
"ui.proxy.ccswitchImport": { "zh": "从 cc-switch 导入", "en": "Import from cc-switch", "zh-TW": "從 cc-switch 匯入", "ko": "cc-switch에서 가져오기", "ja": "cc-switchからインポート", "de": "Von cc-switch importieren", "es": "Importar de cc-switch", "fr": "Importer depuis cc-switch", "it": "Importa da cc-switch", "da": "Importér fra cc-switch", "pl": "Importuj z cc-switch", "ru": "Импорт из cc-switch", "ar": "استيراد من cc-switch", "no": "Importer fra cc-switch", "pt-BR": "Importar do cc-switch", "th": "นำเข้าจาก cc-switch", "tr": "cc-switch'ten içe aktar", "uk": "Імпорт із cc-switch" },
11520-
"ui.proxy.ccswitchImportHint": { "zh": "读取本地 cc-switch 的供应商配置,自动生成代理选项(仅 Claude 类型,用户自建代理不受影响)", "en": "Reads local cc-switch providers and generates proxy options (Claude type only; user-created proxies are preserved)", "zh-TW": "讀取本地 cc-switch 的供應商設定,自動產生代理選項(僅 Claude 類型,使用者自建代理不受影響)", "ko": "로컬 cc-switch 공급자를 읽어 프록시 옵션을 생성합니다 (Claude 타입만, 사용자가 만든 프록시는 유지됨)", "ja": "ローカルの cc-switch プロバイダを読み取りプロキシオプションを生成します (Claude 型のみ、ユーザー作成プロキシは保持)", "de": "Liest lokale cc-switch-Anbieter und erstellt Proxy-Optionen (nur Claude-Typ, benutzerdefinierte Proxys bleiben erhalten)", "es": "Lee los proveedores locales de cc-switch y genera opciones de proxy (solo tipo Claude, los proxies del usuario se conservan)", "fr": "Lit les fournisseurs locaux cc-switch et génère des options de proxy (type Claude uniquement, les proxies utilisateur sont conservés)", "it": "Legge i provider locali di cc-switch e genera opzioni proxy (solo tipo Claude, i proxy dell'utente sono conservati)", "da": "Læser lokale cc-switch-udbydere og genererer proxy-indstillinger (kun Claude-type, brugeroprettede proxys bevares)", "pl": "Odczytuje lokalnych dostawców cc-switch i generuje opcje proxy (tylko typ Claude, proxy użytkownika są zachowane)", "ru": "Читает локальных провайдеров cc-switch и создаёт опции прокси (только тип Claude, пользовательские прокси сохраняются)", "ar": "يقرأ مزودي cc-switch المحليين وينشئ خيارات الوكيل (نوع Claude فقط، يتم الاحتفاظ بوكيلات المستخدم)", "no": "Leser lokale cc-switch-leverandører og genererer proxy-alternativer (kun Claude-type, brukeropprettede proxyer bevares)", "pt-BR": "Lê provedores locais do cc-switch e gera opções de proxy (apenas tipo Claude, proxies do usuário são preservados)", "th": "อ่านผู้ให้บริการ cc-switch ในเครื่องและสร้างตัวเลือกพร็อกซี (ประเภท Claude เท่านั้น พร็อกซีที่ผู้ใช้สร้างจะถูกคงไว้)", "tr": "Yerel cc-switch sağlayıcılarını okur ve proxy seçenekleri oluşturur (yalnızca Claude türü, kullanıcı tarafından oluşturulan proxy'ler korunur)", "uk": "Читає локальних провайдерів cc-switch і створює опції проксі (лише тип Claude, проксі користувача зберігаються)" },
1152111520
"ui.proxy.ccswitchImported": { "zh": "已导入 {imported} 个,更新 {updated} 个", "en": "Imported {imported}, updated {updated}", "zh-TW": "已匯入 {imported} 個,更新 {updated} 個", "ko": "{imported}개 가져옴, {updated}개 업데이트됨", "ja": "{imported}件インポート、{updated}件更新", "de": "{imported} importiert, {updated} aktualisiert", "es": "{imported} importados, {updated} actualizados", "fr": "{imported} importés, {updated} mis à jour", "it": "{imported} importati, {updated} aggiornati", "da": "{imported} importeret, {updated} opdateret", "pl": "Importowano {imported}, zaktualizowano {updated}", "ru": "Импортировано {imported}, обновлено {updated}", "ar": "تم استيراد {imported}، تحديث {updated}", "no": "{imported} importert, {updated} oppdatert", "pt-BR": "{imported} importados, {updated} atualizados", "th": "นำเข้า {imported} รายการ อัปเดต {updated} รายการ", "tr": "{imported} içe aktarıldı, {updated} güncellendi", "uk": "Імпортовано {imported}, оновлено {updated}" },
1152211521
"ui.proxy.ccswitchNodeUnsupported": { "zh": "导入不可用:当前 Node.js 缺少内置 SQLite 模块(需 Node ≥ 23.4,或 22.5+ 并加 --experimental-sqlite)", "en": "Import unavailable: this Node.js runtime lacks the built-in SQLite module (requires Node ≥ 23.4, or 22.5+ with --experimental-sqlite)", "zh-TW": "匯入不可用:目前 Node.js 缺少內建 SQLite 模組(需 Node ≥ 23.4,或 22.5+ 並加 --experimental-sqlite)", "ko": "가져오기를 사용할 수 없음: 현재 Node.js 런타임에 내장 SQLite 모듈이 없습니다 (Node ≥ 23.4 필요, 또는 22.5+ 및 --experimental-sqlite)", "ja": "インポート不可: この Node.js ランタイムには組み込み SQLite モジュールがありません (Node ≥ 23.4、または 22.5+ と --experimental-sqlite が必要)", "de": "Import nicht verfügbar: dieser Node.js-Laufzeit fehlt das eingebaute SQLite-Modul (erfordert Node ≥ 23.4 oder 22.5+ mit --experimental-sqlite)", "es": "Importación no disponible: este entorno de Node.js carece del módulo SQLite integrado (requiere Node ≥ 23.4, o 22.5+ con --experimental-sqlite)", "fr": "Importation indisponible : ce runtime Node.js ne dispose pas du module SQLite intégré (nécessite Node ≥ 23.4, ou 22.5+ avec --experimental-sqlite)", "it": "Importazione non disponibile: questo runtime Node.js non ha il modulo SQLite integrato (richiede Node ≥ 23.4, o 22.5+ con --experimental-sqlite)", "da": "Import utilgængelig: denne Node.js-runtime mangler det indbyggede SQLite-modul (kræver Node ≥ 23.4, eller 22.5+ med --experimental-sqlite)", "pl": "Import niedostępny: to środowisko Node.js nie ma wbudowanego modułu SQLite (wymaga Node ≥ 23.4 lub 22.5+ z --experimental-sqlite)", "ru": "Импорт недоступен: в этой среде Node.js нет встроенного модуля SQLite (требуется Node ≥ 23.4 или 22.5+ с --experimental-sqlite)", "ar": "الاستيراد غير متاح: بيئة Node.js هذه تفتقر إلى وحدة SQLite المدمجة (يتطلب Node ≥ 23.4، أو 22.5+ مع --experimental-sqlite)", "no": "Import utilgjengelig: denne Node.js-kjøretiden mangler den innebygde SQLite-modulen (krever Node ≥ 23.4, eller 22.5+ med --experimental-sqlite)", "pt-BR": "Importação indisponível: este runtime Node.js não possui o módulo SQLite integrado (requer Node ≥ 23.4, ou 22.5+ com --experimental-sqlite)", "th": "นำเข้าไม่ได้: รันไทม์ Node.js นี้ไม่มีโมดูล SQLite ในตัว (ต้องใช้ Node ≥ 23.4 หรือ 22.5+ พร้อม --experimental-sqlite)", "tr": "İçe aktarma kullanılamıyor: bu Node.js çalışma zamanında yerleşik SQLite modülü yok (Node ≥ 23.4 veya --experimental-sqlite ile 22.5+ gerekir)", "uk": "Імпорт недоступний: у цьому середовищі Node.js немає вбудованого модуля SQLite (потрібен Node ≥ 23.4 або 22.5+ з --experimental-sqlite)" },
1152311522
"ui.proxy.ccswitchImportFail": { "zh": "导入失败(未检测到 cc-switch 或读取出错)", "en": "Import failed (cc-switch not detected or read error)", "zh-TW": "匯入失敗(未偵測到 cc-switch 或讀取錯誤)", "ko": "가져오기 실패 (cc-switch를 감지하지 못했거나 읽기 오류)", "ja": "インポート失敗 (cc-switchが検出されないか読み取りエラー)", "de": "Import fehlgeschlagen (cc-switch nicht erkannt oder Lesefehler)", "es": "Importación fallida (cc-switch no detectado o error de lectura)", "fr": "Échec de l'importation (cc-switch non détecté ou erreur de lecture)", "it": "Importazione fallita (cc-switch non rilevato o errore di lettura)", "da": "Import mislykkedes (cc-switch ikke fundet eller læsefejl)", "pl": "Import nieudany (nie wykryto cc-switch lub błąd odczytu)", "ru": "Ошибка импорта (cc-switch не обнаружен или ошибка чтения)", "ar": "فشل الاستيراد (لم يتم اكتشاف cc-switch أو خطأ في القراءة)", "no": "Import mislyktes (cc-switch ikke funnet eller lesefeil)", "pt-BR": "Falha na importação (cc-switch não detectado ou erro de leitura)", "th": "นำเข้าล้มเหลว (ไม่พบ cc-switch หรือข้อผิดพลาดในการอ่าน)", "tr": "İçe aktarma başarısız (cc-switch algılanamadı veya okuma hatası)", "uk": "Помилка імпорту (cc-switch не виявлено або помилка читання)" },

0 commit comments

Comments
 (0)