diff --git a/apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx b/apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx index 27a42aa5467..0acc030420b 100644 --- a/apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx +++ b/apps/web/src/components/chat/CompactComposerControlsMenu.browser.tsx @@ -100,7 +100,7 @@ async function mountMenu(props?: { modelSelection?: ModelSelection; prompt?: str ], ["ultrathink"], ), - booleanDescriptor("fastMode", "Fast Mode"), + booleanDescriptor("fastMode", "Speed"), ], }), }, @@ -181,7 +181,7 @@ describe("CompactComposerControlsMenu", () => { }); }); - it("shows fast mode controls for Opus", async () => { + it("shows speed controls for Opus", async () => { await using _ = await mountMenu({ modelSelection: createModelSelection( ProviderInstanceId.make("claudeAgent"), @@ -193,13 +193,13 @@ describe("CompactComposerControlsMenu", () => { await vi.waitFor(() => { const text = document.body.textContent ?? ""; - expect(text).toContain("Fast Mode"); - expect(text).toContain("On"); - expect(text).toContain("Off"); + expect(text).toContain("Speed"); + expect(text).toContain("Normal"); + expect(text).toContain("Fast"); }); }); - it("hides fast mode controls for non-Opus Claude models", async () => { + it("hides speed controls for non-Opus Claude models", async () => { await using _ = await mountMenu({ modelSelection: createModelSelection( ProviderInstanceId.make("claudeAgent"), @@ -210,7 +210,7 @@ describe("CompactComposerControlsMenu", () => { await page.getByLabelText("More composer controls").click(); await vi.waitFor(() => { - expect(document.body.textContent ?? "").not.toContain("Fast Mode"); + expect(document.body.textContent ?? "").not.toContain("Speed"); }); }); diff --git a/apps/web/src/components/chat/SelectedModelBadge.tsx b/apps/web/src/components/chat/SelectedModelBadge.tsx new file mode 100644 index 00000000000..a1463214038 --- /dev/null +++ b/apps/web/src/components/chat/SelectedModelBadge.tsx @@ -0,0 +1,18 @@ +import { CheckIcon } from "lucide-react"; + +import { Badge } from "../ui/badge"; + +export function SelectedModelBadge() { + return ; +} + +export function DefaultBadge() { + return ( + + Default + + ); +} diff --git a/apps/web/src/components/chat/TraitsPicker.tsx b/apps/web/src/components/chat/TraitsPicker.tsx index 5a32a780289..c8e8446fc55 100644 --- a/apps/web/src/components/chat/TraitsPicker.tsx +++ b/apps/web/src/components/chat/TraitsPicker.tsx @@ -14,9 +14,16 @@ import { getProviderOptionDescriptors, isClaudeUltrathinkPrompt, } from "@t3tools/shared/model"; -import { memo, useCallback, useState } from "react"; +import { Fragment, memo, useCallback, useState } from "react"; import type { VariantProps } from "class-variance-authority"; -import { ChevronDownIcon } from "lucide-react"; +import { + BookOpenIcon, + BrainIcon, + ChevronDownIcon, + RabbitIcon, + TurtleIcon, + ZapIcon, +} from "lucide-react"; import { Button, buttonVariants } from "../ui/button"; import { Menu, @@ -30,6 +37,8 @@ import { import { useComposerDraftStore, DraftId } from "../../composerDraftStore"; import { getProviderModelCapabilities } from "../../providerModels"; import { cn } from "~/lib/utils"; +import { Separator } from "../ui/separator"; +import { DefaultBadge, SelectedModelBadge } from "./SelectedModelBadge"; type ProviderOptions = ReadonlyArray; @@ -46,6 +55,50 @@ type TraitsPersistence = const ULTRATHINK_PROMPT_PREFIX = "Ultrathink:\n"; +function getBooleanDescriptorLabel( + descriptor: Extract, +): string { + return descriptor.id === "fastMode" ? "Speed" : descriptor.label; +} + +function getBooleanDescriptorOptionLabel( + descriptor: Extract, + value: "on" | "off", +): string { + if (descriptor.id === "fastMode") { + return value === "on" ? "Fast" : "Normal"; + } + return value === "on" ? "On" : "Off"; +} + +function getSpeedOptionDescription(input: { + provider: ProviderDriverKind; + model: string | null | undefined; + value: "on" | "off"; +}): string { + if (input.value === "off") { + return "Uses standard speed and normal usage."; + } + if (input.provider === "claudeAgent") { + return "Works up to 2.5x faster, but uses 6x more usage than normal."; + } + if (input.model === "gpt-5.5") { + return "Works up to 1.5x faster, but uses 2.5x more usage than normal."; + } + return "Works up to 1.5x faster, but uses 2x more usage than normal."; +} + +function BooleanDescriptorOptionIcon(props: { + descriptor: Extract; + value: "on" | "off"; +}) { + if (props.descriptor.id !== "fastMode") { + return null; + } + const Icon = props.value === "on" ? RabbitIcon : TurtleIcon; + return