Skip to content

feat(ai): community AI providers, credit balances & per-call cost (desktop + mobile) - #108

Merged
ddutchie merged 22 commits into
mainfrom
ddutchie/mobileproviders
Aug 2, 2026
Merged

ddutchie merged 22 commits into
mainfrom
ddutchie/mobileproviders

Conversation

@ddutchie

@ddutchie ddutchie commented Aug 1, 2026 •

Copy link
Copy Markdown
Owner

What does this PR do?

Browse Providers (mobile) — a new Browse Providers screen (Settings → AI) installs preset OpenAI-compatible providers from the cairn-community providers.json catalog with one tap — the user just enters their API key (stored in the device keychain). Mirrors the desktop feature shipped in v2.5.19.

Provider credit balances + per-call cost (desktop & mobile) — remaining-credit badges in AI settings now resolve each provider's balance endpoint from the community manifest's credits descriptor, not just OpenRouter's /v1/key. DeepSeek's /user/balance, Neuralwatt's /v1/quota, and OpenRouter's /key all feed the badge. The parsing/resolution logic lives once in shared/chat/provider-credits.ts, used by both the desktop IPC handler (ai:fetchKeyInfo) and mobile's getKeyInfo. And when a provider reports a USD cost per call (usage.cost, e.g. Neuralwatt), both context rings show a Cost row alongside the token breakdown (mobile persists it in chat.lastUsage).

Type of change

  • Bug fix
  • New feature
  • Refactor / code quality
  • Docs / changelog
  • Tests

Screenshots / recording

Checklist

  • Mobile type-check passes (npx tsc --noEmit in mobile/, exit 0)
  • Desktop type-check passes (npm run type-check:all)
  • Node test suite passes (vitest run --project node)
  • Ran on a device/simulator (verify Add flow + iOS/Android key prompt)
  • Changelog entries added (mobile/changelogs/v0.1.4.md, changelogs/v2.5.20.md)
  • No hardcoded colours — theme tokens only (t.accent, t.textPrimary, etc.)
  • API keys stored in expo-secure-store only (never SQLite, never synced)
  • Uses the shared, Zod-validated provider schema (@cairn/shared/chat/registry-schema) — no duplicated types

Notes for reviewer

  • Separate manifest by design. Providers use providers.json (new mobile/src/chat/providers-registry.ts), parallel to registry.ts for tools/commands — same fetch/cache/ETag shape, different URL + meta-DB keys. Hard-refresh cache-busts to dodge stale CDN 304s, matching the desktop fix.
  • SavedProvider gained source/communityId (round-trip through the existing readProviders type-guard unchanged). installCommunityProvider dedups by communityId/name, reuses the row id so the keychain key survives a re-install, and — unlike addSavedProvider — does not auto-select the provider (matches desktop). The name fallback only matches community-sourced rows, so a manual provider with a colliding name is never hijacked.
  • Entry point: a Browse Providers nav row in AiSettingsForm.tsx; a useFocusEffect refreshes the provider list on return so a newly-added provider shows in the switcher (without clobbering in-progress edits to the active provider).
  • Key prompt: iOS uses Alert.prompt (secure-text); Android uses the in-app modal, copying the pattern from settings/tools.tsx.
  • Credit badges. resolveCreditSpec matches the configured endpoint against the manifest's credits descriptor (url + shape), then parseCredits interprets the response (deepseek / openai-grants / neuralwatt / openrouter). Shared in shared/chat/provider-credits.ts; desktop probes via probeCredits (live-checked in electron/lib/provider-credits.live-check.test.ts against real endpoints), mobile probes via expo/fetch. Unknown shapes fall back to the OpenRouter /v1/key probe and degrade to a hidden badge.
  • Per-call cost. Desktop: usage.cost flows through chat:usage/chat:done → useChatStream → ContextRing (agent + subagent blocks). Mobile: captured in the OpenAI streamer, persisted in chat.lastUsage, shown in the context-ring popover.
  • The .expo/types/router.d.ts typed-route for /settings/providers is regenerated by expo (git-ignored), so it's not in this diff.
  • The matching cairn-community catalog (11 providers incl. Pollinations & Merge) is already live on main; the credits descriptors for deepseek/neuralwatt/openrouter ship with it.

Summary by CodeRabbit

  • New Features

    • Added Browse Providers with search, filtering, refresh, installation, updates, and secure API-key storage.
    • Added model favorites, searchable model selection, pricing, provider logos, capability indicators, and tool-support warnings.
    • Display provider balances, currencies, and per-turn usage costs.
    • Updated the default AI model to gpt-5.6-luna.
    • Added model-aware image and PDF attachments, including PDF previews and compatibility handling.
  • Bug Fixes

    • Added cached provider and model data for offline use.
    • Improved provider loading, refresh, installation, and update error handling.
    • Improved quick-settings positioning in narrow windows.

Bring the desktop community-providers feature to mobile, reusing the shared
providers.json schema and the existing keychain / meta-DB / ConnectorLogo
infrastructure.

- providers-registry.ts: fetch + cache the separate providers.json manifest
  (conditional GET + ETag, offline meta-DB cache, hard-refresh cache-buster),
  mirroring registry.ts and the desktop fetcher
- ai-config.ts: SavedProvider gains source/communityId; installCommunityProvider
  (dedup by communityId/name, reuse id so the secure-store key survives, store
  key in expo-secure-store, no auto-select) + isCommunityProviderOutdated
- app/settings/providers.tsx: Browse Providers screen — search + category chips,
  Add/Added/Update states, needsApiKey gating (iOS Alert.prompt / Android modal),
  "Get a key" link
- AiSettingsForm.tsx: Browse Providers nav row; refresh the provider list on
  focus so a newly-added provider appears in the switcher
- changelog: v0.1.4 entry
@coderabbitai

coderabbitai Bot commented Aug 1, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This change adds mobile community-provider browsing and installation, shared provider credit and model catalogs, provider balance probing, provider-reported usage-cost tracking, PDF attachments, model capability indicators, attachment gating, and Radix-based chat quick settings.

Changes

Provider platform enhancements

Layer / File(s) Summary
Shared provider, model, and attachment contracts
shared/chat/*, shared/models/*
Adds validated provider credit descriptors, model metadata utilities, attachment conversion helpers, cost formatting, and tests.
Provider registry and installation
mobile/src/chat/providers-registry.ts, mobile/src/chat/ai-config.ts, mobile/app/settings/providers.tsx
Adds cached manifest fetching, community-provider installation and updates, secure key storage, search, filtering, refresh, and platform-specific key entry.
Model catalog and capability integration
mobile/src/chat/models-dev.ts, src/lib/models-dev.ts, mobile/src/components/AiSettingsForm.tsx, src/components/ui/model-picker.tsx
Adds normalized catalog caching, subscriptions, logos, pricing, capability indicators, favorites, and model-based attachment gating.
Credit probing and usage costs
shared/chat/provider-credits.ts, electron/lib/provider-credits.ts, electron/ipc/*, electron/lib/chat-loop.ts, electron/ipc/chat.ts, mobile/src/chat/providers/openai.ts
Resolves provider credit endpoints, parses balances, probes provider APIs, and propagates numeric provider-reported costs through chat usage.
Multimodal chat and interface updates
shared/models/pdf-attach.ts, src/components/chat/*, electron/lib/chat-subagent-loop.ts, electron/lib/llm.ts, src/lib/pdf-*.ts
Adds PDF document parts, PDF rasterization, bounded token estimates, typed attachment kinds, PDF rendering, and model-specific input controls.
Balance display, defaults, and supporting UI
src/components/agent/*, mobile/src/components/ContextRing.tsx, src/components/chat/chat-panel/ChatQuickSettings.tsx, src/lib/constants.ts, changelogs/*
Displays costs and balances, updates default model fallbacks, uses a portaled quick-settings popover, and records the changes in changelogs.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ProvidersSettingsScreen
  participant fetchProvidersManifest
  participant installCommunityProvider
  participant SecureStorage

  User->>ProvidersSettingsScreen: Open or filter provider catalog
  ProvidersSettingsScreen->>fetchProvidersManifest: Load or refresh manifest
  fetchProvidersManifest-->>ProvidersSettingsScreen: Return manifest or cached data
  User->>ProvidersSettingsScreen: Install provider with API key
  ProvidersSettingsScreen->>installCommunityProvider: Submit provider definition
  installCommunityProvider->>SecureStorage: Store API key
  installCommunityProvider-->>ProvidersSettingsScreen: Return saved provider ID
Loading
sequenceDiagram
  participant Chat
  participant runToolLoop
  participant Provider
  participant ContextRing
  participant BalanceCache

  Chat->>runToolLoop: Process streamed usage
  runToolLoop->>Provider: Read provider-reported cost
  Provider-->>runToolLoop: Return numeric cost
  runToolLoop-->>Chat: Emit accumulated costUsd
  Chat->>ContextRing: Pass persisted costUsd
  ContextRing->>BalanceCache: Check cached provider balance
  BalanceCache-->>ContextRing: Return balance or null
  ContextRing-->>Chat: Render cost and balance
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 77.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: community providers, credit balances, and per-call costs across desktop and mobile.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ddutchie/mobileproviders

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mobile/app/settings/providers.tsx`:
- Around line 66-92: Guard the state updates in loadRegistry against completion
after the owning effect has been cleaned up. Pass or otherwise associate the
effect’s cancelled state with loadRegistry, and only call setProviders and
setError while still active; preserve the existing loading and refresh
completion behavior.
- Around line 223-232: Update the Pressable handler around def.apiKeyUrl to
handle rejected promises from Linking.openURL, ensuring failures are caught
rather than left unhandled while preserving the existing “Get a key” behavior.
- Around line 210-263: Update the row rendering logic around installedRow and
outdated to derive the outdated state directly from installedRow, using the same
version comparison semantics as isCommunityProviderOutdated. Remove the
per-entry call to isCommunityProviderOutdated(entry), while preserving the
existing installed and update-button behavior.

In `@mobile/src/chat/ai-config.ts`:
- Around line 275-305: Update the existing-provider lookup in
installCommunityProvider so the name-based fallback only matches providers whose
source is "community"; retain the communityId match behavior and leave manually
configured providers unmatched when names collide.

In `@mobile/src/chat/providers-registry.ts`:
- Around line 75-113: Update fetchProvidersManifest so the If-None-Match
conditional request is used only when getCachedProvidersManifest() returns a
valid manifest. When cached is null, omit the ETag header (and treat the request
as an unconditional fetch) so the server returns content that can be revalidated
and recached; preserve the existing 304 behavior for valid cached manifests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bbc1e3ad-265b-4ff4-bd09-8cd970ff0afb

📥 Commits

Reviewing files that changed from the base of the PR and between 8d6e449 and f634027.

📒 Files selected for processing (5)
  • mobile/app/settings/providers.tsx
  • mobile/changelogs/v0.1.4.md
  • mobile/src/chat/ai-config.ts
  • mobile/src/chat/providers-registry.ts
  • mobile/src/components/AiSettingsForm.tsx

Comment thread mobile/app/settings/providers.tsx
Comment thread mobile/app/settings/providers.tsx
Comment thread mobile/app/settings/providers.tsx
Comment thread mobile/src/chat/ai-config.ts
Comment thread mobile/src/chat/providers-registry.ts
- Share credit/balance parsing across desktop + mobile via
  shared/chat/provider-credits.ts: manifest descriptor resolution and shape
  parsers for OpenRouter, DeepSeek, OpenAI grants and Neuralwatt.
- Desktop: ai:fetchKeyInfo resolves the community manifest's credits
  descriptor per endpoint; settings endpoint rows show a CreditsBadge.
- Mobile: AI-settings keyInfo card resolves the manifest descriptor too, so
  DeepSeek/Neuralwatt balances render alongside OpenRouter.
- Per-call USD cost (usage.cost) flows through both context rings
  (desktop chat usage/done events + mobile persisted lastUsage).
- Registry schema grows the credits descriptor (url + shape incl. neuralwatt);
  add live endpoint check and unit tests.
- Changelogs: changelogs/v2.5.20.md, mobile/changelogs/v0.1.4.md.
…, a11y

- providers.tsx: guard loadRegistry state updates against effect teardown
  (drop results when the owning effect was cancelled); catch rejected
  Linking.openURL promises for 'Get a key'; derive the Update/outdated state
  from the fetched installed row instead of a per-entry list scan.
- ai-config.ts: installCommunityProvider name-based fallback now only matches
  community-sourced providers, so manually-configured same-name providers are
  left untouched.
- providers-registry.ts: only send If-None-Match when a valid cached manifest
  exists; empty/corrupt cache triggers an unconditional fetch.
@ddutchie ddutchie changed the title feat(mobile): community AI providers (Browse Providers) feat(ai): community AI providers, credit balances & per-call cost (desktop + mobile) Aug 2, 2026
- provider-credits.test.ts: drop unused shape-parser imports (eslint).
- provider-credits.live-check.test.ts: defer the manifest read into the test
  body so an absent cairn-community repo (CI/fresh clone) collects cleanly and
  skips instead of throwing ENOENT at suite collection.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@electron/ipc/chat.ts`:
- Line 177: Update costUsd in the chat usage flow to remain optional rather than
defaulting to 0, and only include it in emitted usage after validating that the
value is finite and non-negative. Preserve an explicitly reported cost of 0, and
adjust finalUsage() so cost-only usage is retained instead of being discarded by
the promptTokens > 0 condition.
- Line 262: Propagate subagent cost through the usage contract: add optional
costUsd to SubagentEvents.onSubagentUsage, preload onSubagentUsage,
ChatSubagent.lastUsage, and useChatStream.onSubagentUsage; update
electron/ipc/chat.ts chat:subagent-usage to pass subagent.lastUsage.costUsd and
include metrics.promptTokens when it represents the subagent’s own context-turn
total; update ChatSubagentBlock to accept costUsd and render it via ContextRing.
Apply the type change at src/types/index.ts:523 and the IPC change at
electron/ipc/chat.ts:262.

In `@electron/lib/provider-credits.live-check.test.ts`:
- Around line 31-34: Move the fs.readFileSync(manifestPath, "utf8") manifest
initialization out of the describe.skipIf(!hasManifest) factory and into the
guarded it.skipIf(!hasAnyKey) test callback. Keep manifest-dependent assertions
inside that callback so the file is never read when the manifest is unavailable.

In `@mobile/src/components/ContextRing.tsx`:
- Around line 263-266: Update formatUsd in
mobile/src/components/ContextRing.tsx:263-266 to preserve at least one non-zero
significant digit for small positive costs such as 0.000001, then use this
corrected formatter in the non-iOS alert at
mobile/src/components/ContextRing.tsx:128-130 and iOS popover at
mobile/src/components/ContextRing.tsx:240-246; apply the same formatting
behavior in src/components/agent/ContextRing.tsx:201-210.

In `@shared/chat/provider-credits.test.ts`:
- Around line 2-10: Remove the unused parseOpenRouterCredits,
parseDeepSeekCredits, and parseOpenAiGrantsCredits imports from the
provider-credits test file; retain the imports used by existing tests, including
parseNeuralwattCredits and parseCredits.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 65348933-5c14-4141-893b-dbaa62ddbba7

📥 Commits

Reviewing files that changed from the base of the PR and between f634027 and 424d3ce.

📒 Files selected for processing (29)
  • changelogs/v2.5.20.md
  • electron/ipc/ai-handlers.ts
  • electron/ipc/chat.ts
  • electron/lib/chat-loop.ts
  • electron/lib/provider-credits.live-check.test.ts
  • electron/lib/provider-credits.ts
  • electron/preload.ts
  • mobile/app/(tabs)/chat/index.tsx
  • mobile/app/settings/providers.tsx
  • mobile/changelogs/v0.1.4.md
  • mobile/src/chat/ai-config.ts
  • mobile/src/chat/providers-registry.ts
  • mobile/src/chat/providers/openai.ts
  • mobile/src/chat/providers/types.ts
  • mobile/src/components/ContextRing.tsx
  • mobile/src/db/chat-store.ts
  • shared/chat/provider-credits.test.ts
  • shared/chat/provider-credits.ts
  • shared/chat/registry-schema.test.ts
  • shared/chat/registry-schema.ts
  • src/components/agent/AgentChatPane.tsx
  • src/components/agent/AgentMessageBubble.tsx
  • src/components/agent/ContextRing.tsx
  • src/components/chat/chat-panel/ChatSubagentBlock.tsx
  • src/components/chat/chat-panel/index.tsx
  • src/components/settings/endpoint-components.tsx
  • src/hooks/useChatStream.ts
  • src/store/slices/chat.ts
  • src/types/index.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • mobile/src/chat/providers-registry.ts
  • mobile/src/chat/ai-config.ts
  • mobile/app/settings/providers.tsx
  • mobile/changelogs/v0.1.4.md

Comment thread electron/ipc/chat.ts Outdated
Comment thread electron/ipc/chat.ts
Comment thread electron/lib/provider-credits.live-check.test.ts Outdated
Comment thread mobile/src/components/ContextRing.tsx Outdated
Comment thread shared/chat/provider-credits.test.ts Outdated
…cost formatting

- chat.ts: costUsd stays undefined until a provider reports it; only non-negative
  finite values accumulate (explicit 0 preserved), and finalUsage keeps cost-only
  usage instead of dropping it on the promptTokens > 0 gate.
- subagent cost: SubagentEvents.onSubagentUsage + preload onSubagentUsage +
  useChatStream carry costUsd, and runSubagent accumulates each subagent's cost
  (incl. a forced final answer) onto its own context ring. The chat:subagent-usage
  passthrough and promptTokens-as-own-context semantics were already correct.
- ContextRing (mobile + desktop agent): formatUsd keeps at least one significant
  digit for tiny costs (e.g. $0.000001) instead of collapsing to $0.
…ngs popover

- Context ring gains an account-level Balance row (remaining credits or
  "Free tier"), TTL-cached (1min, shared via useSyncExternalStore) so ring
  instances don't re-probe; hidden when the provider exposes no balance or
  offline; excluded from subagent rings (showBalance=false).
- Mobile chat-header ring shows the same Balance row (Alert + iOS popover)
  via getKeyInfo with the same TTL cache.
- ChatQuickSettings dropdown is now a portaled Radix Popover so it no longer
  clips in narrow windows / docked panels.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
electron/lib/provider-credits.live-check.test.ts (1)

55-55: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Pass an abort signal to bound each live probe.

probeCredits accepts an optional AbortSignal, and the production path in electron/ipc/ai-handlers.ts uses a 12-second AbortController. This test passes no signal. If a provider endpoint hangs, the test blocks until the Vitest timeout and gives no per-provider diagnostic. Add a per-probe timeout so the loop reports a failed provider instead of stalling.

♻️ Proposed change
-        const probe = await probeCredits(resolved.url, key, resolved.shape);
+        const ac = new AbortController();
+        const timer = setTimeout(() => ac.abort(), 12_000);
+        let probe;
+        try {
+          probe = await probeCredits(resolved.url, key, resolved.shape, ac.signal);
+        } finally {
+          clearTimeout(timer);
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/lib/provider-credits.live-check.test.ts` at line 55, Update the live
probe flow around probeCredits to create a per-provider AbortController with the
same 12-second timeout used by electron/ipc/ai-handlers.ts, pass its signal as
the optional argument, and clear the timeout after completion so hanging
providers fail through the existing diagnostic path instead of blocking the
test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mobile/src/components/ContextRing.tsx`:
- Around line 19-21: Update the mobile balance cache definition and its
read/write logic in ContextRing to include active.id alongside the existing base
URL key. Ensure cache hits require the provider id, base URL, and API key to
match, and store all three values when refreshing the balance, preserving the
existing TTL behavior.
- Around line 71-90: Update the balance-loading useEffect in ContextRing to
depend on getActiveProviderId(), so it reruns when the active provider changes
while preserving the existing cache and cancellation behavior.

In `@src/components/agent/ContextRing.tsx`:
- Around line 112-126: Move the duplicated formatUsd and formatBalance helpers
into shared/chat/provider-credits.ts, then update both
src/components/agent/ContextRing.tsx lines 112-126 and
mobile/src/components/ContextRing.tsx lines 311-325 to reuse them. Preserve
negative signs by using Math.abs only for digit selection and placing the
currency symbol after the minus sign, while retaining existing formatting
behavior for positive values and balance states.
- Around line 72-107: Deduplicate concurrent balance probes in the ContextRing
balance-loading effect by adding a module-level in-flight promise keyed by the
balance key and reusing it when the cache is cold or expired. Store and clear
the shared promise around fetchKeyInfo, update the shared balance store from the
resolved result, and remove the per-effect cancelled guard since store
notifications already handle unmounted subscribers.

---

Nitpick comments:
In `@electron/lib/provider-credits.live-check.test.ts`:
- Line 55: Update the live probe flow around probeCredits to create a
per-provider AbortController with the same 12-second timeout used by
electron/ipc/ai-handlers.ts, pass its signal as the optional argument, and clear
the timeout after completion so hanging providers fail through the existing
diagnostic path instead of blocking the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9c7c6ede-3a61-4c6e-9a94-f3c2e677d4f6

📥 Commits

Reviewing files that changed from the base of the PR and between 424d3ce and 359f805.

📒 Files selected for processing (13)
  • changelogs/v2.5.20.md
  • electron/ipc/chat.ts
  • electron/lib/chat-subagent-loop.ts
  • electron/lib/provider-credits.live-check.test.ts
  • electron/preload.ts
  • mobile/changelogs/v0.1.4.md
  • mobile/src/components/ContextRing.tsx
  • shared/chat/provider-credits.test.ts
  • src/components/agent/AgentMessageBubble.tsx
  • src/components/agent/ContextRing.tsx
  • src/components/chat/chat-panel/ChatQuickSettings.tsx
  • src/components/chat/chat-panel/ChatSubagentBlock.tsx
  • src/hooks/useChatStream.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/components/chat/chat-panel/ChatSubagentBlock.tsx
  • src/components/agent/AgentMessageBubble.tsx
  • electron/ipc/chat.ts
  • electron/preload.ts
  • shared/chat/provider-credits.test.ts
  • src/hooks/useChatStream.ts

Comment thread mobile/src/components/ContextRing.tsx Outdated
Comment thread mobile/src/components/ContextRing.tsx Outdated
Comment thread src/components/agent/ContextRing.tsx
Comment thread src/components/agent/ContextRing.tsx Outdated
Fresh installs, resets, and unset-model fallbacks use gpt-5.6-luna instead of
gpt-4o / gpt-4o-mini (chat, coding agent, notes AI actions, flow summaries);
mobile's DEFAULT_OPENAI_MODEL switches from gpt-4.1-mini. UI placeholders and
onboarding preset list updated to match; the model picker still lists the
endpoint's real models.
…rs, probe dedup

- Desktop: dedupe concurrent balance probes via a module-level in-flight
  promise keyed by provider key (cold/expired cache); drop the per-effect
  cancelled guard (the external-store subscription already handles unmounts).
- Mobile: key the balance cache on provider id + base URL + API key (two
  providers can share a base URL with different keys) and rerun the probe when
  the active provider id changes.
- Move formatUsd/formatBalance into shared/chat/provider-credits.ts and reuse
  from both context rings; negative amounts render the currency after the minus
  sign (-¥5). Cover both with unit tests.
- Live-check test: probe each provider through a 12s AbortController like the
  ai:fetchKeyInfo handler, cleared on completion.
Extends the existing models.dev catalog lookup (context ring) into a full
per-model metadata layer shared by desktop + mobile:

- shared/models/model-catalog.ts: parse api.json into {context, input/output
  per-1M cost, image-input, tool-call, provider}, id normalization/fuzzy
  lookup, formatModelCost (tiny-cost safe), providerLogoUrl, supportsImageInput
- desktop + mobile models-dev: persist the full info map (localStorage /
  app_settings), add a subscribe/version API so pickers re-render on load
- model pickers (desktop dropdown + trigger, mobile AI settings list) show the
  provider logo (theme-aware: inverted on dark), $in/$out cost and a warning
  for models without tool calling
- chat image attach gated per model: hidden/disabled when the active model is
  known not to accept images; permissive default for unknown models
Extends the shared models.dev ModelInfo to carry the raw input modalities and
renders compact capability chips next to each model: on the desktop picker
trigger (closed state) and dropdown rows, and on the mobile AI-settings model
list. Tooltips explain each chip (Text/Image/PDF/Video/Audio input).
- Accessors (supportsImageInput, supportsPdfInput, modelInputChips) no longer
  assume the cached ModelInfo has the modes array — a cache written by the
  previous build (pre-modes shape) crashed ChatPanel. Cache loads now normalize
  entries (normalizeModelInfo) and migrate the legacy image boolean.
- Capability chips now render lucide icons (Type / Image / FileText / Video /
  AudioLines) instead of text letters, on the desktop picker trigger + rows and
  the mobile model list.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mobile/app/`(tabs)/chat/index.tsx:
- Around line 84-111: Include the value returned by useSyncExternalStore in the
allowImages useMemo dependency array so the memo recomputes when the model
catalog updates. Keep openAiModelActive as the existing input and preserve the
current permissive behavior for unknown or unavailable models.

In `@shared/chat/provider-credits.ts`:
- Around line 168-173: Update formatUsd to format based on the absolute cost
magnitude while preserving the negative sign separately, matching
formatBalance’s fmt helper; ensure large negative values such as -123.456 render
with the correct amount. Add a negative-cost test in provider-credits.test.ts,
and apply the same correction to the duplicate formatter in ContextRing.tsx if
present.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 27d6e425-9df6-4c67-94ec-9805e4f5c126

📥 Commits

Reviewing files that changed from the base of the PR and between 359f805 and 4e4b428.

📒 Files selected for processing (38)
  • changelogs/v2.5.20.md
  • electron/ipc/ai-handlers.ts
  • electron/ipc/chat.ts
  • electron/ipc/flow-handlers.ts
  • electron/ipc/pi-agent.ts
  • electron/ipc/tool-builder.ts
  • electron/lib/provider-credits.live-check.test.ts
  • mobile/app/(tabs)/chat/index.tsx
  • mobile/changelogs/v0.1.4.md
  • mobile/src/chat/ai-config.ts
  • mobile/src/chat/models-dev.ts
  • mobile/src/components/AiSettingsForm.tsx
  • mobile/src/components/ContextRing.tsx
  • mobile/src/components/ProviderLogo.tsx
  • mobile/src/components/ai-settings/styles.ts
  • mobile/src/components/chat/Composer.tsx
  • shared/chat/provider-credits.test.ts
  • shared/chat/provider-credits.ts
  • shared/models/model-catalog.test.ts
  • shared/models/model-catalog.ts
  • shared/models/pdf-attach.test.ts
  • shared/models/pdf-attach.ts
  • src/app/globals.css
  • src/components/agent/ContextRing.tsx
  • src/components/agent/SpawnAgentModal.tsx
  • src/components/chat/ChatInput.tsx
  • src/components/chat/chat-panel/ChatQuickSettings.tsx
  • src/components/chat/chat-panel/index.tsx
  • src/components/flow/nodes/AiSummaryNode.tsx
  • src/components/notes/notes-view/PrdModal.tsx
  • src/components/onboarding/StepAISetup.tsx
  • src/components/onboarding/index.tsx
  • src/components/settings/ProviderManager.tsx
  • src/components/settings/endpoint-components.tsx
  • src/components/ui/model-picker.tsx
  • src/lib/constants.ts
  • src/lib/models-dev.ts
  • src/store/slices/notes.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • electron/lib/provider-credits.live-check.test.ts
  • mobile/changelogs/v0.1.4.md
  • electron/ipc/ai-handlers.ts
  • electron/ipc/chat.ts
  • mobile/src/chat/ai-config.ts
  • src/components/chat/chat-panel/ChatQuickSettings.tsx
  • src/components/settings/endpoint-components.tsx
  • shared/chat/provider-credits.test.ts
  • mobile/src/components/ContextRing.tsx
  • src/components/agent/ContextRing.tsx

Comment thread mobile/app/(tabs)/chat/index.tsx
Comment thread shared/chat/provider-credits.ts Outdated
…allback)

The chat composer now accepts PDFs alongside images. Models known (via
models.dev) to take PDF input receive the raw bytes as an Anthropic-style
`document` base64 part — lossless, and smaller than rasterized pages for
text-heavy files. Models that only take images get the PDF rasterized
in-browser (pdf.js legacy build, main-thread fake worker, up to 8 pages)
into image parts so the file still arrives.

- shared/models/pdf-attach.ts: buildAttachmentParts emits document parts
  for kind:"pdf" (kind optional, defaults to image); pdfTokenEstimate
  bounds the context-ring cost of a document part.
- src/lib/pdf-document.ts + test: node-safe pdf.js loader (page count,
  base64 decode) backed by a committed 4-page fixture.
- src/lib/pdf-rasterize.ts: page->PNG data-URL rendering. Uses pdf.js's
  main-thread fake worker (importing pdf.worker.min.mjs self-registers
  globalThis.pdfjsWorker) instead of a ?url asset import, which Turbopack
  can't compile for the static export.
- electron/ipc/chat.ts + lib/chat-subagent-loop.ts: user message content
  built via buildAttachmentParts; lib/llm.ts tokContent bills document
  parts via pdfTokenEstimate (not raw base64 length).
- desktop UI: ChatInput accept=image/pdf + PDF chip in the preview strip
  and in sent bubbles; chat-panel gates attach on supportsPdfInput /
  supportsImageInput and rasterizes as the image-only fallback.
- mobile: the OpenAI provider maps application/pdf file parts to document
  parts (latent — mobile has no document picker yet).

changelog: changelogs/v2.5.20.md
…ating

- shared/chat/provider-credits.ts: formatUsd now formats by absolute
  magnitude with the minus preserved before the symbol (matching
  formatBalance). Negative costs like -123.456 previously fell into the
  tiny-cost toPrecision(2) branch and rendered as "$-120"; they now show
  "-$123.456". No duplicate formatter in ContextRing — both desktop and
  mobile import the shared one. Added negative-cost cases to the tests.
- mobile/app/(tabs)/chat/index.tsx: the allowImages memo now depends on
  the useSyncExternalStore catalog version, so image gating recomputes
  when the models.dev catalog loads/refreshes after the active model was
  resolved on focus (previously the memo stayed stale until the model
  name changed). Permissive behaviour for unknown/absent models unchanged.
The bare model text field in AI settings is now a dropdown-style selector
matching the desktop picker: the current model shows on a tappable row with
its provider logo, cost, and capability markers, expanding into a searchable
list plus a "Custom model…" input for ids the endpoint didn't list.

The list now populates itself — the core of the "first need to fetch"
friction. Model ids are cached per endpoint (meta DB, 7-day TTL, mirroring
desktop's endpoint-models-cache) and hydrated instantly on open; with an API
key present, the endpoint is refreshed in the background (debounced +
generation-guarded) so a fresh provider always lists its models without a
manual tap. The "Fetch models" button becomes a "Refresh" affordance.

- mobile/src/chat/ai-config.ts: readModelsCache/writeModelsCache
  (normCacheBaseUrl keying).
- AiSettingsForm.tsx: dropdown trigger + expandable searchable list +
  custom-model toggle; auto-fetch effect (cache hydrate + debounced refresh);
  fetchModels now takes explicit base/key so auto-refresh can't mix a
  mid-switch provider's url with the old key.

changelog: mobile/changelogs/v0.1.4.md
Each dropdown row gets a star (nested pressable on the leading edge) that
toggles a favorite without selecting the model. Favorites are persisted as a
global id set in the meta DB (mirroring desktop's localStorage favoriteModels)
and pinned above the rest in a "Favorites" section at the top of the list, so
usual models are one tap away instead of buried in the full list.

- mobile/src/chat/ai-config.ts: getFavoriteModels/toggleFavoriteModel
  (key ai.favoriteModels, JSON array of ids).
- AiSettingsForm.tsx: favorites state + shared renderModelRow with star,
  favModels/restModels split of the filtered list, section label + separator.
- ai-settings/styles.ts: modelStar/modelSectionLabel/modelSectionSeparator.

changelog: mobile/changelogs/v0.1.4.md
models.dev lists each model under every host provider, so the flattened
api.json catalog can't tell who OWNS a model — `deepseek/deepseek-v4-flash`
resolved to the hpc-ai logo and bare `deepseek-v4-flash` to cortecs. We now
also fetch models.json (222KB canonical <provider>/<model> map) alongside
api.json and resolve the logo from the canonical owner, with the brand-name
heuristic as a mid-tier fallback (covers models not in the canonical list and
ids the fuzzy match can't reach).

- shared: parseCanonicalCatalog + canonicalProviderFor (exact + normalized
  fuzzy match like lookupModelInfo); logoProviderFor/providerLogoUrlFor take an
  optional canonical map and prefer it over the heuristic.
- desktop + mobile models-dev: fetch + cache models.json (per-platform
  localStorage/SQLite, same 7-day TTL), expose getLogoProvider/modelLogoUrl.
- model-picker.tsx + AiSettingsForm.tsx: resolve logos via the composed lookup.

Verified against the live catalogs: deepseek-v4-flash→deepseek,
openai/gpt-5.2→openai, zhipuai/glm-5→zhipuai, xai/grok-4.3→xai,
gemma-3→google (were hpc-ai/perplexity-agent/cortecs/vercel/inference).

changelogs: v2.5.20.md + mobile v0.1.4.md
Saved community AI providers now render their real brand icon (ConnectorLogo)
in the saved-providers switcher instead of the generic Server glyph — on both
desktop (trigger + dropdown rows) and mobile (provider chips).

The logo is resolved from the community catalog by communityId, with a
fallback to a normalized base-URL match so a provider added manually that
happens to share an endpoint with a catalog entry (e.g. added before it was
in the registry) still picks up its brand mark.

- desktop ProviderManager.tsx: logoMap keyed by id + normBaseUrl; logoFor.
- mobile AiSettingsForm.tsx: buildProviderLogoMap keyed by id + normBaseUrl;
  prewarm cache-first + network refresh; passed to ProviderList.
- mobile ProviderList.tsx: renders ConnectorLogo on each chip, resolved by
  communityId → normalized baseUrl.

changelogs: v2.5.20.md + mobile v0.1.4.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
electron/ipc/chat.ts (1)

266-266: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep dispatcher costs in the main usage contract.

Subagent mode does not carry dispatcher-generated costs into the main chat:done usage. This underreports provider-reported USD usage.

  • electron/ipc/chat.ts#L266-L266: Return the dispatcher cost from runDispatchLoop and merge it before calling finalUsage().
  • electron/lib/chat-subagent-loop.ts#L520-L522: Pass the dispatcher cost accumulator to forceFinalAnswer so forced synthesis cost is recorded.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@electron/ipc/chat.ts` at line 266, Update electron/ipc/chat.ts lines 266-266
so runDispatchLoop returns its dispatcher cost and that cost is merged into the
main usage before finalUsage() is called. Update
electron/lib/chat-subagent-loop.ts lines 520-522 to pass the dispatcher cost
accumulator to forceFinalAnswer, ensuring forced synthesis costs are included in
the final chat:done usage.
mobile/src/chat/providers/openai.ts (1)

180-188: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Validate usage.cost before assigning to costUsd.

mobile/src/chat/providers/openai.ts currently assigns any numeric chunk.usage.cost at lines 263-265, so invalid values can reach usage.costUsd. Use the same guard as electron/ipc/chat.ts: typeof cost === "number" && Number.isFinite(cost) && cost >= 0. Preserve 0 and leave costUsd undefined when no valid cost is reported.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/chat/providers/openai.ts` around lines 180 - 188, Validate the
cost value before assigning it to costUsd in the OpenAI streaming usage
handling. Apply the guard typeof cost === "number", Number.isFinite(cost), and
cost >= 0, preserving valid 0 values and leaving costUsd undefined for missing
or invalid costs; use the existing costUsd and chunk.usage handling symbols.
🧹 Nitpick comments (2)
src/components/chat/chat-panel/index.tsx (1)

361-380: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Limit the size of rasterized PDF attachments.

rasterizePdfToImages returns up to 8 PNG pages at up to 2048 px edge. Each page becomes a base64 data URL in pendingAttachments. addMessage at Line 412 persists these data URLs to the store, to disk, and over IPC. A multi-page PDF can therefore add tens of megabytes per message. Consider a total byte budget, JPEG output, or a smaller edge cap for the fallback path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/chat/chat-panel/index.tsx` around lines 361 - 380, Limit the
fallback rasterization in the attachment-processing flow around
rasterizePdfToImages before pushing items into pendingAttachments. Enforce a
bounded total payload for generated page data, preferably by reducing image
dimensions or using JPEG output, and stop or skip pages once the budget is
reached while preserving existing PDF/image handling and error behavior.
mobile/src/components/AiSettingsForm.tsx (1)

507-518: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the capability-icon mapping into one helper.

The same c.key → icon chain exists in renderModelRow and in the collapsed trigger. Duplicate mapping can diverge when a new capability key is added. Move the map to a module-level constant and render from it in both places.

♻️ Proposed refactor
+const CAP_ICONS = { text: Type, image: ImageIcon, pdf: FileText, video: Video, audio: AudioLines } as const;
+
+function CapabilityIcons({ info, color }: { info: ModelInfo | null; color: string }) {
+  return (
+    <>
+      {modelInputChips(info).map((c) => {
+        const CapIcon = CAP_ICONS[c.key as keyof typeof CAP_ICONS];
+        return CapIcon ? <CapIcon key={c.key} size={10} color={color} /> : null;
+      })}
+    </>
+  );
+}

Also applies to: 776-787

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@mobile/src/components/AiSettingsForm.tsx` around lines 507 - 518, Extract the
capability-to-icon mapping used by renderModelRow and the collapsed trigger into
a shared module-level constant. Replace both inline c.key conditional chains
with lookups from that constant, preserving the existing icon rendering and null
behavior for unsupported keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@mobile/src/chat/ai-config.ts`:
- Around line 423-425: Update normCacheBaseUrl so normalization removes only
trailing slashes and preserves endpoint paths such as /v1, keeping root and /v1
model-discovery URLs in distinct cache entries; ensure the cache key or complete
model-list URL uses this preserved normalized path.
- Around line 454-474: The favorite storage keys are inconsistent between
getFavoriteModels/toggleFavoriteModel and the UI store's global favorites. Align
FAVORITE_MODELS_KEY and the corresponding mobile read/write flow with the
established "favoriteModels" key, or migrate existing "ai.favoriteModels" data
so current mobile favorites are preserved and both model-picker paths share one
set.
- Around line 429-446: Validate persisted entries in readModelsCache and
writeModelsCache before trusting or storing them: require models to be an array
of strings and ts to be a finite number, rejecting malformed entries by
returning null during reads and avoiding persistence during writes. Anchor the
validation around the ModelsCache entry accessed via normCacheBaseUrl, while
preserving the existing TTL behavior for valid entries.

In `@shared/models/model-catalog.ts`:
- Around line 71-112: Lowercase the model-id segment used by the
LOGO_BRAND_MATCHES loop in logoProviderFor, while preserving the existing token
lookup and fallback behavior. Ensure mixed-case brand prefixes such as Claude or
GPT match the same providers as their lowercase equivalents.

In `@src/components/chat/ChatInput.tsx`:
- Around line 302-310: The file picker and upload flow must enforce the selected
model’s media capabilities. In src/components/chat/ChatInput.tsx lines 302-310,
update the file input accept value to independently include image types only
when allowImages is true and PDF types only when allowPdf is true; in
src/components/chat/chat-panel/index.tsx lines 346-360, skip image files before
invoking FileReader when allowImages is false, while preserving PDF handling.

---

Outside diff comments:
In `@electron/ipc/chat.ts`:
- Line 266: Update electron/ipc/chat.ts lines 266-266 so runDispatchLoop returns
its dispatcher cost and that cost is merged into the main usage before
finalUsage() is called. Update electron/lib/chat-subagent-loop.ts lines 520-522
to pass the dispatcher cost accumulator to forceFinalAnswer, ensuring forced
synthesis costs are included in the final chat:done usage.

In `@mobile/src/chat/providers/openai.ts`:
- Around line 180-188: Validate the cost value before assigning it to costUsd in
the OpenAI streaming usage handling. Apply the guard typeof cost === "number",
Number.isFinite(cost), and cost >= 0, preserving valid 0 values and leaving
costUsd undefined for missing or invalid costs; use the existing costUsd and
chunk.usage handling symbols.

---

Nitpick comments:
In `@mobile/src/components/AiSettingsForm.tsx`:
- Around line 507-518: Extract the capability-to-icon mapping used by
renderModelRow and the collapsed trigger into a shared module-level constant.
Replace both inline c.key conditional chains with lookups from that constant,
preserving the existing icon rendering and null behavior for unsupported keys.

In `@src/components/chat/chat-panel/index.tsx`:
- Around line 361-380: Limit the fallback rasterization in the
attachment-processing flow around rasterizePdfToImages before pushing items into
pendingAttachments. Enforce a bounded total payload for generated page data,
preferably by reducing image dimensions or using JPEG output, and stop or skip
pages once the budget is reached while preserving existing PDF/image handling
and error behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4fe036cc-9510-4a5a-9056-af8b553bb3c0

📥 Commits

Reviewing files that changed from the base of the PR and between 4e4b428 and 90f0fa8.

⛔ Files ignored due to path filters (2)
  • package-lock.json is excluded by !**/package-lock.json
  • src/lib/__fixtures__/markdown-parity-test.pdf is excluded by !**/*.pdf
📒 Files selected for processing (29)
  • changelogs/v2.5.20.md
  • electron/ipc/chat.ts
  • electron/lib/chat-subagent-loop.ts
  • electron/lib/llm.ts
  • electron/lib/tools.ts
  • mobile/app/(tabs)/chat/index.tsx
  • mobile/changelogs/v0.1.4.md
  • mobile/src/chat/ai-config.ts
  • mobile/src/chat/models-dev.ts
  • mobile/src/chat/providers/openai.ts
  • mobile/src/components/AiSettingsForm.tsx
  • mobile/src/components/ai-settings/styles.ts
  • package.json
  • shared/chat/provider-credits.test.ts
  • shared/chat/provider-credits.ts
  • shared/models/model-catalog.test.ts
  • shared/models/model-catalog.ts
  • shared/models/pdf-attach.ts
  • src/components/chat/ChatInput.tsx
  • src/components/chat/chat-panel/ChatMessageBubble.tsx
  • src/components/chat/chat-panel/index.tsx
  • src/components/ui/model-picker.tsx
  • src/hooks/useChatStream.ts
  • src/lib/models-dev.ts
  • src/lib/pdf-document.test.ts
  • src/lib/pdf-document.ts
  • src/lib/pdf-rasterize.ts
  • src/types/index.ts
  • src/types/pdfjs.d.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/types/index.ts
  • shared/chat/provider-credits.ts
  • mobile/src/chat/models-dev.ts
  • src/lib/models-dev.ts
  • mobile/app/(tabs)/chat/index.tsx
  • src/components/ui/model-picker.tsx
  • mobile/changelogs/v0.1.4.md
  • shared/models/pdf-attach.ts
  • shared/chat/provider-credits.test.ts

Comment thread mobile/src/chat/ai-config.ts Outdated
Comment thread mobile/src/chat/ai-config.ts
Comment thread mobile/src/chat/ai-config.ts
Comment thread shared/models/model-catalog.ts
Comment thread src/components/chat/ChatInput.tsx
…ints

Saved providers whose hostname maps to a direct vendor that models.dev serves
(OpenAI, Anthropic, Google, DeepSeek, OpenRouter, Together, Groq, Fireworks,
Neuralwatt, …) now render that vendor's models.dev logo when the community
catalog has no inline iconSvg for them — covering the 5 community providers
currently missing an icon, plus manually-added providers sharing those
endpoints.

Logo resolution priority for a saved provider:
  1. community iconSvg (by communityId, then normalized baseUrl)
  2. models.dev slug via curated hostname table (endpointLogoSlug)
  3. generic Server glyph

- shared: ENDPOINT_LOGO_SLUGS hostname→slug table + endpointLogoSlug(baseUrl)
  (parses the URL hostname, falls back to a bare-host regex). Tested.
- desktop ProviderManager.tsx: logoFor returns a discriminated
  {iconSvg}|{url} union; renderProviderLogo draws ConnectorLogo or an <img>.
- mobile AiSettingsForm.tsx: builds a per-provider resolved logo map
  (memoized) merging community iconSvg → endpoint slug; ProviderList renders
  ConnectorLogo or ProviderLogo accordingly. Added ResolvedProviderLogo type.

changelogs: v2.5.20.md + mobile v0.1.4.md
Saved-provider chips and community-browse rows on both desktop and mobile
now render models.dev provider SVGs through ConnectorLogo on the same
fixed light chip background as community icons, instead of raw <img>
(desktop) or theme-tinted glyphs (mobile). Adds getOrFetchLogoSvg to the
mobile models-dev module (mirroring desktop's), with useSyncExternalStore
subscriptions in ProviderManager, BrowseProvidersModal, ProviderList, and
ProvidersSettingsScreen so rows pop in their brand mark once the lazy
SVG fetch lands.
The selected provider's fields (Name, Base URL, API key, Model, Context
window) are now hidden behind a read-only summary card until you tap
Edit. Selecting another provider shows its summary; a freshly added
provider opens straight in the editor. Declutters the screen and
prevents accidental edits.

Browse Providers is now hidden while the Apple Intelligence or Rork
backend is selected, since community presets only apply to OpenAI-
compatible providers; it reappears when you switch back to OpenAI.
- ChatInput: build file-input accept from allowImages/allowPdf independently;
  skip image files before FileReader when allowImages is false (no silent drop)
- model-catalog: lowercase base segment before LOGO_BRAND_MATCHES so mixed-case
  ids (Claude/GPT/DeepSeek) resolve their brand mark; add tests
- mobile ai-config: normCacheBaseUrl preserves /v1 (distinct cache entries);
  readModelsCache/writeModelsCache validate entry shape (array of strings +
  finite ts) before trusting persisted JSON
- changelogs (desktop v2.5.20, mobile v0.1.4)
@ddutchie
ddutchie merged commit 1ea6507 into main Aug 2, 2026
6 checks passed
@ddutchie
ddutchie deleted the ddutchie/mobileproviders branch August 2, 2026 19:16
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