Skip to content

fix(ai): route local inference through provider router + revamp settings UI#2580

Merged
senamakel merged 12 commits into
tinyhumansai:mainfrom
senamakel:fix/local-inference-providers
May 24, 2026
Merged

fix(ai): route local inference through provider router + revamp settings UI#2580
senamakel merged 12 commits into
tinyhumansai:mainfrom
senamakel:fix/local-inference-providers

Conversation

@senamakel
Copy link
Copy Markdown
Member

@senamakel senamakel commented May 24, 2026

Summary

  • Route local inference (Ollama/LM Studio) through the unified provider router instead of a parallel local::service::public_infer path.
  • Revamp the AI settings panel into three modes — Managed, Use Your Own Models, and Advanced — replacing the prior per-workload Managed/Default/Custom chips.
  • Drop the separate external-openai-compat endpoint key surface; null/empty routing strings now parse to { kind: 'default' }.
  • Update unit tests to match the new UI controls and parser semantics.

Problem

The local provider had its own dispatch (local::service::public_infer) that bypassed the workload-aware routing/factory layer. This led to drift between cloud and local code paths and made the settings UI carry two parallel concepts (workload-level chips and a separate OpenAI-compat endpoint key). The settings panel had also outgrown the per-row Managed/Default/Custom UI as the number of workloads grew.

Solution

  • Inline local inference selection into provider::factory::create_local_chat_provider_from_string and dispatch through inference_test_provider_model / shared chat provider trait.
  • Delete inference/local/service/public_infer.rs and inference/local/ops.rs along with their tests; collapse the schemas accordingly.
  • Reorganise AIPanel.tsx around the Managed / Use Your Own Models / Advanced segmented control. Advanced reveals the full workload routing matrix; Use Your Own Models surfaces the connected providers + model picker.
  • Introduce a { kind: 'default' } ProviderRef and treat null / 'cloud' wire values as that.
  • Rewrite the affected Vitest specs to drive the new controls (Add Custom Provider button, Change Model dialog, Advanced toggle) and remove tests for the now-deleted OpenAI-compat endpoint helpers.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • N/A: diff-coverage gate enforced in CI — relying on workflow to verify
  • N/A: behaviour change to existing surface, no new feature row
  • N/A: no matrix feature IDs touched
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • N/A: settings refactor, not a release-cut surface
  • N/A: no linked issue

Impact

  • Desktop only. No mobile/web/CLI behaviour change.
  • Settings UI for AI providers is materially redesigned; users on the old cloud/openhuman routing sentinels now resolve to { kind: 'default' }, which preserves prior managed-routing behaviour.

Related

  • Closes:
  • Follow-up PR(s)/TODOs:

AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/local-inference-providers
  • Commit SHA: 19053d7

Validation Run

  • N/A: format applied via Husky pre-push hook
  • pnpm compile (tsc --noEmit)
  • Focused tests: AIPanel.test, aiSettingsApi.test — all green
  • N/A: Rust changes already covered by upstream commits + pre-push cargo check
  • N/A: Tauri shell not touched in this PR

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: local inference now flows through the unified provider router; settings panel reorganised into Managed/Use Your Own Models/Advanced.
  • User-visible effect: redesigned AI settings UI; null routing values default to managed routing instead of an explicit openhuman sentinel.

Parity Contract

  • Legacy behavior preserved: parseProviderString continues to recognise "openhuman" and "openhuman:<model>" as { kind: 'openhuman' }; the new { kind: 'default' } covers the historical null / "cloud" cases that already routed to managed.
  • Guard/fallback/dispatch parity checks: local model dispatch goes through create_local_chat_provider_from_string and back through simple_chat, matching the cloud path.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: this one
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Redesigned AI settings interface with clearer routing modes: Managed, Use Your Own Models, and Advanced.
    • Added support for LM Studio local models alongside Ollama.
    • New global model selector for applying models across workloads.
    • Temperature override capability for testing models.
  • UI/UX Improvements

    • Refactored provider authentication from list-style to chip-based toggles.
    • Simplified workload routing configuration with top-level mode cards.
    • Enhanced custom routing dialog with per-workload model hints.

Review Change Stack

senamakel added 5 commits May 24, 2026 11:01
Drop tests for removed helpers (updateRouting, OpenAI-compat endpoint
key API) and update assertions for the Managed / Use Your Own Models /
Advanced controls. Null routing strings now parse to { kind: 'default' }.
@senamakel senamakel requested a review from a team May 24, 2026 20:26
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Warning

Review limit reached

@senamakel, we couldn't start this review because you've used your available PR reviews for now.

Your plan includes 5 reviews of capacity. Refill in 9 minutes and 39 seconds.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more review capacity refills, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than trial, open-source, and free plans. In all cases, review capacity refills continuously over time.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 50e1d634-d3eb-404d-81e5-c86daee876f1

📥 Commits

Reviewing files that changed from the base of the PR and between 726f0ee and 241fd58.

📒 Files selected for processing (18)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/lib/i18n/chunks/ar-1.ts
  • app/src/lib/i18n/chunks/bn-1.ts
  • app/src/lib/i18n/chunks/de-1.ts
  • app/src/lib/i18n/chunks/en-1.ts
  • app/src/lib/i18n/chunks/es-1.ts
  • app/src/lib/i18n/chunks/fr-1.ts
  • app/src/lib/i18n/chunks/hi-1.ts
  • app/src/lib/i18n/chunks/id-1.ts
  • app/src/lib/i18n/chunks/it-1.ts
  • app/src/lib/i18n/chunks/ko-1.ts
  • app/src/lib/i18n/chunks/pt-1.ts
  • app/src/lib/i18n/chunks/ru-1.ts
  • app/src/lib/i18n/chunks/zh-CN-1.ts
  • app/src/lib/i18n/en.ts
  • src/openhuman/inference/ops.rs
  • src/openhuman/inference/provider/factory.rs
  • src/openhuman/inference/provider/factory_test.rs
📝 Walkthrough

Walkthrough

This PR redesigns the AI provider routing model from openhuman-centric to a default-centric approach, replaces local multi-turn chat with single-turn inference and reaction decisions, introduces LM Studio provider support via the factory layer, removes the OpenAI-compatible endpoint API surface, and substantially refactors the AIPanel UI to use managed/own/advanced routing cards instead of per-row Default/Custom segmentation.

Changes

Provider routing and local inference refactor

Layer / File(s) Summary
Provider routing contract and parsing
app/src/components/settings/panels/AIPanel.tsx, app/src/services/api/aiSettingsApi.ts, app/src/services/api/__tests__/aiSettingsApi.test.ts
ProviderRef union adds { kind: 'default' } variant serialized as "cloud"; parseProviderString treats empty/"cloud" as default (vs legacy openhuman); serializeProviderRef maps default to "cloud"; routing initialization and test expectations updated.
Cloud provider key storage
app/src/services/api/aiSettingsApi.ts, app/src/components/settings/panels/AIPanel.tsx, app/src/services/api/__tests__/aiSettingsApi.test.ts
OpenAI-compatible endpoint key/status API removed; new slug-keyed setCloudProviderKey(slug, apiKey) and clearCloudProviderKey(slug) exports store/remove credentials using provider:<slug> format; loadAISettings derives has_api_key per cloud provider checking both slug-keyed and legacy keys.
Local multi-turn chat API removal
app/src/utils/tauriCommands/localAi.ts, src/openhuman/inference/local/ops.rs, src/openhuman/inference/local/ops_tests.rs
Removes LocalAiChatMessage and openhumanLocalAiChat RPC; replaces with ReactionDecision struct and local_ai_should_react operation that validates input, gates on model readiness, constructs emoji-selection prompt, and extracts/parses first emoji with flag/ZWJ/skin-tone support.
Local provider single-turn inference API
src/openhuman/inference/local/service/public_infer.rs, src/openhuman/inference/local/service/public_infer_tests.rs
Removes chat_with_history multi-turn helper; adds inference wrapper and temperature-controlled methods (inference_with_temperature, inference_with_temperature_allow_empty, inference_with_temperature_internal) with scheduler gating, no_think branching, provider selection (LM Studio vs Ollama), HTTP error handling, token metrics, and empty-response control.
Provider factory and LM Studio support
src/openhuman/inference/provider/factory.rs, src/openhuman/inference/local/mod.rs
Adds LM_STUDIO_PROVIDER_PREFIX constant and create_local_chat_provider_from_string(provider, config) function to build local providers (Ollama/LM Studio) without session gate; implements make_lm_studio_provider with auth-style selection based on API key presence; updates Ollama provider to use centralized ollama_base_url_from_config for endpoint normalization.
Factory tests and live provider discovery
src/openhuman/inference/provider/factory_test.rs, scripts/debug/live-local-provider-smoke.sh
Adds LM Studio model-string parsing and credential tests; introduces helper functions to discover non-embedding models from local LM Studio/Ollama servers (with OPENHUMAN_LIVE_LMSTUDIO_MODEL/OPENHUMAN_LIVE_OLLAMA_MODEL environment-variable overrides); adds ignored live streaming tests for both providers with sentinel text verification and mock /v1/chat/completions HTTP servers.
RPC schema and inference chat removal
src/openhuman/inference/schemas.rs, src/openhuman/inference/schemas_tests.rs
Removes inference_chat RPC handler, chat-specific parameter types (InferenceChatMessageParam, InferenceChatParams), and controller schema registration; updates schema catalog count and function-name stability tests to reflect removed chat endpoint.
Inference operations and test provider model refactor
src/openhuman/inference/ops.rs, src/openhuman/inference/ops_tests.rs, src/openhuman/subconscious/executor.rs
Removes inference_chat export; refactors inference_test_provider_model to use create_local_chat_provider_from_string and simple_chat instead of manual provider parsing and LocalAiChatMessage construction; adds Axum mock server helper (spawn_mock) and provider routing tests; updates executor to build ChatMessage list and use provider-based API with config.default_temperature.
AIPanel routing types and state hooks
app/src/components/settings/panels/AIPanel.tsx
Adds routing type updates with default variant, WORKLOAD_MODEL_HINTS per-workload guidance dictionary, routing-mode inference utilities (providerRefSignature, inferRoutingMode, inferSharedModelRef, routingWithAllWorkloads), and refactors local state from separate provider/routing editors to unified routingEditorMode (managed/own/custom).
AIPanel provider authentication UI
app/src/components/settings/panels/AIPanel.tsx
Refactors provider auth from list-style editor to chip-based toggles with expanded built-in cloud slugs (gmi, fireworks, moonshot), always-on Managed chip, async local-runtime toggle handlers that scrub routing entries to kind: 'default' when disabled, "Add Custom Provider" button for custom editor access, and updated reserved-slug validation and API key input styling (text instead of password).
AIPanel workload routing UI redesign
app/src/components/settings/panels/AIPanel.tsx
Introduces top-level routing-mode cards (Managed/Use Your Own/Advanced) with conditional rendering; adds GlobalOwnModelSelector component for single provider/model selection across workloads (excluding embeddings); refactors WorkloadRow to simplified resolved-model display with "Change Model" action; adds per-workload model hints to custom routing dialog; expands cloud provider endpoints for new slugs.
AIPanel test updates
app/src/components/settings/panels/__tests__/AIPanel.test.tsx
Removes OpenAI-compat endpoint test coverage; verifies Managed chip rendering and routing control visibility; refactors custom routing dialog tests to use Advanced section + "Change Model" entry point; updates routing-scrub expectations to reset entries to kind: 'default'; adjusts custom provider editor interaction paths from "Connect Custom" to "Add Custom Provider" button.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • tinyhumansai/openhuman#1858: Implements unified per-workload provider routing with new ProviderRef grammar (default/cloud vs openhuman mapping) and shared provider-factory integration that aligns directly with this PR's routing and factory changes.
  • tinyhumansai/openhuman#2383: Documents opt-in chat_provider/reasoning_provider routing rules that parallel this PR's workload provider routing semantics and default-route handling.
  • tinyhumansai/openhuman#2142: Updates provider resolution so "cloud" (unset) routes through custom/primary instead of OpenHuman, matching this PR's default-route semantics shift.

Suggested labels

feature, rust-core

Suggested reviewers

  • laith-max
  • graycyrus

🐰 A local model's tale, in code we weave,
No more chats long—just single pleas,
Routing flows through "default" skies,
LM Studio now may rise 🚀
Managed chips guide all the wise!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: routing local inference through a provider router and revamping the settings UI.
Docstring Coverage ✅ Passed Docstring coverage is 91.84% which is sufficient. The required threshold is 80.00%.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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

Adopt upstream's providerToggleAriaLabel and i18n aria-labels while
preserving the revamped routing UI and password-manager suppression
attributes on the API key input.
@senamakel
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels May 24, 2026
The error message format is "{provider_label} API key not set", not
"cloud API key not set". Match on the common suffix instead.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

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

⚠️ Outside diff range comments (5)
src/openhuman/inference/provider/factory_test.rs (1)

580-992: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Split live-provider coverage into a separate test module.

This file is now ~992 lines; adding discovery/live streaming coverage here pushes it far beyond the repo’s module-size threshold. Please move the live helpers/tests into a dedicated sibling test module (for example factory_live_test.rs) and keep this file focused on unit-level factory behavior.

As per coding guidelines: "**/*.{ts,tsx,rs}: File size should not exceed approximately 500 lines. When a module grows beyond this threshold, split it into smaller, more focused modules with clear responsibilities."

🤖 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/openhuman/inference/provider/factory_test.rs` around lines 580 - 992, The
live-provider discovery helpers (discover_live_lmstudio_model,
discover_live_ollama_model) and the two asynchronous #[tokio::test] functions
(live_lmstudio_provider_streams_thinking_and_text,
live_ollama_provider_streams_text) should be moved out of this large
factory_test.rs into a new dedicated test module (e.g., factory_live_test.rs) to
keep file size under the ~500-line guideline; create the new file as a sibling
module, copy the helpers and live tests there preserving #[ignore] and imports,
update any crate:: paths if necessary (references include inference_test_guard,
create_local_chat_provider_from_string, create_local_chat_provider_from_string,
Config, ChatMessage, ChatRequest, ProviderDelta, discover_live_lmstudio_model,
discover_live_ollama_model) and leave only unit-level factory tests in the
original file; run cargo test to ensure module import paths compile and adjust
use statements to expose needed symbols.
app/src/services/api/aiSettingsApi.ts (1)

211-218: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep ollama in the persisted provider list.

AIPanel uses a slugged ollama entry in draft.cloudProviders to keep the local-runtime chip enabled and to probe models after reload. Filtering it out here makes the enablement transient: reload drops the chip state, and any later saveAISettings() call writes a config without the Ollama entry.

Suggested fix
   const cloudProviders: CloudProviderView[] = config.cloud_providers
-    .filter(p => !['', 'cloud', 'openhuman', 'ollama', 'pid'].includes(p.slug.trim()))
+    .filter(p => !['', 'cloud', 'openhuman', 'pid'].includes(p.slug.trim()))
     .map(p => {
       const newKey = authKeyForSlug(p.slug).toLowerCase();
       const legacyKey = p.slug.toLowerCase();
       const has_api_key = profileProviders.has(newKey) || profileProviders.has(legacyKey);
       return { ...p, has_api_key };
@@
     patch.cloud_providers = next.cloudProviders
-      .filter(p => !['', 'cloud', 'openhuman', 'ollama', 'pid'].includes(p.slug.trim()))
+      .filter(p => !['', 'cloud', 'openhuman', 'pid'].includes(p.slug.trim()))
       .map(({ id, slug, label, endpoint, auth_style }) => ({
         id,
         slug,
         label,
         endpoint,

Also applies to: 259-267

🤖 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 `@app/src/services/api/aiSettingsApi.ts` around lines 211 - 218, The filter
that builds cloudProviders is incorrectly excluding the 'ollama' provider;
update the exclusion array used in the config.cloud_providers.filter(...) so it
no longer contains 'ollama' (i.e., only exclude ['', 'cloud', 'openhuman',
'pid']), and apply the same change to the second occurrence that covers lines
~259-267 so that p.slug === 'ollama' remains persisted and the AIPanel
draft.cloudProviders behavior is preserved; locate these filters around the
cloudProviders mapping and the analogous block further down (they reference
config.cloud_providers, authKeyForSlug, profileProviders and set has_api_key)
and remove 'ollama' from the excluded slugs.
app/src/components/settings/panels/AIPanel.tsx (3)

1816-1825: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Advanced routing is missing a way back to Managed/default.

This dialog only lets the user save cloud or local refs. Once a workload is pinned here, there is no way to restore { kind: 'openhuman' } or { kind: 'default' }, even though the surrounding UI copy says rows can stay Managed or use the shared default.

Also applies to: 1920-1933, 1998-2028

🤖 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 `@app/src/components/settings/panels/AIPanel.tsx` around lines 1816 - 1825, The
initialSource computation currently only returns cloud/local/custom choices and
thus prevents restoring managed/default/openhuman kinds; update the logic in the
initializer for initialSource (and the analogous blocks around the other ranges)
to handle initial.kind values 'openhuman' and 'default' (or 'managed') by
returning the corresponding CustomDialogSource objects (e.g., { kind:
'openhuman' } or { kind: 'default' }) instead of falling through to cloud/local
defaults, and ensure any UI selection list used by the dialog includes entries
for those kinds so a user can re-select Managed/default; modify the branches
that reference initial, CustomDialogSource, customCloud, and localAvailable
accordingly.

113-149: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Localize the new provider labels and workload hints.

These strings are rendered in the settings UI, but they are still hard-coded English literals in module-level metadata. Please move them behind translation keys and resolve them through useT() at render time instead of shipping raw copy here.

As per coding guidelines, "Every user-visible string in app/src/** (headings, labels, button text, placeholders, status chips, toasts, dialog copy, aria-label, etc.) must go through useT() from app/src/lib/i18n/I18nContext. Hard-coded literals in JSX or props are not allowed. Add the new key to app/src/lib/i18n/en.ts in the same PR."

Also applies to: 198-216

🤖 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 `@app/src/components/settings/panels/AIPanel.tsx` around lines 113 - 149,
BUILTIN_PROVIDER_META currently contains hard-coded English labels and tones;
change it to only store non-user strings (e.g., keys and tone classes) and
relocate all user-visible text (provider labels and workload hint strings
referenced near lines 198-216) to i18n keys resolved at render time using useT()
from app/src/lib/i18n/I18nContext (call useT() inside the AIPanel component and
pass translated strings into the UI instead of literal text). Add the
corresponding new keys and English copy to app/src/lib/i18n/en.ts in this PR and
update any JSX/props that used the literal labels to use t('...') results; keep
the tone CSS classes in BUILTIN_PROVIDER_META as-is.

2874-2886: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Disabling Ollama leaves kind: 'local' routes behind.

The scrub only resets cloud refs whose providerSlug matches localKind. Ollama workloads are stored as { kind: 'local', model }, so turning Ollama off leaves those routes intact and the next inference call still targets a disabled runtime.

Suggested fix
                           const nextRouting = Object.fromEntries(
                             Object.entries(draft.routing).map(([wid, ref]) => [
                               wid,
-                              ref.kind === 'cloud' && ref.providerSlug === localKind
+                              (localKind === 'ollama' && ref.kind === 'local') ||
+                              (ref.kind === 'cloud' && ref.providerSlug === localKind)
                                 ? ({ kind: 'default' } as const)
                                 : ref,
                             ])
                           ) as typeof draft.routing;
🤖 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 `@app/src/components/settings/panels/AIPanel.tsx` around lines 2874 - 2886, The
scrub currently only resets cloud refs matching localKind, leaving Ollama routes
of shape { kind: 'local', model } intact; change the routing normalization in
nextRouting to also clear refs that are local runtimes for the provider being
removed: compute the set of removed provider slugs by comparing
draft.cloudProviders (original) against remaining, then map draft.routing and
set a route to { kind: 'default' } when (ref.kind === 'cloud' &&
ref.providerSlug === localKind) OR (ref.kind === 'local' &&
removedSlugs.has(localKind) / the removed provider was the local runtime),
updating nextRouting and then calling persist with the cleaned routing and
cloudProviders as you already do (references: nextRouting, draft.routing,
draft.cloudProviders, remaining, localKind, persist).
🧹 Nitpick comments (5)
app/src/components/settings/panels/__tests__/AIPanel.test.tsx (2)

717-722: ⚡ Quick win

Extract the repeated “open Advanced → open Reasoning model dialog” flow into a test helper.

This sequence is duplicated across multiple tests; pulling it into a helper will reduce drift and make failures easier to diagnose.

Also applies to: 775-780, 822-827, 862-867

🤖 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 `@app/src/components/settings/panels/__tests__/AIPanel.test.tsx` around lines
717 - 722, Extract the repeated "open Advanced → open Reasoning model dialog"
sequence into a test helper (e.g., openReasoningModelDialog) so tests call a
single function instead of duplicating fireEvent.click(await
screen.findByRole('button', { name: /Advanced/i })); locating the Reasoning row
via screen.findByText('Reasoning') and clicking the "Change Model" button with
within(...).getByRole('button', { name: /Change Model/i }). Implement the helper
in the test file (or a shared test-utils module) and replace the duplicated
blocks at the locations shown (around the code using fireEvent.click/
screen.findByText/ within) with calls to this helper to ensure DRYness and
easier maintenance.

241-247: 🏗️ Heavy lift

Split this spec into smaller feature-focused test files.

This test module is now ~1000+ lines and keeps growing with new scenarios; splitting by feature area (provider chips, custom provider editor, routing dialog, background loop controls) would improve maintainability and reviewability.

As per coding guidelines: "**/*.{ts,tsx,rs}: File size should not exceed approximately 500 lines. When a module grows beyond this threshold, split it into smaller, more focused modules with clear responsibilities."

Also applies to: 249-254, 256-263, 265-284, 398-406, 553-556, 583-586

🤖 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 `@app/src/components/settings/panels/__tests__/AIPanel.test.tsx` around lines
241 - 247, The AIPanel test file is too large and should be split into focused
test modules; extract related specs into separate test files organized by
feature (e.g., provider-chips.test.tsx for provider chips,
custom-provider-editor.test.tsx for the custom provider editor,
routing-dialog.test.tsx for routing dialog tests,
background-loop-controls.test.tsx for loop controls) while keeping shared
setup/helpers (renderWithProviders, any mock providers, and common fixtures) in
a test-utils or a shared setup file; update each new test file to import AIPanel
only where needed and to use the existing renderWithProviders/utility functions,
move the specific tests you see around lines asserting "OpenHuman" and the lists
referenced in the review into the appropriate new file (provider chips), and
run/adjust any describe/test names and imports to ensure tests still run and
maintain the same assertions and waits.
scripts/debug/live-local-provider-smoke.sh (1)

18-22: ⚡ Quick win

Bound model-discovery HTTP calls with timeouts.

discover_json_field currently has no timeout, so smoke runs can hang on local networking issues.

🔧 Proposed patch
 discover_json_field() {
   local url="$1"
   local js="$2"
-  curl -fsS "$url" | node -e "$js"
+  curl -fsS --connect-timeout 2 --max-time 10 "$url" | node -e "$js"
 }
🤖 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 `@scripts/debug/live-local-provider-smoke.sh` around lines 18 - 22, The
discover_json_field function makes an unbounded curl call and can hang; update
it to pass a curl timeout (e.g., --max-time or -m) and optionally a connect
timeout (e.g., --connect-timeout) to the curl invocation so the HTTP request
fails fast on network issues; modify the curl line inside discover_json_field
(which takes url and js) to include something like --connect-timeout 5
--max-time 10 (or your desired values) so the node -e "$js" invocation only runs
on a bounded curl response.
src/openhuman/inference/schemas_tests.rs (1)

4-9: ⚡ Quick win

Pin the exact controller count here.

>= 19 weakens this guard enough that a contract shrink can slip through as long as declared and registered shrink together. Since this test is already validating the public inference surface, keep the exact expected count instead.

Suggested change
-    assert!(declared.len() >= 19);
+    assert_eq!(declared.len(), 19);
🤖 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/openhuman/inference/schemas_tests.rs` around lines 4 - 9, The test
inference_catalog_counts_match_and_nonempty currently uses a loose guard
assert!(declared.len() >= 19); change this to pin the exact expected controller
count by asserting the concrete length (e.g., assert_eq!(declared.len(), 19));
keep the existing assert_eq!(declared.len(), registered.len()) and replace the
>= check with the exact-number assertion so the test fails if the public
inference surface changes unexpectedly; locate this in the function
inference_catalog_counts_match_and_nonempty where declared =
all_controller_schemas() and registered = all_registered_controllers().
src/openhuman/inference/ops.rs (1)

126-147: ⚡ Quick win

Avoid hardcoding the local-provider prefixes in this RPC.

inference_test_provider_model() now owns its own lmstudio:/ollama: dispatch list even though the factory already owns local-provider parsing. If another local prefix is added in provider::factory, this handler will silently fall through to create_chat_provider_from_string() and regress the test-provider flow. Push the local-vs-remote decision behind the factory so there is a single source of truth.

🤖 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/openhuman/inference/ops.rs` around lines 126 - 147, Remove the hardcoded
"lmstudio:"/"ollama:" prefix checks from inference_test_provider_model and
delegate the local-vs-remote decision to the provider factory: stop calling
create_local_chat_provider_from_string directly from ops.rs and instead call a
single factory entry (e.g., create_chat_provider_from_string or add a new
create_provider_for_test) that encapsulates local-prefix parsing inside
crate::openhuman::inference::provider::factory; if the factory lacks such
unified behavior, update the factory so it returns the same (chat_provider,
model) tuple for both local and remote providers and use that single factory
function in inference_test_provider_model (referencing
inference_test_provider_model, create_local_chat_provider_from_string,
create_chat_provider_from_string, and provider::factory).
🤖 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 `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 230-240: The EMPTY_ROUTING constant currently sets every workload
to { kind: 'default' }, which causes inferRoutingMode() to treat a fresh config
as 'custom' and open the Advanced editor; change the default initialization
(EMPTY_ROUTING) to use the same sentinel as managed mode (e.g., { kind:
'openhuman' } or whatever inferRoutingMode() expects) OR update
inferRoutingMode() to treat 'default' as equivalent to managed when all refs
match; locate EMPTY_ROUTING and inferRoutingMode() in AIPanel.tsx and make the
defaults and the inference logic consistent so a fresh config lands in the
managed/simple editor rather than Advanced.
- Around line 1693-1713: The JSX in AIPanel (component AIPanel.tsx, e.g., the
resolved display and the button labels like "Choose Model", "Change Model", "No
model selected" and other literals mentioned) contains hard-coded user-visible
strings; replace each literal with useT() lookups (import and call useT() from
app/src/lib/i18n/I18nContext inside AIPanel) and pass the translated keys
instead of raw strings, then add corresponding keys and English values to
app/src/lib/i18n/en.ts in this PR; ensure you update all other mentioned ranges
(around lines ~2430-2533, 2751-2909, 2941-3065) in their components similarly so
every user-visible string uses useT() and has an en.ts entry.
- Around line 2412-2425: The applySelection function sets setSaving(true) then
awaits onApply(...) but never resets setSaving(false) if onApply rejects; update
applySelection to ensure setSaving(false) always runs (e.g., wrap the await
onApply call in try/finally or attach .catch/.finally) so failures from
onApply()/persist() clear the saving state; refer to applySelection, onApply,
and setSaving when making the change.

In `@src/openhuman/inference/provider/factory_test.rs`:
- Around line 931-934: The test currently hard-fails on saw_thinking
(assert!(saw_thinking, ...)) after using the auto-discovery helper that picks
the first non-embedding LM Studio model; change the test so it does not require
a ThinkingDelta unconditionally: detect whether the discovered model can/should
emit reasoning deltas (via whatever capability flag or model metadata the
discovery helper returns) and only assert saw_thinking when that capability is
advertised, otherwise skip or downgrade to a non-failing warning; update the
assertion around saw_thinking and any related test setup that relies on
ThinkingDelta to be conditional based on the model capability advertised by the
discovery helper.

In `@src/openhuman/inference/provider/factory.rs`:
- Around line 263-300: create_local_chat_provider_from_string lacks diagnostic
logs for entry, branch selection, and error cases; add debug/info logs at the
start of the function (logging the raw provider string p), before each branch
decision indicating which prefix matched (use OLLAMA_PROVIDER_PREFIX and
LM_STUDIO_PROVIDER_PREFIX), and on the validation/error paths (empty model and
final unsupported-provider bail) so failures are observable; use the existing
logging facility used elsewhere in this module (or the module's logger) and
include the model and temperature_override where applicable and the original
provider string when logging errors, while keeping logs concise and
non-sensitive.

In `@src/openhuman/subconscious/executor.rs`:
- Around line 205-225: Add diagnostic debug logs around the local-provider path:
log the resolved model id (from effective_chat_model_id), the constructed
provider_string (from provider_from_config and the format branches), and the
resulting provider/model returned by create_local_chat_provider_from_string
before calling chat_with_history; then log a debug/info success message
including provider_string and model on successful completion of
provider.chat_with_history(...) and log an error-level message including the
same provider_string/model and the error when chat_with_history(...) returns
Err. Use the existing logging facility (e.g., debug! / error!) to match project
conventions and include the provider_string, model, and formatted error details
in the messages.

---

Outside diff comments:
In `@app/src/components/settings/panels/AIPanel.tsx`:
- Around line 1816-1825: The initialSource computation currently only returns
cloud/local/custom choices and thus prevents restoring managed/default/openhuman
kinds; update the logic in the initializer for initialSource (and the analogous
blocks around the other ranges) to handle initial.kind values 'openhuman' and
'default' (or 'managed') by returning the corresponding CustomDialogSource
objects (e.g., { kind: 'openhuman' } or { kind: 'default' }) instead of falling
through to cloud/local defaults, and ensure any UI selection list used by the
dialog includes entries for those kinds so a user can re-select Managed/default;
modify the branches that reference initial, CustomDialogSource, customCloud, and
localAvailable accordingly.
- Around line 113-149: BUILTIN_PROVIDER_META currently contains hard-coded
English labels and tones; change it to only store non-user strings (e.g., keys
and tone classes) and relocate all user-visible text (provider labels and
workload hint strings referenced near lines 198-216) to i18n keys resolved at
render time using useT() from app/src/lib/i18n/I18nContext (call useT() inside
the AIPanel component and pass translated strings into the UI instead of literal
text). Add the corresponding new keys and English copy to app/src/lib/i18n/en.ts
in this PR and update any JSX/props that used the literal labels to use t('...')
results; keep the tone CSS classes in BUILTIN_PROVIDER_META as-is.
- Around line 2874-2886: The scrub currently only resets cloud refs matching
localKind, leaving Ollama routes of shape { kind: 'local', model } intact;
change the routing normalization in nextRouting to also clear refs that are
local runtimes for the provider being removed: compute the set of removed
provider slugs by comparing draft.cloudProviders (original) against remaining,
then map draft.routing and set a route to { kind: 'default' } when (ref.kind ===
'cloud' && ref.providerSlug === localKind) OR (ref.kind === 'local' &&
removedSlugs.has(localKind) / the removed provider was the local runtime),
updating nextRouting and then calling persist with the cleaned routing and
cloudProviders as you already do (references: nextRouting, draft.routing,
draft.cloudProviders, remaining, localKind, persist).

In `@app/src/services/api/aiSettingsApi.ts`:
- Around line 211-218: The filter that builds cloudProviders is incorrectly
excluding the 'ollama' provider; update the exclusion array used in the
config.cloud_providers.filter(...) so it no longer contains 'ollama' (i.e., only
exclude ['', 'cloud', 'openhuman', 'pid']), and apply the same change to the
second occurrence that covers lines ~259-267 so that p.slug === 'ollama' remains
persisted and the AIPanel draft.cloudProviders behavior is preserved; locate
these filters around the cloudProviders mapping and the analogous block further
down (they reference config.cloud_providers, authKeyForSlug, profileProviders
and set has_api_key) and remove 'ollama' from the excluded slugs.

In `@src/openhuman/inference/provider/factory_test.rs`:
- Around line 580-992: The live-provider discovery helpers
(discover_live_lmstudio_model, discover_live_ollama_model) and the two
asynchronous #[tokio::test] functions
(live_lmstudio_provider_streams_thinking_and_text,
live_ollama_provider_streams_text) should be moved out of this large
factory_test.rs into a new dedicated test module (e.g., factory_live_test.rs) to
keep file size under the ~500-line guideline; create the new file as a sibling
module, copy the helpers and live tests there preserving #[ignore] and imports,
update any crate:: paths if necessary (references include inference_test_guard,
create_local_chat_provider_from_string, create_local_chat_provider_from_string,
Config, ChatMessage, ChatRequest, ProviderDelta, discover_live_lmstudio_model,
discover_live_ollama_model) and leave only unit-level factory tests in the
original file; run cargo test to ensure module import paths compile and adjust
use statements to expose needed symbols.

---

Nitpick comments:
In `@app/src/components/settings/panels/__tests__/AIPanel.test.tsx`:
- Around line 717-722: Extract the repeated "open Advanced → open Reasoning
model dialog" sequence into a test helper (e.g., openReasoningModelDialog) so
tests call a single function instead of duplicating fireEvent.click(await
screen.findByRole('button', { name: /Advanced/i })); locating the Reasoning row
via screen.findByText('Reasoning') and clicking the "Change Model" button with
within(...).getByRole('button', { name: /Change Model/i }). Implement the helper
in the test file (or a shared test-utils module) and replace the duplicated
blocks at the locations shown (around the code using fireEvent.click/
screen.findByText/ within) with calls to this helper to ensure DRYness and
easier maintenance.
- Around line 241-247: The AIPanel test file is too large and should be split
into focused test modules; extract related specs into separate test files
organized by feature (e.g., provider-chips.test.tsx for provider chips,
custom-provider-editor.test.tsx for the custom provider editor,
routing-dialog.test.tsx for routing dialog tests,
background-loop-controls.test.tsx for loop controls) while keeping shared
setup/helpers (renderWithProviders, any mock providers, and common fixtures) in
a test-utils or a shared setup file; update each new test file to import AIPanel
only where needed and to use the existing renderWithProviders/utility functions,
move the specific tests you see around lines asserting "OpenHuman" and the lists
referenced in the review into the appropriate new file (provider chips), and
run/adjust any describe/test names and imports to ensure tests still run and
maintain the same assertions and waits.

In `@scripts/debug/live-local-provider-smoke.sh`:
- Around line 18-22: The discover_json_field function makes an unbounded curl
call and can hang; update it to pass a curl timeout (e.g., --max-time or -m) and
optionally a connect timeout (e.g., --connect-timeout) to the curl invocation so
the HTTP request fails fast on network issues; modify the curl line inside
discover_json_field (which takes url and js) to include something like
--connect-timeout 5 --max-time 10 (or your desired values) so the node -e "$js"
invocation only runs on a bounded curl response.

In `@src/openhuman/inference/ops.rs`:
- Around line 126-147: Remove the hardcoded "lmstudio:"/"ollama:" prefix checks
from inference_test_provider_model and delegate the local-vs-remote decision to
the provider factory: stop calling create_local_chat_provider_from_string
directly from ops.rs and instead call a single factory entry (e.g.,
create_chat_provider_from_string or add a new create_provider_for_test) that
encapsulates local-prefix parsing inside
crate::openhuman::inference::provider::factory; if the factory lacks such
unified behavior, update the factory so it returns the same (chat_provider,
model) tuple for both local and remote providers and use that single factory
function in inference_test_provider_model (referencing
inference_test_provider_model, create_local_chat_provider_from_string,
create_chat_provider_from_string, and provider::factory).

In `@src/openhuman/inference/schemas_tests.rs`:
- Around line 4-9: The test inference_catalog_counts_match_and_nonempty
currently uses a loose guard assert!(declared.len() >= 19); change this to pin
the exact expected controller count by asserting the concrete length (e.g.,
assert_eq!(declared.len(), 19)); keep the existing assert_eq!(declared.len(),
registered.len()) and replace the >= check with the exact-number assertion so
the test fails if the public inference surface changes unexpectedly; locate this
in the function inference_catalog_counts_match_and_nonempty where declared =
all_controller_schemas() and registered = all_registered_controllers().
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2959572f-c634-4414-8c70-98617c8b3904

📥 Commits

Reviewing files that changed from the base of the PR and between 61cfabb and 726f0ee.

📒 Files selected for processing (18)
  • app/src/components/settings/panels/AIPanel.tsx
  • app/src/components/settings/panels/__tests__/AIPanel.test.tsx
  • app/src/services/api/__tests__/aiSettingsApi.test.ts
  • app/src/services/api/aiSettingsApi.ts
  • app/src/utils/tauriCommands/localAi.ts
  • scripts/debug/live-local-provider-smoke.sh
  • src/openhuman/inference/local/mod.rs
  • src/openhuman/inference/local/ops.rs
  • src/openhuman/inference/local/ops_tests.rs
  • src/openhuman/inference/local/service/public_infer.rs
  • src/openhuman/inference/local/service/public_infer_tests.rs
  • src/openhuman/inference/ops.rs
  • src/openhuman/inference/ops_tests.rs
  • src/openhuman/inference/provider/factory.rs
  • src/openhuman/inference/provider/factory_test.rs
  • src/openhuman/inference/schemas.rs
  • src/openhuman/inference/schemas_tests.rs
  • src/openhuman/subconscious/executor.rs
💤 Files with no reviewable changes (6)
  • src/openhuman/inference/local/ops_tests.rs
  • app/src/utils/tauriCommands/localAi.ts
  • src/openhuman/inference/local/service/public_infer_tests.rs
  • src/openhuman/inference/schemas.rs
  • src/openhuman/inference/local/ops.rs
  • src/openhuman/inference/local/service/public_infer.rs

Comment thread app/src/components/settings/panels/AIPanel.tsx
Comment thread app/src/components/settings/panels/AIPanel.tsx
Comment thread app/src/components/settings/panels/AIPanel.tsx
Comment thread src/openhuman/inference/provider/factory_test.rs
Comment thread src/openhuman/inference/provider/factory.rs
Comment thread src/openhuman/subconscious/executor.rs
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 24, 2026
senamakel added 2 commits May 24, 2026 14:13
…logs

- Fix inferRoutingMode to treat 'default' as managed (not custom)
- Add try/finally around setSaving in GlobalOwnModelSelector
- Extract all hard-coded routing UI strings into i18n keys
- Add debug logs to local provider factory and test invocation path
senamakel added 2 commits May 24, 2026 14:21
The i18n coverage check requires keys present in en.ts to also
exist in the chunked locale files (en-N.ts).
The i18n coverage check requires new keys to exist in every locale's
chunk file, not just en-1.ts. English fallback values used until
translations are provided.
@senamakel senamakel merged commit 76ce014 into tinyhumansai:main May 24, 2026
28 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant