Skip to content

feat(web): model settings popover in the LLM provider modal - #14131

Merged
nmgarza5 merged 22 commits into
mainfrom
nikg/model-settings-admin-ui
Aug 21, 2026
Merged

feat(web): model settings popover in the LLM provider modal#14131
nmgarza5 merged 22 commits into
mainfrom
nikg/model-settings-admin-ui

Conversation

@nmgarza5

@nmgarza5 nmgarza5 commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a Model Settings popover to each model row in the LLM provider setup modal, where an admin sets the reasoning cap, the reasoning default, and the temperature default for that model. Backend enforcement merged in #14078.

The first commit is a pure move: the slider, the setting row, and the reasoning stop constants were private to the chat model selector, and the admin side needs the same controls.

The popover offers only the levels the backend says the model distinguishes, hides the reasoning section for a reasoning model that takes no effort parameter, and disables temperature for reasoning models, which the backend pins to 1. Dragging Max below Default pulls Default down, so the pair cannot reach a state the API rejects. A stored setting is clamped by current capability on read, so a level the model lost since the save is neither shown nor resubmitted.

The model rows also gain the mock's set-as-default action: the current org default shows a Default Model tag, every other visible row gets a hover Set as Default button.

Pre-existing bugs in the surrounding code are fixed here, since the change sits on top of them:

  • An unsaved model rename was discarded by a model-list refetch. The merge only carried is_visible.
  • Eleven refetch call sites across nine provider modals merged against a Formik snapshot captured when the request started, so an edit made while a fetch was in flight was reverted. They now use the functional setValues.
  • Pressing Enter on the rename pencil toggled the model's visibility, because the synthesized click reached the row.
  • Clicking a model title to rename it also toggled the model's visibility.
  • Enabling auto-update discarded unsaved edits and dropped models discovered after the modal opened.
  • Custom provider models neither clamped nor persisted their settings on save.

How Has This Been Tested?

  • bun run types:check clean, oxfmt / oxlint / ripsecrets pass on every touched file.
  • Local Greptile review loop plus two independent review agents over the full diff. Nine real defects found and fixed.
  • Manually exercised live on a dev stack: popover editing, clamping, set-as-default, rename, visibility toggling, auto-update round trip.

Demo (open modal, hover actions, popover, reasoning default change):

2026-08-21 10 04 06
After: provider modal After: model settings popover
modal popover

Before: the modal renders plain checkbox rows with no per-model controls. The popover, the hover actions, and the Default Model tag are all new surfaces.

Additional Options

  • [Optional] Please cherry-pick this PR to the latest release version.
  • [Optional] Override Linear Check

The slider, the setting row, and the reasoning stop constants were private to
the chat model selector. The admin provider modal needs the same controls, so
move them out rather than grow a second implementation.

Pure move. maxSupportedReasoningStop stays behind as a one-line adapter over
maxReasoningStop, since the admin side holds a raw ModelConfiguration rather
than an LLMOption.
Adds a Model Settings popover to each model row in the provider setup modal,
where an admin sets the reasoning cap, the reasoning default, and the
temperature default for that model.

The reasoning section renders only the levels the backend says the model
distinguishes, and hides entirely for a reasoning model that takes no effort
parameter. The temperature section hides for reasoning models, which the
backend pins to 1. Dragging Max below Default pulls Default down with it, so
the pair cannot be saved in a state the API rejects.

Also carries the settings through the model-refresh merge. That merge kept
only is_visible from prior form state, so refreshing the model list inside the
modal would have discarded unsaved settings.
@nmgarza5
nmgarza5 requested a review from a team as a code owner August 20, 2026 20:50
@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds per-model reasoning and temperature settings, default-model actions, and safer reconciliation of fetched model data in provider forms.

  • Adds the model settings popover and shared slider controls.
  • Preserves unsaved model edits across refetches and auto-update transitions.
  • Adds inline rename safeguards and default-model selection controls.
  • Extends custom-provider model settings persistence and capability clamping.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
web/src/sections/modals/languageModels/shared.tsx Adds row actions and preserves current model edits while restoring only saved visibility during the auto-mode transition.
web/src/sections/modals/languageModels/ModelSettingsPopover.tsx Implements capability-aware reasoning and temperature controls with named component prop interfaces.
web/src/sections/modals/languageModels/utils.ts Adds policy clamping and functional fetched-model reconciliation that retains unsaved form state.
web/lib/opal/src/layouts/content/ContentMd.tsx Exposes an imperative edit handle and prevents title-edit interactions from propagating to host rows.
web/src/lib/languageModels/cache.ts Adds the default-model update flow and refreshes the affected centralized SWR caches.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Provider model row] --> B[Rename model]
  A --> C[Open model settings]
  A --> D[Set as default]
  C --> E[Update Formik model configuration]
  F[Model refetch] --> G[Functional state reconciliation]
  G --> E
  E --> H[Submit provider configuration]
Loading

Reviews (5): Last reviewed commit: "fix(web): uniform range for the default ..." | Re-trigger Greptile

Comment thread web/src/sections/modals/languageModels/shared.tsx
Comment thread web/src/sections/modals/languageModels/ModelSettingsPopover.tsx Outdated
Enabling auto-update restored the full initial model snapshot, discarding
unsaved renames and admin settings along with the visibility it meant to
reset. The restore now takes only visibility from the snapshot.

Also names the ResetToAuto props interface per the frontend standards.
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

🖼️ Visual Regression Report

Project Changed Added Removed Unchanged Report
admin 2 0 0 185 View Report
exclusive 0 0 0 10 ✅ No changes

The popover was 192px with the chat pane's full setting rows, so titles
wrapped and value readouts clipped. Rebuilt to the mock's structure: 280px,
one icon-and-caption header per section, small labeled sliders indented to
the icon column, and stop labels under each slider with the active stop
emphasized.

Also tightens the model rows to the mock's compact height.
…r modal

A provider saved with an empty custom_config object opened the custom
provider form. The well-known route now requires custom config keys, not
just a non-null object.
Custom provider rows get the same settings popover as well-known
providers. The model rows carry the reasoning and temperature fields
through the form so the popover can edit them.
Each visible model row gets the mock's hover action to make it the org
default. The current default shows a static Default Model label instead.
Saving uses the existing default-model endpoint and refreshes the
provider caches.
Clicking an editable ContentMd title started the rename and also
bubbled to the host row, which toggled the model's visibility.
Portaling the popover into the dialog made a tall popover overflow the
dialog box, so the modal grew a second scrollbar. The popover now stays
on the body portal and opens as a modal popover, which keeps its clicks
and focus away from the dialog's dismiss and focus-trap layers.
The current default shows an always visible Default Model tag in the
selection color. Other rows get a hover Set as Default internal button
instead of a plain text action.
The toggle rebuilt the model list from the mount snapshot, so models a
refetch discovered later vanished from the form. The restore now walks
the current list and takes only visibility from the snapshot.
setMax compared the clamped display value, so a stored default above the
model's current capability survived in form state and the API rejected
the save. The comparison now uses the raw stored default.
CustomModal seeded the form with raw saved reasoning settings, so a
capability that shrank since the save produced values the API rejects.
The rows now pass through the same clamp as well-known providers.
Popover.Content caps its height at the available viewport space and
clips overflow, so a short window cut off the temperature section. The
content now scrolls inside the popover when it cannot fit.
The submit mapper dropped the reasoning and temperature fields and
forced supports_reasoning to false, so popover edits on custom models
saved as no-ops. The mapper now carries the fields through.
The caption and label wrappers rendered inline spans on the parent's
24px line box, inflating the popover past the viewport and forcing the
scroll shell. Flex wrappers collapse the strut to the fonts' 16px
lines, headers drop their bottom padding per the mock, and the scroll
shell goes away since the popover now fits.
Extracts setDefaultLlmModelAndRefresh and withFetchedModels to kill
duplicated blocks, clamps settings through maxReasoningStop so an
unordered capability list cannot raise the ceiling, narrows the popover
prop to the fields it reads, and keeps hover actions visible while the
portaled popover is open. Also tightens stale or garbled comments.
Clicking the row to finish an inline rename blurs the edit input, which
unmounts before the click reaches the row, so the interactive-element
guard missed it and the model's visibility flipped. The row now samples
the edit input's presence at pointerdown.
@nmgarza5

Copy link
Copy Markdown
Contributor Author

@greptile

@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Preview Deployment

Status Preview Commit Updated
https://onyx-preview-j2fbb6ixb-danswer.vercel.app 812852b 2026-08-21 18:31:33 UTC

The pointerdown sample matched any input in the row, and the checkbox
always renders a hidden one, so every row click read as a rename commit
and visibility toggling died. CI caught it in the provider setup specs.
@nmgarza5

Copy link
Copy Markdown
Contributor Author

@greptile

The popover hand-rolled the section headers as raw divs, but the mock's
headers are instances of the same design component ContentMd implements,
so ContentAction renders them. The slider blocks become Sections with
margin insets, since Section silences padding utilities. No raw layout
elements remain.
@nmgarza5

Copy link
Copy Markdown
Contributor Author

@greptile

The default slider's range shrank to the current max, so its thumb
disagreed with the mark labels and lowering the max visibly dragged the
default around. Both sliders now span every stop and only the input
clamps to the cap, and an unset default parks at medium bounded by the
cap.
@nmgarza5

Copy link
Copy Markdown
Contributor Author

@greptile

Comment thread web/lib/opal/src/layouts/content/components.tsx Outdated
Every external edit trigger also wants the internal pencil gone, so the
separate hideEditButton flag collapses into editHandle. Suggested by
review.
@nmgarza5
nmgarza5 added this pull request to the merge queue Aug 21, 2026
Merged via the queue into main with commit 132b355 Aug 21, 2026
52 checks passed
@nmgarza5
nmgarza5 deleted the nikg/model-settings-admin-ui branch August 21, 2026 18:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants