giga-code
Version:
A personal AI CLI assistant powered by Grok for local development.
229 lines • 12.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importStar(require("react"));
const ink_1 = require("ink");
const api_keys_1 = require("../../utils/api-keys");
const dynamic_model_fetcher_1 = require("../../utils/dynamic-model-fetcher");
const fuzzy_search_1 = require("../../utils/fuzzy-search");
function AddModel({ providers, onClose, onAddModel }) {
const [availableProviders, setAvailableProviders] = (0, react_1.useState)([]);
const [stage, setStage] = (0, react_1.useState)('provider');
const [selectedProviderIndex, setSelectedProviderIndex] = (0, react_1.useState)(0);
const [selectedModelIndex, setSelectedModelIndex] = (0, react_1.useState)(0);
const [currentSearchQuery, setCurrentSearchQuery] = (0, react_1.useState)("");
const [filteredModels, setFilteredModels] = (0, react_1.useState)([]);
const [isLoadingModels, setIsLoadingModels] = (0, react_1.useState)(false);
const [allModels, setAllModels] = (0, react_1.useState)([]);
(0, react_1.useEffect)(() => {
initializeProviders();
}, []);
(0, react_1.useEffect)(() => {
if (stage === 'model' && availableProviders[selectedProviderIndex] && !isLoadingModels) {
updateFilteredModels();
}
}, [stage, selectedProviderIndex, currentSearchQuery, availableProviders, allModels]);
const initializeProviders = () => {
const apiKeys = (0, api_keys_1.loadApiKeys)();
const providersWithKeys = [];
for (const provider of providers) {
const keyValue = apiKeys[provider.keyName];
// Ollama always has a base URL (defaults to localhost), so it's always available
if (keyValue || provider.keyName === 'ollamaBaseUrl') {
providersWithKeys.push(provider);
}
}
setAvailableProviders(providersWithKeys);
};
const loadModelsForProvider = async (provider) => {
setIsLoadingModels(true);
setAllModels([]);
const apiKeys = (0, api_keys_1.loadApiKeys)();
const apiKey = apiKeys[provider.keyName];
const providerName = provider.name.toLowerCase();
try {
const { models } = await (0, dynamic_model_fetcher_1.fetchModelsWithFallback)(providerName, apiKey);
setAllModels(models);
}
catch (error) {
console.error(`Failed to load models for ${provider.name}:`, error);
setAllModels([]);
}
finally {
setIsLoadingModels(false);
}
};
const updateFilteredModels = () => {
if (!currentSearchQuery.trim()) {
setFilteredModels(allModels);
return;
}
const filtered = (0, fuzzy_search_1.fuzzySearch)(currentSearchQuery, allModels, (model) => model, 50 // Show up to 50 results
);
setFilteredModels(filtered);
};
(0, ink_1.useInput)((inputChar, key) => {
if (key.ctrl && inputChar === "c") {
onClose();
return;
}
if (key.escape) {
if (stage === 'model') {
// Go back to provider selection
setStage('provider');
setCurrentSearchQuery("");
setSelectedModelIndex(0);
}
else {
onClose();
}
return;
}
if (stage === 'provider') {
if (key.upArrow) {
setSelectedProviderIndex(prev => prev === 0 ? availableProviders.length - 1 : prev - 1);
return;
}
if (key.downArrow) {
setSelectedProviderIndex(prev => (prev + 1) % availableProviders.length);
return;
}
if (key.return) {
if (availableProviders.length > 0) {
const selectedProvider = availableProviders[selectedProviderIndex];
// Move to model selection stage and load models
setStage('model');
setCurrentSearchQuery("");
setSelectedModelIndex(0);
loadModelsForProvider(selectedProvider);
}
return;
}
}
else if (stage === 'model') {
if (key.upArrow) {
setSelectedModelIndex(prev => prev === 0 ? Math.max(0, filteredModels.length - 1) : prev - 1);
return;
}
if (key.downArrow) {
setSelectedModelIndex(prev => filteredModels.length === 0 ? 0 : (prev + 1) % Math.min(10, filteredModels.length));
return;
}
if (key.return) {
if (filteredModels.length > 0 && selectedModelIndex < filteredModels.length) {
const selectedModel = filteredModels[selectedModelIndex];
const selectedProvider = availableProviders[selectedProviderIndex];
onAddModel(selectedProvider.name, selectedModel);
}
return;
}
if (key.backspace || key.delete) {
setCurrentSearchQuery(prev => prev.slice(0, -1));
setSelectedModelIndex(0);
return;
}
if (inputChar && !key.ctrl && !key.meta) {
setCurrentSearchQuery(prev => prev + inputChar);
setSelectedModelIndex(0);
return;
}
}
});
if (availableProviders.length === 0) {
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", paddingX: 2, paddingY: 1 },
react_1.default.createElement(ink_1.Text, { color: "red" }, "\u274C No API keys found"),
react_1.default.createElement(ink_1.Text, { color: "gray" }, "Configure API keys in /providers first"),
react_1.default.createElement(ink_1.Box, { marginTop: 1 },
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true }, "Press Esc to close"))));
}
if (stage === 'provider') {
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", paddingX: 2, paddingY: 1 },
react_1.default.createElement(ink_1.Text, { color: "yellow" }, "\uD83D\uDD0D Select Provider"),
react_1.default.createElement(ink_1.Box, { marginBottom: 1 },
react_1.default.createElement(ink_1.Text, { color: "gray" }, "Choose a provider to browse models from:")),
react_1.default.createElement(ink_1.Box, { flexDirection: "column", marginBottom: 1 }, availableProviders.map((provider, index) => {
const isSelected = index === selectedProviderIndex;
return (react_1.default.createElement(ink_1.Box, { key: provider.keyName, borderStyle: "round", borderColor: isSelected ? "blue" : "gray", paddingX: 1, marginBottom: 1 },
react_1.default.createElement(ink_1.Box, { width: 12 },
react_1.default.createElement(ink_1.Text, { color: isSelected ? "blue" : "white" }, provider.name)),
react_1.default.createElement(ink_1.Box, { flexGrow: 1 },
react_1.default.createElement(ink_1.Text, { color: "green" }, "\u2713 API key configured")),
react_1.default.createElement(ink_1.Box, { width: 25 },
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true }, provider.description))));
})),
react_1.default.createElement(ink_1.Box, { flexDirection: "column", marginTop: 1 },
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true }, "\u2022 Use \u2191/\u2193 arrows to navigate"),
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true }, "\u2022 Press Enter to select provider"),
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true }, "\u2022 Press Esc to cancel"))));
}
// Model selection stage
const selectedProvider = availableProviders[selectedProviderIndex];
if (isLoadingModels) {
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", paddingX: 2, paddingY: 1 },
react_1.default.createElement(ink_1.Text, { color: "yellow" },
"\uD83D\uDD0D Add Model from ",
selectedProvider.name),
react_1.default.createElement(ink_1.Box, { marginTop: 2 },
react_1.default.createElement(ink_1.Text, { color: "blue" },
"\uD83D\uDD04 Loading models from ",
selectedProvider.name,
"...")),
react_1.default.createElement(ink_1.Box, { marginTop: 1 },
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true }, "Press Esc to go back"))));
}
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", paddingX: 2, paddingY: 1 },
react_1.default.createElement(ink_1.Text, { color: "yellow" },
"\uD83D\uDD0D Add Model from ",
selectedProvider.name),
react_1.default.createElement(ink_1.Box, { marginBottom: 1 },
react_1.default.createElement(ink_1.Text, { color: "gray" }, "Search: "),
react_1.default.createElement(ink_1.Text, null,
currentSearchQuery || "",
"\u2588")),
react_1.default.createElement(ink_1.Box, { flexDirection: "column", marginBottom: 1 },
filteredModels.length === 0 ? (react_1.default.createElement(ink_1.Box, { borderStyle: "round", borderColor: "gray", paddingX: 1 },
react_1.default.createElement(ink_1.Text, { color: "gray" }, allModels.length === 0
? "No models available"
: `No models found matching "${currentSearchQuery}"`))) : (filteredModels.slice(0, 10).map((model, index) => {
const isSelected = index === selectedModelIndex;
return (react_1.default.createElement(ink_1.Box, { key: model, borderStyle: "round", borderColor: isSelected ? "blue" : "gray", paddingX: 1, marginBottom: 1 },
react_1.default.createElement(ink_1.Text, { color: isSelected ? "blue" : "white" }, model)));
})),
filteredModels.length > 10 && (react_1.default.createElement(ink_1.Box, { paddingX: 1 },
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true },
"... and ",
filteredModels.length - 10,
" more")))),
react_1.default.createElement(ink_1.Box, { flexDirection: "column", marginTop: 1 },
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true },
"\u2022 Type to search models (",
allModels.length,
" total)"),
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true }, "\u2022 Use \u2191/\u2193 arrows to navigate"),
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true }, "\u2022 Press Enter to add selected model"),
react_1.default.createElement(ink_1.Text, { color: "gray", dimColor: true }, "\u2022 Press Esc to go back"))));
}
exports.default = AddModel;
//# sourceMappingURL=add-model.js.map