Show model selector provider errors
This commit is contained in:
@@ -23,7 +23,6 @@ import type { AgentProvider } from "@getpaseo/protocol/agent-types";
|
|||||||
import type { SheetHeader } from "@/components/adaptive-modal-sheet";
|
import type { SheetHeader } from "@/components/adaptive-modal-sheet";
|
||||||
import { useProviderSettingsStore } from "@/stores/provider-settings-store";
|
import { useProviderSettingsStore } from "@/stores/provider-settings-store";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useProvidersSnapshot } from "@/hooks/use-providers-snapshot";
|
|
||||||
const IS_WEB = platformIsWeb;
|
const IS_WEB = platformIsWeb;
|
||||||
|
|
||||||
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
import { Combobox, ComboboxItem, type ComboboxOption } from "@/components/ui/combobox";
|
||||||
@@ -66,6 +65,10 @@ import {
|
|||||||
|
|
||||||
// TODO: this should be configured per provider in the provider manifest
|
// TODO: this should be configured per provider in the provider manifest
|
||||||
const PROVIDERS_WITH_MODEL_DESCRIPTIONS = new Set(["opencode", "pi"]);
|
const PROVIDERS_WITH_MODEL_DESCRIPTIONS = new Set(["opencode", "pi"]);
|
||||||
|
const DESKTOP_PROVIDER_VIEW_MIN_HEIGHT = 220;
|
||||||
|
const DESKTOP_PROVIDER_VIEW_MAX_HEIGHT = 400;
|
||||||
|
const DESKTOP_PROVIDER_VIEW_BASE_HEIGHT = 80;
|
||||||
|
const DESKTOP_MODEL_ROW_HEIGHT = 40;
|
||||||
|
|
||||||
type SelectorView =
|
type SelectorView =
|
||||||
| { kind: "all" }
|
| { kind: "all" }
|
||||||
@@ -87,6 +90,8 @@ interface CombinedModelSelectorProps {
|
|||||||
}) => React.ReactNode;
|
}) => React.ReactNode;
|
||||||
onOpen?: () => void;
|
onOpen?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
|
onRetryProvider?: (provider: AgentProvider) => void;
|
||||||
|
isRetryingProvider?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
serverId?: string | null;
|
serverId?: string | null;
|
||||||
}
|
}
|
||||||
@@ -101,7 +106,8 @@ interface SelectorContentProps {
|
|||||||
onSelect: (provider: string, modelId: string) => void;
|
onSelect: (provider: string, modelId: string) => void;
|
||||||
onToggleFavorite?: (provider: string, modelId: string) => void;
|
onToggleFavorite?: (provider: string, modelId: string) => void;
|
||||||
onDrillDown: (providerId: string, providerLabel: string) => void;
|
onDrillDown: (providerId: string, providerLabel: string) => void;
|
||||||
serverId: string | null;
|
onRetryProvider?: (provider: AgentProvider) => void;
|
||||||
|
isRetryingProvider: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeSearchQuery(value: string): string {
|
function normalizeSearchQuery(value: string): string {
|
||||||
@@ -418,26 +424,29 @@ function ProviderModelRows({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ProviderErrorEmptyState({
|
function ProviderErrorEmptyState({
|
||||||
serverId,
|
|
||||||
providerId,
|
providerId,
|
||||||
message,
|
message,
|
||||||
|
onRetryProvider,
|
||||||
|
isRetryingProvider,
|
||||||
}: {
|
}: {
|
||||||
serverId: string | null;
|
|
||||||
providerId: string;
|
providerId: string;
|
||||||
message: string;
|
message: string;
|
||||||
|
onRetryProvider?: (provider: AgentProvider) => void;
|
||||||
|
isRetryingProvider: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const { refresh, isRefreshing } = useProvidersSnapshot(serverId);
|
|
||||||
const handleRetry = useCallback(() => {
|
const handleRetry = useCallback(() => {
|
||||||
void refresh([providerId]);
|
onRetryProvider?.(providerId);
|
||||||
}, [refresh, providerId]);
|
}, [onRetryProvider, providerId]);
|
||||||
return (
|
return (
|
||||||
<View style={styles.emptyState}>
|
<View style={styles.emptyState}>
|
||||||
<AlertTriangle size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
<AlertTriangle size={theme.iconSize.md} color={theme.colors.foregroundMuted} />
|
||||||
<Text style={styles.emptyStateText}>{message}</Text>
|
<Text style={styles.emptyStateText}>{message}</Text>
|
||||||
<Button variant="default" size="sm" onPress={handleRetry} disabled={isRefreshing}>
|
{onRetryProvider ? (
|
||||||
{isRefreshing ? "Retrying…" : "Retry"}
|
<Button variant="default" size="sm" onPress={handleRetry} disabled={isRetryingProvider}>
|
||||||
</Button>
|
{isRetryingProvider ? "Retrying…" : "Retry"}
|
||||||
|
</Button>
|
||||||
|
) : null}
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -452,7 +461,8 @@ function SelectorContent({
|
|||||||
onSelect,
|
onSelect,
|
||||||
onToggleFavorite,
|
onToggleFavorite,
|
||||||
onDrillDown,
|
onDrillDown,
|
||||||
serverId,
|
onRetryProvider,
|
||||||
|
isRetryingProvider,
|
||||||
}: SelectorContentProps) {
|
}: SelectorContentProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const normalizedQuery = useMemo(() => normalizeSearchQuery(searchQuery), [searchQuery]);
|
const normalizedQuery = useMemo(() => normalizeSearchQuery(searchQuery), [searchQuery]);
|
||||||
@@ -502,9 +512,10 @@ function SelectorContent({
|
|||||||
if (drillSelection.kind === "error") {
|
if (drillSelection.kind === "error") {
|
||||||
return (
|
return (
|
||||||
<ProviderErrorEmptyState
|
<ProviderErrorEmptyState
|
||||||
serverId={serverId}
|
|
||||||
providerId={view.providerId}
|
providerId={view.providerId}
|
||||||
message={drillSelection.message}
|
message={drillSelection.message}
|
||||||
|
onRetryProvider={onRetryProvider}
|
||||||
|
isRetryingProvider={isRetryingProvider}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -556,6 +567,8 @@ export function CombinedModelSelector({
|
|||||||
renderTrigger,
|
renderTrigger,
|
||||||
onOpen,
|
onOpen,
|
||||||
onClose,
|
onClose,
|
||||||
|
onRetryProvider,
|
||||||
|
isRetryingProvider = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
serverId = null,
|
serverId = null,
|
||||||
}: CombinedModelSelectorProps) {
|
}: CombinedModelSelectorProps) {
|
||||||
@@ -630,8 +643,17 @@ export function CombinedModelSelector({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
const provider = providers.find((entry) => entry.id === view.providerId);
|
const provider = providers.find((entry) => entry.id === view.providerId);
|
||||||
const modelCount = provider ? getProviderModelRows(provider).length : 0;
|
if (!provider || provider.modelSelection.kind !== "models") {
|
||||||
return Math.min(80 + modelCount * 40, 400);
|
return DESKTOP_PROVIDER_VIEW_MIN_HEIGHT;
|
||||||
|
}
|
||||||
|
const modelCount = getProviderModelRows(provider).length;
|
||||||
|
return Math.min(
|
||||||
|
Math.max(
|
||||||
|
DESKTOP_PROVIDER_VIEW_MIN_HEIGHT,
|
||||||
|
DESKTOP_PROVIDER_VIEW_BASE_HEIGHT + modelCount * DESKTOP_MODEL_ROW_HEIGHT,
|
||||||
|
),
|
||||||
|
DESKTOP_PROVIDER_VIEW_MAX_HEIGHT,
|
||||||
|
);
|
||||||
}, [providers, view]);
|
}, [providers, view]);
|
||||||
|
|
||||||
const triggerLabel = useMemo(() => {
|
const triggerLabel = useMemo(() => {
|
||||||
@@ -804,7 +826,8 @@ export function CombinedModelSelector({
|
|||||||
onSelect={handleSelect}
|
onSelect={handleSelect}
|
||||||
onToggleFavorite={onToggleFavorite}
|
onToggleFavorite={onToggleFavorite}
|
||||||
onDrillDown={handleDrillDown}
|
onDrillDown={handleDrillDown}
|
||||||
serverId={serverId}
|
onRetryProvider={onRetryProvider}
|
||||||
|
isRetryingProvider={isRetryingProvider}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<View style={styles.sheetLoadingState}>
|
<View style={styles.sheetLoadingState}>
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { getProviderIcon } from "@/components/provider-icons";
|
|||||||
import { CombinedModelSelector } from "@/components/combined-model-selector";
|
import { CombinedModelSelector } from "@/components/combined-model-selector";
|
||||||
import {
|
import {
|
||||||
buildProviderSelectorProviders,
|
buildProviderSelectorProviders,
|
||||||
|
buildSelectableProviderSelectorProviders,
|
||||||
type ProviderSelectorProvider,
|
type ProviderSelectorProvider,
|
||||||
} from "@/provider-selection/provider-selection";
|
} from "@/provider-selection/provider-selection";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
@@ -91,6 +92,8 @@ interface ControlledAgentControlsProps {
|
|||||||
onSetFeature?: (featureId: string, value: unknown) => void;
|
onSetFeature?: (featureId: string, value: unknown) => void;
|
||||||
onDropdownClose?: () => void;
|
onDropdownClose?: () => void;
|
||||||
onModelSelectorOpen?: () => void;
|
onModelSelectorOpen?: () => void;
|
||||||
|
onRetryModelProvider?: (provider: AgentProvider) => void;
|
||||||
|
isRetryingModelProvider?: boolean;
|
||||||
/** Extra elements rendered inline with the agent controls (desktop only). */
|
/** Extra elements rendered inline with the agent controls (desktop only). */
|
||||||
desktopExtras?: ReactNode;
|
desktopExtras?: ReactNode;
|
||||||
modelSelectorServerId?: string | null;
|
modelSelectorServerId?: string | null;
|
||||||
@@ -117,6 +120,8 @@ export interface DraftAgentControlsProps {
|
|||||||
onSetFeature?: (featureId: string, value: unknown) => void;
|
onSetFeature?: (featureId: string, value: unknown) => void;
|
||||||
onDropdownClose?: () => void;
|
onDropdownClose?: () => void;
|
||||||
onModelSelectorOpen?: () => void;
|
onModelSelectorOpen?: () => void;
|
||||||
|
onRetryModelProvider?: (provider: AgentProvider) => void;
|
||||||
|
isRetryingModelProvider?: boolean;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
modelSelectorServerId?: string | null;
|
modelSelectorServerId?: string | null;
|
||||||
}
|
}
|
||||||
@@ -175,17 +180,6 @@ function getFeatureIconColor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveDisplayModel(
|
|
||||||
isModelLoading: boolean,
|
|
||||||
modelOptions: AgentControlOption[] | undefined,
|
|
||||||
selectedModelId: string | undefined,
|
|
||||||
) {
|
|
||||||
if (isModelLoading && (!modelOptions || modelOptions.length === 0)) {
|
|
||||||
return "Loading models...";
|
|
||||||
}
|
|
||||||
return findOptionLabel(modelOptions, selectedModelId, "Select model");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mobile agent controls only — strip namespace prefix so providers like OpenCode
|
// Mobile agent controls only — strip namespace prefix so providers like OpenCode
|
||||||
// show "gpt-5.5" instead of "openrouter/gpt-5.5". Full label still appears in
|
// show "gpt-5.5" instead of "openrouter/gpt-5.5". Full label still appears in
|
||||||
// the model picker.
|
// the model picker.
|
||||||
@@ -411,6 +405,8 @@ function ControlledAgentControls({
|
|||||||
onSetFeature,
|
onSetFeature,
|
||||||
onDropdownClose,
|
onDropdownClose,
|
||||||
onModelSelectorOpen,
|
onModelSelectorOpen,
|
||||||
|
onRetryModelProvider,
|
||||||
|
isRetryingModelProvider = false,
|
||||||
desktopExtras,
|
desktopExtras,
|
||||||
modelSelectorServerId = null,
|
modelSelectorServerId = null,
|
||||||
}: ControlledAgentControlsProps) {
|
}: ControlledAgentControlsProps) {
|
||||||
@@ -432,7 +428,6 @@ function ControlledAgentControls({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const displayProvider = findOptionLabel(providerOptions, selectedProviderId, "Provider");
|
const displayProvider = findOptionLabel(providerOptions, selectedProviderId, "Provider");
|
||||||
const displayModel = resolveDisplayModel(isModelLoading, modelOptions, selectedModelId);
|
|
||||||
const formattedThinkingOptions = useMemo(
|
const formattedThinkingOptions = useMemo(
|
||||||
() => toThinkingControlOptions(thinkingOptions),
|
() => toThinkingControlOptions(thinkingOptions),
|
||||||
[thinkingOptions],
|
[thinkingOptions],
|
||||||
@@ -588,6 +583,8 @@ function ControlledAgentControls({
|
|||||||
onToggleFavoriteModel={onToggleFavoriteModel}
|
onToggleFavoriteModel={onToggleFavoriteModel}
|
||||||
onDropdownClose={onDropdownClose}
|
onDropdownClose={onDropdownClose}
|
||||||
onModelSelectorOpen={onModelSelectorOpen}
|
onModelSelectorOpen={onModelSelectorOpen}
|
||||||
|
onRetryModelProvider={onRetryModelProvider}
|
||||||
|
isRetryingModelProvider={isRetryingModelProvider}
|
||||||
favoriteKeys={favoriteKeys}
|
favoriteKeys={favoriteKeys}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
isModelLoading={isModelLoading}
|
isModelLoading={isModelLoading}
|
||||||
@@ -599,7 +596,6 @@ function ControlledAgentControls({
|
|||||||
comboboxProviderOptions={comboboxProviderOptions}
|
comboboxProviderOptions={comboboxProviderOptions}
|
||||||
comboboxThinkingOptions={comboboxThinkingOptions}
|
comboboxThinkingOptions={comboboxThinkingOptions}
|
||||||
displayProvider={displayProvider}
|
displayProvider={displayProvider}
|
||||||
displayModel={displayModel}
|
|
||||||
displayThinking={displayThinking}
|
displayThinking={displayThinking}
|
||||||
openSelector={openSelector}
|
openSelector={openSelector}
|
||||||
providerAnchorRef={providerAnchorRef}
|
providerAnchorRef={providerAnchorRef}
|
||||||
@@ -628,6 +624,8 @@ function ControlledAgentControls({
|
|||||||
onToggleFavoriteModel={onToggleFavoriteModel}
|
onToggleFavoriteModel={onToggleFavoriteModel}
|
||||||
onDropdownClose={onDropdownClose}
|
onDropdownClose={onDropdownClose}
|
||||||
onModelSelectorOpen={onModelSelectorOpen}
|
onModelSelectorOpen={onModelSelectorOpen}
|
||||||
|
onRetryModelProvider={onRetryModelProvider}
|
||||||
|
isRetryingModelProvider={isRetryingModelProvider}
|
||||||
favoriteKeys={favoriteKeys}
|
favoriteKeys={favoriteKeys}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
isModelLoading={isModelLoading}
|
isModelLoading={isModelLoading}
|
||||||
@@ -665,6 +663,8 @@ interface DesktopAgentControlsContentProps {
|
|||||||
onToggleFavoriteModel?: (provider: string, modelId: string) => void;
|
onToggleFavoriteModel?: (provider: string, modelId: string) => void;
|
||||||
onDropdownClose?: () => void;
|
onDropdownClose?: () => void;
|
||||||
onModelSelectorOpen?: () => void;
|
onModelSelectorOpen?: () => void;
|
||||||
|
onRetryModelProvider?: (provider: AgentProvider) => void;
|
||||||
|
isRetryingModelProvider: boolean;
|
||||||
favoriteKeys: Set<string>;
|
favoriteKeys: Set<string>;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
isModelLoading: boolean;
|
isModelLoading: boolean;
|
||||||
@@ -676,7 +676,6 @@ interface DesktopAgentControlsContentProps {
|
|||||||
comboboxProviderOptions: ComboboxOption[];
|
comboboxProviderOptions: ComboboxOption[];
|
||||||
comboboxThinkingOptions: ComboboxOption[];
|
comboboxThinkingOptions: ComboboxOption[];
|
||||||
displayProvider: string;
|
displayProvider: string;
|
||||||
displayModel: string;
|
|
||||||
displayThinking: string;
|
displayThinking: string;
|
||||||
openSelector: AgentControlSelector | null;
|
openSelector: AgentControlSelector | null;
|
||||||
providerAnchorRef: RefObject<View | null>;
|
providerAnchorRef: RefObject<View | null>;
|
||||||
@@ -717,6 +716,8 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
|
|||||||
onToggleFavoriteModel,
|
onToggleFavoriteModel,
|
||||||
onDropdownClose,
|
onDropdownClose,
|
||||||
onModelSelectorOpen,
|
onModelSelectorOpen,
|
||||||
|
onRetryModelProvider,
|
||||||
|
isRetryingModelProvider,
|
||||||
favoriteKeys,
|
favoriteKeys,
|
||||||
disabled,
|
disabled,
|
||||||
isModelLoading,
|
isModelLoading,
|
||||||
@@ -728,7 +729,6 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
|
|||||||
comboboxProviderOptions,
|
comboboxProviderOptions,
|
||||||
comboboxThinkingOptions,
|
comboboxThinkingOptions,
|
||||||
displayProvider,
|
displayProvider,
|
||||||
displayModel,
|
|
||||||
displayThinking,
|
displayThinking,
|
||||||
openSelector,
|
openSelector,
|
||||||
providerAnchorRef,
|
providerAnchorRef,
|
||||||
@@ -779,12 +779,7 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
|
|||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{canSelectModel ? (
|
{canSelectModel ? (
|
||||||
<Tooltip
|
<Tooltip delayDuration={0} enabledOnDesktop enabledOnMobile={false}>
|
||||||
key={`model-${displayModel}`}
|
|
||||||
delayDuration={0}
|
|
||||||
enabledOnDesktop
|
|
||||||
enabledOnMobile={false}
|
|
||||||
>
|
|
||||||
<TooltipTrigger asChild triggerRefProp="ref">
|
<TooltipTrigger asChild triggerRefProp="ref">
|
||||||
<View>
|
<View>
|
||||||
<CombinedModelSelector
|
<CombinedModelSelector
|
||||||
@@ -798,6 +793,8 @@ function DesktopAgentControlsContent(props: DesktopAgentControlsContentProps) {
|
|||||||
disabled={modelDisabled}
|
disabled={modelDisabled}
|
||||||
onOpen={onModelSelectorOpen}
|
onOpen={onModelSelectorOpen}
|
||||||
onClose={onDropdownClose}
|
onClose={onDropdownClose}
|
||||||
|
onRetryProvider={onRetryModelProvider}
|
||||||
|
isRetryingProvider={isRetryingModelProvider}
|
||||||
serverId={modelSelectorServerId}
|
serverId={modelSelectorServerId}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -870,6 +867,8 @@ interface SheetAgentControlsContentProps {
|
|||||||
onToggleFavoriteModel?: (provider: string, modelId: string) => void;
|
onToggleFavoriteModel?: (provider: string, modelId: string) => void;
|
||||||
onDropdownClose?: () => void;
|
onDropdownClose?: () => void;
|
||||||
onModelSelectorOpen?: () => void;
|
onModelSelectorOpen?: () => void;
|
||||||
|
onRetryModelProvider?: (provider: AgentProvider) => void;
|
||||||
|
isRetryingModelProvider: boolean;
|
||||||
favoriteKeys: Set<string>;
|
favoriteKeys: Set<string>;
|
||||||
disabled: boolean;
|
disabled: boolean;
|
||||||
isModelLoading: boolean;
|
isModelLoading: boolean;
|
||||||
@@ -906,6 +905,8 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
|
|||||||
onToggleFavoriteModel,
|
onToggleFavoriteModel,
|
||||||
onDropdownClose,
|
onDropdownClose,
|
||||||
onModelSelectorOpen,
|
onModelSelectorOpen,
|
||||||
|
onRetryModelProvider,
|
||||||
|
isRetryingModelProvider,
|
||||||
favoriteKeys,
|
favoriteKeys,
|
||||||
disabled,
|
disabled,
|
||||||
isModelLoading,
|
isModelLoading,
|
||||||
@@ -992,6 +993,8 @@ function SheetAgentControlsContent(props: SheetAgentControlsContentProps) {
|
|||||||
disabled={modelDisabled}
|
disabled={modelDisabled}
|
||||||
onOpen={onModelSelectorOpen}
|
onOpen={onModelSelectorOpen}
|
||||||
onClose={onDropdownClose}
|
onClose={onDropdownClose}
|
||||||
|
onRetryProvider={onRetryModelProvider}
|
||||||
|
isRetryingProvider={isRetryingModelProvider}
|
||||||
renderTrigger={renderModelTrigger}
|
renderTrigger={renderModelTrigger}
|
||||||
serverId={modelSelectorServerId}
|
serverId={modelSelectorServerId}
|
||||||
/>
|
/>
|
||||||
@@ -1353,6 +1356,8 @@ export const AgentControls = memo(function AgentControls({
|
|||||||
const {
|
const {
|
||||||
entries: snapshotEntries,
|
entries: snapshotEntries,
|
||||||
isLoading: snapshotIsLoading,
|
isLoading: snapshotIsLoading,
|
||||||
|
isRefreshing: snapshotIsRefreshing,
|
||||||
|
refresh: refreshSnapshot,
|
||||||
refetchIfStale: refetchSnapshotIfStale,
|
refetchIfStale: refetchSnapshotIfStale,
|
||||||
} = useProvidersSnapshot(serverId, { cwd: agent?.cwd });
|
} = useProvidersSnapshot(serverId, { cwd: agent?.cwd });
|
||||||
|
|
||||||
@@ -1373,14 +1378,15 @@ export const AgentControls = memo(function AgentControls({
|
|||||||
() => buildAgentProviderModels(agent?.provider, models),
|
() => buildAgentProviderModels(agent?.provider, models),
|
||||||
[agent?.provider, models],
|
[agent?.provider, models],
|
||||||
);
|
);
|
||||||
const agentModelSelectorProviders = useMemo(
|
const agentModelSelectorProviders = useMemo(() => {
|
||||||
() =>
|
if (snapshotSelectedEntry) {
|
||||||
buildProviderSelectorProviders({
|
return buildSelectableProviderSelectorProviders([snapshotSelectedEntry]);
|
||||||
providerDefinitions: agentProviderDefinitions,
|
}
|
||||||
modelsByProvider: agentProviderModels,
|
return buildProviderSelectorProviders({
|
||||||
}),
|
providerDefinitions: agentProviderDefinitions,
|
||||||
[agentProviderDefinitions, agentProviderModels],
|
modelsByProvider: agentProviderModels,
|
||||||
);
|
});
|
||||||
|
}, [agentProviderDefinitions, agentProviderModels, snapshotSelectedEntry]);
|
||||||
|
|
||||||
const modelSelection = resolveAgentModelSelection({
|
const modelSelection = resolveAgentModelSelection({
|
||||||
models,
|
models,
|
||||||
@@ -1504,6 +1510,13 @@ export const AgentControls = memo(function AgentControls({
|
|||||||
refetchSnapshotIfStale(agentProvider);
|
refetchSnapshotIfStale(agentProvider);
|
||||||
}, [agentProvider, refetchSnapshotIfStale]);
|
}, [agentProvider, refetchSnapshotIfStale]);
|
||||||
|
|
||||||
|
const handleRetryModelProvider = useCallback(
|
||||||
|
(provider: AgentProvider) => {
|
||||||
|
void refreshSnapshot([provider]);
|
||||||
|
},
|
||||||
|
[refreshSnapshot],
|
||||||
|
);
|
||||||
|
|
||||||
const modeChip = useMemo(
|
const modeChip = useMemo(
|
||||||
() => <AgentModeControl serverId={serverId} agentId={agentId} placement="toolbar" />,
|
() => <AgentModeControl serverId={serverId} agentId={agentId} placement="toolbar" />,
|
||||||
[serverId, agentId],
|
[serverId, agentId],
|
||||||
@@ -1529,6 +1542,8 @@ export const AgentControls = memo(function AgentControls({
|
|||||||
onSetFeature={handleSetFeature}
|
onSetFeature={handleSetFeature}
|
||||||
isModelLoading={snapshotIsLoading || selectedProviderIsLoading}
|
isModelLoading={snapshotIsLoading || selectedProviderIsLoading}
|
||||||
onModelSelectorOpen={handleModelSelectorOpen}
|
onModelSelectorOpen={handleModelSelectorOpen}
|
||||||
|
onRetryModelProvider={handleRetryModelProvider}
|
||||||
|
isRetryingModelProvider={snapshotIsRefreshing}
|
||||||
onDropdownClose={onDropdownClose}
|
onDropdownClose={onDropdownClose}
|
||||||
disabled={!client}
|
disabled={!client}
|
||||||
desktopExtras={modeChip}
|
desktopExtras={modeChip}
|
||||||
@@ -1558,6 +1573,8 @@ export function DraftAgentControls({
|
|||||||
onSetFeature,
|
onSetFeature,
|
||||||
onDropdownClose,
|
onDropdownClose,
|
||||||
onModelSelectorOpen,
|
onModelSelectorOpen,
|
||||||
|
onRetryModelProvider,
|
||||||
|
isRetryingModelProvider = false,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
modelSelectorServerId = null,
|
modelSelectorServerId = null,
|
||||||
}: DraftAgentControlsProps) {
|
}: DraftAgentControlsProps) {
|
||||||
@@ -1627,6 +1644,8 @@ export function DraftAgentControls({
|
|||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
onOpen={onModelSelectorOpen}
|
onOpen={onModelSelectorOpen}
|
||||||
onClose={onDropdownClose}
|
onClose={onDropdownClose}
|
||||||
|
onRetryProvider={onRetryModelProvider}
|
||||||
|
isRetryingProvider={isRetryingModelProvider}
|
||||||
serverId={modelSelectorServerId}
|
serverId={modelSelectorServerId}
|
||||||
/>
|
/>
|
||||||
{selectedProvider ? (
|
{selectedProvider ? (
|
||||||
@@ -1638,6 +1657,8 @@ export function DraftAgentControls({
|
|||||||
features={features}
|
features={features}
|
||||||
onSetFeature={onSetFeature}
|
onSetFeature={onSetFeature}
|
||||||
onDropdownClose={onDropdownClose}
|
onDropdownClose={onDropdownClose}
|
||||||
|
onRetryModelProvider={onRetryModelProvider}
|
||||||
|
isRetryingModelProvider={isRetryingModelProvider}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
desktopExtras={draftModeChip}
|
desktopExtras={draftModeChip}
|
||||||
/>
|
/>
|
||||||
@@ -1663,6 +1684,8 @@ export function DraftAgentControls({
|
|||||||
features={features}
|
features={features}
|
||||||
onSetFeature={onSetFeature}
|
onSetFeature={onSetFeature}
|
||||||
onModelSelectorOpen={onModelSelectorOpen}
|
onModelSelectorOpen={onModelSelectorOpen}
|
||||||
|
onRetryModelProvider={onRetryModelProvider}
|
||||||
|
isRetryingModelProvider={isRetryingModelProvider}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
modelSelectorServerId={modelSelectorServerId}
|
modelSelectorServerId={modelSelectorServerId}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ export function buildDraftAgentControls(input: {
|
|||||||
onSetFeature,
|
onSetFeature,
|
||||||
onDropdownClose,
|
onDropdownClose,
|
||||||
onModelSelectorOpen: formState.refetchProviderModelsIfStale,
|
onModelSelectorOpen: formState.refetchProviderModelsIfStale,
|
||||||
|
onRetryModelProvider: formState.refreshProviderModels,
|
||||||
|
isRetryingModelProvider: formState.isProviderModelsRefreshing,
|
||||||
modelSelectorServerId: formState.selectedServerId,
|
modelSelectorServerId: formState.selectedServerId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ vi.mock("@/hooks/use-agent-form-state", () => ({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
isAllModelsLoading: false,
|
isAllModelsLoading: false,
|
||||||
|
isProviderModelsRefreshing: false,
|
||||||
availableThinkingOptions: [
|
availableThinkingOptions: [
|
||||||
{ id: "medium", label: "Medium" },
|
{ id: "medium", label: "Medium" },
|
||||||
{ id: "high", label: "High", isDefault: true },
|
{ id: "high", label: "High", isDefault: true },
|
||||||
|
|||||||
@@ -69,10 +69,11 @@ export interface UseAgentFormStateResult {
|
|||||||
allProviderModels: Map<string, AgentModelDefinition[]>;
|
allProviderModels: Map<string, AgentModelDefinition[]>;
|
||||||
modelSelectorProviders: ProviderSelectorProvider[];
|
modelSelectorProviders: ProviderSelectorProvider[];
|
||||||
isAllModelsLoading: boolean;
|
isAllModelsLoading: boolean;
|
||||||
|
isProviderModelsRefreshing: boolean;
|
||||||
availableThinkingOptions: NonNullable<AgentModelDefinition["thinkingOptions"]>;
|
availableThinkingOptions: NonNullable<AgentModelDefinition["thinkingOptions"]>;
|
||||||
isModelLoading: boolean;
|
isModelLoading: boolean;
|
||||||
modelError: string | null;
|
modelError: string | null;
|
||||||
refreshProviderModels: () => void;
|
refreshProviderModels: (provider?: AgentProvider) => void;
|
||||||
refetchProviderModelsIfStale: () => void;
|
refetchProviderModelsIfStale: () => void;
|
||||||
setProviderAndModelFromUser: (provider: AgentProvider, modelId: string) => void;
|
setProviderAndModelFromUser: (provider: AgentProvider, modelId: string) => void;
|
||||||
workingDirIsEmpty: boolean;
|
workingDirIsEmpty: boolean;
|
||||||
@@ -208,6 +209,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
|||||||
const {
|
const {
|
||||||
entries: snapshotEntries,
|
entries: snapshotEntries,
|
||||||
isLoading: snapshotIsLoading,
|
isLoading: snapshotIsLoading,
|
||||||
|
isRefreshing: snapshotIsRefreshing,
|
||||||
error: snapshotError,
|
error: snapshotError,
|
||||||
refresh: refreshSnapshot,
|
refresh: refreshSnapshot,
|
||||||
refetchIfStale: refetchSnapshotIfStale,
|
refetchIfStale: refetchSnapshotIfStale,
|
||||||
@@ -475,9 +477,12 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
|||||||
dispatch({ type: "SET_SERVER_ID", value });
|
dispatch({ type: "SET_SERVER_ID", value });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const refreshProviderModels = useCallback(() => {
|
const refreshProviderModels = useCallback(
|
||||||
void refreshSnapshot();
|
(provider?: AgentProvider) => {
|
||||||
}, [refreshSnapshot]);
|
void refreshSnapshot(provider ? [provider] : undefined);
|
||||||
|
},
|
||||||
|
[refreshSnapshot],
|
||||||
|
);
|
||||||
|
|
||||||
const refetchProviderModelsIfStale = useCallback(() => {
|
const refetchProviderModelsIfStale = useCallback(() => {
|
||||||
refetchSnapshotIfStale(reducerStateRef.current.form.provider);
|
refetchSnapshotIfStale(reducerStateRef.current.form.provider);
|
||||||
@@ -535,6 +540,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
|||||||
allProviderModels,
|
allProviderModels,
|
||||||
modelSelectorProviders,
|
modelSelectorProviders,
|
||||||
isAllModelsLoading,
|
isAllModelsLoading,
|
||||||
|
isProviderModelsRefreshing: snapshotIsRefreshing,
|
||||||
availableThinkingOptions,
|
availableThinkingOptions,
|
||||||
isModelLoading,
|
isModelLoading,
|
||||||
modelError,
|
modelError,
|
||||||
@@ -568,6 +574,7 @@ export function useAgentFormState(options: UseAgentFormStateOptions = {}): UseAg
|
|||||||
allProviderModels,
|
allProviderModels,
|
||||||
modelSelectorProviders,
|
modelSelectorProviders,
|
||||||
isAllModelsLoading,
|
isAllModelsLoading,
|
||||||
|
snapshotIsRefreshing,
|
||||||
availableThinkingOptions,
|
availableThinkingOptions,
|
||||||
isModelLoading,
|
isModelLoading,
|
||||||
modelError,
|
modelError,
|
||||||
|
|||||||
@@ -259,6 +259,27 @@ describe("combined model selector data", () => {
|
|||||||
).toBe("Default");
|
).toBe("Default");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("keeps provider snapshot errors visible in the selected trigger label", () => {
|
||||||
|
const providers = buildSelectableProviderSelectorProviders([
|
||||||
|
snapshotEntry({
|
||||||
|
provider: "opencode",
|
||||||
|
label: "OpenCode",
|
||||||
|
status: "error",
|
||||||
|
error: "OpenCode app.agents timed out after 10s",
|
||||||
|
models: [],
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resolveSelectedModelLabel({
|
||||||
|
providers,
|
||||||
|
selectedProvider: "opencode",
|
||||||
|
selectedModel: "",
|
||||||
|
isLoading: false,
|
||||||
|
}),
|
||||||
|
).toBe("Error");
|
||||||
|
});
|
||||||
|
|
||||||
it("returns observable submission readiness reasons", () => {
|
it("returns observable submission readiness reasons", () => {
|
||||||
expect(
|
expect(
|
||||||
resolveSubmissionReadiness({
|
resolveSubmissionReadiness({
|
||||||
|
|||||||
@@ -162,6 +162,9 @@ export function resolveSelectedModelLabel(input: {
|
|||||||
if (provider.modelSelection.kind === "loading") {
|
if (provider.modelSelection.kind === "loading") {
|
||||||
return "Loading...";
|
return "Loading...";
|
||||||
}
|
}
|
||||||
|
if (provider.modelSelection.kind === "error") {
|
||||||
|
return "Error";
|
||||||
|
}
|
||||||
if (provider.modelSelection.kind !== "models") {
|
if (provider.modelSelection.kind !== "models") {
|
||||||
return "Select model";
|
return "Select model";
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user