704 Commits

  • fix(providers): remove per-key headers input for openai-compatibility
    The backend OpenAICompatibilityAPIKey struct only has api-key and
    proxy-url (internal/config/config.go:570-577). PUT /openai-compatibility
    unmarshals into this struct (config_lists.go:439) and discards any
    unknown field, so per-entry headers submitted from the UI never persist
    and never reach the runtime request path. The GET response wrapper
    also has no headers field (config_auth_index.go:31-34).
    
    Drop the per-entry headers input from BaseProviderForm and the
    ApiKeyEntry/ApiKeyEntryInput types. Connectivity test and model
    discovery stop reading entry-level headers — they continue to apply
    provider-level headers, which is the supported contract.
  • fix(auth-files): infer counts/names for single-item success path
    Backend's POST /auth-files and DELETE /auth-files single-item paths
    return only {status:"ok"}, with no uploaded/deleted/files
    (auth_files.go:680 and :794). Multi-item paths return the full payload.
    
    85c8b34 simplified the normalizer assuming the full payload was always
    present, which caused single-file uploads and single-item deletes to be
    read as "0 succeeded" — the upload page skipped its success toast and
    list refresh, and batch delete reported "(0)" with no row removal.
    
    Re-introduce a narrow fallback: when failed is empty and the count
    field is omitted, derive uploaded/deleted and files from requestedNames.
    The fallback only kicks in for the documented single-item shape, not
    for the partial/failure paths.
  • refactor(transformers): tighten normalizeBoolean to accept real booleans only
    All bool-tagged config fields on the backend are Go bools and serialize
    as JSON true/false (e.g. Debug, RequestLog, WebsocketAuth, Disabled,
    Websockets, ForceModelPrefix). normalizeBoolean is only called against
    those response paths, so the number / string / Boolean(value) fallbacks
    never fire.
  • refactor(auth-files): simplify batch response and field normalization
    Backend batch upload/delete handlers always return a complete payload:
    status (string), uploaded/deleted (number), files (string array), and
    failed (only on partial). See auth_files.go:702-711. The fallback chains
    that re-derived uploaded/deleted counts and file names from the
    requestedNames list were unreachable.
    
    Likewise, runtime_only is always a real bool from Go (auth_files.go:403),
    and entry.modified is never emitted, so isRuntimeOnlyEntry collapses
    to a strict equality check and readDateField drops the modified alias.
  • refactor(api-call): drop dead statusCode/headers field aliases
    The backend api-call response schema (api_tools.go:54-56) fixes the
    field names to status_code / header / body. camelCase statusCode and
    plural headers are never emitted.
  • refactor(config): drop dead key aliases in single-field GETs
    Backend handlers emit single-field GETs with a single kebab-case key:
    - GetLogsMaxTotalSizeMB returns {"logs-max-total-size-mb": n}
      (config_basic.go:206)
    - GetForceModelPrefix returns {"force-model-prefix": b}
      (config_basic.go:276)
    - GetRoutingStrategy returns {"strategy": s}
      (config_basic.go:295-301)
    
    The camelCase fallbacks (logsMaxTotalSizeMb / forceModelPrefix /
    routing-strategy / routingStrategy) were never reachable.
  • refactor(providers): simplify section and array payload extraction
    Backend list endpoints (config_lists.go: GetGeminiKeys / GetClaudeKeys /
    GetCodexKeys / GetOpenAICompat / GetVertexCompatKeys) always wrap the
    result as {"<kebab-section>": [...]}, never as a raw array, never under
    "items" or "data". And /config emits the same kebab keys, so the camelCase
    aliases (geminiApiKey, openAICompatibility, ...) are unreachable.
    
    Remove RAW_SECTION_ALIASES and inline the lookup; drop the raw-array
    and items/data fallbacks from extractArrayPayload.
  • refactor(providers): trim provider field allowlists to kebab-case
    The backend emits provider config with kebab-case JSON tags only
    (internal/config/config.go: ClaudeKey/CodexKey/GeminiKey/OpenAICompatibility,
    internal/api/handlers/management/config_auth_index.go for auth-index).
    The camelCase / snake_case entries in PROVIDER_KEY_FIELDS,
    OPENAI_PROVIDER_FIELDS, MODEL_ALIAS_FIELDS, API_KEY_ENTRY_FIELDS,
    CLOAK_FIELDS, RESPONSE_ONLY_FIELDS were dead — same for the identity
    helpers and the apiKeyEntries fallback in mergeOpenAIProviderPayload.
  • refactor(transformers): drop kebab/camel/snake aliases in /config parsing
    The backend always serializes config fields with kebab-case JSON tags
    (internal/config/config.go). The camelCase and snake_case fallbacks in
    normalizeApiKeyEntry / normalizeProviderKeyConfig / normalizeGeminiKeyConfig /
    normalizeOpenAIProvider / normalizeAmpcode* / normalizeConfigResponse are
    dead paths. Read kebab-case only.
    
    The legacy openai-compatibility `api-keys` (flat string array) flat-string
    branch is also gone — backend startup migration is disabled
    (internal/config/config.go:657).
  • Merge remote-tracking branch 'origin/pr/292' into dev
    # Conflicts:
    #	src/features/providers/sheets/forms/BaseProviderForm.tsx
  • fix(ui): address review feedback for API key toggle PR
    - Remove applyRawApiKey helper; populate apiKey directly in buildInitialForm
    - Revert useState hooks to single-line form
    - Replace duplicated .passwordInput styles with @extend .input
  • fix(ui): resolve connectivity status mismatch after prepend insertion
    Prepending new API key entries shifted all existing indices, causing useConnectivityTest index-based status tracking to desync.
    
    Fix: keep array operations as append (stable indices) and reverse the rendering order so new entries appear at the top visually. Use realIdx for status lookups, React keys, and all updateField operations.
  • fix(ui): add show/hide toggle for API key inputs and populate key on edit
    Add eye toggle button for all API key input fields so users can verify their keys before saving.
    
    Changes:
    
    - BaseProviderForm: wrap single API key field (Gemini/Codex/Claude/Vertex) with passwordField + toggle button
    
    - BaseProviderForm: wrap per-entry API key fields (OpenAI-compatible) with passwordField + toggle button
    
    - BaseProviderForm: extract applyRawApiKey helper to populate apiKey from resource.raw in edit mode
    
    - BaseProviderForm: sync initialFormSignature with applyRawApiKey to prevent false isDirty
    
    - sharedForm.module.scss: add .passwordField, .passwordInput, .passwordToggle styles
    
    - i18n: add showApiKey/hideApiKey keys to en, zh-CN, zh-TW, ru locale files
    
    - accessibility: add aria-label and title attributes on both toggle buttons
  • fix(providers): confirm discarding unsaved edits on category switch
    Previously confirmDiscardIfDirty only fired for Sheet-driven close paths
    (Cancel button, backdrop, escape). Clicking a different provider in the
    left rail bypassed the prompt and silently dropped the form changes.
    ProviderSheet now exposes the existing guard via an imperative handle,
    which the workbench page calls before swapping the active brand.
  • fix(providers): honor authIndex for OpenAI discovery and connectivity test
    - useModelDiscovery: openaiCompatibility branch now forwards
      resolvedAuthIndex into modelsApi.fetchModelsViaApiCall, mirroring the
      gemini/codex/claude branches. Configs that rely on a backend-resolved
      auth index without a local plaintext key can now list models.
    - useConnectivityTest: runOpenAIKey accepts an authIndex fallback when
      entry.apiKey is empty, sets Authorization: Bearer $TOKEN$ in that
      case, and forwards authIndex on the /api-call request so the gateway
      can substitute the upstream token.
    - useModelDiscovery: reset stale results when baseUrl / apiKey /
      apiKeyEntries / headers / authIndex change, so reopening the discovery
      panel after editing fields re-fetches instead of showing the previous
      endpoint's models.
  • fix(providers): fetch recent requests on mount
    The new workbench only wired refreshRecentRequests into the manual refresh
    button; the hook itself just set an interval (240s). As a result the
    status bar showed empty/zero until the first poll. Load on mount when
    enabled (cache hit avoids a network round-trip).
  • chore(login): remove orphan Login.module.scss
    The actual login page lives at src/pages/LoginPage.{tsx,module.scss};
    this stale src/pages/Login/Login.module.scss has had no importers since
    it was committed in 450964f.
  • chore(i18n): drop unused ai_providers namespace and providersPage orphans
    - Remove the legacy ai_providers.* block (~268 keys per locale) — only
      nav.ai_providers under a different namespace is still referenced
    - Remove providersPage.{disconnected,error,actions.back,
      actions.openModelCatalog,ampcode.behaviorSection,detail.empty,
      status.notAvailable,modelCatalog.{loading,openAction,summaryCount,
      summaryError,summaryTitle}} which were never wired up after the
      ProvidersWorkbench rewrite
  • chore(ui): remove unused HeaderInputList and ModelInputList
    These helpers were only consumed by the old AiProviders edit pages and
    became orphaned after the providers refactor. modelInputListUtils.ts was
    only used by ModelInputList, so it goes too.
  • chore(providers): remove dead exports left over from refactor
    - Drop empty barrel src/components/providers/index.ts (no external importers)
    - Drop src/components/providers/types.ts (only referenced in a stale comment)
    - Trim src/components/providers/utils.ts to the 10 exports actually used
      externally; remaining helpers (ampcode mappers, key builders, base URL
      normalizers, redundant recent-stats wrappers) were dead after the
      ProvidersWorkbench rewrite
  • feat(select): add small size option for Select component and corresponding styles
    fix(provider): update button icon styles in ProviderHeaderCard
    style(provider): enhance layout and typography in ProviderHeaderCard and ProviderResourcePanel
    fix(provider): update status styles in ProviderResourceTable and sharedForm
  • fix(providers): label missing authIndex as not-set instead of unavailable
    The detail metadata section was rendering authIndex with the
    status.notAvailable label (zh: "不可用"), which reads like the
    provider doesn't support the feature at all. Every provider supports
    authIndex; it's just absent until you link an auth file. Reuse the
    status.notSet label so the wording matches baseUrl / proxyUrl and
    makes it clear that the entry simply doesn't have a value yet.
  • fix(providers): forward stored apiKey and authIndex to discovery and tests
    The form's apiKey field is intentionally empty in edit mode (the
    placeholder reads "leave blank to keep unchanged"), so model discovery
    and the Claude connectivity test were sending requests with no
    credentials and getting 401 for Codex / Claude / Gemini providers.
    
    - BaseProviderForm derives a fallbackAuthIndex from resource.raw.authIndex
      alongside the existing fallbackApiKey and passes both into the
      discovery and connectivity hooks
    - modelsApi.fetch{V1,Claude,Gemini,}ModelsViaApiCall accept an optional
      authIndex and forward it on apiCallApi.request so the backend can
      inject the OAuth/auth-file token for providers that store the key
      externally
    - useModelDiscovery threads the authIndex through to every brand
    - useConnectivityTest threads authIndex through the Claude path and no
      longer rejects the request as "API key required" when an authIndex is
      available
  • feat(providers): pick the connectivity test model from a dropdown
    Replace the free-form test-model input on the OpenAI-compatible and
    Claude edit sheets with a Select whose options are derived from the
    already-added models. An Auto entry (annotated with the first model
    name) maps to an empty form.testModel so connectivity tests keep
    falling back to the first configured model when the user hasn't made
    an explicit pick. If an existing config carried a test model that
    isn't in form.models, surface it as a custom option so the value is
    preserved instead of silently dropped.
  • fix(providers): confirm before discarding unsaved edits in the provider sheet
    - BaseProviderForm and AmpcodeForm now snapshot the initial form
      signature on mount and report dirty/clean state to the parent via a
      new onDirtyChange callback
    - ProviderSheet tracks the form's dirty flag and wraps the cancel
      button plus the new Sheet.confirmClose hook with a confirmation
      prompt; the prompt is skipped during submit and in detail mode
    - Sheet gains an optional confirmClose hook so that Escape, overlay
      click and the close button all route through the same async check
      before starting the close animation
    - Add i18n keys (en/zh-CN/zh-TW/ru) for the unsaved-changes prompt
  • fix(providers): retry OpenAI model discovery without auth on failure
    When the OpenAI-compatible endpoint rejects the first authenticated
    /models request (some upstreams expose the route unauthenticated, or
    the configured key only covers chat completions), retry once with no
    key and no custom headers before surfacing the original error so the
    discovery panel can populate.
  • fix(providers): preserve alias when applying discovered models
    ModelDiscoveryPanel now hands the full ModelInfo objects to onApply
    instead of just the model names, and BaseProviderForm copies each
    incoming alias into the new form.models row so suggestions from
    endpoints that return alias metadata are no longer silently dropped.
  • feat(providers): bring back status bar, sort and model filter on the table
    - Wire useProviderRecentRequests into ProvidersWorkbenchPage and pass
      usageByProvider down to the resource table; the header refresh now
      refreshes recent requests in parallel with the provider snapshot
    - ProviderResourceTable status column hosts the existing status badge
      plus an inline ProviderStatusBar driven by the recent-requests usage;
      column widths rebalanced so the bar fits on a single row
    - New OpenAIBrandToolbar gives the OpenAI-compatible brand a sort
      control (name / priority / recent-success with direction) and a
      multi-select model filter dropdown; ProviderResourcePanel renders it
      via the new openaiControls prop
    - ProvidersWorkbenchPage computes the available model union, sorts /
      filters resources when the OpenAI brand is active, and resets the
      selected models when switching brands
    - Add providerStatusBar.module.scss for the in-row status bar styling
      and i18n keys (en/zh-CN/zh-TW/ru) for the new toolbar
  • feat(providers): add model discovery panel inside the models section
    - New useModelDiscovery hook fans out to modelsApi.fetchGemini /
      fetchV1 / fetchClaude / fetchModels based on brand and surfaces
      loading, error and the normalized model list
    - New ModelDiscoveryPanel renders an inline search, select-all and
      per-model checkboxes with an Already added marker for entries that
      already exist in form.models
    - BaseProviderForm: show a Fetch from endpoint button at the top of
      the models section for the supported brands, with apply merging
      the picked names into form.models while skipping duplicates and
      replacing the empty placeholder row
    - Add i18n keys (en/zh-CN/zh-TW/ru) for the discovery toolbar, list
      states and apply count
  • fix(providers): reset connectivity status only for the changed key entry
    Track per-entry signatures (apiKey + proxyUrl + headersText) so editing
    one OpenAI key entry no longer clears the success/error state of the
    other entries. The global signature (baseUrl/headers/models/testModel)
    keeps clearing every entry as before.
  • feat(providers): add OpenAI/Claude connectivity test in edit sheet
    - Introduce useConnectivityTest hook that drives per-key and bulk tests
      via apiCallApi against the chat-completions / messages endpoints, with
      signature-based status reset
    - OpenAI form: each API key entry header now shows a status icon plus a
      per-key Test button, and the entries section gains a Test all button
    - Claude form: enable the Test model field and add a Test button with
      inline status; falls back to the persisted apiKey when the edit form
      field is left blank
    - Add ConnectivityStatusIcon, related styles (spin keyframes, buttons,
      error banner), and i18n keys (en/zh-CN/zh-TW/ru) for the test labels
      and validation messages
  • feat(providers): expose inline enable/disable toggle in resource table
    - Wire useProviderWorkbench.toggleDisabled through ProviderResourcePanel
      to ProviderResourceTable as an inline ToggleSwitch in the actions cell
    - Hide the toggle for the Ampcode singleton brand
    - Add toast on success/failure and i18n keys (en/zh-CN/zh-TW/ru) for the
      enable/disable labels and a toggleFailed error message