2025-04-16 12:56:08 -04:00
|
|
|
|
import TypeaheadOverlay from "./typeahead-overlay.js";
|
|
|
|
|
|
import {
|
|
|
|
|
|
getAvailableModels,
|
2025-04-20 23:59:34 -04:00
|
|
|
|
RECOMMENDED_MODELS as _RECOMMENDED_MODELS,
|
2025-04-16 12:56:08 -04:00
|
|
|
|
} from "../utils/model-utils.js";
|
2025-04-20 23:59:34 -04:00
|
|
|
|
import { providers } from "../utils/providers.js";
|
2025-04-16 12:56:08 -04:00
|
|
|
|
import { Box, Text, useInput } from "ink";
|
|
|
|
|
|
import React, { useEffect, useState } from "react";
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Props for <ModelOverlay>.
|
|
|
|
|
|
*
|
|
|
|
|
|
* 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;
|
2025-04-20 23:59:34 -04:00
|
|
|
|
currentProvider?: string;
|
2025-04-16 12:56:08 -04:00
|
|
|
|
hasLastResponse: boolean;
|
|
|
|
|
|
onSelect: (model: string) => void;
|
2025-04-20 23:59:34 -04:00
|
|
|
|
onSelectProvider?: (provider: string) => void;
|
2025-04-16 12:56:08 -04:00
|
|
|
|
onExit: () => void;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
export default function ModelOverlay({
|
|
|
|
|
|
currentModel,
|
2025-04-20 23:59:34 -04:00
|
|
|
|
currentProvider = "openai",
|
2025-04-16 12:56:08 -04:00
|
|
|
|
hasLastResponse,
|
|
|
|
|
|
onSelect,
|
2025-04-20 23:59:34 -04:00
|
|
|
|
onSelectProvider,
|
2025-04-16 12:56:08 -04:00
|
|
|
|
onExit,
|
|
|
|
|
|
}: Props): JSX.Element {
|
|
|
|
|
|
const [items, setItems] = useState<Array<{ label: string; value: string }>>(
|
|
|
|
|
|
[],
|
|
|
|
|
|
);
|
2025-04-20 23:59:34 -04:00
|
|
|
|
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<boolean>(true);
|
2025-04-16 12:56:08 -04:00
|
|
|
|
|
2025-04-20 23:59:34 -04:00
|
|
|
|
// This effect will run when the provider changes to update the model list
|
2025-04-16 12:56:08 -04:00
|
|
|
|
useEffect(() => {
|
2025-04-20 23:59:34 -04:00
|
|
|
|
setIsLoading(true);
|
2025-04-16 12:56:08 -04:00
|
|
|
|
(async () => {
|
2025-04-20 23:59:34 -04:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2025-04-16 12:56:08 -04:00
|
|
|
|
})();
|
2025-04-20 23:59:34 -04:00
|
|
|
|
}, [currentProvider]);
|
2025-04-16 12:56:08 -04:00
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
// 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).
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
|
2025-04-20 23:59:34 -04:00
|
|
|
|
// Register input handling for switching between model and provider selection
|
2025-04-16 12:56:08 -04:00
|
|
|
|
useInput((_input, key) => {
|
|
|
|
|
|
if (hasLastResponse && (key.escape || key.return)) {
|
|
|
|
|
|
onExit();
|
2025-04-20 23:59:34 -04:00
|
|
|
|
} else if (!hasLastResponse) {
|
|
|
|
|
|
if (key.tab) {
|
|
|
|
|
|
setMode(mode === "model" ? "provider" : "model");
|
|
|
|
|
|
}
|
2025-04-16 12:56:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
if (hasLastResponse) {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<Box
|
|
|
|
|
|
flexDirection="column"
|
|
|
|
|
|
borderStyle="round"
|
|
|
|
|
|
borderColor="gray"
|
|
|
|
|
|
width={80}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Box paddingX={1}>
|
|
|
|
|
|
<Text bold color="red">
|
|
|
|
|
|
Unable to switch model
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
<Box paddingX={1}>
|
|
|
|
|
|
<Text>
|
|
|
|
|
|
You can only pick a model before the assistant sends its first
|
|
|
|
|
|
response. To use a different model please start a new chat.
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
<Box paddingX={1}>
|
|
|
|
|
|
<Text dimColor>press esc or enter to close</Text>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-20 23:59:34 -04:00
|
|
|
|
if (mode === "provider") {
|
|
|
|
|
|
return (
|
|
|
|
|
|
<TypeaheadOverlay
|
|
|
|
|
|
title="Select provider"
|
|
|
|
|
|
description={
|
|
|
|
|
|
<Box flexDirection="column">
|
|
|
|
|
|
<Text>
|
|
|
|
|
|
Current provider:{" "}
|
|
|
|
|
|
<Text color="greenBright">{currentProvider}</Text>
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
<Text dimColor>press tab to switch to model selection</Text>
|
|
|
|
|
|
</Box>
|
|
|
|
|
|
}
|
|
|
|
|
|
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}
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-16 12:56:08 -04:00
|
|
|
|
return (
|
|
|
|
|
|
<TypeaheadOverlay
|
2025-04-20 23:59:34 -04:00
|
|
|
|
title="Select model"
|
2025-04-16 12:56:08 -04:00
|
|
|
|
description={
|
2025-04-20 23:59:34 -04:00
|
|
|
|
<Box flexDirection="column">
|
|
|
|
|
|
<Text>
|
|
|
|
|
|
Current model: <Text color="greenBright">{currentModel}</Text>
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
<Text>
|
|
|
|
|
|
Current provider: <Text color="greenBright">{currentProvider}</Text>
|
|
|
|
|
|
</Text>
|
|
|
|
|
|
{isLoading && <Text color="yellow">Loading models...</Text>}
|
|
|
|
|
|
<Text dimColor>press tab to switch to provider selection</Text>
|
|
|
|
|
|
</Box>
|
2025-04-16 12:56:08 -04:00
|
|
|
|
}
|
|
|
|
|
|
initialItems={items}
|
|
|
|
|
|
currentValue={currentModel}
|
|
|
|
|
|
onSelect={onSelect}
|
|
|
|
|
|
onExit={onExit}
|
|
|
|
|
|
/>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|