-
Notifications
You must be signed in to change notification settings - Fork 3.2k
feat(web): show fast mode as a bolt instead of a "Normal" label #4488
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| import { describe, expect, it } from "vite-plus/test"; | ||
| import type { ProviderOptionDescriptor } from "@t3tools/contracts"; | ||
| import { buildTraitsTriggerDisplay } from "./TraitsPicker"; | ||
|
|
||
| function selectDescriptor( | ||
| id: string, | ||
| options: ReadonlyArray<{ id: string; label: string }>, | ||
| currentValue: string, | ||
| ): Extract<ProviderOptionDescriptor, { type: "select" }> { | ||
| return { id, label: id, type: "select", options: [...options], currentValue }; | ||
| } | ||
|
|
||
| function fastModeDescriptor( | ||
| currentValue: boolean, | ||
| ): Extract<ProviderOptionDescriptor, { type: "boolean" }> { | ||
| return { id: "fastMode", label: "Fast Mode", type: "boolean", currentValue }; | ||
| } | ||
|
|
||
| const EFFORT = selectDescriptor( | ||
| "effort", | ||
| [ | ||
| { id: "high", label: "High" }, | ||
| { id: "max", label: "Max" }, | ||
| ], | ||
| "high", | ||
| ); | ||
| const CONTEXT_WINDOW = selectDescriptor( | ||
| "contextWindow", | ||
| [ | ||
| { id: "200k", label: "200k" }, | ||
| { id: "1m", label: "1M" }, | ||
| ], | ||
| "1m", | ||
| ); | ||
|
|
||
| function display(descriptors: ReadonlyArray<ProviderOptionDescriptor>, fastModeEnabled: boolean) { | ||
| return buildTraitsTriggerDisplay({ | ||
| descriptors, | ||
| primarySelectDescriptorId: "effort", | ||
| ultrathinkPromptControlled: false, | ||
| fastModeEnabled, | ||
| }); | ||
| } | ||
|
|
||
| describe("buildTraitsTriggerDisplay", () => { | ||
| it("omits fast mode from the label entirely when it is off", () => { | ||
| expect(display([EFFORT, fastModeDescriptor(false), CONTEXT_WINDOW], false)).toEqual({ | ||
| label: "High · 1M", | ||
| showFastModeIcon: false, | ||
| }); | ||
| }); | ||
|
|
||
| it("shows the bolt instead of a text label when fast mode is on", () => { | ||
| expect(display([EFFORT, fastModeDescriptor(true), CONTEXT_WINDOW], true)).toEqual({ | ||
| label: "High · 1M", | ||
| showFastModeIcon: true, | ||
| }); | ||
| }); | ||
|
|
||
| it("keeps non-fastMode booleans as text labels", () => { | ||
| const thinking: Extract<ProviderOptionDescriptor, { type: "boolean" }> = { | ||
| id: "thinking", | ||
| label: "Thinking", | ||
| type: "boolean", | ||
| currentValue: true, | ||
| }; | ||
| expect(display([EFFORT, thinking], false)).toEqual({ | ||
| label: "High · Thinking On", | ||
| showFastModeIcon: false, | ||
| }); | ||
| }); | ||
|
|
||
| it("falls back to a text label when fast mode is the only trait", () => { | ||
| expect(display([fastModeDescriptor(true)], true)).toEqual({ | ||
| label: "Fast", | ||
| showFastModeIcon: false, | ||
| }); | ||
| expect(display([fastModeDescriptor(false)], false)).toEqual({ | ||
| label: "Normal", | ||
| showFastModeIcon: false, | ||
| }); | ||
| }); | ||
|
|
||
| it("stays blank when descriptors resolve to no label and there is no fast mode", () => { | ||
| // A select with neither a currentValue nor an isDefault option yields no | ||
| // label. Without a fastMode descriptor present that must stay blank rather | ||
| // than falling through to a bogus "Normal". | ||
| const unresolved: Extract<ProviderOptionDescriptor, { type: "select" }> = { | ||
| id: "effort", | ||
| label: "effort", | ||
| type: "select", | ||
| options: [ | ||
| { id: "low", label: "Low" }, | ||
| { id: "high", label: "High" }, | ||
| ], | ||
| }; | ||
| expect(display([unresolved], false)).toEqual({ label: "", showFastModeIcon: false }); | ||
| }); | ||
|
|
||
| it("still renders the prompt-controlled ultrathink label alongside the bolt", () => { | ||
| expect( | ||
| buildTraitsTriggerDisplay({ | ||
| descriptors: [EFFORT, fastModeDescriptor(true)], | ||
| primarySelectDescriptorId: "effort", | ||
| ultrathinkPromptControlled: true, | ||
| fastModeEnabled: true, | ||
| }), | ||
| ).toEqual({ label: "Ultrathink", showFastModeIcon: true }); | ||
| }); | ||
| }); |
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,7 +16,7 @@ import { | ||||||||||||||||||||||||||||||
| } from "@t3tools/shared/model"; | |||||||||||||||||||||||||||||||
| import { memo, useCallback, useState } from "react"; | |||||||||||||||||||||||||||||||
| import type { VariantProps } from "class-variance-authority"; | |||||||||||||||||||||||||||||||
| import { ChevronDownIcon } from "lucide-react"; | |||||||||||||||||||||||||||||||
| import { ChevronDownIcon, ZapIcon } from "lucide-react"; | |||||||||||||||||||||||||||||||
| import { Button, buttonVariants } from "../ui/button"; | |||||||||||||||||||||||||||||||
| import { | |||||||||||||||||||||||||||||||
| Menu, | |||||||||||||||||||||||||||||||
|
|
@@ -379,6 +379,46 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ | ||||||||||||||||||||||||||||||
| ); | |||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||
| /** | |||||||||||||||||||||||||||||||
| * Build the traits trigger's text label plus whether the fast-mode bolt should | |||||||||||||||||||||||||||||||
| * render. Fast mode is a lightning bolt when on and nothing at all when off — | |||||||||||||||||||||||||||||||
| * "Normal" is the near-universal case and isn't worth the horizontal space. The | |||||||||||||||||||||||||||||||
| * one exception is when fast mode is the only trait, where a bare bolt (or bare | |||||||||||||||||||||||||||||||
| * chevron) would leave the trigger unreadable. | |||||||||||||||||||||||||||||||
| */ | |||||||||||||||||||||||||||||||
| export function buildTraitsTriggerDisplay(input: { | |||||||||||||||||||||||||||||||
| descriptors: ReadonlyArray<ProviderOptionDescriptor>; | |||||||||||||||||||||||||||||||
| primarySelectDescriptorId: string | null; | |||||||||||||||||||||||||||||||
| ultrathinkPromptControlled: boolean; | |||||||||||||||||||||||||||||||
| fastModeEnabled: boolean; | |||||||||||||||||||||||||||||||
| }): { label: string; showFastModeIcon: boolean } { | |||||||||||||||||||||||||||||||
| let hasFastMode = false; | |||||||||||||||||||||||||||||||
| const labels: Array<string> = []; | |||||||||||||||||||||||||||||||
| for (const descriptor of input.descriptors) { | |||||||||||||||||||||||||||||||
| if (descriptor.id === "fastMode" && descriptor.type === "boolean") { | |||||||||||||||||||||||||||||||
| hasFastMode = true; | |||||||||||||||||||||||||||||||
| continue; | |||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||
| const label = | |||||||||||||||||||||||||||||||
| input.ultrathinkPromptControlled && descriptor.id === input.primarySelectDescriptorId | |||||||||||||||||||||||||||||||
| ? "Ultrathink" | |||||||||||||||||||||||||||||||
| : descriptor.type === "boolean" | |||||||||||||||||||||||||||||||
| ? `${descriptor.label} ${descriptor.currentValue === true ? "On" : "Off"}` | |||||||||||||||||||||||||||||||
| : getProviderOptionCurrentLabel(descriptor); | |||||||||||||||||||||||||||||||
| if (typeof label === "string" && label.length > 0) { | |||||||||||||||||||||||||||||||
| labels.push(label); | |||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||
| // Only fall back to text when fast mode is genuinely the sole trait. Keying | |||||||||||||||||||||||||||||||
| // off an empty label list alone would also catch descriptors that resolved to | |||||||||||||||||||||||||||||||
| // no label at all, printing a bogus "Normal" for a model without fast mode. | |||||||||||||||||||||||||||||||
| if (labels.length === 0 && hasFastMode) { | |||||||||||||||||||||||||||||||
| return { label: input.fastModeEnabled ? "Fast" : "Normal", showFastModeIcon: false }; | |||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||
|
Comment on lines
+416
to
+418
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Medium When a model has
Suggested change
🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I checked this one against the actual behavior and I'm going to decline the suggestion — it makes the case strictly worse. The scenario is real:
Since the other descriptor produced no label, there is nothing else to display. Gating on
There is no real trait display to drop in this branch — Verified both paths directly rather than reasoning from the diff: current returns Worth noting this is a doubly-degenerate state — a provider would have to ship a select with neither a |
|||||||||||||||||||||||||||||||
| return { label: labels.join(" · "), showFastModeIcon: input.fastModeEnabled }; | |||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||
| export const TraitsPicker = memo(function TraitsPicker({ | |||||||||||||||||||||||||||||||
| provider, | |||||||||||||||||||||||||||||||
| instanceId, | |||||||||||||||||||||||||||||||
|
|
@@ -393,7 +433,7 @@ export const TraitsPicker = memo(function TraitsPicker({ | ||||||||||||||||||||||||||||||
| ...persistence | |||||||||||||||||||||||||||||||
| }: TraitsMenuContentProps & TraitsPersistence) { | |||||||||||||||||||||||||||||||
| const [isMenuOpen, setIsMenuOpen] = useState(false); | |||||||||||||||||||||||||||||||
| const { descriptors, primarySelectDescriptor, ultrathinkPromptControlled } = | |||||||||||||||||||||||||||||||
| const { descriptors, primarySelectDescriptor, ultrathinkPromptControlled, fastModeEnabled } = | |||||||||||||||||||||||||||||||
| getTraitsSectionVisibility({ | |||||||||||||||||||||||||||||||
| provider, | |||||||||||||||||||||||||||||||
| models, | |||||||||||||||||||||||||||||||
|
|
@@ -415,23 +455,18 @@ export const TraitsPicker = memo(function TraitsPicker({ | ||||||||||||||||||||||||||||||
| return null; | |||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||
| const triggerLabels: Array<string> = []; | |||||||||||||||||||||||||||||||
| for (const descriptor of descriptors) { | |||||||||||||||||||||||||||||||
| const label = | |||||||||||||||||||||||||||||||
| ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id | |||||||||||||||||||||||||||||||
| ? "Ultrathink" | |||||||||||||||||||||||||||||||
| : descriptor.type === "boolean" | |||||||||||||||||||||||||||||||
| ? descriptor.id === "fastMode" | |||||||||||||||||||||||||||||||
| ? descriptor.currentValue === true | |||||||||||||||||||||||||||||||
| ? "Fast" | |||||||||||||||||||||||||||||||
| : "Normal" | |||||||||||||||||||||||||||||||
| : `${descriptor.label} ${descriptor.currentValue === true ? "On" : "Off"}` | |||||||||||||||||||||||||||||||
| : getProviderOptionCurrentLabel(descriptor); | |||||||||||||||||||||||||||||||
| if (typeof label === "string" && label.length > 0) { | |||||||||||||||||||||||||||||||
| triggerLabels.push(label); | |||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||
| const triggerLabel = triggerLabels.join(" · "); | |||||||||||||||||||||||||||||||
| const { label: triggerLabel, showFastModeIcon } = buildTraitsTriggerDisplay({ | |||||||||||||||||||||||||||||||
| descriptors, | |||||||||||||||||||||||||||||||
| primarySelectDescriptorId: primarySelectDescriptor?.id ?? null, | |||||||||||||||||||||||||||||||
| ultrathinkPromptControlled, | |||||||||||||||||||||||||||||||
| fastModeEnabled, | |||||||||||||||||||||||||||||||
| }); | |||||||||||||||||||||||||||||||
| const fastModeIcon = showFastModeIcon ? ( | |||||||||||||||||||||||||||||||
| <> | |||||||||||||||||||||||||||||||
| <ZapIcon aria-hidden="true" className="size-3 shrink-0 text-foreground/80 opacity-100" /> | |||||||||||||||||||||||||||||||
| <span className="sr-only">Fast mode on</span> | |||||||||||||||||||||||||||||||
| </> | |||||||||||||||||||||||||||||||
| ) : null; | |||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||
| const isCodexStyle = provider === "codex"; | |||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||
|
|
@@ -457,12 +492,14 @@ export const TraitsPicker = memo(function TraitsPicker({ | ||||||||||||||||||||||||||||||
| } | |||||||||||||||||||||||||||||||
| > | |||||||||||||||||||||||||||||||
| {isCodexStyle ? ( | |||||||||||||||||||||||||||||||
| <span className="flex min-w-0 w-full items-center gap-2 overflow-hidden"> | |||||||||||||||||||||||||||||||
| {triggerLabel} | |||||||||||||||||||||||||||||||
| <span className="flex min-w-0 w-full items-center gap-1.5 overflow-hidden"> | |||||||||||||||||||||||||||||||
| {fastModeIcon} | |||||||||||||||||||||||||||||||
| <span className="min-w-0 truncate">{triggerLabel}</span> | |||||||||||||||||||||||||||||||
| <ChevronDownIcon aria-hidden="true" className="size-3 shrink-0 opacity-60" /> | |||||||||||||||||||||||||||||||
| </span> | |||||||||||||||||||||||||||||||
| ) : ( | |||||||||||||||||||||||||||||||
| <> | |||||||||||||||||||||||||||||||
| {fastModeIcon} | |||||||||||||||||||||||||||||||
| <span>{triggerLabel}</span> | |||||||||||||||||||||||||||||||
| <ChevronDownIcon aria-hidden="true" className="size-3 opacity-60" /> | |||||||||||||||||||||||||||||||
| </> | |||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.