UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

85 lines 4.37 kB
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { Box, Text } from 'ink'; import { memo } from 'react'; import wrapAnsi from 'wrap-ansi'; import { useTerminalWidth } from '../hooks/useTerminalWidth.js'; import { useTheme } from '../hooks/useTheme.js'; import { calculateTokens } from '../utils/token-calculator.js'; // Strip VS Code context blocks from display (code is still sent to LLM) function stripVSCodeContext(message) { return message.replace(/<!--vscode-context-->[\s\S]*?<!--\/vscode-context-->/g, ''); } // Pre-wrap text to avoid Ink's trim:false leaving leading spaces on wrapped lines. // Wraps each original line individually and trims only the artifact spaces // from continuation lines, preserving intentional indentation. function wrapWithTrimmedContinuations(text, width) { if (width <= 0) return text; const originalLines = text.split('\n'); const result = []; for (const line of originalLines) { if (line === '') { result.push(''); continue; } const wrapped = wrapAnsi(line, width, { trim: false, hard: true }); const subLines = wrapped.split('\n'); result.push(subLines[0] ?? ''); for (let i = 1; i < subLines.length; i++) { result.push((subLines[i] ?? '').replace(/^((?:\x1b\[[0-9;]*m)*)\s/, '$1')); } } return result.join('\n'); } // Parse a line and return segments with file placeholders highlighted function parseLineWithPlaceholders(line) { const segments = []; const filePattern = /\[@[^\]]+\]/g; let lastIndex = 0; let match; while ((match = filePattern.exec(line)) !== null) { // Add text before the placeholder if (match.index > lastIndex) { segments.push({ text: line.slice(lastIndex, match.index), isPlaceholder: false, }); } // Add the placeholder segments.push({ text: match[0], isPlaceholder: true, }); lastIndex = match.index + match[0].length; } // Add remaining text if (lastIndex < line.length) { segments.push({ text: line.slice(lastIndex), isPlaceholder: false, }); } return segments; } export default memo(function UserMessage({ message, tokenContent, }) { const { colors } = useTheme(); const boxWidth = useTerminalWidth(); const tokens = calculateTokens(tokenContent ?? message); // Inner text width: outer width minus left border (1) and padding (1 each side) const textWidth = boxWidth - 3; // Strip VS Code context blocks and pre-wrap to avoid Ink's trim:false // leaving leading spaces on wrapped lines const displayMessage = wrapWithTrimmedContinuations(stripVSCodeContext(message), textWidth); const lines = displayMessage.split('\n'); return (_jsxs(_Fragment, { children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: colors.primary, bold: true, children: "You:" }) }), _jsx(Box, { flexDirection: "column", marginBottom: 1, backgroundColor: colors.base, width: boxWidth, padding: 1, borderStyle: "bold", borderLeft: true, borderRight: false, borderTop: false, borderBottom: false, borderLeftColor: colors.primary, children: _jsx(Box, { flexDirection: "column", children: lines.map((line, lineIndex) => { // Skip empty lines - they create paragraph spacing via marginBottom if (line.trim() === '') { return null; } const segments = parseLineWithPlaceholders(line); const isEndOfParagraph = lineIndex + 1 < lines.length && lines[lineIndex + 1].trim() === ''; return (_jsx(Box, { marginBottom: isEndOfParagraph ? 1 : 0, children: _jsx(Text, { children: segments.map((segment, segIndex) => (_jsx(Text, { color: segment.isPlaceholder ? colors.info : colors.text, bold: segment.isPlaceholder, children: segment.text }, segIndex))) }) }, lineIndex)); }) }) }), _jsx(Box, { marginBottom: 2, children: _jsxs(Text, { color: colors.secondary, dimColor: true, children: ["~", tokens.toLocaleString(), " tokens"] }) })] })); }); //# sourceMappingURL=user-message.js.map