Open History to see the last commit
1import { type ProviderDriverKind, type ProviderInstanceId } from "@t3tools/contracts";
2import { memo } from "react";
3import { CheckIcon, StarIcon } from "lucide-react";
4import { getDisplayModelName } from "./providerIconUtils";
5import { ComboboxItem } from "../ui/combobox";
6import { Button } from "../ui/button";
7import { Badge } from "../ui/badge";
8import { Kbd } from "../ui/kbd";
9import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip";
10import { cn } from "~/lib/utils";
11import { modelPickerModelKey } from "./modelPickerKeys";
12import { ProviderInstanceIcon } from "./ProviderInstanceIcon";
13
14export const ModelListRow = memo(function ModelListRow(props: {
15 index: number;
16 slug: string;
17 name: string;
18 subProvider?: string | undefined;
19 instanceId: ProviderInstanceId;
20 driverKind: ProviderDriverKind;
21 providerDisplayName: string;
22 providerAccentColor?: string | undefined;
23 acpRegistryAgentId?: string | undefined;
24 acpRegistryIconUrl?: string | undefined;
25 isFavorite: boolean;
26 /** In Favorites every row is starred, so the star shows only on hover. */
27 favoriteStarOnHover?: boolean;
28 isSelected: boolean;
29 /** Marks this instance apart from another of the same provider. */
30 showInstanceBadge: boolean;
31 showNewBadge?: boolean;
32 unavailable?: boolean;
33 jumpLabel?: string | null;
34 disabledReason?: string | null;
35 onToggleFavorite: (instanceId: ProviderInstanceId, slug: string) => void;
36}) {
37 const model = {
38 slug: props.slug,
39 name: props.name,
40 subProvider: props.subProvider,
41 };
42
43 const row = (
44 <ComboboxItem
45 hideIndicator
46 selectedFill={false}
47 index={props.index}
48 value={modelPickerModelKey(props.instanceId, props.slug)}
49 disabled={Boolean(props.disabledReason)}
50 className={cn(
51 "group relative w-full !min-w-0 max-w-full cursor-pointer",
52 props.disabledReason &&
53 "data-disabled:pointer-events-auto data-disabled:cursor-not-allowed",
54 )}
55 >
56 {/* One row shape in every tab: the logo names the provider, so Favorites needs no second line. */}
57 <ProviderInstanceIcon
58 driverKind={props.driverKind}
59 displayName={props.providerDisplayName}
60 accentColor={props.providerAccentColor}
61 acpRegistryAgentId={props.acpRegistryAgentId}
62 acpRegistryIconUrl={props.acpRegistryIconUrl}
63 showBadge={props.showInstanceBadge}
64 className="size-4"
65 iconClassName="size-4"
66 indicatorBackground="var(--popover)"
67 badgeClassName="right-[-0.125rem] bottom-[-0.125rem] h-3 min-w-3 px-0.5 text-5xs"
68 />
69 <div className="flex min-w-0 flex-1 items-center gap-2 text-left">
70 <div
71 className="min-w-0 truncate text-sm font-normal leading-snug"
72 // Names the row in full on hover only when it is cut off.
73 onPointerEnter={(event) => {
74 const name = event.currentTarget;
75 name.title = name.scrollWidth > name.clientWidth ? getDisplayModelName(model) : "";
76 }}
77 >
78 {getDisplayModelName(model)}
79 {/* The logo is decorative, so name the provider for screen readers. */}
80 <span className="sr-only">, {props.providerDisplayName}</span>
81 </div>
82 {props.isSelected ? <CheckIcon className="size-3.5 shrink-0" aria-hidden="true" /> : null}
83 {props.subProvider ? (
84 <span className="min-w-0 shrink truncate text-xs text-muted-foreground/70">
85 {props.subProvider}
86 </span>
87 ) : null}
88 {props.showNewBadge ? (
89 <span
90 className="shrink-0 rounded border border-update/35 bg-update/15 px-0.5 py-px text-3xs font-bold uppercase leading-none tracking-wide text-update-foreground"
91 aria-label="New model"
92 >
93 New
94 </span>
95 ) : null}
96 {props.unavailable ? (
97 <Badge variant="outline" size="sm">
98 Unavailable
99 </Badge>
100 ) : null}
101 </div>
102
103 <div className="flex shrink-0 items-center gap-1.5">
104 <Tooltip>
105 <TooltipTrigger
106 render={
107 <Button
108 size="icon-xs"
109 variant="ghost-muted"
110 onClick={(event) => {
111 event.stopPropagation();
112 props.onToggleFavorite(props.instanceId, props.slug);
113 }}
114 onKeyDown={(event) => {
115 event.stopPropagation();
116 }}
117 disabled={Boolean(props.disabledReason)}
118 aria-label={props.isFavorite ? "Remove from favorites" : "Add to favorites"}
119 >
120 <StarIcon
121 className={cn(
122 "size-3.5 sm:size-3",
123 props.isFavorite && "fill-current text-warning",
124 (!props.isFavorite || props.favoriteStarOnHover) &&
125 "opacity-0 group-hover:opacity-100 group-data-highlighted:opacity-100 in-focus-visible:opacity-100 pointer-coarse:opacity-100",
126 )}
127 />
128 </Button>
129 }
130 />
131 <TooltipPopup side="top" align="center">
132 {props.isFavorite ? "Remove from favorites" : "Add to favorites"}
133 </TooltipPopup>
134 </Tooltip>
135 {props.jumpLabel ? <Kbd>{props.jumpLabel}</Kbd> : null}
136 </div>
137 </ComboboxItem>
138 );
139
140 if (!props.disabledReason) {
141 return row;
142 }
143
144 return (
145 <Tooltip>
146 <TooltipTrigger render={row} />
147 <TooltipPopup side="left" align="center">
148 {props.disabledReason}
149 </TooltipPopup>
150 </Tooltip>
151 );
152});