| author | |
| committer | |
| log | 5c66110e269cc5a852763e5f06a81f2a763d3181 |
| tree | 988805f7a6913fd53b48b92667c0a92f0a225737 |
| parent | da3c2030f710c4e1243035613eda94f6526afa1f |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Julius Marminge <julius0216@outlook.com>
Co-authored-by: codex <codex@users.noreply.github.com>3 files changed, 131 insertions(+), 88 deletions(-)
apps/web/src/components/chat/ChatComposer.tsx+19-9| ... | ... | @@ -95,6 +95,7 @@ import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; |
| 95 | 95 | import { toastManager } from "../ui/toast"; |
| 96 | 96 | import { |
| 97 | 97 | BotIcon, |
| 98 | CheckIcon, | |
| 98 | 99 | CircleAlertIcon, |
| 99 | 100 | ListTodoIcon, |
| 100 | 101 | PencilRulerIcon, |
| ... | ... | @@ -273,16 +274,25 @@ const ComposerFooterModeControls = memo(function ComposerFooterModeControls(prop |
| 273 | 274 | {runtimeModeOptions.map((mode) => { |
| 274 | 275 | const option = runtimeModeConfig[mode]; |
| 275 | 276 | const OptionIcon = option.icon; |
| 277 | const isSelected = props.runtimeMode === mode; | |
| 276 | 278 | return ( |
| 277 | <SelectItem key={mode} value={mode} className="min-w-64 py-2"> | |
| 278 | <div className="grid min-w-0 gap-0.5"> | |
| 279 | <span className="inline-flex items-center gap-1.5 font-medium text-foreground"> | |
| 280 | <OptionIcon className="size-3.5 shrink-0 text-muted-foreground" /> | |
| 281 | {option.label} | |
| 282 | </span> | |
| 283 | <span className="text-muted-foreground text-xs leading-4"> | |
| 284 | {option.description} | |
| 285 | </span> | |
| 279 | <SelectItem key={mode} value={mode} hideIndicator className="min-w-64 py-2"> | |
| 280 | <div className="flex min-w-0 items-center gap-3"> | |
| 281 | <div className="grid min-w-0 flex-1 gap-0.5"> | |
| 282 | <span className="inline-flex items-center gap-1.5 font-medium text-foreground"> | |
| 283 | <OptionIcon className="size-3.5 shrink-0 text-muted-foreground" /> | |
| 284 | {option.label} | |
| 285 | </span> | |
| 286 | <span className="text-muted-foreground text-xs leading-4"> | |
| 287 | {option.description} | |
| 288 | </span> | |
| 289 | </div> | |
| 290 | <CheckIcon | |
| 291 | className={cn( | |
| 292 | "size-4 text-blue-400", | |
| 293 | isSelected ? "opacity-100" : "opacity-0", | |
| 294 | )} | |
| 295 | /> | |
| 286 | 296 | </div> |
| 287 | 297 | </SelectItem> |
| 288 | 298 | ); |
apps/web/src/components/chat/TraitsPicker.tsx+84-61| ... | ... | @@ -16,7 +16,7 @@ import { |
| 16 | 16 | } from "@t3tools/shared/model"; |
| 17 | 17 | import { memo, useCallback, useState } from "react"; |
| 18 | 18 | import type { VariantProps } from "class-variance-authority"; |
| 19 | import { ChevronDownIcon } from "lucide-react"; | |
| 19 | import { CheckIcon, ChevronDownIcon } from "lucide-react"; | |
| 20 | 20 | import { Button, buttonVariants } from "../ui/button"; |
| 21 | 21 | import { |
| 22 | 22 | Menu, |
| ... | ... | @@ -297,67 +297,90 @@ export const TraitsMenuContent = memo(function TraitsMenuContentImpl({ |
| 297 | 297 | |
| 298 | 298 | return ( |
| 299 | 299 | <> |
| 300 | {selectDescriptors.map((descriptor, index) => ( | |
| 301 | <div key={descriptor.id}> | |
| 302 | {index > 0 ? <MenuDivider /> : null} | |
| 303 | <MenuGroup> | |
| 304 | <div className="px-2 pt-1.5 pb-1 font-medium text-muted-foreground text-xs"> | |
| 305 | {descriptor.label} | |
| 306 | </div> | |
| 307 | {ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id ? ( | |
| 308 | <div className="px-2 pb-1.5 text-muted-foreground/80 text-xs"> | |
| 309 | Your prompt contains &quot;ultrathink&quot; in the text. Remove it to change this | |
| 310 | option. | |
| 300 | {selectDescriptors.map((descriptor, index) => { | |
| 301 | const selectedValue = | |
| 302 | ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id | |
| 303 | ? "ultrathink" | |
| 304 | : (getDescriptorStringValue(descriptor) ?? ""); | |
| 305 | ||
| 306 | return ( | |
| 307 | <div key={descriptor.id}> | |
| 308 | {index > 0 ? <MenuDivider /> : null} | |
| 309 | <MenuGroup> | |
| 310 | <div className="px-2 pt-1.5 pb-1 font-medium text-muted-foreground text-xs"> | |
| 311 | {descriptor.label} | |
| 312 | </div> | |
| 313 | {ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id ? ( | |
| 314 | <div className="px-2 pb-1.5 text-muted-foreground/80 text-xs"> | |
| 315 | Your prompt contains &quot;ultrathink&quot; in the text. Remove it to change this | |
| 316 | option. | |
| 317 | </div> | |
| 318 | ) : null} | |
| 319 | <MenuRadioGroup | |
| 320 | value={selectedValue} | |
| 321 | onValueChange={(value) => handleSelectChange(descriptor, value)} | |
| 322 | > | |
| 323 | {descriptor.options.map((option) => ( | |
| 324 | <MenuRadioItem | |
| 325 | key={option.id} | |
| 326 | value={option.id} | |
| 327 | hideIndicator | |
| 328 | disabled={ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id} | |
| 329 | > | |
| 330 | <span className="flex w-full min-w-0 items-center justify-between gap-3"> | |
| 331 | <span className="min-w-0 truncate"> | |
| 332 | {option.label} | |
| 333 | {option.isDefault ? ( | |
| 334 | <> | |
| 335 | {" "} | |
| 336 | <DefaultBadge /> | |
| 337 | </> | |
| 338 | ) : null} | |
| 339 | </span> | |
| 340 | {option.id === selectedValue ? ( | |
| 341 | <CheckIcon className="size-3.5 shrink-0 text-blue-400" /> | |
| 342 | ) : null} | |
| 343 | </span> | |
| 344 | </MenuRadioItem> | |
| 345 | ))} | |
| 346 | </MenuRadioGroup> | |
| 347 | </MenuGroup> | |
| 348 | </div> | |
| 349 | ); | |
| 350 | })} | |
| 351 | {booleanDescriptors.map((descriptor, index) => { | |
| 352 | const selectedValue = descriptor.currentValue === true ? "on" : "off"; | |
| 353 | ||
| 354 | return ( | |
| 355 | <div key={descriptor.id}> | |
| 356 | {index > 0 || selectDescriptors.length > 0 ? <MenuDivider /> : null} | |
| 357 | <MenuGroup> | |
| 358 | <div className="px-2 py-1.5 font-medium text-muted-foreground text-xs"> | |
| 359 | {descriptor.label} | |
| 311 | 360 | </div> |
| 312 | ) : null} | |
| 313 | <MenuRadioGroup | |
| 314 | value={ | |
| 315 | ultrathinkPromptControlled && descriptor.id === primarySelectDescriptor?.id | |
| 316 | ? "ultrathink" | |
| 317 | : (getDescriptorStringValue(descriptor) ?? "") | |
| 318 | } | |
| 319 | onValueChange={(value) => handleSelectChange(descriptor, value)} | |
| 320 | > | |
| 321 | {descriptor.options.map((option) => ( | |
| 322 | <MenuRadioItem | |
| 323 | key={option.id} | |
| 324 | value={option.id} | |
| 325 | disabled={ultrathinkInBodyText && descriptor.id === primarySelectDescriptor?.id} | |
| 326 | > | |
| 327 | {option.label} | |
| 328 | {option.isDefault ? ( | |
| 329 | <> | |
| 330 | {" "} | |
| 331 | <DefaultBadge /> | |
| 332 | </> | |
| 333 | ) : null} | |
| 334 | </MenuRadioItem> | |
| 335 | ))} | |
| 336 | </MenuRadioGroup> | |
| 337 | </MenuGroup> | |
| 338 | </div> | |
| 339 | ))} | |
| 340 | {booleanDescriptors.map((descriptor, index) => ( | |
| 341 | <div key={descriptor.id}> | |
| 342 | {index > 0 || selectDescriptors.length > 0 ? <MenuDivider /> : null} | |
| 343 | <MenuGroup> | |
| 344 | <div className="px-2 py-1.5 font-medium text-muted-foreground text-xs"> | |
| 345 | {descriptor.label} | |
| 346 | </div> | |
| 347 | <MenuRadioGroup | |
| 348 | value={descriptor.currentValue === true ? "on" : "off"} | |
| 349 | onValueChange={(value) => { | |
| 350 | updateDescriptors( | |
| 351 | replaceDescriptorCurrentValue(descriptors, descriptor.id, value === "on"), | |
| 352 | ); | |
| 353 | }} | |
| 354 | > | |
| 355 | <MenuRadioItem value="on">On</MenuRadioItem> | |
| 356 | <MenuRadioItem value="off">Off</MenuRadioItem> | |
| 357 | </MenuRadioGroup> | |
| 358 | </MenuGroup> | |
| 359 | </div> | |
| 360 | ))} | |
| 361 | <MenuRadioGroup | |
| 362 | value={selectedValue} | |
| 363 | onValueChange={(value) => { | |
| 364 | updateDescriptors( | |
| 365 | replaceDescriptorCurrentValue(descriptors, descriptor.id, value === "on"), | |
| 366 | ); | |
| 367 | }} | |
| 368 | > | |
| 369 | {(["on", "off"] as const).map((value) => ( | |
| 370 | <MenuRadioItem key={value} value={value} hideIndicator> | |
| 371 | <span className="flex w-full min-w-0 items-center justify-between gap-3"> | |
| 372 | <span>{value === "on" ? "On" : "Off"}</span> | |
| 373 | {value === selectedValue ? ( | |
| 374 | <CheckIcon className="size-3.5 shrink-0 text-blue-400" /> | |
| 375 | ) : null} | |
| 376 | </span> | |
| 377 | </MenuRadioItem> | |
| 378 | ))} | |
| 379 | </MenuRadioGroup> | |
| 380 | </MenuGroup> | |
| 381 | </div> | |
| 382 | ); | |
| 383 | })} | |
| 361 | 384 | </> |
| 362 | 385 | ); |
| 363 | 386 | }); |
apps/web/src/components/ui/menu.tsx+28-18| ... | ... | @@ -147,32 +147,42 @@ function MenuRadioGroup(props: MenuPrimitive.RadioGroup.Props) { |
| 147 | 147 | return <MenuPrimitive.RadioGroup data-slot="menu-radio-group" {...props} />; |
| 148 | 148 | } |
| 149 | 149 | |
| 150 | function MenuRadioItem({ className, children, ...props }: MenuPrimitive.RadioItem.Props) { | |
| 150 | function MenuRadioItem({ | |
| 151 | className, | |
| 152 | children, | |
| 153 | hideIndicator = false, | |
| 154 | ...props | |
| 155 | }: MenuPrimitive.RadioItem.Props & { | |
| 156 | hideIndicator?: boolean; | |
| 157 | }) { | |
| 151 | 158 | return ( |
| 152 | 159 | <MenuPrimitive.RadioItem |
| 153 | 160 | className={cn( |
| 154 | "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", | |
| 161 | "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", | |
| 162 | hideIndicator ? "grid-cols-[1fr] ps-3 pe-3" : "grid-cols-[1rem_1fr] ps-2 pe-4", | |
| 155 | 163 | className, |
| 156 | 164 | )} |
| 157 | 165 | data-slot="menu-radio-item" |
| 158 | 166 | {...props} |
| 159 | 167 | > |
| 160 | <MenuPrimitive.RadioItemIndicator className="col-start-1"> | |
| 161 | <svg | |
| 162 | fill="none" | |
| 163 | height="24" | |
| 164 | stroke="currentColor" | |
| 165 | strokeLinecap="round" | |
| 166 | strokeLinejoin="round" | |
| 167 | strokeWidth="2" | |
| 168 | viewBox="0 0 24 24" | |
| 169 | width="24" | |
| 170 | xmlns="http://www.w3.org/2000/svg" | |
| 171 | > | |
| 172 | <path d="M5.252 12.7 10.2 18.63 18.748 5.37" /> | |
| 173 | </svg> | |
| 174 | </MenuPrimitive.RadioItemIndicator> | |
| 175 | <span className="col-start-2">{children}</span> | |
| 168 | {hideIndicator ? null : ( | |
| 169 | <MenuPrimitive.RadioItemIndicator className="col-start-1"> | |
| 170 | <svg | |
| 171 | fill="none" | |
| 172 | height="24" | |
| 173 | stroke="currentColor" | |
| 174 | strokeLinecap="round" | |
| 175 | strokeLinejoin="round" | |
| 176 | strokeWidth="2" | |
| 177 | viewBox="0 0 24 24" | |
| 178 | width="24" | |
| 179 | xmlns="http://www.w3.org/2000/svg" | |
| 180 | > | |
| 181 | <path d="M5.252 12.7 10.2 18.63 18.748 5.37" /> | |
| 182 | </svg> | |
| 183 | </MenuPrimitive.RadioItemIndicator> | |
| 184 | )} | |
| 185 | <span className={hideIndicator ? "col-start-1" : "col-start-2"}>{children}</span> | |
| 176 | 186 | </MenuPrimitive.RadioItem> |
| 177 | 187 | ); |
| 178 | 188 | } |