capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
139 lines • 6.46 kB
JavaScript
import React, { useState, useEffect, useRef } from 'react';
import { Box, Text, useInput } from 'ink';
import chalk from 'chalk';
import { getProviderColor } from '../utils/provider-colors.js';
export const InputBox = ({ value: controlledValue, onSubmit, onChange, onPasteStart, onPasteEnd, provider = 'openai' }) => {
const [internalValue, setInternalValue] = useState('');
const [cursorPosition, setCursorPosition] = useState(0);
const [showCursor, setShowCursor] = useState(true);
const [pasteBuffer, setPasteBuffer] = useState('');
const [lastInputTime, setLastInputTime] = useState(0);
const pasteTimeoutRef = useRef(null);
const pastedContentRef = useRef({});
const value = controlledValue !== undefined ? controlledValue : internalValue;
const providerColor = getProviderColor(provider);
useEffect(() => {
const timer = setInterval(() => {
setShowCursor(prev => !prev);
}, 500);
return () => clearInterval(timer);
}, []);
useInput((input, key) => {
if (input === '\\\r' || input === '\\' || input === '\\\n') {
const newValue = value.slice(0, cursorPosition) + '\\' + value.slice(cursorPosition);
handleChange(newValue);
const lines = newValue.split('\\');
const currentLineIndex = newValue.substring(0, cursorPosition + 1).split('\\').length - 1;
const isNewLine = currentLineIndex > 0 && lines[currentLineIndex] === '';
if (isNewLine) {
setTimeout(() => setCursorPosition(cursorPosition + 1), 10);
}
else {
setCursorPosition(cursorPosition + 1);
}
return;
}
if (key.return && !key.shift) {
let finalValue = value;
const summaryRegex = /\[Pasted text #\d+ \+\d+ lines\]/g;
const summaries = value.match(summaryRegex);
if (summaries) {
summaries.forEach(summary => {
const actualContent = pastedContentRef.current[summary];
if (actualContent) {
finalValue = finalValue.replace(summary, actualContent);
}
});
}
onSubmit(finalValue);
handleChange('');
setCursorPosition(0);
pastedContentRef.current = {};
return;
}
if (key.backspace || key.delete) {
if (cursorPosition > 0) {
const newValue = value.slice(0, cursorPosition - 1) + value.slice(cursorPosition);
handleChange(newValue);
setCursorPosition(cursorPosition - 1);
}
return;
}
if (key.leftArrow) {
setCursorPosition(Math.max(0, cursorPosition - 1));
return;
}
if (key.rightArrow) {
setCursorPosition(Math.min(value.length, cursorPosition + 1));
return;
}
if (input && input.length >= 1) {
const now = Date.now();
const timeSinceLastInput = now - lastInputTime;
setLastInputTime(now);
const looksLikePaste = input.length > 10 || input.includes('\r') || (timeSinceLastInput < 50 && pasteBuffer.length > 0);
if (looksLikePaste) {
setPasteBuffer(prev => prev + input);
if (pasteTimeoutRef.current) {
clearTimeout(pasteTimeoutRef.current);
}
pasteTimeoutRef.current = setTimeout(() => {
const fullPaste = pasteBuffer + input;
const cleanedPaste = fullPaste.replace(/\r/g, '\n');
const lines = cleanedPaste.split('\n').filter(line => line.trim());
onPasteStart?.();
const lineCount = Math.max(lines.length, Math.ceil(cleanedPaste.length / 80));
const summary = `[Pasted text #1 +${lineCount} lines]`;
pastedContentRef.current[summary] = cleanedPaste;
const newValue = value.slice(0, cursorPosition) + summary + value.slice(cursorPosition);
handleChange(newValue);
setTimeout(() => {
setCursorPosition(cursorPosition + summary.length);
}, 100);
setTimeout(() => onPasteEnd?.(), 100);
setPasteBuffer('');
}, 50);
return;
}
if (timeSinceLastInput > 100) {
setPasteBuffer('');
}
const newValue = value.slice(0, cursorPosition) + input + value.slice(cursorPosition);
handleChange(newValue);
const beforeCursor = value.substring(0, cursorPosition);
const isStartOfLine = cursorPosition === 0 || beforeCursor.endsWith('\\');
if (isStartOfLine) {
setTimeout(() => setCursorPosition(cursorPosition + input.length), 10);
}
else {
setCursorPosition(cursorPosition + input.length);
}
}
}, { isActive: true });
const handleChange = (newValue) => {
if (controlledValue === undefined) {
setInternalValue(newValue);
}
onChange?.(newValue);
};
const lines = value.split('\\');
let remainingPos = cursorPosition;
let cursorLine = 0;
let cursorCol = 0;
for (let i = 0; i < lines.length; i++) {
if (remainingPos <= lines[i].length) {
cursorLine = i;
cursorCol = remainingPos;
break;
}
remainingPos -= lines[i].length + 1;
}
return (React.createElement(Box, { flexDirection: "column", borderStyle: "round", borderColor: providerColor, paddingX: 1 }, lines.map((line, index) => (React.createElement(Box, { key: index },
React.createElement(Text, null, index === 0 ? chalk.hex(providerColor)('> ') : chalk.hex(providerColor)(' ')),
index === cursorLine ? (React.createElement(Text, { color: providerColor },
line.substring(0, cursorCol),
showCursor ? chalk.inverse(' ') : ' ',
line.substring(cursorCol))) : (React.createElement(Text, { color: providerColor }, line)),
index < lines.length - 1 && React.createElement(Text, null, chalk.gray('\\')))))));
};
//# sourceMappingURL=InputBox.js.map