askeroo
Version:
A modern CLI prompt library with flow control, history navigation, and conditional prompts
124 lines • 5.79 kB
JavaScript
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
import { useEffect, useState } from "react";
import { Box, Text } from "ink";
import { streamStore } from "./stream-store.js";
// Main component for the stream plugin
export const StreamDisplay = ({ node, options, events, }) => {
const [spinnerFrame, setSpinnerFrame] = useState(0);
// Use the stream ID from options (should always be provided)
const streamId = options.streamId ||
`stream_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;
// Subscribe to the stream store - auto-updates on any store change
const store = streamStore.use();
// Extract data for this stream
const streamState = store.streams.get(streamId) || {
status: "active",
lines: [],
};
// Animated spinner frames (same as tasks)
const spinnerFrames = ["⠂", "-", "–", "—", "–", "-"];
// Animate spinner only when active/streaming
useEffect(() => {
if (streamState.status !== "active") {
return;
}
const interval = setInterval(() => {
setSpinnerFrame((prev) => (prev + 1) % spinnerFrames.length);
}, 150);
return () => clearInterval(interval);
}, [streamState.status]);
// Auto-submit immediately (to avoid blocking runtime)
// This allows the runtime to proceed while the stream continues updating in the background
// Use deferCompletion to prevent the node from being marked as completed immediately
useEffect(() => {
if (node.state === "active" && events.onSubmit) {
// Submit immediately to allow runtime to continue, but defer completion
events.onSubmit({ type: "auto", deferCompletion: true });
}
}, [node.state, events.onSubmit]);
// Mark node as completed when stream finishes
useEffect(() => {
if ((streamState.status === "completed" ||
streamState.status === "error") &&
events.onComplete) {
// If there's a submitDelay, wait for it before marking as complete
if (options.submitDelay && options.submitDelay > 0) {
const timer = setTimeout(() => {
events.onComplete(); // PluginWrapper handles the promptId
}, options.submitDelay);
return () => clearTimeout(timer);
}
else {
// Mark as complete immediately
events.onComplete(); // PluginWrapper handles the promptId
}
}
}, [streamState.status, events.onComplete, options.submitDelay]);
// Track whether we should hide after delay
const [shouldHideAfterDelay, setShouldHideAfterDelay] = useState(false);
// Handle delayed hiding when stream completes with submitDelay
useEffect(() => {
// Only applies when hideOnCompletion is true and there's a submitDelay
if (!options.hideOnCompletion ||
!options.submitDelay ||
options.submitDelay === 0) {
return;
}
// When stream finishes, wait for submitDelay then trigger hiding
if (streamState.status === "completed" ||
streamState.status === "error") {
const timer = setTimeout(() => {
setShouldHideAfterDelay(true);
}, options.submitDelay);
return () => clearTimeout(timer);
}
}, [streamState.status, options.submitDelay, options.hideOnCompletion]);
// Hide if hideOnCompletion is true and conditions are met
if (options.hideOnCompletion && node.state === "completed") {
const streamFinished = streamState.status === "completed" ||
streamState.status === "error";
// If stream finished and either no delay or delay has elapsed
if (streamFinished) {
// No delay: hide immediately
if (!options.submitDelay || options.submitDelay === 0) {
return null;
}
// With delay: hide after delay timer completes
if (shouldHideAfterDelay) {
return null;
}
}
}
// Get the lines to display (respect maxLines if set)
const linesToDisplay = options.maxLines
? streamState.lines.slice(-options.maxLines)
: streamState.lines;
// Determine status symbol (without color)
const getStatusSymbol = (status) => {
if (status === "completed")
return "■";
if (status === "error")
return "✗";
return spinnerFrames[spinnerFrame]; // active - animated
};
// Determine status color
const getStatusColor = (status) => {
if (status === "completed")
return "green";
if (status === "error")
return "red";
return "blue"; // active
};
// Get label if set
const label = streamState.label || options.label;
return (_jsxs(Box, { flexDirection: "column", children: [label && (_jsxs(Text, { color: getStatusColor(streamState.status), children: [getStatusSymbol(streamState.status), " ", label] })), linesToDisplay.map((line, index) => {
const actualLineNumber = options.maxLines
? streamState.lines.length -
linesToDisplay.length +
index +
1
: index + 1;
return (_jsxs(Box, { children: [options.showLineNumbers && (_jsxs(Text, { dimColor: true, children: [actualLineNumber.toString().padStart(3), " "] })), options.prefixSymbol && (_jsxs(Text, { dimColor: true, children: [options.prefixSymbol, " "] })), _jsx(Text, { children: line })] }, `${streamId}-${actualLineNumber}`));
})] }));
};
//# sourceMappingURL=Stream.js.map