import TypeaheadOverlay from "./typeahead-overlay.js"; import { getAvailableModels, RECOMMENDED_MODELS as _RECOMMENDED_MODELS, } from "../utils/model-utils.js"; import { providers } from "../utils/providers.js"; import { Box, Text, useInput } from "ink"; import React, { useEffect, useState } from "react"; /** * Props for . * * When `hasLastResponse` is true the user has already received at least one * assistant response in the current session which means switching models is no * longer supported – the overlay should therefore show an error and only allow * the user to close it. */ type Props = { currentModel: string; currentProvider?: string; hasLastResponse: boolean; onSelect: (model: string) => void; onSelectProvider?: (provider: string) => void; onExit: () => void; }; export default function ModelOverlay({ currentModel, currentProvider = "openai", hasLastResponse, onSelect, onSelectProvider, onExit, }: Props): JSX.Element { const [items, setItems] = useState>( [], ); const [providerItems, _setProviderItems] = useState< Array<{ label: string; value: string }> >(Object.values(providers).map((p) => ({ label: p.name, value: p.name }))); const [mode, setMode] = useState<"model" | "provider">("model"); const [isLoading, setIsLoading] = useState(true); // This effect will run when the provider changes to update the model list useEffect(() => { setIsLoading(true); (async () => { try { const models = await getAvailableModels(currentProvider); // Convert the models to the format needed by TypeaheadOverlay setItems( models.map((m) => ({ label: m, value: m, })), ); } catch (error) { // Silently handle errors - remove console.error // console.error("Error loading models:", error); } finally { setIsLoading(false); } })(); }, [currentProvider]); // --------------------------------------------------------------------------- // If the conversation already contains a response we cannot change the model // anymore because the backend requires a consistent model across the entire // run. In that scenario we replace the regular typeahead picker with a // simple message instructing the user to start a new chat. The only // available action is to dismiss the overlay (Esc or Enter). // --------------------------------------------------------------------------- // Register input handling for switching between model and provider selection useInput((_input, key) => { if (hasLastResponse && (key.escape || key.return)) { onExit(); } else if (!hasLastResponse) { if (key.tab) { setMode(mode === "model" ? "provider" : "model"); } } }); if (hasLastResponse) { return ( Unable to switch model You can only pick a model before the assistant sends its first response. To use a different model please start a new chat. press esc or enter to close ); } if (mode === "provider") { return ( Current provider:{" "} {currentProvider} press tab to switch to model selection } initialItems={providerItems} currentValue={currentProvider} onSelect={(provider) => { if (onSelectProvider) { onSelectProvider(provider); // Immediately switch to model selection so user can pick a model for the new provider setMode("model"); } }} onExit={onExit} /> ); } return ( Current model: {currentModel} Current provider: {currentProvider} {isLoading && Loading models...} press tab to switch to provider selection } initialItems={items} currentValue={currentModel} onSelect={onSelect} onExit={onExit} /> ); }