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
46 changes: 34 additions & 12 deletions apps/web/src/components/chat/ModelListRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export const ModelListRow = memo(function ModelListRow(props: {
acpRegistryIconUrl?: string | undefined;
isFavorite: boolean;
isSelected: boolean;
showProvider: boolean;
inlineProviderIcon: boolean;
showInstanceBadge: boolean;
showNewBadge?: boolean;
unavailable?: boolean;
jumpLabel?: string | null;
Expand All @@ -37,6 +38,27 @@ export const ModelListRow = memo(function ModelListRow(props: {
subProvider: props.subProvider,
};
const providerLabel = getProviderRowLabel(props.providerDisplayName, props.subProvider);
const providerIcon = props.inlineProviderIcon ? (
<ProviderInstanceIcon
driverKind={props.driverKind}
displayName={props.providerDisplayName}
accentColor={props.providerAccentColor}
acpRegistryAgentId={props.acpRegistryAgentId}
acpRegistryIconUrl={props.acpRegistryIconUrl}
showBadge={props.showInstanceBadge}
size="compact"
className="size-3.5"
/>
) : (
<ProviderInstanceIcon
driverKind={props.driverKind}
displayName={props.providerDisplayName}
acpRegistryAgentId={props.acpRegistryAgentId}
acpRegistryIconUrl={props.acpRegistryIconUrl}
className="size-3"
iconClassName="size-3"
/>
);

const row = (
<ComboboxItem
Expand All @@ -52,8 +74,13 @@ export const ModelListRow = memo(function ModelListRow(props: {
>
<div className="min-w-0 flex-1 text-left">
<div className="flex min-w-0 items-center gap-2">
{props.inlineProviderIcon ? <span aria-hidden="true">{providerIcon}</span> : null}
<div className="min-w-0 truncate text-sm font-normal leading-snug">
{getDisplayModelName(model)}
{/* The inline icon is decorative, so name the provider for screen readers. */}
{props.inlineProviderIcon ? (
<span className="sr-only">, {props.providerDisplayName}</span>
) : null}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>
{props.showNewBadge ? (
<span
Expand All @@ -69,21 +96,16 @@ export const ModelListRow = memo(function ModelListRow(props: {
</Badge>
) : null}
</div>
{props.showProvider && (
<div className="mt-1 flex items-center gap-1.5">
<ProviderInstanceIcon
driverKind={props.driverKind}
displayName={props.providerDisplayName}
acpRegistryAgentId={props.acpRegistryAgentId}
acpRegistryIconUrl={props.acpRegistryIconUrl}
className="size-3"
iconClassName="size-3"
/>
{props.subProvider ? (
Comment thread
coderabbitai[bot] marked this conversation as resolved.
<div
className={cn("mt-1 flex items-center gap-1.5", props.inlineProviderIcon && "pl-5.5")}
>
{props.inlineProviderIcon ? null : providerIcon}
<span className="truncate text-xs font-normal leading-snug text-muted-foreground/70">
{providerLabel}
</span>
</div>
)}
) : null}
</div>

<div className="flex shrink-0 items-center gap-1.5">
Expand Down
15 changes: 11 additions & 4 deletions apps/web/src/components/chat/ModelPickerContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { InlineButton } from "../ui/button";
import {
isProviderInstancePickerReady,
isProviderInstancePickerVisible,
shouldShowInstanceBadge,
type ProviderInstanceEntry,
} from "../../providerInstances";
import { providerModelKey, sortProviderModelItems } from "../../modelOrdering";
Expand Down Expand Up @@ -575,8 +576,8 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: {

const isLocked = lockedProvider !== null;
const isSearching = searchQuery.trim().length > 0;
// Inside one provider's tab the provider line repeats the selected rail icon on every row.
const rowsMixProviders = isSearching || selectedInstanceId === "favorites";
// Rows that mix providers mark each with an inline icon; a provider's own tab already shows it on the rail.
const inlineProviderIcon = isSearching || selectedInstanceId === "favorites";
// Fit the tallest tab so switching tabs never resizes the popup and moves the rail under the cursor.
const tallestTabUnits = (() => {
const unitsByTab = new Map<ProviderInstanceId | "favorites", number>();
Expand All @@ -592,7 +593,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: {
for (const model of flatModels) {
if (!matchesLockedProvider(model)) continue;
const isFavorite = favoritesSet.has(providerModelKey(model.instanceId, model.slug));
if (isFavorite) addRow("favorites", true);
if (isFavorite) addRow("favorites", Boolean(model.subProvider));
if (model.isLegacy && !isFavorite) {
tabsWithLegacyRow.add(model.instanceId);
} else {
Expand Down Expand Up @@ -993,6 +994,7 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: {
}
const disabledReason =
getModelDisabledReason?.(model.instanceId, model.slug) ?? null;
const entry = entryByInstanceId.get(model.instanceId);
return (
<ModelListRow
key={modelKey}
Expand All @@ -1012,7 +1014,12 @@ export const ModelPickerContent = memo(function ModelPickerContent(props: {
? selectedModelKeySet.has(modelKey)
: modelKey === activeModelKey
}
showProvider={rowsMixProviders || Boolean(model.subProvider)}
inlineProviderIcon={inlineProviderIcon}
showInstanceBadge={
inlineProviderIcon &&
entry !== undefined &&
shouldShowInstanceBadge(entry, instanceEntries)
}
showNewBadge={model.badge === "new"}
unavailable={model.isUnavailable === true}
jumpLabel={modelJumpLabelByKey.get(modelKey) ?? null}
Expand Down
Loading