| author | |
| committer | |
| log | 0e512db34d787b21b827f07c6a62fba7fb00369e |
| tree | 042470d52a00edf8e081bf8640e4908c65c6a91c |
| parent | 71c9b693137c070b4887b365c68659f1a2fb7aed |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>15 files changed, 100 insertions(+), 137 deletions(-)
apps/web/src/components/chat/ChatComposer.tsx+24-29| ... | ... | @@ -1100,14 +1100,8 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop |
| 1100 | 1100 | render={ |
| 1101 | 1101 | <ComposerControl |
| 1102 | 1102 | size={size} |
| 1103 | className={cn( | |
| 1104 | "shrink-0 whitespace-nowrap", | |
| 1105 | props.interactionMode === "plan" | |
| 1106 | ? "bg-accent text-accent-foreground hover:bg-accent/80" | |
| 1107 | : size === "xs" | |
| 1108 | ? undefined | |
| 1109 | : "text-secondary-label hover:text-foreground", | |
| 1110 | )} | |
| 1103 | className="shrink-0 whitespace-nowrap" | |
| 1104 | aria-pressed={props.interactionMode === "plan"} | |
| 1111 | 1105 | type="button" |
| 1112 | 1106 | onClick={props.onToggleInteractionMode} |
| 1113 | 1107 | aria-label={interactionModeTooltip} |
| ... | ... | @@ -1152,7 +1146,6 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop |
| 1152 | 1146 | <ComposerSelectControl |
| 1153 | 1147 | data-composer-shortcut="composer.mode" |
| 1154 | 1148 | size={size} |
| 1155 | className={size === "xs" ? undefined : "font-medium"} | |
| 1156 | 1149 | aria-label="Runtime mode" |
| 1157 | 1150 | /> |
| 1158 | 1151 | } |
| ... | ... | @@ -4994,10 +4987,8 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 4994 | 4987 | .slice(restingBlockDefs.length - restingHiddenBlockCount) |
| 4995 | 4988 | .map((def) => def.id); |
| 4996 | 4989 | const composerControls = showProviderUnavailable ? ( |
| 4997 | <Button | |
| 4990 | <ComposerControl | |
| 4998 | 4991 | type="button" |
| 4999 | size="sm" | |
| 5000 | variant="ghost" | |
| 5001 | 4992 | disabled={!providerSetupInstanceId} |
| 5002 | 4993 | onClick={() => { |
| 5003 | 4994 | if (providerSetupInstanceId) { |
| ... | ... | @@ -5005,11 +4996,11 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 5005 | 4996 | } |
| 5006 | 4997 | }} |
| 5007 | 4998 | data-chat-provider-unavailable="true" |
| 5008 | className="shrink-0 gap-2 px-2 text-secondary-label sm:px-3" | |
| 4999 | className="shrink-0" | |
| 5009 | 5000 | > |
| 5010 | 5001 | <CircleAlertIcon className="size-4" /> |
| 5011 | 5002 | {providerSetupInstanceId ? "Open provider settings" : "No provider available"} |
| 5012 | </Button> | |
| 5003 | </ComposerControl> | |
| 5013 | 5004 | ) : ( |
| 5014 | 5005 | <> |
| 5015 | 5006 | {composerControlsInStrip && restingControlsHaveLeadingContext ? ( |
| ... | ... | @@ -6590,9 +6581,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 6590 | 6581 | <TooltipTrigger |
| 6591 | 6582 | render={ |
| 6592 | 6583 | <Button |
| 6593 | variant="ghost" | |
| 6584 | variant="overlay" | |
| 6594 | 6585 | size="icon-xs" |
| 6595 | className="absolute bottom-1 left-1 bg-background/85 hover:bg-background/95" | |
| 6586 | className="absolute bottom-1 left-1" | |
| 6596 | 6587 | onClick={() => |
| 6597 | 6588 | retryAttachmentUpload({ |
| 6598 | 6589 | environmentId, |
| ... | ... | @@ -6609,19 +6600,23 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 6609 | 6600 | <TooltipPopup side="top">{upload.reason}</TooltipPopup> |
| 6610 | 6601 | </Tooltip> |
| 6611 | 6602 | )} |
| 6612 | <Button | |
| 6613 | variant="ghost" | |
| 6614 | size="icon-xs" | |
| 6603 | {/* Snap-shot frames reveal their remove button on hover or focus. */} | |
| 6604 | <span | |
| 6615 | 6605 | className={cn( |
| 6616 | "absolute right-1 top-1 bg-background/80 hover:bg-background/90", | |
| 6606 | "absolute right-1 top-1 flex", | |
| 6617 | 6607 | image.source?.kind === "snap-shot" && |
| 6618 | "opacity-0 transition-opacity pointer-coarse:opacity-100 focus-visible:opacity-100 group-hover/attachment:opacity-100 group-focus-within/attachment:opacity-100", | |
| 6608 | "opacity-0 transition-opacity pointer-coarse:opacity-100 group-hover/attachment:opacity-100 group-focus-within/attachment:opacity-100", | |
| 6619 | 6609 | )} |
| 6620 | onClick={() => removeComposerImage(image.id)} | |
| 6621 | aria-label={`Remove ${image.name}`} | |
| 6622 | 6610 | > |
| 6623 | <XIcon /> | |
| 6624 | </Button> | |
| 6611 | <Button | |
| 6612 | variant="media-close" | |
| 6613 | size="icon-xs" | |
| 6614 | onClick={() => removeComposerImage(image.id)} | |
| 6615 | aria-label={`Remove ${image.name}`} | |
| 6616 | > | |
| 6617 | <XIcon /> | |
| 6618 | </Button> | |
| 6619 | </span> | |
| 6625 | 6620 | </SnapShotAttachmentFrame> |
| 6626 | 6621 | ); |
| 6627 | 6622 | }) |
| ... | ... | @@ -6685,9 +6680,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 6685 | 6680 | <TooltipTrigger |
| 6686 | 6681 | render={ |
| 6687 | 6682 | <Button |
| 6688 | variant="ghost" | |
| 6683 | variant="overlay" | |
| 6689 | 6684 | size="icon-xs" |
| 6690 | className="absolute bottom-1 left-1 bg-background/85 hover:bg-background/95" | |
| 6685 | className="absolute bottom-1 left-1" | |
| 6691 | 6686 | onClick={() => |
| 6692 | 6687 | retryAttachmentUpload({ |
| 6693 | 6688 | environmentId, |
| ... | ... | @@ -6705,9 +6700,9 @@ export const ChatComposer = memo(function ChatComposer(props: ChatComposerProps) |
| 6705 | 6700 | </Tooltip> |
| 6706 | 6701 | )} |
| 6707 | 6702 | <Button |
| 6708 | variant="ghost" | |
| 6703 | variant="media-close" | |
| 6709 | 6704 | size="icon-xs" |
| 6710 | className="absolute right-1 top-1 bg-background/80 hover:bg-background/90" | |
| 6705 | className="absolute right-1 top-1" | |
| 6711 | 6706 | onClick={() => removeComposerFileFromDraft(file.id)} |
| 6712 | 6707 | aria-label={`Remove ${file.name}`} |
| 6713 | 6708 | > |
apps/web/src/components/chat/CompactComposerControlsMenu.tsx+1-2| ... | ... | @@ -38,8 +38,7 @@ export const CompactComposerControlsMenu = memo(function CompactComposerControls |
| 38 | 38 | render={ |
| 39 | 39 | <ComposerControl |
| 40 | 40 | size={size} |
| 41 | variant="ghost" | |
| 42 | className={size === "xs" ? "shrink-0" : "shrink-0 px-2"} | |
| 41 | className="shrink-0" | |
| 43 | 42 | aria-label="More composer controls" |
| 44 | 43 | data-composer-shortcut={ |
| 45 | 44 | props.traitsMenuContent ? "composer.mode composer.effort" : "composer.mode" |
apps/web/src/components/chat/ComposerControl.tsx+40-38| ... | ... | @@ -1,45 +1,49 @@ |
| 1 | import { mergeProps } from "@base-ui/react/merge-props"; | |
| 2 | import { Select as SelectPrimitive } from "@base-ui/react/select"; | |
| 3 | import { useRender } from "@base-ui/react/use-render"; | |
| 1 | 4 | import type { ComponentProps } from "react"; |
| 2 | 5 | import { ChevronDownIcon, type LucideIcon } from "lucide-react"; |
| 3 | 6 | |
| 4 | 7 | import { cn } from "~/lib/utils"; |
| 5 | import { Button } from "../ui/button"; | |
| 6 | import { SelectTrigger } from "../ui/select"; | |
| 7 | 8 | import { Separator } from "../ui/separator"; |
| 8 | 9 | |
| 9 | 10 | export type ComposerControlSize = "sm" | "xs"; |
| 10 | 11 | |
| 11 | type ComposerControlProps = Omit<ComponentProps<typeof Button>, "size"> & { | |
| 12 | size?: ComposerControlSize; | |
| 13 | }; | |
| 12 | /** | |
| 13 | * The composer toolbar's control look. `sm` is the expanded toolbar; `xs` is the dimmer resting | |
| 14 | * strip. `aria-pressed` marks a toggle that is on (plan mode). This is an app control, not a | |
| 15 | * restyled Button, so it owns its classes. | |
| 16 | */ | |
| 17 | function composerControlClassName(size: ComposerControlSize, className?: string) { | |
| 18 | return cn( | |
| 19 | "relative inline-flex shrink-0 cursor-pointer items-center justify-center whitespace-nowrap rounded-[var(--control-radius)] border border-transparent text-base outline-none hover:bg-accent data-pressed:bg-accent focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 data-disabled:pointer-events-none data-disabled:opacity-64 pointer-coarse:after:absolute pointer-coarse:after:size-full pointer-coarse:after:min-h-11 pointer-coarse:after:min-w-11 [&:active:not([aria-haspopup])]:scale-[0.97] [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:-mx-0.5 [&_svg[data-composer-control-icon]]:mx-0 [&_svg:not([class*='text-'])]:text-[var(--control-icon-color)]", | |
| 20 | size === "xs" | |
| 21 | ? "h-7 gap-1 px-[calc(--spacing(2)-1px)] font-normal text-muted-foreground/70 text-sm [--control-icon-color:currentColor] hover:text-foreground/80 sm:h-6 sm:text-xs [&_svg:not([class*='size-'])]:size-4 sm:[&_svg:not([class*='size-'])]:size-3.5 [&_svg[data-composer-control-chevron]]:ms-0 [&_svg[data-composer-control-chevron]]:-me-1" | |
| 22 | : "h-7 gap-1.5 px-2.5 font-medium text-secondary-label [--control-icon-color:var(--contrast-muted-foreground)] hover:text-foreground sm:text-sm [&_svg:not([class*='size-'])]:size-4.5 sm:[&_svg:not([class*='size-'])]:size-4", | |
| 23 | "aria-pressed:bg-accent aria-pressed:text-accent-foreground aria-pressed:hover:bg-accent/80", | |
| 24 | className, | |
| 25 | ); | |
| 26 | } | |
| 14 | 27 | |
| 15 | type ComposerSelectControlProps = Omit<ComponentProps<typeof SelectTrigger>, "size"> & { | |
| 28 | type ComposerControlProps = useRender.ComponentProps<"button"> & { | |
| 16 | 29 | size?: ComposerControlSize; |
| 17 | 30 | }; |
| 18 | 31 | |
| 19 | const composerControlClassName = | |
| 20 | "rounded-[var(--control-radius)] text-secondary-label transition-none hover:text-foreground [&_svg[data-composer-control-chevron]]:-mx-0.5 [&_svg[data-composer-control-icon]]:mx-0"; | |
| 21 | const expandedComposerControlClassName = "h-7 min-h-7 gap-1.5 px-2.5"; | |
| 22 | const restingComposerControlClassName = | |
| 23 | "[--control-icon-color:currentColor] font-normal text-muted-foreground/70 hover:text-foreground/80 [&_svg[data-composer-control-chevron]]:-me-1 [&_svg[data-composer-control-chevron]]:ms-0"; | |
| 24 | ||
| 25 | 32 | export function ComposerControl({ |
| 26 | 33 | className, |
| 27 | 34 | size = "sm", |
| 28 | variant = "ghost", | |
| 35 | render, | |
| 29 | 36 | ...props |
| 30 | 37 | }: ComposerControlProps) { |
| 31 | return ( | |
| 32 | <Button | |
| 33 | className={cn( | |
| 34 | composerControlClassName, | |
| 35 | size === "xs" ? restingComposerControlClassName : expandedComposerControlClassName, | |
| 36 | className, | |
| 37 | )} | |
| 38 | size={size} | |
| 39 | variant={variant} | |
| 40 | {...props} | |
| 41 | /> | |
| 42 | ); | |
| 38 | const defaultProps = { | |
| 39 | className: composerControlClassName(size, className), | |
| 40 | type: render ? undefined : ("button" as const), | |
| 41 | }; | |
| 42 | return useRender({ | |
| 43 | defaultTagName: "button", | |
| 44 | props: mergeProps<"button">(defaultProps, props), | |
| 45 | render, | |
| 46 | }); | |
| 43 | 47 | } |
| 44 | 48 | |
| 45 | 49 | export function ComposerControlIcon({ |
| ... | ... | @@ -105,21 +109,19 @@ export function ComposerControlSeparator({ |
| 105 | 109 | |
| 106 | 110 | export function ComposerSelectControl({ |
| 107 | 111 | className, |
| 112 | children, | |
| 108 | 113 | size = "sm", |
| 109 | variant = "ghost", | |
| 110 | 114 | ...props |
| 111 | }: ComposerSelectControlProps) { | |
| 115 | }: Omit<SelectPrimitive.Trigger.Props, "className"> & { | |
| 116 | className?: string | undefined; | |
| 117 | size?: ComposerControlSize; | |
| 118 | }) { | |
| 112 | 119 | return ( |
| 113 | <SelectTrigger | |
| 114 | className={cn( | |
| 115 | composerControlClassName, | |
| 116 | size === "xs" ? restingComposerControlClassName : expandedComposerControlClassName, | |
| 117 | className, | |
| 118 | )} | |
| 119 | icon={<ComposerControlChevron size={size} />} | |
| 120 | size={size} | |
| 121 | variant={variant} | |
| 122 | {...props} | |
| 123 | /> | |
| 120 | <SelectPrimitive.Trigger className={composerControlClassName(size, className)} {...props}> | |
| 121 | {children} | |
| 122 | <SelectPrimitive.Icon> | |
| 123 | <ComposerControlChevron size={size} /> | |
| 124 | </SelectPrimitive.Icon> | |
| 125 | </SelectPrimitive.Trigger> | |
| 124 | 126 | ); |
| 125 | 127 | } |
apps/web/src/components/chat/ComposerPrimaryActions.tsx+20-24| ... | ... | @@ -52,6 +52,11 @@ const formatPendingPrimaryActionLabel = (input: { |
| 52 | 52 | return input.questionIndex > 0 ? "Submit answers" : "Submit answer"; |
| 53 | 53 | }; |
| 54 | 54 | |
| 55 | // The composer's labeled primary actions (Submit, Refine, Implement) share the send button's | |
| 56 | // message-action pill, so they are composer-owned buttons rather than restyled Buttons. | |
| 57 | const messageActionPillClassName = | |
| 58 | "inline-flex shrink-0 cursor-pointer items-center justify-center gap-1.5 whitespace-nowrap rounded-full bg-message-action font-medium text-base text-message-action-foreground shadow-xs shadow-message-action/24 outline-none hover:bg-message-action-hover focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1 focus-visible:ring-offset-background disabled:pointer-events-none disabled:opacity-64 disabled:shadow-none sm:text-sm"; | |
| 59 | ||
| 55 | 60 | const preventPointerFocus: PointerEventHandler<HTMLElement> = (event) => { |
| 56 | 61 | event.preventDefault(); |
| 57 | 62 | }; |
| ... | ... | @@ -112,7 +117,6 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ |
| 112 | 117 | <Button |
| 113 | 118 | size="icon-sm" |
| 114 | 119 | variant="outline" |
| 115 | className="rounded-full" | |
| 116 | 120 | {...pointerFocusProps} |
| 117 | 121 | onClick={onPreviousPendingQuestion} |
| 118 | 122 | disabled={pendingAction.isResponding} |
| ... | ... | @@ -124,7 +128,6 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ |
| 124 | 128 | <Button |
| 125 | 129 | size="sm" |
| 126 | 130 | variant="outline" |
| 127 | className="rounded-full" | |
| 128 | 131 | {...pointerFocusProps} |
| 129 | 132 | onClick={onPreviousPendingQuestion} |
| 130 | 133 | disabled={pendingAction.isResponding} |
| ... | ... | @@ -133,13 +136,9 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ |
| 133 | 136 | </Button> |
| 134 | 137 | ) |
| 135 | 138 | ) : null} |
| 136 | <Button | |
| 139 | <button | |
| 137 | 140 | type="submit" |
| 138 | size="sm" | |
| 139 | className={cn( | |
| 140 | "rounded-full bg-message-action text-message-action-foreground hover:bg-message-action-hover", | |
| 141 | compact ? "px-3" : "px-4", | |
| 142 | )} | |
| 141 | className={cn(messageActionPillClassName, "h-8 sm:h-7", compact ? "px-3" : "px-4")} | |
| 143 | 142 | {...pointerFocusProps} |
| 144 | 143 | disabled={ |
| 145 | 144 | isEnvironmentUnavailable || |
| ... | ... | @@ -153,7 +152,7 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ |
| 153 | 152 | isResponding: pendingAction.isResponding, |
| 154 | 153 | questionIndex: pendingAction.questionIndex, |
| 155 | 154 | })} |
| 156 | </Button> | |
| 155 | </button> | |
| 157 | 156 | </div> |
| 158 | 157 | ); |
| 159 | 158 | } |
| ... | ... | @@ -161,39 +160,36 @@ export const ComposerPrimaryActions = memo(function ComposerPrimaryActions({ |
| 161 | 160 | if (showPlanFollowUpPrompt) { |
| 162 | 161 | if (promptHasText) { |
| 163 | 162 | return ( |
| 164 | <Button | |
| 163 | <button | |
| 165 | 164 | type="submit" |
| 166 | size="sm" | |
| 167 | className={cn( | |
| 168 | "rounded-full bg-message-action text-message-action-foreground hover:bg-message-action-hover", | |
| 169 | compact ? "h-9 px-3 sm:h-8" : "h-9 px-4 sm:h-8", | |
| 170 | )} | |
| 165 | className={cn(messageActionPillClassName, "h-9 sm:h-8", compact ? "px-3" : "px-4")} | |
| 171 | 166 | {...pointerFocusProps} |
| 172 | 167 | disabled={isSendBusy || isSendDisabled || isConnecting || isEnvironmentUnavailable} |
| 173 | 168 | > |
| 174 | 169 | {isConnecting || isSendBusy ? "Sending..." : "Refine"} |
| 175 | </Button> | |
| 170 | </button> | |
| 176 | 171 | ); |
| 177 | 172 | } |
| 178 | 173 | |
| 179 | 174 | return ( |
| 180 | 175 | <div data-chat-composer-implement-actions="true" className="flex items-center justify-end"> |
| 181 | <Button | |
| 176 | <button | |
| 182 | 177 | type="submit" |
| 183 | size="sm" | |
| 184 | className="h-9 rounded-l-full rounded-r-none bg-message-action px-4 text-message-action-foreground hover:bg-message-action-hover sm:h-8" | |
| 178 | className={cn(messageActionPillClassName, "h-9 rounded-r-none px-4 sm:h-8")} | |
| 185 | 179 | {...pointerFocusProps} |
| 186 | 180 | disabled={isSendBusy || isSendDisabled || isConnecting || isEnvironmentUnavailable} |
| 187 | 181 | > |
| 188 | 182 | {isConnecting || isSendBusy ? "Sending..." : "Implement"} |
| 189 | </Button> | |
| 183 | </button> | |
| 190 | 184 | <Menu> |
| 191 | 185 | <MenuTrigger |
| 192 | 186 | render={ |
| 193 | <Button | |
| 194 | size="sm" | |
| 195 | variant="default" | |
| 196 | className="h-9 rounded-l-none rounded-r-full border-l-message-action-foreground/20 bg-message-action px-2 text-message-action-foreground hover:bg-message-action-hover sm:h-8" | |
| 187 | <button | |
| 188 | type="button" | |
| 189 | className={cn( | |
| 190 | messageActionPillClassName, | |
| 191 | "h-9 rounded-l-none border-l border-message-action-foreground/20 px-2 sm:h-8", | |
| 192 | )} | |
| 197 | 193 | aria-label="Implementation actions" |
| 198 | 194 | {...pointerFocusProps} |
| 199 | 195 | disabled={isSendBusy || isSendDisabled || isConnecting || isEnvironmentUnavailable} |
apps/web/src/components/chat/ModelListRow.tsx+4-8| ... | ... | @@ -53,10 +53,9 @@ export const ModelListRow = memo(function ModelListRow(props: { |
| 53 | 53 | value={modelPickerModelKey(props.instanceId, props.model.slug)} |
| 54 | 54 | disabled={Boolean(props.disabledReason)} |
| 55 | 55 | className={cn( |
| 56 | "group relative w-full !min-w-0 max-w-full cursor-pointer rounded-md px-2 py-2 transition-[background-color,box-shadow,color]", | |
| 57 | "hover:bg-[color-mix(in_srgb,var(--popover)_90%,var(--contrast-foreground))] data-highlighted:bg-[color-mix(in_srgb,var(--popover)_90%,var(--contrast-foreground))] data-selected:bg-foreground/[0.08] data-selected:text-foreground data-selected:ring-0 [&[data-highlighted][data-selected]]:bg-[color-mix(in_srgb,var(--popover)_90%,var(--contrast-foreground))]", | |
| 56 | "group relative w-full !min-w-0 max-w-full cursor-pointer", | |
| 58 | 57 | props.disabledReason && |
| 59 | "data-disabled:pointer-events-auto data-disabled:cursor-not-allowed data-disabled:hover:bg-transparent", | |
| 58 | "data-disabled:pointer-events-auto data-disabled:cursor-not-allowed", | |
| 60 | 59 | )} |
| 61 | 60 | > |
| 62 | 61 | <div className="min-w-0 flex-1 text-left"> |
| ... | ... | @@ -103,11 +102,8 @@ export const ModelListRow = memo(function ModelListRow(props: { |
| 103 | 102 | render={ |
| 104 | 103 | <Button |
| 105 | 104 | size="icon-xs" |
| 106 | variant="ghost" | |
| 107 | className={cn( | |
| 108 | "-mr-1 shrink-0 text-muted-foreground/70 opacity-64 transition-[color,opacity] hover:text-foreground hover:opacity-100 group-hover:opacity-100", | |
| 109 | props.isFavorite && "text-foreground opacity-100", | |
| 110 | )} | |
| 105 | variant="ghost-muted" | |
| 106 | className="-mr-1 shrink-0" | |
| 111 | 107 | onClick={(event) => { |
| 112 | 108 | event.stopPropagation(); |
| 113 | 109 | props.onToggleFavorite(); |
apps/web/src/components/chat/ModelPickerContent.tsx+7-11| ... | ... | @@ -39,7 +39,7 @@ import { useClientSettings, useUpdateClientSettings } from "~/hooks/useSettings" |
| 39 | 39 | import { cn } from "~/lib/utils"; |
| 40 | 40 | import { getVirtualizedScrollFadeClassName } from "../ui/scroll-area"; |
| 41 | 41 | import { TooltipProvider } from "../ui/tooltip"; |
| 42 | import { Button } from "../ui/button"; | |
| 42 | import { InlineButton } from "../ui/button"; | |
| 43 | 43 | import { |
| 44 | 44 | isProviderInstancePickerReady, |
| 45 | 45 | isProviderInstancePickerVisible, |
| ... | ... | @@ -941,7 +941,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 941 | 941 | |
| 942 | 942 | {/* Model list */} |
| 943 | 943 | <div className="relative min-h-0 flex-1 overflow-hidden pr-px"> |
| 944 | <ComboboxListVirtualized className="not-empty:p-0"> | |
| 944 | <ComboboxListVirtualized> | |
| 945 | 945 | <LegendList<string> |
| 946 | 946 | ref={modelListRef} |
| 947 | 947 | data={filteredItemKeys} |
| ... | ... | @@ -955,7 +955,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 955 | 955 | index={index} |
| 956 | 956 | value={modelKey} |
| 957 | 957 | aria-expanded={legacySection.isExpanded} |
| 958 | className="group w-full cursor-pointer rounded-md px-2 py-2" | |
| 958 | className="group w-full cursor-pointer" | |
| 959 | 959 | > |
| 960 | 960 | <div className="min-w-0 flex-1 text-left"> |
| 961 | 961 | <div className="text-xs font-medium leading-snug">Legacy models</div> |
| ... | ... | @@ -1031,26 +1031,22 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: { |
| 1031 | 1031 | <p className="line-clamp-3 text-muted-foreground"> |
| 1032 | 1032 | {getProviderStatusMessage(entry.snapshot)} |
| 1033 | 1033 | </p> |
| 1034 | <Button | |
| 1035 | className="mt-1 px-0 text-foreground" | |
| 1034 | <InlineButton | |
| 1035 | className="mt-1" | |
| 1036 | 1036 | onClick={() => { |
| 1037 | 1037 | props.onRequestClose?.(); |
| 1038 | 1038 | props.onOpenProviderSetup?.(entry.instanceId); |
| 1039 | 1039 | }} |
| 1040 | size="xs" | |
| 1041 | variant="link" | |
| 1042 | 1040 | > |
| 1043 | 1041 | {providerSetupEntries.length > 1 |
| 1044 | 1042 | ? `Set up ${entry.displayName}` |
| 1045 | 1043 | : "Open provider setup"} |
| 1046 | </Button> | |
| 1044 | </InlineButton> | |
| 1047 | 1045 | </div> |
| 1048 | 1046 | ))} |
| 1049 | 1047 | </div> |
| 1050 | 1048 | ) : ( |
| 1051 | <ComboboxEmpty className="not-empty:py-6 empty:h-0 text-xs font-normal leading-snug"> | |
| 1052 | No models found | |
| 1053 | </ComboboxEmpty> | |
| 1049 | <ComboboxEmpty className="empty:h-0">No models found</ComboboxEmpty> | |
| 1054 | 1050 | )} |
| 1055 | 1051 | </div> |
| 1056 | 1052 | </Combobox> |
apps/web/src/components/chat/ProviderModelPicker.tsx-3| ... | ... | @@ -6,7 +6,6 @@ import { |
| 6 | 6 | } from "@t3tools/contracts"; |
| 7 | 7 | import { memo, useEffect, useMemo, useState } from "react"; |
| 8 | 8 | import { Badge } from "../ui/badge"; |
| 9 | import type { ButtonVariant } from "../ui/button"; | |
| 10 | 9 | import { Popover, PopoverPopup, PopoverTrigger } from "../ui/popover"; |
| 11 | 10 | import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 12 | 11 | import { cn } from "~/lib/utils"; |
| ... | ... | @@ -48,7 +47,6 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { |
| 48 | 47 | disabled?: boolean; |
| 49 | 48 | terminalOpen?: boolean; |
| 50 | 49 | open?: boolean; |
| 51 | triggerVariant?: ButtonVariant; | |
| 52 | 50 | triggerClassName?: string; |
| 53 | 51 | /** Aggregate settings can show a neutral value without claiming one provider is selected. */ |
| 54 | 52 | triggerLabel?: string; |
| ... | ... | @@ -206,7 +204,6 @@ export const ProviderModelPicker = memo(function ProviderModelPicker(props: { |
| 206 | 204 | render={ |
| 207 | 205 | <ComposerControl |
| 208 | 206 | aria-label={props.triggerAriaLabel ?? allModelNames} |
| 209 | variant={props.triggerVariant ?? "ghost"} | |
| 210 | 207 | size={size} |
| 211 | 208 | data-chat-provider-model-picker="true" |
| 212 | 209 | className={cn( |
apps/web/src/components/chat/TraitsPicker.tsx-4| ... | ... | @@ -17,7 +17,6 @@ import { |
| 17 | 17 | } from "@t3tools/shared/model"; |
| 18 | 18 | import { memo, useCallback } from "react"; |
| 19 | 19 | import { GaugeIcon, ZapIcon } from "lucide-react"; |
| 20 | import type { ButtonVariant } from "../ui/button"; | |
| 21 | 20 | import { |
| 22 | 21 | Menu, |
| 23 | 22 | MenuGroup, |
| ... | ... | @@ -276,7 +275,6 @@ export interface TraitsMenuContentProps { |
| 276 | 275 | modelOptions?: ProviderOptions | null | undefined; |
| 277 | 276 | allowPromptInjectedEffort?: boolean; |
| 278 | 277 | planModeEnabled: boolean; |
| 279 | triggerVariant?: ButtonVariant; | |
| 280 | 278 | triggerClassName?: string; |
| 281 | 279 | isComposerOwned?: boolean; |
| 282 | 280 | } |
| ... | ... | @@ -543,7 +541,6 @@ export const TraitsPicker = memo(function TraitsPicker({ |
| 543 | 541 | modelOptions, |
| 544 | 542 | allowPromptInjectedEffort = true, |
| 545 | 543 | planModeEnabled, |
| 546 | triggerVariant, | |
| 547 | 544 | triggerClassName, |
| 548 | 545 | isComposerOwned, |
| 549 | 546 | size = "sm", |
| ... | ... | @@ -622,7 +619,6 @@ export const TraitsPicker = memo(function TraitsPicker({ |
| 622 | 619 | <ComposerControl |
| 623 | 620 | aria-label={accessibleLabel} |
| 624 | 621 | data-composer-shortcut={isComposerOwned ? "composer.effort" : undefined} |
| 625 | variant={triggerVariant ?? "ghost"} | |
| 626 | 622 | size={size} |
| 627 | 623 | className={cn( |
| 628 | 624 | isCodexStyle |
apps/web/src/components/chat/composerProviderState.tsx-4| ... | ... | @@ -15,7 +15,6 @@ import { |
| 15 | 15 | } from "@t3tools/shared/model"; |
| 16 | 16 | import type { ReactNode } from "react"; |
| 17 | 17 | |
| 18 | import type { ButtonVariant } from "../ui/button"; | |
| 19 | 18 | import type { DraftId } from "../../composerDraftStore"; |
| 20 | 19 | import { getProviderModelCapabilities } from "../../providerModels"; |
| 21 | 20 | import type { ComposerControlSize } from "./ComposerControl"; |
| ... | ... | @@ -54,7 +53,6 @@ type TraitsRenderInput = { |
| 54 | 53 | planModeEnabled: boolean; |
| 55 | 54 | size?: ComposerControlSize; |
| 56 | 55 | hidden?: boolean; |
| 57 | triggerVariant?: ButtonVariant; | |
| 58 | 56 | triggerClassName?: string; |
| 59 | 57 | isComposerOwned?: boolean; |
| 60 | 58 | }; |
| ... | ... | @@ -177,7 +175,6 @@ function renderTraitsControl( |
| 177 | 175 | planModeEnabled, |
| 178 | 176 | size, |
| 179 | 177 | hidden, |
| 180 | triggerVariant, | |
| 181 | 178 | triggerClassName, |
| 182 | 179 | isComposerOwned, |
| 183 | 180 | } = input; |
| ... | ... | @@ -216,7 +213,6 @@ function renderTraitsControl( |
| 216 | 213 | planModeEnabled={planModeEnabled} |
| 217 | 214 | {...(size !== undefined ? { size } : {})} |
| 218 | 215 | {...(hidden !== undefined ? { hidden } : {})} |
| 219 | {...(triggerVariant !== undefined ? { triggerVariant } : {})} | |
| 220 | 216 | {...(triggerClassName !== undefined ? { triggerClassName } : {})} |
| 221 | 217 | {...(isComposerOwned ? { isComposerOwned } : {})} |
| 222 | 218 | /> |
apps/web/src/components/settings/ProjectDefaultsSettings.tsx-2| ... | ... | @@ -162,7 +162,6 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting |
| 162 | 162 | lockedProvider={null} |
| 163 | 163 | instanceEntries={entries} |
| 164 | 164 | modelOptionsByInstance={modelOptions} |
| 165 | triggerVariant="outline" | |
| 166 | 165 | triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} |
| 167 | 166 | {...(mixedModel ? { triggerLabel: "Mixed" } : {})} |
| 168 | 167 | getModelDisabledReason={modelDisabledReason} |
| ... | ... | @@ -187,7 +186,6 @@ export function ProjectDefaultsSettings({ category }: { category: ProjectSetting |
| 187 | 186 | modelOptions={selection.options ?? []} |
| 188 | 187 | allowPromptInjectedEffort={false} |
| 189 | 188 | planModeEnabled={settings.planModeEnabled} |
| 190 | triggerVariant="outline" | |
| 191 | 189 | triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} |
| 192 | 190 | onModelOptionsChange={(options) => |
| 193 | 191 | setModel(createModelSelection(selection.instanceId, selection.model, options)) |
apps/web/src/components/settings/SettingsPanels.tsx-2| ... | ... | @@ -3113,7 +3113,6 @@ export function GeneralSettingsPanel() { |
| 3113 | 3113 | lockedProvider={null} |
| 3114 | 3114 | instanceEntries={textGenerationModelInstanceEntries} |
| 3115 | 3115 | modelOptionsByInstance={textGenerationModelOptionsByInstance} |
| 3116 | triggerVariant="outline" | |
| 3117 | 3116 | triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} |
| 3118 | 3117 | {...(mixedTextGenerationModel ? { triggerLabel: "Mixed" } : {})} |
| 3119 | 3118 | getModelDisabledReason={textGenerationModelDisabledReason} |
| ... | ... | @@ -3164,7 +3163,6 @@ export function GeneralSettingsPanel() { |
| 3164 | 3163 | modelOptions={textGenModelOptions} |
| 3165 | 3164 | allowPromptInjectedEffort={false} |
| 3166 | 3165 | planModeEnabled={settings.planModeEnabled} |
| 3167 | triggerVariant="outline" | |
| 3168 | 3166 | triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} |
| 3169 | 3167 | onModelOptionsChange={(nextOptions) => { |
| 3170 | 3168 | updateSettings({ |
apps/web/src/components/settings/SourceControlWritingSettings.tsx-1| ... | ... | @@ -304,7 +304,6 @@ export function SourceControlWritingSettingsSection() { |
| 304 | 304 | lockedProvider={null} |
| 305 | 305 | instanceEntries={instanceEntries} |
| 306 | 306 | modelOptionsByInstance={modelOptionsByInstance} |
| 307 | triggerVariant="outline" | |
| 308 | 307 | triggerClassName={SETTINGS_PICKER_TRIGGER_CLASSNAME} |
| 309 | 308 | triggerAriaLabel="Source control writer model" |
| 310 | 309 | {...(mixedWriterModel ? { triggerLabel: "Mixed" } : {})} |
apps/web/src/components/settings/settingsLayout.tsx+2-7| ... | ... | @@ -137,13 +137,8 @@ export function SettingsSearchTarget({ |
| 137 | 137 | ); |
| 138 | 138 | } |
| 139 | 139 | |
| 140 | /** | |
| 141 | * Trigger classes for the composer model/traits pickers when they sit in a | |
| 142 | * settings row: match the `sm` control box (the composer pins them to 28px at | |
| 143 | * every breakpoint) and drop the composer's max-width. | |
| 144 | */ | |
| 145 | export const SETTINGS_PICKER_TRIGGER_CLASSNAME = | |
| 146 | "h-8 min-h-8 min-w-0 max-w-none shrink-0 text-foreground/90 hover:text-foreground sm:h-7 sm:min-h-7"; | |
| 140 | /** Layout for the composer model/traits pickers in a settings row: drop the composer's max-width. */ | |
| 141 | export const SETTINGS_PICKER_TRIGGER_CLASSNAME = "min-w-0 max-w-none shrink-0"; | |
| 147 | 142 | |
| 148 | 143 | /** Info affordance explaining how a setting interacts with the shared background policy. */ |
| 149 | 144 | export function PolicyTooltip({ children }: { readonly children: string }) { |
apps/web/src/components/ui/combobox.tsx+1-1| ... | ... | @@ -194,7 +194,7 @@ function ComboboxItem({ |
| 194 | 194 | return ( |
| 195 | 195 | <ComboboxPrimitive.Item |
| 196 | 196 | className={cn( |
| 197 | "flex min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-pointer items-center rounded-sm px-2 py-1 text-base outline-none hover:bg-accent data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-selected:bg-foreground/[0.08] data-selected:text-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground [&[data-highlighted][data-selected]]:bg-accent [&[data-highlighted][data-selected]]: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", | |
| 197 | "flex min-h-8 in-data-[side=none]:min-w-[calc(var(--anchor-width)+1.25rem)] cursor-pointer items-center rounded-sm px-2 py-1 text-base outline-none not-data-disabled:hover:bg-accent data-disabled:pointer-events-none data-disabled:cursor-not-allowed data-selected:bg-foreground/[0.08] data-selected:text-foreground data-highlighted:bg-accent data-highlighted:text-accent-foreground [&[data-highlighted][data-selected]]:bg-accent [&[data-highlighted][data-selected]]: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", | |
| 198 | 198 | className, |
| 199 | 199 | )} |
| 200 | 200 | data-slot="combobox-item" |
scripts/lint-restyle-ceiling.ts+1-1| ... | ... | @@ -8,7 +8,7 @@ import * as NodeURL from "node:url"; |
| 8 | 8 | // components/ui exports are migrated to variants (see vite.config.ts). This gate keeps the |
| 9 | 9 | // count from growing: CI fails when findings exceed the ceiling. Lower the ceiling when you |
| 10 | 10 | // migrate a file, and delete this script when the rule becomes an error. |
| 11 | export const RESTYLE_CEILING = 101; | |
| 11 | export const RESTYLE_CEILING = 0; | |
| 12 | 12 | |
| 13 | 13 | const RULE = "shadcn(no-restyle)"; |
| 14 | 14 |