Simplify mobile agent configuration (#3424)
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getCompactSheetSafeAreaPadding } from "@/components/adaptive-modal-sheet-layout";
|
||||
import {
|
||||
getBottomSheetVisibleContentHeight,
|
||||
getCompactSheetSafeAreaPadding,
|
||||
} from "@/components/adaptive-modal-sheet-layout";
|
||||
|
||||
describe("getCompactSheetSafeAreaPadding", () => {
|
||||
it("adds the bottom inset to compact sheet footers", () => {
|
||||
expect(
|
||||
getCompactSheetSafeAreaPadding({
|
||||
isCompact: true,
|
||||
isKeyboardVisible: false,
|
||||
hasFooter: true,
|
||||
baseContentPadding: 24,
|
||||
baseFooterPadding: 12,
|
||||
@@ -18,6 +22,7 @@ describe("getCompactSheetSafeAreaPadding", () => {
|
||||
expect(
|
||||
getCompactSheetSafeAreaPadding({
|
||||
isCompact: true,
|
||||
isKeyboardVisible: false,
|
||||
hasFooter: false,
|
||||
baseContentPadding: 24,
|
||||
baseFooterPadding: 12,
|
||||
@@ -26,10 +31,24 @@ describe("getCompactSheetSafeAreaPadding", () => {
|
||||
).toEqual({ contentPaddingBottom: 58 });
|
||||
});
|
||||
|
||||
it("does not add a safe-area band above the compact keyboard", () => {
|
||||
expect(
|
||||
getCompactSheetSafeAreaPadding({
|
||||
isCompact: true,
|
||||
isKeyboardVisible: true,
|
||||
hasFooter: false,
|
||||
baseContentPadding: 24,
|
||||
baseFooterPadding: 12,
|
||||
safeAreaBottom: 34,
|
||||
}),
|
||||
).toEqual({});
|
||||
});
|
||||
|
||||
it("does not inset desktop sheets", () => {
|
||||
expect(
|
||||
getCompactSheetSafeAreaPadding({
|
||||
isCompact: false,
|
||||
isKeyboardVisible: false,
|
||||
hasFooter: false,
|
||||
baseContentPadding: 24,
|
||||
baseFooterPadding: 12,
|
||||
@@ -38,3 +57,17 @@ describe("getCompactSheetSafeAreaPadding", () => {
|
||||
).toEqual({});
|
||||
});
|
||||
});
|
||||
|
||||
describe("getBottomSheetVisibleContentHeight", () => {
|
||||
it("stops subtracting the retained keyboard height after the keyboard hides", () => {
|
||||
const layout = {
|
||||
containerHeight: 874,
|
||||
contentPosition: 88,
|
||||
handleHeight: 24,
|
||||
keyboardHeight: 344,
|
||||
};
|
||||
|
||||
expect(getBottomSheetVisibleContentHeight({ ...layout, isKeyboardVisible: true })).toBe(418);
|
||||
expect(getBottomSheetVisibleContentHeight({ ...layout, isKeyboardVisible: false })).toBe(762);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export interface CompactSheetSafeAreaPaddingInput {
|
||||
isCompact: boolean;
|
||||
isKeyboardVisible: boolean;
|
||||
hasFooter: boolean;
|
||||
baseContentPadding: number;
|
||||
baseFooterPadding: number;
|
||||
@@ -11,14 +12,37 @@ export interface CompactSheetSafeAreaPadding {
|
||||
footerPaddingBottom?: number;
|
||||
}
|
||||
|
||||
interface BottomSheetVisibleContentHeightInput {
|
||||
containerHeight: number;
|
||||
contentPosition: number;
|
||||
handleHeight: number;
|
||||
keyboardHeight: number;
|
||||
isKeyboardVisible: boolean;
|
||||
}
|
||||
|
||||
export function getBottomSheetVisibleContentHeight({
|
||||
containerHeight,
|
||||
contentPosition,
|
||||
handleHeight,
|
||||
keyboardHeight,
|
||||
isKeyboardVisible,
|
||||
}: BottomSheetVisibleContentHeightInput): number {
|
||||
"worklet";
|
||||
return Math.max(
|
||||
0,
|
||||
containerHeight - contentPosition - handleHeight - (isKeyboardVisible ? keyboardHeight : 0),
|
||||
);
|
||||
}
|
||||
|
||||
export function getCompactSheetSafeAreaPadding({
|
||||
isCompact,
|
||||
isKeyboardVisible,
|
||||
hasFooter,
|
||||
baseContentPadding,
|
||||
baseFooterPadding,
|
||||
safeAreaBottom,
|
||||
}: CompactSheetSafeAreaPaddingInput): CompactSheetSafeAreaPadding {
|
||||
if (!isCompact || safeAreaBottom <= 0) {
|
||||
if (!isCompact || isKeyboardVisible || safeAreaBottom <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
BottomSheetBackdrop,
|
||||
BottomSheetScrollView,
|
||||
BottomSheetTextInput,
|
||||
KEYBOARD_STATUS,
|
||||
useBottomSheetInternal,
|
||||
type BottomSheetBackgroundProps,
|
||||
} from "@gorhom/bottom-sheet";
|
||||
@@ -25,9 +26,13 @@ import {
|
||||
IsolatedBottomSheetModal,
|
||||
useIsolatedBottomSheetVisibility,
|
||||
} from "@/components/ui/isolated-bottom-sheet-modal";
|
||||
import { getCompactSheetSafeAreaPadding } from "@/components/adaptive-modal-sheet-layout";
|
||||
import {
|
||||
getBottomSheetVisibleContentHeight,
|
||||
getCompactSheetSafeAreaPadding,
|
||||
} from "@/components/adaptive-modal-sheet-layout";
|
||||
import { createControlGeometry } from "@/components/ui/control-geometry";
|
||||
import { isNative, isWeb } from "@/constants/platform";
|
||||
import { useKeyboardVisibility } from "@/hooks/use-keyboard-visibility";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
|
||||
// Horizontal indent token shared by the sheet header (title, back arrow,
|
||||
@@ -38,6 +43,8 @@ export const SHEET_HORIZONTAL_PADDING_SCALE = 6;
|
||||
|
||||
export interface SheetHeaderSearch {
|
||||
onChange: (value: string) => void;
|
||||
onFocus?: () => void;
|
||||
onBlur?: () => void;
|
||||
resetKey?: string | number;
|
||||
placeholder?: string;
|
||||
autoFocus?: boolean;
|
||||
@@ -262,14 +269,15 @@ function BottomSheetVisibleContent({ children }: { children: ReactNode }) {
|
||||
? animatedPosition.get()
|
||||
: Math.min(animatedPosition.get(), initialDetentPosition);
|
||||
|
||||
const keyboardState = animatedKeyboardState.get();
|
||||
return {
|
||||
height: Math.max(
|
||||
0,
|
||||
containerHeight -
|
||||
contentPosition -
|
||||
handleHeight -
|
||||
animatedKeyboardState.get().heightWithinContainer,
|
||||
),
|
||||
height: getBottomSheetVisibleContentHeight({
|
||||
containerHeight,
|
||||
contentPosition,
|
||||
handleHeight,
|
||||
keyboardHeight: keyboardState.heightWithinContainer,
|
||||
isKeyboardVisible: keyboardState.status === KEYBOARD_STATUS.SHOWN,
|
||||
}),
|
||||
};
|
||||
}, [animatedDetentsState, animatedKeyboardState, animatedLayoutState, animatedPosition]);
|
||||
|
||||
@@ -409,6 +417,8 @@ export function SheetHeaderView({
|
||||
placeholder={search.placeholder ?? t("common.actions.search")}
|
||||
resetKey={search.resetKey}
|
||||
onChangeText={handleSearchChange}
|
||||
onFocus={search.onFocus}
|
||||
onBlur={search.onBlur}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
autoFocus={search.autoFocus}
|
||||
@@ -466,6 +476,8 @@ export function InlineHeaderView({ header }: { header: SheetHeader }) {
|
||||
placeholder={header.search.placeholder ?? t("common.actions.search")}
|
||||
resetKey={header.search.resetKey}
|
||||
onChangeText={header.search.onChange}
|
||||
onFocus={header.search.onFocus}
|
||||
onBlur={header.search.onBlur}
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
autoFocus={header.search.autoFocus}
|
||||
@@ -518,17 +530,19 @@ export function AdaptiveModalSheet({
|
||||
const { t } = useTranslation();
|
||||
const isMobile = useIsCompactFormFactor();
|
||||
const insets = useSafeAreaInsets();
|
||||
const isKeyboardVisible = useKeyboardVisibility();
|
||||
const resolvedSnapPoints = useMemo(() => snapPoints ?? ["65%", "90%"], [snapPoints]);
|
||||
const compactSafeAreaPadding = useMemo(
|
||||
() =>
|
||||
getCompactSheetSafeAreaPadding({
|
||||
isCompact: isMobile,
|
||||
isKeyboardVisible,
|
||||
hasFooter: Boolean(footer),
|
||||
baseContentPadding: theme.spacing[SHEET_HORIZONTAL_PADDING_SCALE],
|
||||
baseFooterPadding: theme.spacing[3],
|
||||
safeAreaBottom: insets.bottom,
|
||||
}),
|
||||
[footer, insets.bottom, isMobile, theme.spacing],
|
||||
[footer, insets.bottom, isKeyboardVisible, isMobile, theme.spacing],
|
||||
);
|
||||
const compactContentStyle = useMemo(
|
||||
() => [
|
||||
|
||||
@@ -105,13 +105,35 @@ describe("model browser all view", () => {
|
||||
const providers = [claude, copilot, codex];
|
||||
|
||||
it("browses providers while the query is empty", () => {
|
||||
expect(resolveModelBrowserAllView({ providers, normalizedQuery: "" })).toEqual({
|
||||
expect(
|
||||
resolveModelBrowserAllView({ providers, normalizedQuery: "", isSearchFocused: false }),
|
||||
).toEqual({
|
||||
kind: "browse",
|
||||
});
|
||||
});
|
||||
|
||||
it("shows every searchable model as soon as empty search receives focus", () => {
|
||||
const view = resolveModelBrowserAllView({
|
||||
providers,
|
||||
normalizedQuery: "",
|
||||
isSearchFocused: true,
|
||||
});
|
||||
|
||||
expect(view.kind).toBe("searchResults");
|
||||
expect(view.kind === "searchResults" ? view.rows.map((row) => row.favoriteKey) : []).toEqual([
|
||||
"claude:opus-5",
|
||||
"claude:sonnet-4.6",
|
||||
"copilot:claude-opus-5",
|
||||
"codex:gpt-5.4",
|
||||
]);
|
||||
});
|
||||
|
||||
it("ranks the same model label across every provider that offers it", () => {
|
||||
const view = resolveModelBrowserAllView({ providers, normalizedQuery: "opus" });
|
||||
const view = resolveModelBrowserAllView({
|
||||
providers,
|
||||
normalizedQuery: "opus",
|
||||
isSearchFocused: true,
|
||||
});
|
||||
|
||||
expect(view.kind).toBe("searchResults");
|
||||
expect(view.kind === "searchResults" ? view.rows.map((row) => row.favoriteKey) : []).toEqual([
|
||||
@@ -121,7 +143,11 @@ describe("model browser all view", () => {
|
||||
});
|
||||
|
||||
it("matches models by their provider label", () => {
|
||||
const view = resolveModelBrowserAllView({ providers, normalizedQuery: "codex" });
|
||||
const view = resolveModelBrowserAllView({
|
||||
providers,
|
||||
normalizedQuery: "codex",
|
||||
isSearchFocused: true,
|
||||
});
|
||||
|
||||
expect(view.kind === "searchResults" ? view.rows.map((row) => row.modelId) : []).toEqual([
|
||||
"gpt-5.4",
|
||||
@@ -129,9 +155,13 @@ describe("model browser all view", () => {
|
||||
});
|
||||
|
||||
it("reports no matches instead of falling back to the provider list", () => {
|
||||
expect(resolveModelBrowserAllView({ providers, normalizedQuery: "zzzz" })).toEqual({
|
||||
kind: "noSearchMatches",
|
||||
});
|
||||
expect(
|
||||
resolveModelBrowserAllView({
|
||||
providers,
|
||||
normalizedQuery: "zzzz",
|
||||
isSearchFocused: true,
|
||||
}),
|
||||
).toEqual({ kind: "noSearchMatches" });
|
||||
});
|
||||
|
||||
it("ignores providers that are still loading or errored", () => {
|
||||
@@ -147,7 +177,11 @@ describe("model browser all view", () => {
|
||||
};
|
||||
|
||||
expect(
|
||||
resolveModelBrowserAllView({ providers: [loading, failed], normalizedQuery: "opus" }),
|
||||
resolveModelBrowserAllView({
|
||||
providers: [loading, failed],
|
||||
normalizedQuery: "opus",
|
||||
isSearchFocused: true,
|
||||
}),
|
||||
).toEqual({ kind: "noSearchMatches" });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -18,14 +18,17 @@ export type ModelBrowserAllView =
|
||||
export function resolveModelBrowserAllView({
|
||||
providers,
|
||||
normalizedQuery,
|
||||
isSearchFocused,
|
||||
}: {
|
||||
providers: ProviderSelectorProvider[];
|
||||
normalizedQuery: string;
|
||||
isSearchFocused: boolean;
|
||||
}): ModelBrowserAllView {
|
||||
if (!normalizedQuery) {
|
||||
if (!normalizedQuery && !isSearchFocused) {
|
||||
return { kind: "browse" };
|
||||
}
|
||||
const rows = filterAndRankModelRows(getAllProviderModelRows(providers), normalizedQuery);
|
||||
const allRows = getAllProviderModelRows(providers);
|
||||
const rows = normalizedQuery ? filterAndRankModelRows(allRows, normalizedQuery) : allRows;
|
||||
if (rows.length === 0) {
|
||||
return { kind: "noSearchMatches" };
|
||||
}
|
||||
|
||||
@@ -145,6 +145,7 @@ interface ModelBrowserInput {
|
||||
selectedProvider: string;
|
||||
selectedModel: string;
|
||||
isLoading: boolean;
|
||||
autoFocusSearch?: boolean;
|
||||
/** Pinned above the provider list on the root view. `null` hides the section. */
|
||||
profiles?: AgentProfilePicker | null;
|
||||
serverId?: string | null;
|
||||
@@ -157,12 +158,14 @@ export interface ModelBrowserState {
|
||||
profiles: AgentProfilePicker | null;
|
||||
view: ModelBrowserView;
|
||||
searchQuery: string;
|
||||
isSearchFocused: boolean;
|
||||
header: SheetHeader;
|
||||
selectedModelLabel: string;
|
||||
triggerLabel: string;
|
||||
desktopFixedHeight: number | undefined;
|
||||
isProviderView: boolean;
|
||||
prepareToOpen: () => void;
|
||||
showAll: () => void;
|
||||
reset: () => void;
|
||||
drillDown: (providerId: string, providerLabel: string) => void;
|
||||
}
|
||||
@@ -176,6 +179,10 @@ interface ModelBrowserProps {
|
||||
onRetryProvider?: (provider: AgentProvider) => void;
|
||||
isRetryingProvider?: boolean;
|
||||
scrolling?: "sheet" | "independent";
|
||||
/** Empty focused search shows all model rows instead of the browse root. */
|
||||
searchAllOnFocus?: boolean;
|
||||
/** Replaces provider rows only while the all-provider search is empty. */
|
||||
rootBrowseContent?: React.ReactNode;
|
||||
}
|
||||
|
||||
interface ModelBrowserContentProps extends Omit<ModelBrowserProps, "state" | "scrolling"> {
|
||||
@@ -184,9 +191,12 @@ interface ModelBrowserContentProps extends Omit<ModelBrowserProps, "state" | "sc
|
||||
selectedProvider: string;
|
||||
selectedModel: string;
|
||||
searchQuery: string;
|
||||
isSearchFocused: boolean;
|
||||
profiles: AgentProfilePicker | null;
|
||||
onDrillDown: (providerId: string, providerLabel: string) => void;
|
||||
scrolling: "sheet" | "independent";
|
||||
searchAllOnFocus: boolean;
|
||||
rootBrowseContent?: React.ReactNode;
|
||||
}
|
||||
|
||||
type ProviderGlyphTone = "muted" | "foreground";
|
||||
@@ -246,12 +256,14 @@ export function useModelBrowser({
|
||||
selectedProvider,
|
||||
selectedModel,
|
||||
isLoading,
|
||||
autoFocusSearch = isWeb,
|
||||
profiles = null,
|
||||
serverId = null,
|
||||
}: ModelBrowserInput): ModelBrowserState {
|
||||
const { t } = useTranslation();
|
||||
const [view, setView] = useState<ModelBrowserView>({ kind: "all" });
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [isSearchFocused, setIsSearchFocused] = useState(false);
|
||||
const [searchResetKey, bumpSearchResetKey] = useReducer((key: number) => key + 1, 0);
|
||||
const hasProfiles = (profiles?.rows.length ?? 0) > 0;
|
||||
|
||||
@@ -272,10 +284,11 @@ export function useModelBrowser({
|
||||
|
||||
const reset = useCallback(() => {
|
||||
setSearchQuery("");
|
||||
setIsSearchFocused(false);
|
||||
bumpSearchResetKey();
|
||||
}, []);
|
||||
|
||||
const handleBackToAll = useCallback(() => {
|
||||
const showAll = useCallback(() => {
|
||||
setView({ kind: "all" });
|
||||
reset();
|
||||
}, [reset]);
|
||||
@@ -299,9 +312,11 @@ export function useModelBrowser({
|
||||
title: t("modelSelector.title"),
|
||||
search: {
|
||||
onChange: handleSearchQueryChange,
|
||||
onFocus: () => setIsSearchFocused(true),
|
||||
onBlur: () => setIsSearchFocused(false),
|
||||
resetKey: `all:${searchResetKey}`,
|
||||
placeholder: t("modelSelector.searchAllPlaceholder"),
|
||||
autoFocus: isWeb,
|
||||
autoFocus: autoFocusSearch,
|
||||
testID: "model-search-all-input",
|
||||
},
|
||||
};
|
||||
@@ -311,7 +326,7 @@ export function useModelBrowser({
|
||||
leading: (
|
||||
<ModelProviderGlyph provider={view.providerId} size={ICON_SIZE.md} tone="foreground" />
|
||||
),
|
||||
back: singleProviderView ? undefined : { onPress: handleBackToAll },
|
||||
back: singleProviderView ? undefined : { onPress: showAll },
|
||||
actions: (
|
||||
<View style={styles.headerActionRow}>
|
||||
<ProviderSettingsAction
|
||||
@@ -325,18 +340,21 @@ export function useModelBrowser({
|
||||
),
|
||||
search: {
|
||||
onChange: handleSearchQueryChange,
|
||||
onFocus: () => setIsSearchFocused(true),
|
||||
onBlur: () => setIsSearchFocused(false),
|
||||
resetKey: `${view.providerId}:${searchResetKey}`,
|
||||
placeholder: t("modelSelector.searchPlaceholder"),
|
||||
autoFocus: isWeb,
|
||||
autoFocus: autoFocusSearch,
|
||||
testID: "model-search-input",
|
||||
},
|
||||
};
|
||||
}, [
|
||||
handleBackToAll,
|
||||
autoFocusSearch,
|
||||
handleSearchQueryChange,
|
||||
searchResetKey,
|
||||
serverId,
|
||||
singleProviderView,
|
||||
showAll,
|
||||
t,
|
||||
view,
|
||||
]);
|
||||
@@ -371,12 +389,14 @@ export function useModelBrowser({
|
||||
profiles,
|
||||
view,
|
||||
searchQuery,
|
||||
isSearchFocused,
|
||||
header,
|
||||
selectedModelLabel,
|
||||
triggerLabel,
|
||||
desktopFixedHeight,
|
||||
isProviderView: view.kind === "provider",
|
||||
prepareToOpen,
|
||||
showAll,
|
||||
reset,
|
||||
drillDown,
|
||||
};
|
||||
@@ -856,6 +876,7 @@ function IndependentModelList({
|
||||
keyExtractor={getModelRowKey}
|
||||
style={styles.virtualizedModelList}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="on-drag"
|
||||
showsVerticalScrollIndicator={false}
|
||||
contentContainerStyle={styles.virtualizedModelListContent}
|
||||
nestedScrollEnabled
|
||||
@@ -879,6 +900,7 @@ function IndependentProviderList({ children }: { children: React.ReactNode }) {
|
||||
styles.virtualizedProviderListContent,
|
||||
]}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
keyboardDismissMode="on-drag"
|
||||
showsVerticalScrollIndicator={false}
|
||||
nestedScrollEnabled
|
||||
testID="compact-provider-list"
|
||||
@@ -1078,6 +1100,7 @@ function ModelBrowserContent({
|
||||
selectedProvider,
|
||||
selectedModel,
|
||||
searchQuery,
|
||||
isSearchFocused,
|
||||
profiles,
|
||||
onSelect,
|
||||
onApplyProfile,
|
||||
@@ -1086,6 +1109,8 @@ function ModelBrowserContent({
|
||||
onRetryProvider,
|
||||
isRetryingProvider = false,
|
||||
scrolling,
|
||||
searchAllOnFocus,
|
||||
rootBrowseContent,
|
||||
}: ModelBrowserContentProps) {
|
||||
const { t } = useTranslation();
|
||||
const normalizedQuery = useMemo(() => normalizeSearchQuery(searchQuery), [searchQuery]);
|
||||
@@ -1097,10 +1122,15 @@ function ModelBrowserContent({
|
||||
[providers, view],
|
||||
);
|
||||
const allView = useMemo(
|
||||
() => resolveModelBrowserAllView({ providers, normalizedQuery }),
|
||||
[normalizedQuery, providers],
|
||||
() =>
|
||||
resolveModelBrowserAllView({
|
||||
providers,
|
||||
normalizedQuery,
|
||||
isSearchFocused: searchAllOnFocus && isSearchFocused,
|
||||
}),
|
||||
[isSearchFocused, normalizedQuery, providers, searchAllOnFocus],
|
||||
);
|
||||
const hasResults = profiles !== null || providers.length > 0;
|
||||
const hasResults = profiles !== null || providers.length > 0 || rootBrowseContent != null;
|
||||
|
||||
if (view.kind === "provider") {
|
||||
return (
|
||||
@@ -1154,16 +1184,17 @@ function ModelBrowserContent({
|
||||
onEditProfiles={onEditProfiles}
|
||||
/>
|
||||
) : null}
|
||||
{providers.length > 0 ? (
|
||||
<View>
|
||||
{profiles ? (
|
||||
<View style={styles.sectionHeading}>
|
||||
<Text style={styles.sectionHeadingText}>{t("modelSelector.providers")}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<GroupedProviderRows providers={providers} onDrillDown={onDrillDown} />
|
||||
</View>
|
||||
) : null}
|
||||
{rootBrowseContent ??
|
||||
(providers.length > 0 ? (
|
||||
<View>
|
||||
{profiles ? (
|
||||
<View style={styles.sectionHeading}>
|
||||
<Text style={styles.sectionHeadingText}>{t("modelSelector.providers")}</Text>
|
||||
</View>
|
||||
) : null}
|
||||
<GroupedProviderRows providers={providers} onDrillDown={onDrillDown} />
|
||||
</View>
|
||||
) : null)}
|
||||
{!hasResults ? <ModelSearchEmptyState /> : null}
|
||||
</View>
|
||||
);
|
||||
@@ -1183,6 +1214,8 @@ export function ModelBrowser({
|
||||
onRetryProvider,
|
||||
isRetryingProvider = false,
|
||||
scrolling = "sheet",
|
||||
searchAllOnFocus = false,
|
||||
rootBrowseContent,
|
||||
}: ModelBrowserProps) {
|
||||
return (
|
||||
<ModelBrowserContent
|
||||
@@ -1191,6 +1224,7 @@ export function ModelBrowser({
|
||||
selectedProvider={state.selectedProvider}
|
||||
selectedModel={state.selectedModel}
|
||||
searchQuery={state.searchQuery}
|
||||
isSearchFocused={state.isSearchFocused}
|
||||
profiles={state.profiles}
|
||||
onSelect={onSelect}
|
||||
onApplyProfile={onApplyProfile}
|
||||
@@ -1199,6 +1233,8 @@ export function ModelBrowser({
|
||||
onRetryProvider={onRetryProvider}
|
||||
isRetryingProvider={isRetryingProvider}
|
||||
scrolling={scrolling}
|
||||
searchAllOnFocus={searchAllOnFocus}
|
||||
rootBrowseContent={rootBrowseContent}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user