Release v1.01 Enhanced: Vi Control, TUI Gen5, Core Stability
This commit is contained in:
162
bin/ui/components/AutomationTimeline.mjs
Normal file
162
bin/ui/components/AutomationTimeline.mjs
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Automation Timeline Component
|
||||
*
|
||||
* Shows Observe → Intent → Actions → Verify for each automation step
|
||||
*
|
||||
* Credits: Windows-Use verification loop, Browser-Use agent
|
||||
*/
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import { Box, Text } from 'ink';
|
||||
import Spinner from 'ink-spinner';
|
||||
import { colors } from '../../tui-theme.mjs';
|
||||
import { getCapabilities } from '../../terminal-profile.mjs';
|
||||
|
||||
const h = React.createElement;
|
||||
|
||||
// Step phases
|
||||
export const STEP_PHASES = {
|
||||
OBSERVE: 'observe',
|
||||
INTENT: 'intent',
|
||||
ACTIONS: 'actions',
|
||||
VERIFY: 'verify'
|
||||
};
|
||||
|
||||
/**
|
||||
* Single timeline step
|
||||
*/
|
||||
const TimelineStep = ({
|
||||
stepNumber,
|
||||
observe = null, // "What I see now"
|
||||
intent = null, // "What I'm trying next"
|
||||
actions = [], // Array of action descriptions
|
||||
verify = null, // { passed, message }
|
||||
isActive = false,
|
||||
isExpanded = false,
|
||||
width = 60
|
||||
}) => {
|
||||
const caps = getCapabilities();
|
||||
const railV = caps.unicodeOK ? '│' : '|';
|
||||
const bullet = caps.unicodeOK ? '●' : '*';
|
||||
const checkmark = caps.unicodeOK ? '✓' : '+';
|
||||
const crossmark = caps.unicodeOK ? '✗' : 'X';
|
||||
|
||||
// Collapsed view: just step number + status
|
||||
if (!isExpanded && !isActive) {
|
||||
const status = verify
|
||||
? (verify.passed ? 'passed' : 'failed')
|
||||
: 'pending';
|
||||
const statusIcon = verify?.passed ? checkmark : (verify ? crossmark : '…');
|
||||
const statusColor = verify?.passed ? colors.success : (verify ? colors.error : colors.muted);
|
||||
|
||||
return h(Box, { flexDirection: 'row' },
|
||||
h(Text, { color: colors.muted }, `Step ${stepNumber}: `),
|
||||
h(Text, { color: statusColor }, statusIcon),
|
||||
h(Text, { color: colors.muted, dimColor: true },
|
||||
intent ? ` ${intent.slice(0, width - 20)}` : ''
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Expanded/active view
|
||||
return h(Box, { flexDirection: 'column', marginY: 0 },
|
||||
// Step header
|
||||
h(Box, { flexDirection: 'row' },
|
||||
h(Text, { color: isActive ? colors.accent : colors.muted, bold: isActive },
|
||||
`Step ${stepNumber}`
|
||||
),
|
||||
isActive ? h(Box, { marginLeft: 1 },
|
||||
h(Spinner, { type: 'dots' })
|
||||
) : null
|
||||
),
|
||||
|
||||
// Observe section
|
||||
observe ? h(Box, { flexDirection: 'row', paddingLeft: 2 },
|
||||
h(Text, { color: 'cyan' }, `${railV} Observe: `),
|
||||
h(Text, { color: colors.muted, wrap: 'truncate' },
|
||||
observe.slice(0, width - 15)
|
||||
)
|
||||
) : null,
|
||||
|
||||
// Intent section
|
||||
intent ? h(Box, { flexDirection: 'row', paddingLeft: 2 },
|
||||
h(Text, { color: 'yellow' }, `${railV} Intent: `),
|
||||
h(Text, { color: colors.fg }, intent.slice(0, width - 15))
|
||||
) : null,
|
||||
|
||||
// Actions section
|
||||
actions.length > 0 ? h(Box, { flexDirection: 'column', paddingLeft: 2 },
|
||||
h(Text, { color: 'magenta' }, `${railV} Actions:`),
|
||||
...actions.slice(0, 5).map((action, i) =>
|
||||
h(Text, { key: i, color: colors.muted, dimColor: true },
|
||||
`${railV} ${i + 1}. ${action.slice(0, width - 10)}`
|
||||
)
|
||||
),
|
||||
actions.length > 5 ? h(Text, { color: colors.muted, dimColor: true },
|
||||
`${railV} +${actions.length - 5} more`
|
||||
) : null
|
||||
) : null,
|
||||
|
||||
// Verify section
|
||||
verify ? h(Box, { flexDirection: 'row', paddingLeft: 2 },
|
||||
h(Text, { color: verify.passed ? colors.success : colors.error },
|
||||
`${railV} Verify: ${verify.passed ? checkmark : crossmark} `
|
||||
),
|
||||
h(Text, { color: colors.muted }, verify.message || '')
|
||||
) : null
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Automation Timeline
|
||||
*
|
||||
* Props:
|
||||
* - steps: array of step objects
|
||||
* - activeStepIndex: currently executing step (-1 if none)
|
||||
* - isExpanded: show all details
|
||||
* - width: available width
|
||||
*/
|
||||
const AutomationTimeline = ({
|
||||
steps = [],
|
||||
activeStepIndex = -1,
|
||||
isExpanded = false,
|
||||
title = 'Automation',
|
||||
width = 80
|
||||
}) => {
|
||||
const caps = getCapabilities();
|
||||
|
||||
if (steps.length === 0) return null;
|
||||
|
||||
// Count stats
|
||||
const verified = steps.filter(s => s.verify?.passed).length;
|
||||
const failed = steps.filter(s => s.verify && !s.verify.passed).length;
|
||||
const pending = steps.length - verified - failed;
|
||||
|
||||
return h(Box, { flexDirection: 'column' },
|
||||
// Header with stats
|
||||
h(Box, { flexDirection: 'row', marginBottom: 0 },
|
||||
h(Text, { color: colors.muted, bold: true }, `${title} `),
|
||||
h(Text, { color: colors.success }, `${verified}✓ `),
|
||||
failed > 0 ? h(Text, { color: colors.error }, `${failed}✗ `) : null,
|
||||
pending > 0 ? h(Text, { color: colors.muted }, `${pending}… `) : null
|
||||
),
|
||||
|
||||
// Steps
|
||||
...steps.map((step, i) =>
|
||||
h(TimelineStep, {
|
||||
key: i,
|
||||
stepNumber: i + 1,
|
||||
observe: step.observe,
|
||||
intent: step.intent,
|
||||
actions: step.actions || [],
|
||||
verify: step.verify,
|
||||
isActive: i === activeStepIndex,
|
||||
isExpanded: isExpanded || i === activeStepIndex,
|
||||
width: width - 4
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default AutomationTimeline;
|
||||
export { AutomationTimeline, TimelineStep };
|
||||
Reference in New Issue
Block a user