Add Agent Settings UI for AI search#2320
Conversation
🎩 PreviewA preview build has been created at: |
8d45929 to
38e0b45
Compare
38b163d to
bb24190
Compare
bb24190 to
839c5d2
Compare
3cbeeb6 to
26745bd
Compare
0e1f178 to
496907a
Compare
496907a to
718a81a
Compare
26745bd to
0f078bc
Compare
254c824 to
0417a6f
Compare
0f078bc to
c4dd973
Compare
0417a6f to
8e168da
Compare
c4dd973 to
fff5c0d
Compare
8e168da to
f5acd71
Compare
morgan-wowk
left a comment
There was a problem hiding this comment.
🤖 AI-assisted review — one non-blocking a11y follow-up.
| setModel(e.target.value); | ||
| setValidationError(null); | ||
| }} | ||
| aria-label="Model id" |
There was a problem hiding this comment.
🤖 AI-assisted review (non-blocking — fine to punt to a later PR)
The Model field has a visible <Label htmlFor="agent-settings-model">Model</Label> (:201) but the input also sets aria-label="Model id". aria-label wins, so screen readers announce "Model id" while sighted users see "Model", and it overrides the label association (the other two fields share this redundancy).
Options: (a) drop the aria-labels — the <Label htmlFor> already names the field; (b) make visible label and aria-label match; (c) use aria-labelledby pointing at the label.
There was a problem hiding this comment.
🤖 User scenario
A screen-reader user tabs to the Model field and hears "Model id" announced, while the visible label everyone else sees reads "Model" — the mismatch makes the form harder to follow and to reference in docs/support.
f5acd71 to
6ade827
Compare
fff5c0d to
39aee24
Compare
| // Convert to glob patterns for ESLint | ||
| export const REACT_COMPILER_ENABLED_GLOBS = REACT_COMPILER_ENABLED_DIRS.map( | ||
| (dir) => `${dir}/**/*.{ts,tsx}`, | ||
| (path) => (/\.[cm]?[jt]sx?$/.test(path) ? path : `${path}/**/*.{ts,tsx}`), |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
This glob change silently activates the ESLint react-compiler rule on ~30 pre-existing file entries.
REACT_COMPILER_ENABLED_DIRS has long contained individual file paths (e.g. src/hooks/useFavorites.ts, and many *.tsx entries below). Under the old mapper, a file entry became the glob src/hooks/useFavorites.ts/**/*.{ts,tsx}, which matches nothing — so the rule was never actually enforced on any of those individually-listed files. This change passes file entries through verbatim, so the rule now applies to all of them at once, not just the newly-added typography.tsx.
Correct fix, but the blast radius is much larger than this PR's stated scope: any pre-existing compiler-rule violation in those files will now surface as a lint error. No code change needed — just confirm pnpm lint passes repo-wide (not only on typography.tsx), and consider calling this out in the PR description.
| setModel(e.target.value); | ||
| setValidationError(null); | ||
| }} | ||
| aria-label="Model id" |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
Redundant aria-labels duplicate the <Label>, and the Model field's disagrees with its visible label.
Each Input already has an associated <Label htmlFor> that supplies the accessible name, so the added aria-label is redundant — and here it overrides the visible label with different text:
<Label htmlFor="agent-settings-model">Model</Label> // visible: "Model"
aria-label="Model id" // accessible name: "Model id"The visible/accessible-name divergence ("Model" vs "Model id") is what voice-control and screen-reader users trip over. The other two fields are merely redundant (identical text). These aria-labels appear to exist only so the tests can call getByLabelText("Model id"), but getByLabelText already resolves associated <label> text.
Fix: drop the three aria-labels and let the <Label> name each input (update the test to query "Model"). If you want the name to read "Model id", change the visible <Label> text instead so the two stay in sync.
| const { config, update, clear, isConfigured } = useComponentSearchSettings(); | ||
| const notify = useToastNotification(); | ||
|
|
||
| const [apiBase, setApiBase] = useState(config.apiBase); |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
Form state seeds from config once and never re-syncs.
const [apiBase, setApiBase] = useState(config.apiBase);
const [apiKey, setApiKey] = useState(config.apiKey);
const [model, setModel] = useState(config.model);useComponentSearchSettings is backed by useSyncExternalStore with cross-tab sync, so config can change after mount (e.g. saved/cleared in another tab). These local states only capture the initial value, so the form won't reflect such external changes.
Minor for a single-tab settings page, but worth a conscious decision since the underlying hook went to the trouble of supporting cross-tab updates. If you want it to track external changes, sync via an effect keyed on config, or treat config as the source of truth for non-dirty fields.
|
|
||
| setTesting(true); | ||
| try { | ||
| const response = await fetch(`${trimmed.apiBase}/models`, { |
There was a problem hiding this comment.
🤖 This is an AI-generated code review comment.
The connection-test fetch has no timeout/abort.
const response = await fetch(`${trimmed.apiBase}/models`, {
headers: { authorization: `Bearer ${trimmed.apiKey}` },
});The testRunIdRef guard nicely handles Clear-during-test and stale responses, but a provider that hangs (no response) leaves the button stuck on "Testing…" indefinitely, with no recovery short of clicking Clear.
Optional fix: pass AbortSignal.timeout(…) to fetch and surface the abort as a "Test failed: timed out" toast.
6ade827 to
25768e9
Compare
39aee24 to
18bcfe1
Compare
| autoComplete="off" | ||
| spellCheck={false} | ||
| /> | ||
| <Text id="agent-settings-model-hint" size="xs" tone="subdued"> |
There was a problem hiding this comment.
curious if we need a id field on a text component?
| */ | ||
| className?: string; | ||
|
|
||
| /** HTML id used to associate text with form controls and ARIA descriptions. */ |
There was a problem hiding this comment.
isn't that what Label is for?
25768e9 to
8cc6ede
Compare
18bcfe1 to
6a60fc3
Compare
8cc6ede to
a2ec331
Compare
a2ec331 to
fb6f2d0
Compare

Tophatting
Manual tophatting recommended because this PR adds a settings screen.
What changed
Adds the Agent Settings screen for configuring AI search.
Users can enter:
The screen can test whether the provider is reachable and whether the model exists.
Why
Components V2 AI search should work with a user-provided OpenAI-compatible API. This keeps credentials out of the app bundle and lets users choose their own provider/model.
Test plan
pnpm exec vitest run src/routes/Settings/sections/AgentSettings.test.tsxpnpm run typecheck --pretty false