| 1 | import { ComposerSelectControl } from "./chat/ComposerControl"; |
| 2 | import { ComposerContextLabel } from "./ComposerContextLabel"; |
| 3 | import { Tooltip, TooltipTrigger, TooltipPopup } from "./ui/tooltip"; |
| 4 | import type { EnvironmentId } from "@t3tools/contracts"; |
| 5 | import { ScaleIcon } from "lucide-react"; |
| 6 | import { memo, useMemo } from "react"; |
| 7 | |
| 8 | import type { EnvironmentOption } from "./BranchToolbar.logic"; |
| 9 | import { EnvironmentMachineIcon } from "./EnvironmentMachineIcon"; |
| 10 | import { useComposerMenuProps } from "./chat/composerEventScope"; |
| 11 | import { |
| 12 | Select, |
| 13 | SelectGroup, |
| 14 | SelectGroupLabel, |
| 15 | SelectItem, |
| 16 | SelectPopup, |
| 17 | SelectValue, |
| 18 | } from "./ui/select"; |
| 19 | |
| 20 | interface BranchToolbarEnvironmentSelectorProps { |
| 21 | autoEnvironmentLabel?: string | undefined; |
| 22 | onAutoEnvironment?: (() => void) | undefined; |
| 23 | envLocked: boolean; |
| 24 | environmentId: EnvironmentId; |
| 25 | availableEnvironments: readonly EnvironmentOption[]; |
| 26 | onEnvironmentChange?: (environmentId: EnvironmentId) => void; |
| 27 | } |
| 28 | |
| 29 | export const BranchToolbarEnvironmentSelector = memo(function BranchToolbarEnvironmentSelector({ |
| 30 | autoEnvironmentLabel, |
| 31 | onAutoEnvironment, |
| 32 | envLocked, |
| 33 | environmentId, |
| 34 | availableEnvironments, |
| 35 | onEnvironmentChange, |
| 36 | }: BranchToolbarEnvironmentSelectorProps) { |
| 37 | const composerFloatingLayerProps = useComposerMenuProps(); |
| 38 | const activeEnvironment = useMemo(() => { |
| 39 | return availableEnvironments.find((env) => env.environmentId === environmentId) ?? null; |
| 40 | }, [availableEnvironments, environmentId]); |
| 41 | |
| 42 | const environmentItems = useMemo( |
| 43 | () => [ |
| 44 | ...(onAutoEnvironment |
| 45 | ? [{ value: "auto", label: autoEnvironmentLabel ?? "Auto balance" }] |
| 46 | : []), |
| 47 | ...availableEnvironments.map((env) => ({ |
| 48 | value: env.environmentId, |
| 49 | label: env.label, |
| 50 | })), |
| 51 | ], |
| 52 | [availableEnvironments, autoEnvironmentLabel, onAutoEnvironment], |
| 53 | ); |
| 54 | |
| 55 | // The static label carries the xs control's height (h-7 sm:h-6) as well as |
| 56 | // its padding: the composer context strip has no min-height of its own, and |
| 57 | // the glass seam joining it to the composer assumes a fixed strip height, so |
| 58 | // a shorter label would drag the seam out of line whenever this label is the |
| 59 | // only thing in the strip. |
| 60 | if (envLocked || onEnvironmentChange === undefined) { |
| 61 | const lockedRow = ( |
| 62 | <span |
| 63 | className="inline-flex h-7 min-w-0 max-w-full items-center gap-1 border border-transparent px-1.75 font-normal text-muted-foreground/70 text-xs sm:h-6" |
| 64 | data-composer-context-control |
| 65 | > |
| 66 | <EnvironmentMachineIcon |
| 67 | kind={activeEnvironment?.machine ?? "server"} |
| 68 | className="size-3 shrink-0" |
| 69 | /> |
| 70 | <ComposerContextLabel>{activeEnvironment?.label ?? "Run on"}</ComposerContextLabel> |
| 71 | </span> |
| 72 | ); |
| 73 | return ( |
| 74 | <Tooltip> |
| 75 | <TooltipTrigger render={lockedRow} /> |
| 76 | <TooltipPopup>{activeEnvironment?.label ?? "Run on"}</TooltipPopup> |
| 77 | </Tooltip> |
| 78 | ); |
| 79 | } |
| 80 | |
| 81 | return ( |
| 82 | <Select |
| 83 | modal={false} |
| 84 | value={autoEnvironmentLabel ? "auto" : environmentId} |
| 85 | onValueChange={(value) => |
| 86 | value === "auto" ? onAutoEnvironment?.() : onEnvironmentChange(value as EnvironmentId) |
| 87 | } |
| 88 | items={environmentItems} |
| 89 | > |
| 90 | <Tooltip> |
| 91 | <TooltipTrigger |
| 92 | render={ |
| 93 | <ComposerSelectControl |
| 94 | size="xs" |
| 95 | className="min-w-0 max-w-full" |
| 96 | aria-label="Run on" |
| 97 | data-composer-shortcut="composer.host" |
| 98 | data-composer-context-control |
| 99 | /> |
| 100 | } |
| 101 | > |
| 102 | {autoEnvironmentLabel ? ( |
| 103 | <ScaleIcon className="size-3 shrink-0" aria-hidden="true" /> |
| 104 | ) : ( |
| 105 | <EnvironmentMachineIcon |
| 106 | kind={activeEnvironment?.machine ?? "server"} |
| 107 | className="size-3 shrink-0" |
| 108 | /> |
| 109 | )} |
| 110 | <ComposerContextLabel> |
| 111 | <SelectValue /> |
| 112 | </ComposerContextLabel> |
| 113 | </TooltipTrigger> |
| 114 | <TooltipPopup>{autoEnvironmentLabel ?? activeEnvironment?.label ?? "Run on"}</TooltipPopup> |
| 115 | </Tooltip> |
| 116 | <SelectPopup alignItemWithTrigger={false} {...composerFloatingLayerProps}> |
| 117 | <SelectGroup> |
| 118 | <SelectGroupLabel>Run on</SelectGroupLabel> |
| 119 | {onAutoEnvironment && ( |
| 120 | <SelectItem |
| 121 | value="auto" |
| 122 | onClick={() => { |
| 123 | if (autoEnvironmentLabel) onAutoEnvironment?.(); |
| 124 | }} |
| 125 | > |
| 126 | <span className="inline-flex items-center gap-1.5"> |
| 127 | <ScaleIcon className="size-3" aria-hidden="true" /> |
| 128 | {autoEnvironmentLabel ?? "Auto balance"} |
| 129 | </span> |
| 130 | </SelectItem> |
| 131 | )} |
| 132 | {availableEnvironments.map((env) => ( |
| 133 | <SelectItem key={env.environmentId} value={env.environmentId}> |
| 134 | <span className="inline-flex items-center gap-1.5"> |
| 135 | <EnvironmentMachineIcon kind={env.machine} className="size-3" /> |
| 136 | {env.label} |
| 137 | </span> |
| 138 | </SelectItem> |
| 139 | ))} |
| 140 | </SelectGroup> |
| 141 | </SelectPopup> |
| 142 | </Select> |
| 143 | ); |
| 144 | }); |