Skip to content

feat(desktop): API config flow rewrite — provider-aware errors, live URL preview, single source of truth - #42

Closed
hqhq1025 wants to merge 5 commits into
wt/settings-fullpage-apifrom
wt/api-config-rewrite
Closed

hqhq1025 wants to merge 5 commits into
wt/settings-fullpage-apifrom
wt/api-config-rewrite

Conversation

@hqhq1025

Copy link
Copy Markdown
Collaborator

Summary

Rewrites the entire API config flow (add / view / edit / switch / delete provider) addressing user pain: "从配置 api 到保存 api 再到查看 api 再到切换 api 再到添加 api 都跟一坨屎一样".

Based on research synthesized in docs/research/19-api-config-ux-landscape.md (LibreChat / Open WebUI / Continue / Aider / Jan / Cherry Studio).

Built on top of #28 (wt/settings-fullpage-api). Once #28 merges, this PR will retarget to main.

Foundation (one source of truth)

  • normalizeBaseUrl() — single canonical URL normalizer in @open-codesign/shared (Cherry Studio formatApiHost pattern). Trims, strips trailing slash, defaults to https. Never auto-appends /v1 (would corrupt Anthropic and ollama paths). Returns a discriminated union so the UI can render the failure reason inline.
  • resolveModelsEndpoint() — smart endpoint builder that respects existing version segments instead of double-appending /v1.
  • enrichProviderError() in @open-codesign/providers — provider-aware error enrichment (Continue parseError + Aider ExInfo pattern). Each (status, provider) combo produces a tailored message + actionable hint + provider key dashboard URL. Crucially never echoes the underlying SDK URL — fixes the "401 leaks api.openai.com even though the user is on a relay" bug class at the source.
  • pingProvider() now routes all errors through enrichProviderError, so the same enriched copy surfaces in Settings, onboarding, and the active connection test.
  • checkKeyFormat() — live, lossless API-key sanity check (cross-family paste detection: catches Anthropic key pasted into the OpenAI slot, etc.).

UX

  • Inline AddProviderForm replaces the modal-in-modal AddProviderModal. Renders inline at top of the provider list — no overlay stack, no z-index war.
  • Live URL preview below baseUrl input shows resolved endpoint as the user types (Will call: https://...), or the validation error reason for invalid input.
  • Live key format hint as the user types (green checkmark when format matches the chosen provider, warning otherwise).
  • Save & Test unified button: validates first, only persists on success. Standalone Test button still available.
  • ProviderCard expand on click — shows full base URL + resolved endpoint inline without leaving the page.
  • Active provider banner at the top of the list with the provider label.
  • Full i18n — all copy goes through useT() with new settings.providers.* keys (en + zh-CN).

Files of interest

  • packages/shared/src/baseUrl.ts (new) + tests
  • packages/providers/src/errorEnrichment.ts (new) + tests
  • packages/providers/src/validate.ts (rewritten to use both)
  • apps/desktop/src/renderer/src/components/Settings.tsx (AddProviderForm + ProviderCard rewrite)
  • apps/desktop/src/renderer/src/components/Settings/keyFormat.ts (new) + tests
  • packages/i18n/src/locales/{en,zh-CN}.json (new settings.providers.* block)

Tests

  • 20 unit tests covering normalizeBaseUrl (trailing slash / no protocol / IP / localhost / query / path with version segment / unsupported schemes)
  • 14 unit tests covering enrichProviderError (status × provider matrix, retry-after header parsing, URL leak prevention)
  • 7 unit tests covering checkKeyFormat (cross-family paste, too-short, prefix mismatch)
  • 4 unit tests covering resolveModelsEndpoint
  • All existing 120 desktop tests still pass

Test plan

  • manual: configure DuckCoding relay with wrong key → 401 should NOT show platform.openai.com URL
  • manual: type a baseUrl in the form → resolved endpoint appears below input live
  • manual: paste sk-ant-... into OpenAI slot → orange "Expected prefix: sk-" hint appears
  • manual: switch active provider → toast Switched to <name> appears, banner updates
  • manual: zh-CN locale → all provider config UI is Chinese

Compatibility / upgradeability / no bloat / elegance

  • ✅ Compatibility: no IPC schema changes; preload ValidateKeyResult extended with optional hint/providerKeyUrl/retryable (additive)
  • ✅ Upgradeability: normalizeBaseUrl and enrichProviderError are now the single chokepoints — future provider additions only touch these two files
  • ✅ No bloat: zero new dependencies; +1 lucide icon (XCircle, ChevronRight)
  • ✅ Elegance: removes duplicated URL-building string concatenation from 3 sites; replaces nested modal with inline form

Part A — Settings full-page view:
- store: replace settingsOpen/openSettings/closeSettings with view enum
  (AppView = 'workspace' | 'settings') and setView(v) action
- App.tsx: switch main area based on view instead of rendering a modal overlay
- Settings.tsx: remove modal shell, add ← Workspace back button; keep
  AddProviderModal as a local dialog (not a page-level route)
- TopBar.tsx: gear button calls setView('settings')
- CommandPalette.tsx: Open Settings action calls setView('settings')
- ESC keybinding: view==='settings' → setView('workspace')

Part B — API switching pipeline audit + fix:
- ActiveModelSelector: add useEffect to sync local primary/fast state
  when config prop changes, preventing stale models after provider switch
- The main pipeline (getApiKeyForProvider, getBaseUrlForProvider,
  cachedConfig updates) audited and confirmed correct end-to-end

Tests (+6 new):
- store: setView switches view and closes command palette
- store: setView workspace/settings round-trip
- store: sendPrompt uses updated provider after completeOnboarding
- onboarding-ipc: getApiKeyForProvider returns decrypted key when secret exists
- onboarding-ipc: getApiKeyForProvider throws on missing provider secret

Signed-off-by: hqhq1025 <1506751656@qq.com>
When Settings is open and the command palette is also open, pressing ESC
now closes the palette without navigating back to workspace. The escape
handler in App.tsx previously checked `view === 'settings'` first; flip
the priority so `commandPaletteOpen` is evaluated first.

Three new store-level tests cover: palette consumed (view stays), plain
settings-back, and no-op on workspace.

Signed-off-by: hqhq1025 <1506751656@qq.com>
Replace the silent fallback in applyLocaleChange / handleLocaleChange with
an error toast when window.codesign.locale is absent, so users know the
language preference was not saved rather than silently losing it on restart.

Signed-off-by: hqhq1025 <1506751656@qq.com>
Wire the previously-introduced errors.localePersistFailed and
errors.rendererDisconnected locale keys into AppearanceTab's
handleLocaleChange toasts (was hardcoded English). Addresses Codex
review note that the new locale error keys remained unused.

Signed-off-by: hqhq1025 <1506751656@qq.com>
…URL preview, single source of truth

Rewrites the entire API config flow (add / view / edit / switch / delete provider).

Foundation:
- normalizeBaseUrl() in @open-codesign/shared: single canonical URL normalizer
  (Cherry Studio formatApiHost pattern). Trims, strips trailing slash, defaults
  to https. Never auto-appends /v1 (would corrupt Anthropic and ollama paths).
- resolveModelsEndpoint(): smart endpoint builder that respects existing version
  segments instead of double-appending /v1.
- enrichProviderError() in @open-codesign/providers: provider-aware error
  enrichment (Continue parseError + Aider ExInfo pattern). Each (status, provider)
  combo produces a tailored message + hint + provider key dashboard URL. Crucially
  never echoes the underlying SDK URL (api.openai.com leaked through relays).
- pingProvider() now routes all errors through enrichProviderError so the same
  hints surface in Settings, onboarding, and the active connection test.

UX:
- AddProviderForm replaces the modal-in-modal AddProviderModal. Renders inline at
  top of provider list — no overlay stack.
- Live URL preview below baseUrl input: shows resolved endpoint as user types,
  or the validation error reason for invalid input.
- Live key format hint as user types (cross-family paste detection: catches
  Anthropic key pasted into OpenAI slot, etc.).
- Save & Test unified button: validates first, only persists on success. Test
  button still available standalone.
- ProviderCard now expands on click for details: full base URL + resolved
  endpoint shown without leaving the page.
- Active provider banner at top of list with provider label badge.
- All copy goes through useT() with new settings.providers.* keys (en + zh-CN).

Reference: docs/research/19-api-config-ux-landscape.md
@hqhq1025
hqhq1025 deleted the branch wt/settings-fullpage-api April 18, 2026 18:42
@hqhq1025 hqhq1025 closed this Apr 18, 2026
@hqhq1025
hqhq1025 deleted the wt/api-config-rewrite branch May 23, 2026 16:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant