capsule-ai-cli
Version:
The AI Model Orchestrator - Intelligent multi-model workflows with device-locked licensing
328 lines • 15.5 kB
JavaScript
import React from 'react';
import { Box, Text } from 'ink';
import chalk from 'chalk';
import { getProviderColor } from '../utils/provider-colors.js';
import { ToolResultDisplay } from './ToolResultDisplay.js';
import { MarkdownRenderer } from '../utils/markdown-renderer.js';
import { OpenRouterSetupBox } from './OpenRouterSetupBox.js';
import { ActivationSetupBox } from './ActivationSetupBox.js';
function truncateString(str, maxLength = 50) {
const text = typeof str === 'string' ? str : String(str || '');
if (text.length <= maxLength)
return text;
return text.substring(0, maxLength - 3) + '...';
}
function formatEditResult(output) {
if (typeof output === 'string')
return output;
if (output.message) {
let result = output.message;
if (output.diff) {
result += '\n' + output.diff;
}
return result;
}
return JSON.stringify(output, null, 2);
}
function formatSearchResult(output) {
if (typeof output === 'string')
return output;
if (output.files) {
return `Found ${output.files.length} files`;
}
if (output.matches) {
return `Found ${output.matches} matches`;
}
return JSON.stringify(output, null, 2);
}
export const ChatMessage = ({ type, content, metadata, provider = 'openai' }) => {
const providerColor = getProviderColor(provider);
const needsExtraSpacing = type === 'tool-call' && metadata?.toolName === 'Sub-Agents';
if (type === 'user') {
return (React.createElement(Box, { marginBottom: 0.25 },
React.createElement(Text, { color: providerColor }, chalk.hex(providerColor)('> ')),
React.createElement(Box, { flexGrow: 1 },
React.createElement(MarkdownRenderer, { content: content, provider: provider }))));
}
if (type === 'tool-call') {
let params = {};
try {
params = JSON.parse(content);
}
catch {
params = {};
}
let argDisplay = '';
if (params.path || params.file_path) {
argDisplay = truncateString(params.path || params.file_path, 50);
}
else if (params.command) {
argDisplay = truncateString(params.command, 50);
}
else if (params.url) {
argDisplay = truncateString(params.url, 50);
}
else if (params.query || params.pattern) {
argDisplay = truncateString(params.query || params.pattern, 50);
}
const icon = metadata?.success === true ? chalk.hex('#00FF00')('⏺') :
metadata?.success === false ? chalk.hex('#FF0000')('⏺') :
chalk.hex('#FFFF00')('⏺');
return (React.createElement(Box, { flexDirection: "column", marginBottom: needsExtraSpacing ? 0.5 : 0.25, marginTop: needsExtraSpacing ? 0.5 : 0, flexGrow: 1 },
React.createElement(Box, { flexWrap: "wrap" },
React.createElement(Text, { wrap: "wrap" },
icon,
" ",
chalk.bold(metadata?.toolName || 'Tool'),
argDisplay && `(${argDisplay})`))));
}
if (type === 'tool-result') {
let parsedContent = '';
const toolMetadata = {
success: metadata?.success === true,
toolName: metadata?.toolName,
error: metadata?.error
};
try {
let result;
if (typeof content === 'string') {
try {
result = JSON.parse(content);
}
catch {
result = { output: content };
}
}
else {
result = content;
}
if (result.error) {
toolMetadata.error = result.error;
toolMetadata.success = false;
}
else if (!toolMetadata.success && typeof content === 'string' && content.trim()) {
toolMetadata.error = content;
}
if (!toolMetadata.error && toolMetadata.success) {
const originalParams = metadata?.originalParams;
if (originalParams) {
if (originalParams.path || originalParams.file_path) {
toolMetadata.filePath = originalParams.path || originalParams.file_path;
}
if (originalParams.command) {
toolMetadata.command = originalParams.command;
}
if (originalParams.url) {
toolMetadata.url = originalParams.url;
}
if (originalParams.query) {
toolMetadata.query = originalParams.query;
}
}
if (result.path)
toolMetadata.filePath = result.path;
if (result.filePath)
toolMetadata.filePath = result.filePath;
if (result.file_path)
toolMetadata.filePath = result.file_path;
if (result.command)
toolMetadata.command = result.command;
if (result.url)
toolMetadata.url = result.url;
if (metadata?.toolName === 'Read') {
const lines = result.output?.split('\n') || [];
toolMetadata.linesRead = lines.length;
}
else if (metadata?.toolName === 'Write') {
toolMetadata.filePath = result.path || result.filePath;
if (result.lines !== undefined) {
toolMetadata.linesRead = result.lines;
}
else if (result.output) {
const lines = result.output.split('\n') || [];
toolMetadata.linesRead = lines.length;
}
}
else if (metadata?.toolName === 'Edit' || metadata?.toolName === 'MultiEdit') {
const output = result.output || '';
const additions = (output.match(/\+/g) || []).length;
const deletions = (output.match(/-/g) || []).length;
toolMetadata.additions = additions;
toolMetadata.deletions = deletions;
}
else if (metadata?.toolName === 'Grep') {
const output = result.output || '';
const lines = output.split('\n').filter((l) => l.trim());
toolMetadata.matchCount = lines.length;
toolMetadata.fileCount = new Set(lines.map((l) => l.split(':')[0])).size;
}
else if (metadata?.toolName === 'Glob') {
const files = result.output?.split('\n').filter((f) => f.trim()) || [];
toolMetadata.fileCount = files.length;
}
else if (metadata?.toolName === 'Bash') {
toolMetadata.command = result.command || 'command';
}
else if (metadata?.toolName === 'LS' || metadata?.toolName === 'List') {
const files = result.output?.split('\n').filter((f) => f.trim()) || [];
toolMetadata.fileCount = files.length;
}
else if (metadata?.toolName === 'Todo' || metadata?.toolName === 'TodoWrite') {
if (result.display) {
toolMetadata.todoDisplay = result.display;
}
else if (result.todos) {
toolMetadata.todoCount = result.todos.length;
}
}
else if (metadata?.toolName === 'Search') {
if (result.resultCount !== undefined) {
toolMetadata.resultCount = result.resultCount;
}
if (result.type) {
toolMetadata.searchType = result.type;
}
}
else if (metadata?.toolName === 'Git') {
if (result.command) {
const match = result.command.match(/git\s+(\w+)/);
if (match) {
toolMetadata.gitCommand = match[1];
}
}
if (result.summary) {
toolMetadata.gitSummary = result.summary;
}
}
else if (metadata?.toolName === 'Web Fetch') {
if (result.url) {
toolMetadata.url = result.url;
}
if (result.status) {
toolMetadata.status = result.status;
}
}
}
if (result.output) {
if (metadata?.toolName === 'Edit' || metadata?.toolName === 'Update') {
parsedContent = formatEditResult(result.output);
}
else if (metadata?.toolName === 'Search' || metadata?.toolName === 'Grep') {
parsedContent = formatSearchResult(result.output);
}
else {
parsedContent = typeof result.output === 'string' ? result.output : JSON.stringify(result.output, null, 2);
}
}
else if (result.display && (metadata?.toolName === 'Todo' || metadata?.toolName === 'TodoWrite')) {
parsedContent = result.display;
}
else {
parsedContent = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
}
if ((metadata?.toolName === 'Todo' || metadata?.toolName === 'TodoWrite') &&
typeof parsedContent === 'string' &&
parsedContent.includes('Update Todos')) {
toolMetadata.todoDisplay = parsedContent;
}
}
catch (error) {
parsedContent = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
if (!toolMetadata.error) {
toolMetadata.error = 'Failed to parse tool result';
}
}
return (React.createElement(Box, { marginBottom: 0.25 },
React.createElement(ToolResultDisplay, { toolName: metadata?.toolName || 'Unknown', content: parsedContent, metadata: toolMetadata })));
}
if (type === 'assistant') {
const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
if (!contentStr.trim()) {
return null;
}
const finalContent = contentStr.startsWith('⏺') ? contentStr : '⏺ ' + contentStr;
return (React.createElement(Box, { marginBottom: 0.25 },
React.createElement(MarkdownRenderer, { content: finalContent, provider: provider })));
}
if (type === 'system') {
const contentStr = typeof content === 'string' ? content : JSON.stringify(content, null, 2);
if (contentStr === 'openrouter-setup') {
return (React.createElement(Box, { marginBottom: 1 },
React.createElement(OpenRouterSetupBox, null)));
}
if (contentStr === 'activation-setup') {
return (React.createElement(Box, { marginBottom: 1 },
React.createElement(ActivationSetupBox, null)));
}
const formatSystemMessage = (text) => {
return text;
};
if (contentStr.includes('██████╗')) {
const lines = contentStr.split('\n');
const logoLines = lines.slice(0, 7);
const messageLines = lines.slice(7).join('\n').trim();
return (React.createElement(Box, { flexDirection: "column", marginBottom: 0.25 },
React.createElement(Box, { flexDirection: "column" }, logoLines.map((line, i) => (React.createElement(Text, { key: i, color: providerColor }, line)))),
messageLines && (React.createElement(Box, { marginTop: 1 },
React.createElement(Text, { color: providerColor }, formatSystemMessage(messageLines))))));
}
if (contentStr.includes('Welcome to Capsule CLI') ||
contentStr.includes('Mode switched') ||
contentStr.includes('Model changed') ||
contentStr.includes('Provider changed')) {
return (React.createElement(Box, { marginBottom: 1 },
React.createElement(Text, { color: providerColor }, formatSystemMessage(contentStr))));
}
return (React.createElement(Box, { marginBottom: 0.25 },
React.createElement(Text, { color: "yellow" }, contentStr)));
}
if (type === 'error') {
return (React.createElement(Box, { marginBottom: 0.25 },
React.createElement(Text, { color: "red" },
"\u26A0\uFE0F ",
content)));
}
if (type === 'sub-agent-start') {
const statusIcon = metadata?.agentStatus === 'completed' ? '✓' :
metadata?.agentStatus === 'failed' ? '✗' :
metadata?.agentStatus === 'running' ? '◐' : '○';
const statusColor = metadata?.agentStatus === 'completed' ? 'green' :
metadata?.agentStatus === 'failed' ? 'red' :
metadata?.agentStatus === 'running' ? 'yellow' : 'gray';
return (React.createElement(Box, { marginLeft: 2 },
React.createElement(Box, null,
React.createElement(Text, { color: "gray" }, "\u23BF "),
React.createElement(Text, { color: statusColor },
statusIcon,
" "),
React.createElement(Text, null, truncateString(content, 80)))));
}
if (type === 'sub-agent-result') {
const agents = metadata?.agentTasks || [];
const allSuccessful = agents.every((a) => a.status === 'completed');
const summaryColor = allSuccessful ? 'green' : 'yellow';
return (React.createElement(Box, { flexDirection: "column", marginTop: 0.25 },
React.createElement(Box, { marginLeft: 4 },
React.createElement(Text, { color: "gray" }, "\u23BF "),
React.createElement(Text, { bold: true, color: summaryColor },
"\u2728 ",
agents.length,
" task",
agents.length > 1 ? 's' : '',
" completed")),
React.createElement(Box, { flexDirection: "column", marginLeft: 6 }, agents.map((agent) => {
const statusIcon = agent.status === 'completed' ? '✓' : '✗';
const statusColor = agent.status === 'completed' ? 'green' : 'red';
return (React.createElement(Box, { key: agent.id },
React.createElement(Text, { color: statusColor },
statusIcon,
" "),
React.createElement(Text, null, truncateString(agent.task, 60)),
agent.toolsUsed && agent.toolsUsed.length > 0 && (React.createElement(Text, { dimColor: true },
" \u2022 ",
agent.toolsUsed.join(', ')))));
}))));
}
return null;
};
//# sourceMappingURL=ChatMessage.js.map