Files
OpenQode/bin/ui/components/ChannelLanes.mjs

174 lines
5.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Channel Components - Separate lanes for different content types
*
* CHANNEL SEPARATION:
* - ChatLane: user + assistant prose only
* - ToolLane: tool calls, auto-heal, IQ exchange (collapsed by default)
* - ErrorLane: short summary + expandable details
*/
import React, { useEffect, useState } from 'react';
import { Box, Text } from 'ink';
import Spinner from 'ink-spinner';
import { colors } from '../../tui-theme.mjs';
import { icon } from '../../icons.mjs';
import { getCapabilities } from '../../terminal-profile.mjs';
const h = React.createElement;
/**
* ToolLane - Collapsed tool/command output
* Expands on demand, doesn't pollute chat
*/
const ToolLane = ({
name,
status = 'running', // running, done, failed
summary = null,
output = null,
isExpanded = false,
onToggle = null,
width = 80
}) => {
const caps = getCapabilities();
const [expanded, setExpanded] = useState(isExpanded);
useEffect(() => {
setExpanded(isExpanded);
}, [isExpanded]);
const statusConfig = {
running: { color: colors.accent, icon: null, showSpinner: true },
done: { color: colors.success, icon: caps.unicodeOK ? '✓' : '+', showSpinner: false },
failed: { color: colors.error, icon: caps.unicodeOK ? '✗' : 'X', showSpinner: false }
};
const config = statusConfig[status] || statusConfig.running;
const railChar = caps.unicodeOK ? '│' : '|';
// Header line (always shown)
const header = h(Box, { flexDirection: 'row' },
// Rail
h(Text, { color: 'magenta' }, railChar + ' '),
// Spinner or icon
config.showSpinner
? h(Spinner, { type: 'dots' })
: h(Text, { color: config.color }, config.icon),
h(Text, {}, ' '),
// Tool name
h(Text, { color: config.color, bold: true }, name),
// Summary (if any)
summary ? h(Text, { color: colors.muted }, ` ${summary}`) : null,
// Expand hint (if has output)
output && !expanded ? h(Text, { color: colors.muted, dimColor: true },
` [${caps.unicodeOK ? '▼' : 'v'} expand]`
) : null
);
if (!expanded || !output) {
return header;
}
// Expanded view with output
return h(Box, { flexDirection: 'column' },
header,
h(Box, { paddingLeft: 4, marginTop: 0, marginBottom: 1 },
h(Text, {
color: colors.muted,
dimColor: true,
wrap: 'wrap'
}, output.length > 200 ? output.slice(0, 200) + '...' : output)
)
);
};
/**
* ErrorLane - Compact error display
* Short summary line + expandable details
*/
const ErrorLane = ({
message,
details = null,
isExpanded = false,
width = 80
}) => {
const caps = getCapabilities();
const [expanded, setExpanded] = useState(isExpanded);
useEffect(() => {
setExpanded(isExpanded);
}, [isExpanded]);
const railChar = caps.unicodeOK ? '│' : '|';
const errorIcon = caps.unicodeOK ? '✗' : 'X';
// Summary line (always shown)
const summary = h(Box, { flexDirection: 'row' },
h(Text, { color: colors.error }, railChar + ' '),
h(Text, { color: colors.error }, errorIcon + ' '),
h(Text, { color: colors.error, bold: true }, 'Error: '),
h(Text, { color: colors.fg, wrap: 'truncate' },
message.length > 60 ? message.slice(0, 57) + '...' : message
),
details && !expanded ? h(Text, { color: colors.muted, dimColor: true },
` [${caps.unicodeOK ? '▼' : 'v'} details]`
) : null
);
if (!expanded || !details) {
return summary;
}
// Expanded with details
return h(Box, { flexDirection: 'column' },
summary,
h(Box, { paddingLeft: 4, marginTop: 0, marginBottom: 1 },
h(Text, { color: colors.muted, wrap: 'wrap' }, details)
)
);
};
/**
* SystemChip - Single-line system message
* Minimal, doesn't interrupt conversation flow
*/
const SystemChip = ({ message, type = 'info' }) => {
const caps = getCapabilities();
const railChar = caps.unicodeOK ? '│' : '|';
const typeConfig = {
info: { color: colors.accent, icon: caps.unicodeOK ? '' : 'i' },
success: { color: colors.success, icon: caps.unicodeOK ? '✓' : '+' },
warning: { color: colors.warning, icon: caps.unicodeOK ? '⚠' : '!' }
};
const config = typeConfig[type] || typeConfig.info;
return h(Box, { flexDirection: 'row' },
h(Text, { color: config.color, dimColor: true }, railChar + ' '),
h(Text, { color: config.color, dimColor: true }, config.icon + ' '),
h(Text, { color: colors.muted, dimColor: true }, message)
);
};
/**
* IQExchangeChip - IQ Exchange status (single line)
*/
const IQExchangeChip = ({ message, isActive = true }) => {
const caps = getCapabilities();
const railChar = caps.unicodeOK ? '│' : '|';
return h(Box, { flexDirection: 'row' },
h(Text, { color: 'magenta' }, railChar + ' '),
isActive ? h(Spinner, { type: 'dots' }) : null,
isActive ? h(Text, {}, ' ') : null,
h(Text, { color: 'magenta', bold: true }, 'IQ Exchange: '),
h(Text, { color: colors.muted }, message)
);
};
export { ToolLane, ErrorLane, SystemChip, IQExchangeChip };
export default { ToolLane, ErrorLane, SystemChip, IQExchangeChip };