UNPKG

capsule-ai-cli

Version:

The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing

208 lines 9.84 kB
import React, { useState, useMemo } from 'react'; import { Box, Text, useInput } from 'ink'; import { configManager } from '../../core/config.js'; export const OrchestratorConfig = ({ currentPreferences, availableModels, onClose }) => { const [mode, setMode] = useState('menu'); const [selectedIndex, setSelectedIndex] = useState(0); const [taskName, setTaskName] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [autoMode, setAutoMode] = useState(currentPreferences.auto ?? true); const [taskModels, setTaskModels] = useState(currentPreferences.taskModels || {}); const [scrollOffset, setScrollOffset] = useState(0); const formatPrice = (price) => { const numPrice = parseFloat(price); if (numPrice === 0) return 'Free'; const perMillion = numPrice * 1000000; if (perMillion < 1) { return `$${perMillion.toFixed(3)}/1M`; } return `$${perMillion.toFixed(1)}/1M`; }; const filteredModels = useMemo(() => { if (!searchQuery) return availableModels; const query = searchQuery.toLowerCase(); return availableModels.filter(model => model.id.toLowerCase().includes(query) || model.name.toLowerCase().includes(query) || model.provider.toLowerCase().includes(query)); }, [searchQuery, availableModels]); const menuItems = [ { id: 'auto', label: `Auto mode: ${autoMode ? 'ON' : 'OFF'}`, action: 'auto-toggle' }, { id: 'add', label: 'Add task type preference', action: 'add-task' }, ...Object.entries(taskModels).map(([task, config]) => ({ id: task, label: `${task}: ${config.model}`, action: 'edit-task' })), { id: 'save', label: 'Save and exit', action: 'save' }, { id: 'cancel', label: 'Cancel', action: 'cancel' } ]; useInput((input, key) => { if (key.escape) { if (mode !== 'menu') { setMode('menu'); setSearchQuery(''); setTaskName(''); } else { onClose(); } return; } if (mode === 'menu') { if (key.upArrow) { setSelectedIndex(Math.max(0, selectedIndex - 1)); } else if (key.downArrow) { setSelectedIndex(Math.min(menuItems.length - 1, selectedIndex + 1)); } else if (key.return) { const item = menuItems[selectedIndex]; switch (item.action) { case 'auto-toggle': setAutoMode(!autoMode); break; case 'add-task': setMode('add-task'); break; case 'save': configManager.setConfig('orchestratorPreferences', { auto: autoMode, taskModels }); onClose(); break; case 'cancel': onClose(); break; } } else if (key.delete || key.backspace) { const item = menuItems[selectedIndex]; if (item.action === 'edit-task') { const newTaskModels = { ...taskModels }; delete newTaskModels[item.id]; setTaskModels(newTaskModels); } } } else if (mode === 'add-task') { if (key.return && taskName.trim()) { setMode('select-model'); setSelectedIndex(0); setScrollOffset(0); } else if (key.backspace) { setTaskName(taskName.slice(0, -1)); } else if (input && !key.ctrl && !key.meta) { setTaskName(taskName + input); } } else if (mode === 'select-model') { if (key.upArrow) { const newIndex = Math.max(0, selectedIndex - 1); setSelectedIndex(newIndex); if (newIndex < scrollOffset) { setScrollOffset(newIndex); } } else if (key.downArrow) { const newIndex = Math.min(filteredModels.length - 1, selectedIndex + 1); setSelectedIndex(newIndex); const visibleItems = 15; if (newIndex >= scrollOffset + visibleItems) { setScrollOffset(newIndex - visibleItems + 1); } } else if (key.return && filteredModels[selectedIndex]) { const model = filteredModels[selectedIndex]; setTaskModels({ ...taskModels, [taskName]: { model: model.id, provider: model.provider } }); setMode('menu'); setTaskName(''); setSearchQuery(''); setSelectedIndex(0); } else if (key.backspace) { setSearchQuery(searchQuery.slice(0, -1)); } else if (input && !key.ctrl && !key.meta) { setSearchQuery(searchQuery + input); setSelectedIndex(0); setScrollOffset(0); } } }); return (React.createElement(Box, { flexDirection: "column", padding: 1 }, React.createElement(Box, { marginBottom: 1 }, React.createElement(Text, { bold: true, color: "yellow" }, "\uD83C\uDFBC Orchestrator Preferences")), mode === 'menu' && (React.createElement(Box, { flexDirection: "column" }, React.createElement(Box, { marginBottom: 1 }, React.createElement(Text, { dimColor: true }, "Configure how the orchestrator selects models for sub-agents")), menuItems.map((item, index) => (React.createElement(Box, { key: item.id }, React.createElement(Text, { color: selectedIndex === index ? 'yellow' : 'white' }, selectedIndex === index ? '▶ ' : ' ', item.label, item.action === 'edit-task' && (React.createElement(Text, { dimColor: true }, " (delete to remove)")))))))), mode === 'add-task' && (React.createElement(Box, { flexDirection: "column" }, React.createElement(Text, null, "Enter task type name (e.g., \"coding\", \"analysis\", \"quick-edits\"):"), React.createElement(Box, { marginTop: 1 }, React.createElement(Text, null, "\u25B6 ", taskName), React.createElement(Text, { color: "gray" }, "\u2588")), React.createElement(Box, { marginTop: 1 }, React.createElement(Text, { dimColor: true }, "Press Enter to continue, Esc to cancel")))), mode === 'select-model' && (React.createElement(Box, { flexDirection: "column" }, React.createElement(Box, { marginBottom: 1 }, React.createElement(Text, null, "Select model for \"", taskName, "\" tasks:")), React.createElement(Box, { marginBottom: 1 }, React.createElement(Text, null, "Search: "), React.createElement(Text, { color: "yellow" }, searchQuery), React.createElement(Text, { color: "gray" }, "\u2588")), React.createElement(Box, { flexDirection: "column", height: 15 }, filteredModels.length === 0 ? (React.createElement(Text, { dimColor: true }, "No models found matching \"", searchQuery, "\"")) : (React.createElement(React.Fragment, null, scrollOffset > 0 && (React.createElement(Text, { dimColor: true }, "\u2191 ", scrollOffset, " more above")), filteredModels.slice(scrollOffset, scrollOffset + 13).map((model, index) => { const actualIndex = index + scrollOffset; return (React.createElement(Box, { key: model.id }, React.createElement(Text, { color: selectedIndex === actualIndex ? 'yellow' : 'white' }, selectedIndex === actualIndex ? '▶ ' : ' ', model.id), React.createElement(Text, { dimColor: true }, ' ', "(", model.context, "k context, ", formatPrice(model.pricing.prompt), " \u2192 ", formatPrice(model.pricing.completion), ")"))); }), scrollOffset + 13 < filteredModels.length && (React.createElement(Text, { dimColor: true }, "\u2193 ", filteredModels.length - scrollOffset - 13, " more below"))))), React.createElement(Box, { marginTop: 1 }, React.createElement(Text, { dimColor: true }, "Type to search, \u2191\u2193 to navigate, Enter to select, Esc to cancel")))), React.createElement(Box, { marginTop: 1 }, React.createElement(Text, { dimColor: true }, mode === 'menu' && 'Enter: select • Delete: remove preference • Esc: exit', mode !== 'menu' && 'Esc: back to menu')))); }; //# sourceMappingURL=OrchestratorConfig.js.map