Streamline model selector provider data

This commit is contained in:
Mohamed Boudra
2026-05-19 20:01:56 +07:00
parent bc7798af28
commit 5dd6b030f2
8 changed files with 298 additions and 364 deletions
@@ -12,8 +12,7 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
import { useIsCompactFormFactor } from "@/constants/layout";
import { isNative, isWeb as platformIsWeb } from "@/constants/platform";
import { ChevronDown, ChevronRight, Search, Star } from "lucide-react-native";
import type { AgentModelDefinition, AgentProvider } from "@server/server/agent/agent-sdk-types";
import type { AgentProviderDefinition } from "@server/server/agent/provider-manifest";
import type { AgentProvider } from "@server/server/agent/agent-sdk-types";
import type { SheetHeader } from "@/components/adaptive-modal-sheet";
const IS_WEB = platformIsWeb;
@@ -46,12 +45,9 @@ function drillDownRowStyle({
}
import { getProviderIcon } from "@/components/provider-icons";
import {
buildModelRows,
buildProviderGroups,
buildSelectedTriggerLabel,
filterAndRankModelRows,
resolveProviderLabel,
type SelectorProviderGroup,
type ModelSelectorProvider,
type SelectorModelRow,
} from "./combined-model-selector.utils";
@@ -63,13 +59,11 @@ type SelectorView =
| { kind: "provider"; providerId: string; providerLabel: string };
interface CombinedModelSelectorProps {
providerDefinitions: AgentProviderDefinition[];
allProviderModels: Map<string, AgentModelDefinition[]>;
providers: ModelSelectorProvider[];
selectedProvider: string;
selectedModel: string;
onSelect: (provider: AgentProvider, modelId: string) => void;
isLoading: boolean;
canSelectProvider?: (provider: string) => boolean;
favoriteKeys?: Set<string>;
onToggleFavorite?: (provider: string, modelId: string) => void;
renderTrigger?: (input: {
@@ -85,23 +79,21 @@ interface CombinedModelSelectorProps {
interface SelectorContentProps {
view: SelectorView;
providerDefinitions: AgentProviderDefinition[];
allProviderModels: Map<string, AgentModelDefinition[]>;
providers: ModelSelectorProvider[];
selectedProvider: string;
selectedModel: string;
searchQuery: string;
favoriteKeys: Set<string>;
onSelect: (provider: string, modelId: string) => void;
canSelectProvider: (provider: string) => boolean;
onToggleFavorite?: (provider: string, modelId: string) => void;
onDrillDown: (providerId: string, providerLabel: string) => void;
}
function resolveDefaultModelLabel(models: AgentModelDefinition[] | undefined): string {
if (!models || models.length === 0) {
function resolveDefaultModelLabel(rows: SelectorModelRow[] | undefined): string {
if (!rows || rows.length === 0) {
return "Select model";
}
return (models.find((model) => model.isDefault) ?? models[0])?.label ?? "Select model";
return (rows.find((row) => row.isDefault) ?? rows[0])?.modelLabel ?? "Select model";
}
function normalizeSearchQuery(value: string): string {
@@ -128,7 +120,6 @@ function ModelRow({
row,
isSelected,
isFavorite,
disabled = false,
elevated = false,
onPress,
onToggleFavorite,
@@ -136,7 +127,6 @@ function ModelRow({
row: SelectorModelRow;
isSelected: boolean;
isFavorite: boolean;
disabled?: boolean;
elevated?: boolean;
onPress: () => void;
onToggleFavorite?: (provider: string, modelId: string) => void;
@@ -158,7 +148,7 @@ function ModelRow({
);
const trailingSlot = useMemo(
() =>
onToggleFavorite && !disabled ? (
onToggleFavorite ? (
<Pressable
onPress={handleToggleFavorite}
hitSlop={8}
@@ -184,7 +174,6 @@ function ModelRow({
) : null,
[
onToggleFavorite,
disabled,
handleToggleFavorite,
isFavorite,
row.provider,
@@ -202,7 +191,6 @@ function ModelRow({
label={row.modelLabel}
description={showDescription ? row.description : undefined}
selected={isSelected}
disabled={disabled}
elevated={elevated}
onPress={onPress}
leadingSlot={leadingSlot}
@@ -215,7 +203,6 @@ interface SelectableModelRowProps {
row: SelectorModelRow;
isSelected: boolean;
isFavorite: boolean;
disabled?: boolean;
elevated?: boolean;
onSelect: (provider: string, modelId: string) => void;
onToggleFavorite?: (provider: string, modelId: string) => void;
@@ -225,7 +212,6 @@ function SelectableModelRow({
row,
isSelected,
isFavorite,
disabled,
elevated,
onSelect,
onToggleFavorite,
@@ -238,7 +224,6 @@ function SelectableModelRow({
row={row}
isSelected={isSelected}
isFavorite={isFavorite}
disabled={disabled}
elevated={elevated}
onPress={handlePress}
onToggleFavorite={onToggleFavorite}
@@ -252,7 +237,6 @@ function FavoritesSection({
selectedModel,
favoriteKeys,
onSelect,
canSelectProvider,
onToggleFavorite,
}: {
favoriteRows: SelectorModelRow[];
@@ -260,11 +244,8 @@ function FavoritesSection({
selectedModel: string;
favoriteKeys: Set<string>;
onSelect: (provider: string, modelId: string) => void;
canSelectProvider: (provider: string) => boolean;
onToggleFavorite?: (provider: string, modelId: string) => void;
}) {
const { theme: _theme } = useUnistyles();
if (favoriteRows.length === 0) {
return null;
}
@@ -280,7 +261,6 @@ function FavoritesSection({
row={row}
isSelected={row.provider === selectedProvider && row.modelId === selectedModel}
isFavorite={favoriteKeys.has(row.favoriteKey)}
disabled={!canSelectProvider(row.provider)}
elevated
onSelect={onSelect}
onToggleFavorite={onToggleFavorite}
@@ -295,7 +275,6 @@ interface GroupProviderButtonProps {
providerLabel: string;
rowCount: number;
hasNoModels: boolean;
disabled?: boolean;
onDrillDown: (providerId: string, providerLabel: string) => void;
onSelectDefault: (providerId: string) => void;
}
@@ -305,7 +284,6 @@ function GroupProviderButton({
providerLabel,
rowCount,
hasNoModels,
disabled,
onDrillDown,
onSelectDefault,
}: GroupProviderButtonProps) {
@@ -319,7 +297,7 @@ function GroupProviderButton({
onDrillDown(providerId, providerLabel);
}, [hasNoModels, onDrillDown, onSelectDefault, providerId, providerLabel]);
return (
<Pressable disabled={disabled} onPress={handlePress} style={drillDownRowStyle}>
<Pressable onPress={handlePress} style={drillDownRowStyle}>
<ProvIcon size={theme.iconSize.sm} color={theme.colors.foregroundMuted} />
<Text style={styles.drillDownText}>{providerLabel}</Text>
<View style={styles.drillDownTrailing}>
@@ -335,28 +313,26 @@ function GroupProviderButton({
}
function GroupedProviderRows({
groupedRows,
providers,
onDrillDown,
onSelectDefault,
canSelectProvider,
}: {
groupedRows: SelectorProviderGroup[];
providers: ModelSelectorProvider[];
onDrillDown: (providerId: string, providerLabel: string) => void;
onSelectDefault: (providerId: string) => void;
canSelectProvider: (provider: string) => boolean;
}) {
return (
<View>
{groupedRows.map((group, index) => {
{providers.map((provider, index) => {
const hasNoModels = provider.rows.length === 0;
return (
<View key={group.providerId}>
<View key={provider.id}>
{index > 0 ? <View style={styles.separator} /> : null}
<GroupProviderButton
providerId={group.providerId}
providerLabel={group.providerLabel}
rowCount={group.rows.length}
hasNoModels={group.hasNoModels}
disabled={group.hasNoModels && !canSelectProvider(group.providerId)}
providerId={provider.id}
providerLabel={provider.label}
rowCount={provider.rows.length}
hasNoModels={hasNoModels}
onDrillDown={onDrillDown}
onSelectDefault={onSelectDefault}
/>
@@ -370,12 +346,10 @@ function GroupedProviderRows({
function DefaultProviderRow({
providerId,
isSelected,
disabled,
onSelect,
}: {
providerId: string;
isSelected: boolean;
disabled?: boolean;
onSelect: (provider: string, modelId: string) => void;
}) {
const { theme } = useUnistyles();
@@ -392,7 +366,6 @@ function DefaultProviderRow({
<ComboboxItem
label="Default"
selected={isSelected}
disabled={disabled}
onPress={handlePress}
leadingSlot={leadingSlot}
/>
@@ -405,7 +378,6 @@ function ProviderModelRows({
selectedModel,
favoriteKeys,
onSelect,
canSelectProvider,
onToggleFavorite,
normalizedQuery,
}: {
@@ -414,7 +386,6 @@ function ProviderModelRows({
selectedModel: string;
favoriteKeys: Set<string>;
onSelect: (provider: string, modelId: string) => void;
canSelectProvider: (provider: string) => boolean;
onToggleFavorite?: (provider: string, modelId: string) => void;
normalizedQuery: string;
}) {
@@ -430,12 +401,11 @@ function ProviderModelRows({
row={item}
isSelected={item.provider === selectedProvider && item.modelId === selectedModel}
isFavorite={favoriteKeys.has(item.favoriteKey)}
disabled={!canSelectProvider(item.provider)}
onSelect={onSelect}
onToggleFavorite={onToggleFavorite}
/>
),
[canSelectProvider, favoriteKeys, onSelect, onToggleFavorite, selectedModel, selectedProvider],
[favoriteKeys, onSelect, onToggleFavorite, selectedModel, selectedProvider],
);
const keyExtractor = useCallback((row: SelectorModelRow) => row.favoriteKey, []);
@@ -464,45 +434,37 @@ function ProviderModelRows({
function SelectorContent({
view,
providerDefinitions,
allProviderModels,
providers,
selectedProvider,
selectedModel,
searchQuery,
favoriteKeys,
onSelect,
canSelectProvider,
onToggleFavorite,
onDrillDown,
}: SelectorContentProps) {
const { theme } = useUnistyles();
const allRows = useMemo(
() => buildModelRows(providerDefinitions, allProviderModels),
[allProviderModels, providerDefinitions],
);
const scopedRows = useMemo(() => {
if (view.kind === "provider") {
return allRows.filter((row) => row.provider === view.providerId);
}
return allRows;
}, [allRows, view]);
const normalizedQuery = useMemo(() => normalizeSearchQuery(searchQuery), [searchQuery]);
const selectedViewProvider = useMemo(
() =>
view.kind === "provider"
? providers.find((provider) => provider.id === view.providerId)
: null,
[providers, view],
);
const visibleRows = useMemo(
() => filterAndRankModelRows(scopedRows, normalizedQuery),
[normalizedQuery, scopedRows],
() =>
selectedViewProvider
? filterAndRankModelRows(selectedViewProvider.rows, normalizedQuery)
: [],
[normalizedQuery, selectedViewProvider],
);
const favoriteRows = useMemo(
() => visibleRows.filter((row) => favoriteKeys.has(row.favoriteKey)),
[favoriteKeys, visibleRows],
);
const allGroupedRows = useMemo(
() => buildProviderGroups(providerDefinitions, allProviderModels, visibleRows, normalizedQuery),
[allProviderModels, normalizedQuery, providerDefinitions, visibleRows],
() =>
providers.flatMap((provider) =>
provider.rows.filter((row) => favoriteKeys.has(row.favoriteKey)),
),
[favoriteKeys, providers],
);
const handleSelectDefaultProvider = useCallback(
(providerId: string) => {
@@ -510,7 +472,7 @@ function SelectorContent({
},
[onSelect],
);
const hasResults = favoriteRows.length > 0 || allGroupedRows.length > 0;
const hasResults = favoriteRows.length > 0 || providers.length > 0;
const emptyState = (
<View style={styles.emptyState}>
<Search size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
@@ -519,13 +481,15 @@ function SelectorContent({
);
if (view.kind === "provider") {
const providerModels = allProviderModels.get(view.providerId);
if (providerModels && providerModels.length === 0 && !normalizedQuery) {
if (!selectedViewProvider) {
return emptyState;
}
if (selectedViewProvider.rows.length === 0 && !normalizedQuery) {
return (
<DefaultProviderRow
providerId={view.providerId}
isSelected={view.providerId === selectedProvider && !selectedModel}
disabled={!canSelectProvider(view.providerId)}
onSelect={onSelect}
/>
);
@@ -542,7 +506,6 @@ function SelectorContent({
selectedModel={selectedModel}
favoriteKeys={favoriteKeys}
onSelect={onSelect}
canSelectProvider={canSelectProvider}
onToggleFavorite={onToggleFavorite}
normalizedQuery={normalizedQuery}
/>
@@ -557,16 +520,14 @@ function SelectorContent({
selectedModel={selectedModel}
favoriteKeys={favoriteKeys}
onSelect={onSelect}
canSelectProvider={canSelectProvider}
onToggleFavorite={onToggleFavorite}
/>
{allGroupedRows.length > 0 ? (
{providers.length > 0 ? (
<GroupedProviderRows
groupedRows={allGroupedRows}
providers={providers}
onDrillDown={onDrillDown}
onSelectDefault={handleSelectDefaultProvider}
canSelectProvider={canSelectProvider}
/>
) : null}
@@ -576,13 +537,11 @@ function SelectorContent({
}
export function CombinedModelSelector({
providerDefinitions,
allProviderModels,
providers,
selectedProvider,
selectedModel,
onSelect,
isLoading,
canSelectProvider = () => true,
favoriteKeys = new Set<string>(),
onToggleFavorite,
renderTrigger,
@@ -598,26 +557,26 @@ export function CombinedModelSelector({
const [searchQuery, setSearchQuery] = useState("");
const [searchResetKey, bumpSearchResetKey] = useReducer((key: number) => key + 1, 0);
// Single-provider mode: only one provider with models → skip Level 1 entirely
// Single-provider mode: only one provider → skip Level 1 entirely
const singleProviderView = useMemo<SelectorView | null>(() => {
const providers = Array.from(allProviderModels.keys());
if (providers.length !== 1) return null;
const providerId = providers[0];
const label = resolveProviderLabel(providerDefinitions, providerId);
return { kind: "provider", providerId, providerLabel: label };
}, [allProviderModels, providerDefinitions]);
const provider = providers[0];
if (!provider) return null;
return { kind: "provider", providerId: provider.id, providerLabel: provider.label };
}, [providers]);
const computeInitialView = useCallback((): SelectorView => {
if (singleProviderView) return singleProviderView;
const selectedFavoriteKey = `${selectedProvider}:${selectedModel}`;
if (selectedProvider && selectedModel && !favoriteKeys.has(selectedFavoriteKey)) {
const label = resolveProviderLabel(providerDefinitions, selectedProvider);
return { kind: "provider", providerId: selectedProvider, providerLabel: label };
const provider = providers.find((entry) => entry.id === selectedProvider);
if (provider)
return { kind: "provider", providerId: provider.id, providerLabel: provider.label };
}
return { kind: "all" };
}, [singleProviderView, selectedProvider, selectedModel, favoriteKeys, providerDefinitions]);
}, [singleProviderView, selectedProvider, selectedModel, favoriteKeys, providers]);
const handleOpenChange = useCallback(
(open: boolean) => {
@@ -652,28 +611,28 @@ export function CombinedModelSelector({
if (!hasSelectedProvider) {
return "Select model";
}
const models = allProviderModels.get(selectedProvider);
if (models && models.length === 0) {
const provider = providers.find((entry) => entry.id === selectedProvider);
if (provider?.rows.length === 0) {
return "Default";
}
return isLoading ? "Loading..." : "Select model";
}
const models = allProviderModels.get(selectedProvider);
if (!models) {
const provider = providers.find((entry) => entry.id === selectedProvider);
if (!provider) {
return isLoading ? "Loading..." : "Select model";
}
const model = models.find((entry) => entry.id === selectedModel);
return model?.label ?? resolveDefaultModelLabel(models);
}, [allProviderModels, hasSelectedProvider, isLoading, selectedModel, selectedProvider]);
const model = provider.rows.find((entry) => entry.modelId === selectedModel);
return model?.modelLabel ?? resolveDefaultModelLabel(provider.rows);
}, [hasSelectedProvider, isLoading, providers, selectedModel, selectedProvider]);
const desktopFixedHeight = useMemo(() => {
if (view.kind !== "provider") {
return undefined;
}
const models = allProviderModels.get(view.providerId);
const modelCount = models?.length ?? 0;
const modelCount =
providers.find((provider) => provider.id === view.providerId)?.rows.length ?? 0;
return Math.min(80 + modelCount * 40, 400);
}, [allProviderModels, view]);
}, [providers, view]);
const triggerLabel = useMemo(() => {
if (selectedModelLabel === "Loading..." || selectedModelLabel === "Select model") {
@@ -808,14 +767,12 @@ export function CombinedModelSelector({
{isContentReady ? (
<SelectorContent
view={view}
providerDefinitions={providerDefinitions}
allProviderModels={allProviderModels}
providers={providers}
selectedProvider={selectedProvider}
selectedModel={selectedModel}
searchQuery={searchQuery}
favoriteKeys={favoriteKeys}
onSelect={handleSelect}
canSelectProvider={canSelectProvider}
onToggleFavorite={onToggleFavorite}
onDrillDown={handleDrillDown}
/>