mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 00:49:41 +02:00
feat(tui): add RalphPanel component for loop tracking
- Display Ralph loop status (active/idle) - Show completion phrase, cycle count, elapsed time - Display todo list with status icons (pending/in_progress/completed) - Truncate long todo items, limit to 5 visible - Use magenta border to match web UI styling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,115 @@
|
|||||||
|
/**
|
||||||
|
* @fileoverview RalphPanel component
|
||||||
|
*
|
||||||
|
* Displays Ralph Wiggum loop tracking information:
|
||||||
|
* - Loop status and completion phrase
|
||||||
|
* - Todo list with progress
|
||||||
|
* - Cycle count and elapsed time
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { Box, Text } from 'ink';
|
||||||
|
import type { InnerLoopState, InnerTodoItem } from '../../types.js';
|
||||||
|
|
||||||
|
interface RalphPanelProps {
|
||||||
|
loopState: InnerLoopState | null;
|
||||||
|
todos: InnerTodoItem[];
|
||||||
|
visible?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status icon for todo items
|
||||||
|
*/
|
||||||
|
function getStatusIcon(status: string): { icon: string; color: string } {
|
||||||
|
switch (status) {
|
||||||
|
case 'completed':
|
||||||
|
return { icon: '\u2713', color: 'green' };
|
||||||
|
case 'in_progress':
|
||||||
|
return { icon: '\u25CF', color: 'yellow' };
|
||||||
|
case 'pending':
|
||||||
|
default:
|
||||||
|
return { icon: '\u25CB', color: 'gray' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats elapsed hours to human-readable string
|
||||||
|
*/
|
||||||
|
function formatElapsed(hours: number | null): string {
|
||||||
|
if (hours === null) return '-';
|
||||||
|
if (hours < 1) {
|
||||||
|
const minutes = Math.round(hours * 60);
|
||||||
|
return `${minutes}m`;
|
||||||
|
}
|
||||||
|
return `${hours.toFixed(1)}h`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RalphPanel component showing loop tracking
|
||||||
|
*/
|
||||||
|
export function RalphPanel({
|
||||||
|
loopState,
|
||||||
|
todos,
|
||||||
|
visible = true,
|
||||||
|
}: RalphPanelProps): React.ReactElement | null {
|
||||||
|
// Don't render if not visible or no loop state
|
||||||
|
if (!visible || !loopState || !loopState.enabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const completedCount = todos.filter((t) => t.status === 'completed').length;
|
||||||
|
const totalCount = todos.length;
|
||||||
|
const progressPercent = totalCount > 0 ? Math.round((completedCount / totalCount) * 100) : 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box flexDirection="column" borderStyle="single" borderColor="magenta" marginBottom={1}>
|
||||||
|
{/* Header */}
|
||||||
|
<Box paddingX={1} justifyContent="space-between">
|
||||||
|
<Text color="magenta" bold>
|
||||||
|
Ralph Loop {loopState.active ? '(active)' : '(idle)'}
|
||||||
|
</Text>
|
||||||
|
{loopState.completionPhrase && (
|
||||||
|
<Text dimColor>
|
||||||
|
phrase: {loopState.completionPhrase}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Stats row */}
|
||||||
|
<Box paddingX={1}>
|
||||||
|
<Text>
|
||||||
|
<Text dimColor>cycles:</Text> {loopState.cycleCount}
|
||||||
|
{loopState.maxIterations && <Text>/{loopState.maxIterations}</Text>}
|
||||||
|
<Text> | </Text>
|
||||||
|
<Text dimColor>elapsed:</Text> {formatElapsed(loopState.elapsedHours)}
|
||||||
|
<Text> | </Text>
|
||||||
|
<Text dimColor>todos:</Text> {completedCount}/{totalCount} ({progressPercent}%)
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Todo list (max 5 items) */}
|
||||||
|
{todos.length > 0 && (
|
||||||
|
<Box flexDirection="column" paddingX={1} marginTop={1}>
|
||||||
|
{todos.slice(0, 5).map((todo) => {
|
||||||
|
const { icon, color } = getStatusIcon(todo.status);
|
||||||
|
const content = todo.content.length > 50
|
||||||
|
? todo.content.slice(0, 47) + '...'
|
||||||
|
: todo.content;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box key={todo.id}>
|
||||||
|
<Text color={color}>{icon} </Text>
|
||||||
|
<Text color={todo.status === 'completed' ? 'gray' : undefined}>
|
||||||
|
{content}
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
{todos.length > 5 && (
|
||||||
|
<Text dimColor>... and {todos.length - 5} more</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,3 +7,4 @@ export { TabBar } from './TabBar.js';
|
|||||||
export { TerminalView } from './TerminalView.js';
|
export { TerminalView } from './TerminalView.js';
|
||||||
export { StatusBar } from './StatusBar.js';
|
export { StatusBar } from './StatusBar.js';
|
||||||
export { HelpOverlay } from './HelpOverlay.js';
|
export { HelpOverlay } from './HelpOverlay.js';
|
||||||
|
export { RalphPanel } from './RalphPanel.js';
|
||||||
|
|||||||
Reference in New Issue
Block a user