capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
208 lines • 9.84 kB
JavaScript
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