capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
113 lines • 4.79 kB
JavaScript
import React, { useState, useEffect } from 'react';
import { Box, Text, useInput, useApp } from 'ink';
import Spinner from 'ink-spinner';
import chalk from 'chalk';
export const InteractiveChat = ({ onSendMessage, initialMessages = [] }) => {
const [messages, setMessages] = useState(initialMessages);
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [cursorPosition, setCursorPosition] = useState(0);
const { exit } = useApp();
useInput((input, key) => {
if (key.escape || (key.ctrl && input === 'c')) {
exit();
return;
}
if (key.return && !isLoading && input.trim()) {
handleSendMessage();
return;
}
if (key.backspace || key.delete) {
if (cursorPosition > 0) {
setInput(prev => prev.slice(0, cursorPosition - 1) + prev.slice(cursorPosition));
setCursorPosition(prev => Math.max(0, prev - 1));
}
return;
}
if (key.leftArrow) {
setCursorPosition(prev => Math.max(0, prev - 1));
return;
}
if (key.rightArrow) {
setCursorPosition(prev => Math.min(input.length, prev + 1));
return;
}
if (input && !key.ctrl && !key.meta) {
setInput(prev => prev.slice(0, cursorPosition) + input + prev.slice(cursorPosition));
setCursorPosition(prev => prev + input.length);
}
});
const handleSendMessage = async () => {
const userMessage = {
role: 'user',
content: input,
timestamp: new Date()
};
setMessages(prev => [...prev, userMessage]);
setInput('');
setCursorPosition(0);
setIsLoading(true);
try {
await onSendMessage(input);
}
catch (error) {
}
finally {
setIsLoading(false);
}
};
return (React.createElement(Box, { flexDirection: "column", height: "100%" },
React.createElement(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1 },
messages.map((msg, index) => (React.createElement(Box, { key: index, marginY: 0.5 },
React.createElement(MessageDisplay, { message: msg })))),
isLoading && (React.createElement(Box, { marginY: 0.5 },
React.createElement(Text, { color: "cyan" },
React.createElement(Spinner, { type: "dots" }),
" Thinking...")))),
React.createElement(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginX: 1, marginBottom: 1 },
React.createElement(Text, null,
React.createElement(Text, { color: "cyan" }, "\u276F "),
input.slice(0, cursorPosition),
React.createElement(Text, { backgroundColor: "cyan", color: "black" }, input[cursorPosition] || ' '),
input.slice(cursorPosition + 1))),
React.createElement(Box, { paddingX: 1, paddingBottom: 1 },
React.createElement(StatusLine, null)),
React.createElement(Box, { paddingX: 1 },
React.createElement(Text, { color: "yellow" }, "\u26A1 Model Power Level: Over 9000! \u26A1"))));
};
const MessageDisplay = ({ message }) => {
const roleColor = message.role === 'user' ? 'blue' : 'green';
const roleSymbol = message.role === 'user' ? '▶' : '◀';
return (React.createElement(Box, null,
React.createElement(Text, { color: roleColor },
roleSymbol,
" "),
React.createElement(Text, null, formatContent(message.content)),
message.cost && (React.createElement(Text, { dimColor: true },
" ($",
message.cost.toFixed(4),
")"))));
};
const StatusLine = () => {
const [time, setTime] = useState(new Date());
useEffect(() => {
const timer = setInterval(() => setTime(new Date()), 1000);
return () => clearInterval(timer);
}, []);
return (React.createElement(Box, { justifyContent: "space-between", width: "100%" },
React.createElement(Text, { dimColor: true }, process.cwd().replace(process.env.HOME || '', '~')),
React.createElement(Text, { dimColor: true },
"Capsule CLI v0.1.0 | ",
time.toLocaleTimeString())));
};
function formatContent(content) {
if (content.includes('```')) {
return content.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
return chalk.gray('```' + (lang || '')) + '\n' +
chalk.yellow(code.trim()) + '\n' +
chalk.gray('```');
});
}
return content;
}
//# sourceMappingURL=InteractiveChat.js.map