Skip to content

fix(web): preserve selected language model#1152

Open
slumbi wants to merge 3 commits intosourcebot-dev:mainfrom
slumbi:fix-languege-model-selection
Open

fix(web): preserve selected language model#1152
slumbi wants to merge 3 commits intosourcebot-dev:mainfrom
slumbi:fix-languege-model-selection

Conversation

@slumbi
Copy link
Copy Markdown

@slumbi slumbi commented Apr 26, 2026

Summary

  • Fix chat language model selection when configured model names or model IDs are similar.
  • Prevent broken fallback logic from alternating between models in a React hook loop, which caused repeated re-renders and made it impossible to change the selected model in some cases.
  • Make language model command items match by stable key plus display name, model, and provider.

Tests

  • Not run (not requested).

Summary by CodeRabbit

  • New Features

    • Improved model selector search: models are now matched with clearer keywords (name, identifier, provider) for easier discovery.
    • Selector now uses a resolved current model across the UI for more consistent display.
  • Bug Fixes

    • More robust selection persistence and fallback handling when available models change, reducing incorrect or missing selections.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 26, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 53e267a2-8ef3-4c6a-a0cd-56a8dae386d1

📥 Commits

Reviewing files that changed from the base of the PR and between 3578370 and 323e262.

📒 Files selected for processing (1)
  • packages/web/src/features/chat/useSelectedLanguageModel.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/web/src/features/chat/useSelectedLanguageModel.ts

Walkthrough

Reconciles stored, in-memory, and fallback language model selection; exposes a new resolvedSelectedLanguageModel. Adds explicit value and keywords metadata to model selector items and updates toolbar to feed the selector with the resolved selection.

Changes

Cohort / File(s) Summary
Language Model Selector UI
packages/web/src/features/chat/components/chatBox/languageModelSelector.tsx
CommandItem entries now include explicit value (from getLanguageModelKey(model)) and keywords (display name fallback, model id, provider).
Selection Hook & Rehydration
packages/web/src/features/chat/useSelectedLanguageModel.ts
useLocalStorage init changed to undefined; adds safe parsing/validation of stored selection; computes availableSelectedLanguageModel; returns new resolvedSelectedLanguageModel that falls back to first available or provided fallback; effect reconciles stored/current/fallback selections and updated dependencies.
Toolbar Integration
packages/web/src/features/chat/components/chatBox/chatBoxToolbar.tsx
ChatBoxToolbar now passes resolvedSelectedLanguageModel into LanguageModelSelector while still providing setSelectedLanguageModel as the updater.

Sequence Diagram(s)

sequenceDiagram
  participant Hook as useSelectedLanguageModel
  participant Local as LocalStorage
  participant Toolbar as ChatBoxToolbar
  participant Selector as LanguageModelSelector

  Hook->>Local: read "selectedLanguageModel" (parse & validate)
  Hook-->>Hook: compute availableSelectedLanguageModel (match by key)
  Hook-->>Hook: determine resolvedSelectedLanguageModel (available or fallback)
  Toolbar->>Hook: request current model
  Hook-->>Toolbar: return resolvedSelectedLanguageModel
  Toolbar->>Selector: render with resolvedSelectedLanguageModel
  Selector->>Hook: onChange -> setSelectedLanguageModel(new)
  Hook->>Local: persist new selectedLanguageModel
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • brendan-kellam
🚥 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 'fix(web): preserve selected language model' accurately reflects the main objective: fixing language model selection preservation. It directly matches the PR's primary goal of maintaining selected model state without unintended fallbacks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

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: 1

🧹 Nitpick comments (1)
packages/web/src/features/chat/components/chatBox/languageModelSelector.tsx (1)

127-134: LGTM — explicit value plus keywords correctly addresses the cmdk collision for similar model names.

Pinning value to getLanguageModelKey(model) prevents cmdk from auto-deriving (and potentially deduping) values from rendered text when display names look alike, and keywords keeps search ergonomics by matching on display name, model id, and provider.

Optional nit: when model.displayName is missing, "" is pushed into keywords. cmdk handles this fine, but filtering empty strings keeps the keyword set tidy.

♻️ Optional cleanup
-                                                keywords={[
-                                                    model.displayName ?? "",
-                                                    model.model,
-                                                    model.provider,
-                                                ]}
+                                                keywords={[
+                                                    model.displayName,
+                                                    model.model,
+                                                    model.provider,
+                                                ].filter((kw): kw is string => Boolean(kw))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/features/chat/components/chatBox/languageModelSelector.tsx`
around lines 127 - 134, The keywords array passed to CommandItem includes an
empty string when model.displayName is missing; update the keywords construction
used in the CommandItem with value={getLanguageModelKey(model)} so it filters
out falsy/empty values (e.g., only include model.displayName if truthy, plus
model.model and model.provider) to avoid pushing "" into keywords while
preserving search behavior; locate the CommandItem in languageModelSelector.tsx
and adjust the keywords assembly around model.displayName, model.model, and
model.provider to exclude empty strings.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/web/src/features/chat/useSelectedLanguageModel.ts`:
- Around line 37-41: The hook now returns resolvedSelectedLanguageModel (which
always yields a model) breaking consumers' falsy checks; change the hook
(useSelectedLanguageModel) to return the raw selectedLanguageModel from
useLocalStorage instead of resolvedSelectedLanguageModel so callers (chatBox.tsx
and chatThread.tsx) can still detect undefined, and keep
availableSelectedLanguageModel/resolvedSelectedLanguageModel only for internal
fallback display logic; alternatively, if implicit fallback is desired, update
the two consumers (chatBox.tsx and chatThread.tsx) to remove/adjust their
undefined checks and handle implicit selection explicitly.

---

Nitpick comments:
In `@packages/web/src/features/chat/components/chatBox/languageModelSelector.tsx`:
- Around line 127-134: The keywords array passed to CommandItem includes an
empty string when model.displayName is missing; update the keywords construction
used in the CommandItem with value={getLanguageModelKey(model)} so it filters
out falsy/empty values (e.g., only include model.displayName if truthy, plus
model.model and model.provider) to avoid pushing "" into keywords while
preserving search behavior; locate the CommandItem in languageModelSelector.tsx
and adjust the keywords assembly around model.displayName, model.model, and
model.provider to exclude empty strings.
🪄 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: ad11318a-0e3f-4226-b0b4-3ab4990f97ec

📥 Commits

Reviewing files that changed from the base of the PR and between 105ddf4 and 5bc8cba.

📒 Files selected for processing (2)
  • packages/web/src/features/chat/components/chatBox/languageModelSelector.tsx
  • packages/web/src/features/chat/useSelectedLanguageModel.ts

Comment thread packages/web/src/features/chat/useSelectedLanguageModel.ts
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.

🧹 Nitpick comments (2)
packages/web/src/features/chat/useSelectedLanguageModel.ts (2)

12-23: Minor: unchecked cast on parsed JSON.

JSON.parse(storedValue) as LanguageModelInfo will silently accept any object shape persisted under "selectedLanguageModel". Practically harmless here because downstream matching via getLanguageModelKey(...) will simply not find a match for a malformed shape and the resolution falls through to fallbackLanguageModel. Still, a minimal shape check (provider/model/displayName are strings) would make the helper more defensible if other code paths start consuming the raw result.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/features/chat/useSelectedLanguageModel.ts` around lines 12 -
23, getStoredSelectedLanguageModel currently does an unchecked cast of
JSON.parse(...) to LanguageModelInfo; update it to validate the parsed object's
minimal shape before returning. After JSON.parse(storedValue) inside
getStoredSelectedLanguageModel, assert that the result is an object and that
result.provider, result.model, and result.displayName are all strings; only then
return it cast as LanguageModelInfo, otherwise return undefined (preserving the
existing try/catch behavior).

45-69: The suggested simplification is unsafe and would introduce bugs.

The analysis misses a critical configuration detail: initializeWithValue: false at line 33. This setting intentionally prevents the hook from initializing with the stored value on first render, causing selectedLanguageModel to be undefined on mount even if localStorage contains data. This design choice necessitates the getStoredSelectedLanguageModel() call to handle hydration mismatches and recover the stored value when languageModels updates.

You're correct that the second branch (lines 61–65) checking currentSelectedLanguageModel is redundant — the outer guard already established that the selected model is unavailable. However, collapsing the entire setter to just setSelectedLanguageModel(fallbackLanguageModel) would eliminate the first branch's stored value recovery, breaking the fallback logic for the hydration case.

A safer improvement would be to simplify only the redundant second branch while preserving the stored value logic:

♻️ Possible simplification
-        if (selectedLanguageModel && !availableSelectedLanguageModel) {
-            setSelectedLanguageModel((currentSelectedLanguageModel) => {
-                const storedSelectedLanguageModel = getStoredSelectedLanguageModel();
-                const availableStoredSelectedLanguageModel = storedSelectedLanguageModel && languageModels.find(
-                    (model) => getLanguageModelKey(model) === getLanguageModelKey(storedSelectedLanguageModel)
-                );
-
-                if (availableStoredSelectedLanguageModel) {
-                    return availableStoredSelectedLanguageModel;
-                }
-
-                if (currentSelectedLanguageModel && languageModels.find(
-                    (model) => getLanguageModelKey(model) === getLanguageModelKey(currentSelectedLanguageModel)
-                )) {
-                    return currentSelectedLanguageModel;
-                }
-
-                return fallbackLanguageModel;
-            });
-        }
+        if (selectedLanguageModel && !availableSelectedLanguageModel) {
+            setSelectedLanguageModel((currentSelectedLanguageModel) => {
+                const storedSelectedLanguageModel = getStoredSelectedLanguageModel();
+                const availableStoredSelectedLanguageModel = storedSelectedLanguageModel && languageModels.find(
+                    (model) => getLanguageModelKey(model) === getLanguageModelKey(storedSelectedLanguageModel)
+                );
+
+                return availableStoredSelectedLanguageModel ?? fallbackLanguageModel;
+            });
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/features/chat/useSelectedLanguageModel.ts` around lines 45 -
69, The current effect in useSelectedLanguageModel must retain the stored-value
recovery because initializeWithValue: false means selectedLanguageModel can be
undefined on mount; therefore, in the setSelectedLanguageModel callback (used
when selectedLanguageModel && !availableSelectedLanguageModel), remove the
redundant check against currentSelectedLanguageModel but keep the
getStoredSelectedLanguageModel lookup and fallback behavior — i.e., inside the
callback look up availableStoredSelectedLanguageModel via
getStoredSelectedLanguageModel() and languageModels.find, return that if found,
otherwise return fallbackLanguageModel; keep references to
availableSelectedLanguageModel, getStoredSelectedLanguageModel,
setSelectedLanguageModel, languageModels, and fallbackLanguageModel to ensure
hydration recovery remains intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/web/src/features/chat/useSelectedLanguageModel.ts`:
- Around line 12-23: getStoredSelectedLanguageModel currently does an unchecked
cast of JSON.parse(...) to LanguageModelInfo; update it to validate the parsed
object's minimal shape before returning. After JSON.parse(storedValue) inside
getStoredSelectedLanguageModel, assert that the result is an object and that
result.provider, result.model, and result.displayName are all strings; only then
return it cast as LanguageModelInfo, otherwise return undefined (preserving the
existing try/catch behavior).
- Around line 45-69: The current effect in useSelectedLanguageModel must retain
the stored-value recovery because initializeWithValue: false means
selectedLanguageModel can be undefined on mount; therefore, in the
setSelectedLanguageModel callback (used when selectedLanguageModel &&
!availableSelectedLanguageModel), remove the redundant check against
currentSelectedLanguageModel but keep the getStoredSelectedLanguageModel lookup
and fallback behavior — i.e., inside the callback look up
availableStoredSelectedLanguageModel via getStoredSelectedLanguageModel() and
languageModels.find, return that if found, otherwise return
fallbackLanguageModel; keep references to availableSelectedLanguageModel,
getStoredSelectedLanguageModel, setSelectedLanguageModel, languageModels, and
fallbackLanguageModel to ensure hydration recovery remains intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cdf813bb-1b8f-4eb6-a3db-7826c5d4f370

📥 Commits

Reviewing files that changed from the base of the PR and between 5bc8cba and 3578370.

📒 Files selected for processing (2)
  • packages/web/src/features/chat/components/chatBox/chatBoxToolbar.tsx
  • packages/web/src/features/chat/useSelectedLanguageModel.ts

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