The assertion pinned the Tailwind track string, which jsdom can never
evaluate: it does no layout, so the test could not fail for the reason
that matters, and its pattern accepted any minmax minimum, staying green
if that minimum changed enough to break the layout outright.
Only a viewport-resize browser test can observe this, which belongs in
tests/e2e/ui rather than a route's unit tests. The responsive behaviour
itself is unchanged; the widths it reproduces are recorded in b341a22339
Replaces antd and Tremor with shadcn primitives across the six route-owned,
form-free playground components: the compare view and its panel, message input
and unified selector, plus the realtime playground and the agent builder.
Markup only, no behaviour change. The characterisation tests added in the
previous commit are untouched here and stay green through the swap.
Adds ui/slider.tsx via the shadcn CLI and retires the six antd
no-restricted-imports suppressions the migration made obsolete.
The antd Row and Col the migration replaced never squeezed the summary
cards below their content width; they wrapped onto a second line instead.
Measured on a live dashboard, antd laid out five per row at 1280 and 1024,
then four plus one at 900 and three plus two at 820, never narrower than
about 126px. A fixed grid-cols-5 kept all five on one line and compressed
them to 86px at 820, so the metric values overflowed their cards.
An auto-fit track with a 7rem minimum reproduces antd's wrap points and
card widths exactly at all four measured viewports, and is identical at
1280, so the route's visual baseline is unchanged.
The added test fails against grid-cols-5 and passes against the track
Rewrite the playground tests that reached for antd class names so they
locate controls by role, text, placeholder or lucide icon instead. Add
characterisation suites for AgentBuilderView and RealtimePlayground,
which had none, including tab state that must survive a round trip
through another tab.
Every assertion here passes against the current antd components.
Replaces the antd Button, Row, Col, Spin and Typography usage in
GuardrailsOverview with the shadcn button, a CSS grid and the house
loading wrapper, and swaps the ant-design icons for lucide equivalents
that the already-migrated GuardrailDetail sibling uses. The busy state
keeps the aria-busy attribute the antd Spin exposed, so the loading
contract is unchanged for assistive tech.
The route's other analyzer-listed file, GuardrailsMonitorView, keeps its
DateRangePickerValue type import because the shared AdvancedDatePicker
prop contract requires it, so there is nothing to migrate there.
Retires the file's now unused no-restricted-imports suppression
Ports the 21 route-owned guardrails components off antd and Tremor onto the
installed shadcn base-vega primitives. Forms, tables and shared components stay
where they are: the route analyzer buckets add_guardrail_form, guardrail_info
and six others as DEFERRED, and eleven components under src/components as
SHARED, so none of them are touched here.
Three behaviours needed explicit handling because Base UI does not match antd by
default. Base UI Tabs mounts only the active panel, so the playground draft and
every panel's local state would reset on a tab switch; TabsContent now carries
keepMounted. ComboboxList takes a function child to render the filtered items,
and mapping over the array instead renders everything and silently disables the
type to filter behaviour antd gave for free. SelectContent gets
alignItemWithTrigger={false} so popups anchor below the trigger the way antd's
did, which also removes a focus race that made the template picker
intermittently unclickable.
The three dialogs reached from inside the still-antd wizard need a layer above
its z-index of 1000 to be visible at all.
Adds three tests rather than editing the characterisation tests from the
previous commit: a guard that every tab panel stays mounted, and one filtering
regression each for the pattern and mode dropdowns.
Prunes the guardrails entries from eslint-suppressions.json.
Replaces antd and Tremor markup on the usage route with the installed shadcn
base-vega primitives. Behaviour is unchanged: the route's characterisation
tests were rewritten to role and text queries in the previous commit, proven
green against the antd components, and pass through this commit unedited.
Tremor tab panels stayed mounted once rendered, so every migrated TabsContent
and the collapsible model sections in activity_metrics carry keepMounted to
keep view-mode and expansion state alive across tab switches.
* chore(ui): remove Agent Platform announcement bell from navbar
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* feat(ui): highlight Auto Router in the navbar announcement
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* fix(ui): open Auto Router docs link in a new tab
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
* Revert "fix(ui): open Auto Router docs link in a new tab"
This reverts commit 3be861bafe.
* chore(ui): title the navbar announcement LiteLLM Auto Router
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: Mubashir Osmani <mubashir@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Covers the header and export action, all five summary metric cards, the
table toolbar heading, the evaluation settings modal wiring, the busy
state and the request failure message. Every assertion is role, title or
text based so it holds against both the antd markup and its shadcn
replacement, letting the migration commit land without editing this file
Rewrites the usage route's library-coupled assertions to role and text
queries so they characterise behaviour rather than the widget library that
happens to render it. Every test here is green against the current antd and
Tremor components and is meant to survive a migration unedited.
Drops the wholesale vi.mock of antd, @ant-design/icons and @tremor/react in
UsagePageView, UsageViewSelect and activity_metrics, and drives the real
controls instead. Two of those mocks were hiding behaviour: activity_metrics
asserted panel order through a heading role that only existed because the
mock faked an h2, and the Tremor tab stubs flattened panel selection away
entirely.
Replaces DOM-shape lookups with anchors that do not move: a chart is found
from its own heading rather than a fixed wrapper depth, an active panel is
identified by the inactive markers both tab libraries set, and select options
are matched by text since antd's real options carry no option role.
Adds the missing characterisation test for team_multi_select, and covers the
mount contracts the route depends on: Tremor keeps every tab panel mounted,
and antd Collapse keeps a section mounted once it has been expanded, which is
what preserves the view-mode state a model section owns.
Rewrite the PatternModal test off antd class selectors onto role and text
queries, and add characterisation tests for the guardrails components that
had none: the keyword modal, the content filter display and configuration,
the guardrail garden and the custom code modal.
Also covers two behaviours the migration must preserve: the action and
severity dropdowns in the content filter tables, and test playground state
surviving a tab switch away and back.
Every assertion here passes against the current antd and Tremor components
so the same file can prove the shadcn versions unedited.
Prettier flagged the two test files added while fixing review findings.
Migrating these components off Ant Design also retired the lint suppressions
they carried, so prune those 15 entries and leave the unrelated ones for the
PRs that made them stale.
antd's Collapse kept the panel mounted once opened, so a tool a user had
expanded stayed expanded after closing and reopening Tools. Base UI renders
only the open branch, so the migration silently reset every ToolItem.
The regression test passes against the antd original, fails against the
migration without keepMounted, and passes with it.
InputCard and OutputCard located SectionHeader's copy button by querying for a
descendant with aria-label="copy", which is the antd CopyOutlined icon. That
selector reaches into SectionHeader's internals, so migrating it off antd left
copyButton undefined and failed four tests.
getByRole("button", { name: /copy/i }) is green against both the antd and the
shadcn SectionHeader, verified by running these two files against each.
The key source trigger rendered the stored value, so the playground showed
session and custom instead of Current UI Session and Virtual Key. Name the
selected option on the trigger.
Clearing the key while models were loading left the selector disabled for
good: the in-flight load skips its reset once cancelled, and the branch that
handles an empty key returned without clearing the loading flag, so nothing
put it back. Clear it on that path too.
* fix(ui): restore playground model filtering by endpoint
Bring back the prior Chat model dropdown filter (including chat models
on responses/anthropic/interactions and image models on image_edits), and
map mode realtime so the realtime endpoint only lists compatible models
* fix(ui): exclude unknown model modes from playground endpoint filters
Modes outside ModelMode (batch, rerank, ocr, etc.) must not collapse to
chat-compatible, or conversational endpoints surface unusable models
* feat(ui): add shared vercel-style playground chat composer (#36131)
* feat(ui): adopt vercel-style chat composer for playground
Replace the compact single-line input with a PromptInput-style composer:
taller auto-growing textarea, rounded card shell, footer tools, and
stop button while a request is in flight
* style(ui): strengthen playground chat composer border and shadow
Make the shared chat input stand out with a fuller border, layered
shadow, and a slightly stronger focus ring
* fix(ui): size chat composer textarea with CSS field-sizing
Drop direct el.style.height mutation in favor of field-sizing:content
* fix(ui): keep the chat composer out of a nested form and focus its textarea
The composer wrapped everything in a native form, so MCP mode nested Ant
Design's tool-arguments form inside it, which is invalid HTML and let Enter
hit either form. The footer also relied on InputGroupAddon focusing the first
input in the group, which is the hidden file input from the attach controls
rather than the message textarea.
Drop the outer form and submit from the send button directly, and have the
addon focus the element marked as the group's control.
* refactor(ui): reuse the endpoint compatibility check when a model is picked
The endpoint guard added upstream duplicated the compatibility families this
PR introduces, so point it at isModelCompatibleWithEndpoint instead. Filtering
also means an incompatible model is no longer offered for an endpoint, so the
test that picked one now asserts it is absent.
* fix(ui): match the image-edit model mode the backend actually sends
model_prices_and_context_window.json labels these models image_edit, but the
mode enum spelled it image_edits, so once unknown modes started being filtered
out every image-edit model vanished from the playground, /v1/images/edits
included. The endpoint key keeps its own spelling.
The compatibility tests stubbed getEndpointType with a hand-written map that
repeated the same wrong spelling, which is how this stayed hidden, so they now
run against the real mapping.
Picking a model reset the endpoint from its mode unconditionally, so choosing
a chat model while on /v1/responses, /v1/messages or interactions bounced the
playground to /v1/chat/completions. Only switch when the current endpoint
cannot serve the picked model.
The temperature and max-token boxes parsed and clamped on every keystroke, so
a decimal lost its point and clearing the field snapped to a bound. They are
now text fields with a numeric input mode that hold what was typed and clamp
on blur; the sliders beside them still give the stepped control.
The image-edit and transcription areas invited a drag but had no drop
handlers after the Ant Design Dragger came out, so drops did nothing. Wire
drop through the same validation the file picker uses.
* feat(complexity_router): calibrate the classifier rubric with worked examples
The built-in rubric stated its tier boundaries as prose alone, and prose
calibrated to consumer chat puts "non-trivial code, multi-step technical work"
at the top of the scale. That is the median request in developer and agent
traffic, so ordinary engineering read as top-tier and the router paid for the
most expensive model on it.
Adds calibration examples to the rubric, selected by a new
classifier_llm_config.rubric preset. The agentic preset (now the default)
anchors routine installs, builds, multi-file edits, and standard debugging at
MEDIUM; the chat preset omits those anchors for deployments serving only
conversational traffic. Both share the same tier criteria, the trust-boundary
paragraph, and the context-window closing line, so this moves where the
boundary sits without changing the taxonomy.
Both presets render byte-identical to the strings a prompt sweep scored, and a
test pins that, so the measured accuracy describes what a router sends.
* feat(ui): pick the classifier rubric preset on an auto-router
Adds a Rubric dropdown to the auto-router's classification panel, so the
agentic and chat presets are selectable rather than config-file only. The
prompt editor prefills from the selected preset, since prefilling agentic text
for a router on chat would show examples its classifier never receives.
The picker is disabled while a custom prompt is set, and the payload builder
drops the preset in that case: a custom prompt is the classifier's whole system
role, so the backend rejects the two together. The builder records the default
preset explicitly, so a later change to which preset is default cannot silently
move an existing router.
* fix(complexity_router): mark an unchosen rubric preset with None, not model_fields_set
The mutual-exclusion check read model_fields_set to tell an explicit preset
from the default. That flag does not survive serialization, and this config is
dumped and handed straight back to ComplexityRouter by /auto_router/test_routing,
where a dump re-states every field. So a custom-prompt classifier saved fine and
then failed validation on preview, rejecting on the second pass what it accepted
on the first.
The preset is now optional, with None meaning the default, matching how None
already means the built-in rubric for system_prompt on the same model. The
default lives in one place, DEFAULT_RUBRIC_PRESET, resolved where the prompt is
assembled. The dashboard stops sending a copy of the default it displays, so a
router nobody configured follows the default rather than pinning today's value,
and UI-built routers behave the same as hand-written config.
Regenerates schema.d.ts, which was left stale by an earlier description edit.
* feat(complexity_router): grandfather existing routers onto the uncalibrated rubric
An unset preset now means LEGACY, the rubric exactly as it shipped before
calibration examples existed, so upgrading cannot move the tier decisions or the
bill of a router that is already running. Config-file routers get this for free
since they name no preset, and a stored config that never had one reads the same
way.
New routers still get the calibrated rubric: switching a classifier to LLM
stamps the agentic preset, because a classifier being configured for the first
time has no prior tier behaviour to preserve. The picker offers legacy so an
existing router's state is representable and opening the form cannot silently
upgrade it.
Each preset is pinned byte-identical to the text the prompt sweep scored,
legacy included, which is what proves an existing router's prompt did not move.
Also collapses the preset data from a NamedTuple with group wrappers and
per-preset frozensets into plain text blocks in a MappingProxyType, matching how
the tier criteria next to it are already stored: 21 lines of prompt text no
longer cost 190 lines of constructors. Tiers are format placeholders so
tier_labels still reach the examples.
* refactor(complexity_router): name the field classification_rubric
`rubric` alone did not say what it selects, and the field sits beside
`system_prompt`, which genuinely is the whole classification prompt. The name
now says which of the two an operator is reaching for: the rubric the built-in
prompt is assembled from, not the prompt itself.
Renames the config field, the query param, the enum, and the dashboard label to
match, and moves the preset text to classification_rubrics.py.
* test(ui): set the preset the mutual-exclusion case is meant to drop
The rename left classification_classification_rubric in the custom-prompt case,
so its input never carried a preset and the assertion held for the wrong reason:
it proved an absent preset stays absent, not that a set one is dropped. A
normalizer that forwards the preset whenever one is set passed with the typo and
fails without it.
tsc reports the typo as TS2353; the earlier sweep grepped for the source file
and not the test, so it went unseen.
* test(ui): scope the role-gate assertions to each page's own endpoint
The memory, workflows, and guardrails-monitor page tests asserted that a denied
role fires no request at all. Their names, and the assertion on the very next
line, say the intent is narrower: the page must not fetch its own data.
Resolving whether a caller is an org admin goes through /organization/list for
every role, since deciding org-admin-for-any-org needs the list, and the route
scopes rows per caller. That legitimate request fails a blanket no-fetch
assertion, so all three files went red on staging for a reason unrelated to
what they test.
Drops the blanket assertion and keeps the scoped one. Bypassing the gate in
memory/page.tsx still fails five tests, so the narrower assertion continues to
catch a genuinely broken gate.
* fix(complexity_router): document that an unset rubric keeps the legacy prompt
The field said 'Leave unset for agentic' while an omitted rubric resolves to
LEGACY, so the OpenAPI schema an operator reads promised calibrated routing
where they got the uncalibrated one.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Update the transitive lockfile entry to the first patched 3.x release so OSV no longer reports GHSA-2v37-7h3g-55p8.
Generated with AI
Co-Authored-By: Codex
Update the transitive lockfile entry to the first patched 3.x release so OSV no longer reports GHSA-2v37-7h3g-55p8.
Generated with AI
Co-Authored-By: Codex
* test(ui): decouple usage table test from antd
* refactor(ui): migrate usage tables to shared DataTable
* test(ui): preserve data utility exports in usage tests
Add a bounded spend-log user facet for the Request Logs picker and
intersect explicit user filters with the caller's own and permitted-team
scope.
Co-Authored-By: Codex