Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions apps/web/src/components/chat/ChatComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
import { toastManager } from "../ui/toast";
import {
BotIcon,
CheckIcon,
CircleAlertIcon,
ListTodoIcon,
PencilRulerIcon,
Expand Down Expand Up @@ -271,16 +272,25 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop
{runtimeModeOptions.map((mode) => {
const option = runtimeModeConfig[mode];
const OptionIcon = option.icon;
const isSelected = props.runtimeMode === mode;
return (
<SelectItem key={mode} value={mode} className="min-w-64 py-2">
<div className="grid min-w-0 gap-0.5">
<span className="inline-flex items-center gap-1.5 font-medium text-foreground">
<OptionIcon className="size-3.5 shrink-0 text-muted-foreground" />
{option.label}
</span>
<span className="text-muted-foreground text-xs leading-4">
{option.description}
</span>
<SelectItem key={mode} value={mode} hideIndicator className="min-w-64 py-2">
<div className="flex min-w-0 items-center gap-3">
<div className="grid min-w-0 flex-1 gap-0.5">
<span className="inline-flex items-center gap-1.5 font-medium text-foreground">
<OptionIcon className="size-3.5 shrink-0 text-muted-foreground" />
{option.label}
</span>
<span className="text-muted-foreground text-xs leading-4">
{option.description}
</span>
</div>
<CheckIcon
className={cn(
"size-4 text-blue-400",
isSelected ? "opacity-100" : "opacity-0",
)}
/>
</div>
</SelectItem>
);
Expand Down
83 changes: 55 additions & 28 deletions apps/web/src/components/chat/TraitsPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 { CheckIcon, ChevronDownIcon } from "lucide-react";
import { Button, buttonVariants } from "../ui/button";
import {
Menu,
Expand Down Expand Up @@ -298,25 +298,39 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
option.
</div>
) : null}
<MenuRadioGroup
value={
{(() => {
const selectedValue =
ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id
? "ultrathink"
: (getDescriptorStringValue(descriptor) ?? "")
}
onValueChange={(value) => handleSelectChange(descriptor, value)}
>
{descriptor.options.map((option) => (
<MenuRadioItem
key={option.id}
value={option.id}
disabled={ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id}
: (getDescriptorStringValue(descriptor) ?? "");
return (
<MenuRadioGroup
value={selectedValue}
onValueChange={(value) => handleSelectChange(descriptor, value)}
>
{option.label}
{option.isDefault ? " (default)" : ""}
</MenuRadioItem>
))}
</MenuRadioGroup>
{descriptor.options.map((option) => (
<MenuRadioItem
key={option.id}
value={option.id}
hideIndicator
disabled={
ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id
}
>
<span className="flex w-full min-w-0 items-center justify-between gap-3">
<span className="min-w-0 truncate">
{option.label}
{option.isDefault ? " (default)" : ""}
</span>
{option.id === selectedValue ? (
<CheckIcon className="size-3.5 shrink-0 text-blue-400" />
) : null}
</span>
</MenuRadioItem>
))}
</MenuRadioGroup>
);
})()}
</MenuGroup>
</div>
))}
Expand All @@ -327,17 +341,30 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({
<div className="px-2 py-1.5 font-medium text-muted-foreground text-xs">
{descriptor.label}
</div>
<MenuRadioGroup
value={descriptor.currentValue === true ? "on" : "off"}
onValueChange={(value) => {
updateDescriptors(
replaceDescriptorCurrentValue(descriptors, descriptor.id, value === "on"),
);
}}
>
<MenuRadioItem value="on">On</MenuRadioItem>
<MenuRadioItem value="off">Off</MenuRadioItem>
</MenuRadioGroup>
{(() => {
const selectedValue = descriptor.currentValue === true ? "on" : "off";
return (
<MenuRadioGroup
value={selectedValue}
onValueChange={(value) => {
updateDescriptors(
replaceDescriptorCurrentValue(descriptors, descriptor.id, value === "on"),
);
}}
>
{(["on", "off"] as const).map((value) => (
<MenuRadioItem key={value} value={value} hideIndicator>
<span className="flex w-full min-w-0 items-center justify-between gap-3">
<span>{value === "on" ? "On" : "Off"}</span>
{value === selectedValue ? (
<CheckIcon className="size-3.5 shrink-0 text-blue-400" />
) : null}
</span>
</MenuRadioItem>
))}
</MenuRadioGroup>
);
})()}
</MenuGroup>
</div>
))}
Expand Down
46 changes: 28 additions & 18 deletions apps/web/src/components/ui/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,32 +147,42 @@ function MenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) {
return <MenuPrimitive.RadioGroup data-slot="menu-radio-group" {...props} />;
}

function MenuRadioItem({ className, children, ...props }: MenuPrimitive.RadioItem.Props) {
function MenuRadioItem({
className,
children,
hideIndicator = false,
...props
}: MenuPrimitive.RadioItem.Props & {
hideIndicator?: boolean;
}) {
return (
<MenuPrimitive.RadioItem
className={cn(
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default grid-cols-[1rem_1fr] items-center gap-2 rounded-sm py-1 ps-2 pe-4 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
"grid min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-default items-center gap-2 rounded-sm py-1 text-base text-foreground outline-none data-disabled:pointer-events-none data-highlighted:bg-accent data-highlighted:text-accent-foreground data-disabled:opacity-64 sm:min-h-7 sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
hideIndicator ? "grid-cols-[1fr] ps-3 pe-3" : "grid-cols-[1rem_1fr] ps-2 pe-4",
className,
)}
data-slot="menu-radio-item"
{...props}
>
<MenuPrimitive.RadioItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</MenuPrimitive.RadioItemIndicator>
<span className="col-start-2">{children}</span>
{hideIndicator ? null : (
<MenuPrimitive.RadioItemIndicator className="col-start-1">
<svg
fill="none"
height="24"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
viewBox="0 0 24 24"
width="24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M5.252 12.7 10.2 18.63 18.748 5.37" />
</svg>
</MenuPrimitive.RadioItemIndicator>
)}
<span className={hideIndicator ? "col-start-1" : "col-start-2"}>{children}</span>
</MenuPrimitive.RadioItem>
);
}
Expand Down
Loading