feat: build

This commit is contained in:
claude-code-best committed 2026-03-31 19:22:47 +08:00
commit f90eee85d8
1906 files changed
+514339

No files matched your search

+61
View File
@@ -0,0 +1,61 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { Text, useTheme } from '../../ink.js';
import { getTheme, type Theme } from '../../utils/theme.js';
import { interpolateColor, parseRGB, toRGBColor } from './utils.js';
type Props = {
char: string;
flashOpacity: number;
messageColor: keyof Theme;
shimmerColor: keyof Theme;
};
export function FlashingChar(t0) {
const $ = _c(9);
const {
char,
flashOpacity,
messageColor,
shimmerColor
} = t0;
const [themeName] = useTheme();
let t1;
if ($[0] !== char || $[1] !== flashOpacity || $[2] !== messageColor || $[3] !== shimmerColor || $[4] !== themeName) {
t1 = Symbol.for("react.early_return_sentinel");
bb0: {
const theme = getTheme(themeName);
const baseColorStr = theme[messageColor];
const shimmerColorStr = theme[shimmerColor];
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null;
const shimmerRGB = shimmerColorStr ? parseRGB(shimmerColorStr) : null;
if (baseRGB && shimmerRGB) {
const interpolated = interpolateColor(baseRGB, shimmerRGB, flashOpacity);
t1 = <Text color={toRGBColor(interpolated)}>{char}</Text>;
break bb0;
}
}
$[0] = char;
$[1] = flashOpacity;
$[2] = messageColor;
$[3] = shimmerColor;
$[4] = themeName;
$[5] = t1;
} else {
t1 = $[5];
}
if (t1 !== Symbol.for("react.early_return_sentinel")) {
return t1;
}
const shouldUseShimmer = flashOpacity > 0.5;
const t2 = shouldUseShimmer ? shimmerColor : messageColor;
let t3;
if ($[6] !== char || $[7] !== t2) {
t3 = <Text color={t2}>{char}</Text>;
$[6] = char;
$[7] = t2;
$[8] = t3;
} else {
t3 = $[8];
}
return t3;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlRleHQiLCJ1c2VUaGVtZSIsImdldFRoZW1lIiwiVGhlbWUiLCJpbnRlcnBvbGF0ZUNvbG9yIiwicGFyc2VSR0IiLCJ0b1JHQkNvbG9yIiwiUHJvcHMiLCJjaGFyIiwiZmxhc2hPcGFjaXR5IiwibWVzc2FnZUNvbG9yIiwic2hpbW1lckNvbG9yIiwiRmxhc2hpbmdDaGFyIiwidDAiLCIkIiwiX2MiLCJ0aGVtZU5hbWUiLCJ0MSIsIlN5bWJvbCIsImZvciIsImJiMCIsInRoZW1lIiwiYmFzZUNvbG9yU3RyIiwic2hpbW1lckNvbG9yU3RyIiwiYmFzZVJHQiIsInNoaW1tZXJSR0IiLCJpbnRlcnBvbGF0ZWQiLCJzaG91bGRVc2VTaGltbWVyIiwidDIiLCJ0MyJdLCJzb3VyY2VzIjpbIkZsYXNoaW5nQ2hhci50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBUZXh0LCB1c2VUaGVtZSB9IGZyb20gJy4uLy4uL2luay5qcydcbmltcG9ydCB7IGdldFRoZW1lLCB0eXBlIFRoZW1lIH0gZnJvbSAnLi4vLi4vdXRpbHMvdGhlbWUuanMnXG5pbXBvcnQgeyBpbnRlcnBvbGF0ZUNvbG9yLCBwYXJzZVJHQiwgdG9SR0JDb2xvciB9IGZyb20gJy4vdXRpbHMuanMnXG5cbnR5cGUgUHJvcHMgPSB7XG4gIGNoYXI6IHN0cmluZ1xuICBmbGFzaE9wYWNpdHk6IG51bWJlclxuICBtZXNzYWdlQ29sb3I6IGtleW9mIFRoZW1lXG4gIHNoaW1tZXJDb2xvcjoga2V5b2YgVGhlbWVcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIEZsYXNoaW5nQ2hhcih7XG4gIGNoYXIsXG4gIGZsYXNoT3BhY2l0eSxcbiAgbWVzc2FnZUNvbG9yLFxuICBzaGltbWVyQ29sb3IsXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IFt0aGVtZU5hbWVdID0gdXNlVGhlbWUoKVxuICBjb25zdCB0aGVtZSA9IGdldFRoZW1lKHRoZW1lTmFtZSlcblxuICBjb25zdCBiYXNlQ29sb3JTdHIgPSB0aGVtZVttZXNzYWdlQ29sb3JdXG4gIGNvbnN0IHNoaW1tZXJDb2xvclN0ciA9IHRoZW1lW3NoaW1tZXJDb2xvcl1cblxuICBjb25zdCBiYXNlUkdCID0gYmFzZUNvbG9yU3RyID8gcGFyc2VSR0IoYmFzZUNvbG9yU3RyKSA6IG51bGxcbiAgY29uc3Qgc2hpbW1lclJHQiA9IHNoaW1tZXJDb2xvclN0ciA/IHBhcnNlUkdCKHNoaW1tZXJDb2xvclN0cikgOiBudWxsXG5cbiAgaWYgKGJhc2VSR0IgJiYgc2hpbW1lclJHQikge1xuICAgIC8vIFNtb290aCBpbnRlcnBvbGF0aW9uIGJldHdlZW4gY29sb3JzXG4gICAgY29uc3QgaW50ZXJwb2xhdGVkID0gaW50ZXJwb2xhdGVDb2xvcihiYXNlUkdCLCBzaGltbWVyUkdCLCBmbGFzaE9wYWNpdHkpXG4gICAgcmV0dXJuIDxUZXh0IGNvbG9yPXt0b1JHQkNvbG9yKGludGVycG9sYXRlZCl9PntjaGFyfTwvVGV4dD5cbiAgfVxuXG4gIC8vIEZhbGxiYWNrIGZvciBBTlNJIHRoZW1lczogYmluYXJ5IHN3aXRjaFxuICBjb25zdCBzaG91bGRVc2VTaGltbWVyID0gZmxhc2hPcGFjaXR5ID4gMC41XG4gIHJldHVybiAoXG4gICAgPFRleHQgY29sb3I9e3Nob3VsZFVzZVNoaW1tZXIgPyBzaGltbWVyQ29sb3IgOiBtZXNzYWdlQ29sb3J9PntjaGFyfTwvVGV4dD5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxJQUFJLEVBQUVDLFFBQVEsUUFBUSxjQUFjO0FBQzdDLFNBQVNDLFFBQVEsRUFBRSxLQUFLQyxLQUFLLFFBQVEsc0JBQXNCO0FBQzNELFNBQVNDLGdCQUFnQixFQUFFQyxRQUFRLEVBQUVDLFVBQVUsUUFBUSxZQUFZO0FBRW5FLEtBQUtDLEtBQUssR0FBRztFQUNYQyxJQUFJLEVBQUUsTUFBTTtFQUNaQyxZQUFZLEVBQUUsTUFBTTtFQUNwQkMsWUFBWSxFQUFFLE1BQU1QLEtBQUs7RUFDekJRLFlBQVksRUFBRSxNQUFNUixLQUFLO0FBQzNCLENBQUM7QUFFRCxPQUFPLFNBQUFTLGFBQUFDLEVBQUE7RUFBQSxNQUFBQyxDQUFBLEdBQUFDLEVBQUE7RUFBc0I7SUFBQVAsSUFBQTtJQUFBQyxZQUFBO0lBQUFDLFlBQUE7SUFBQUM7RUFBQSxJQUFBRSxFQUtyQjtFQUNOLE9BQUFHLFNBQUEsSUFBb0JmLFFBQVEsQ0FBQyxDQUFDO0VBQUEsSUFBQWdCLEVBQUE7RUFBQSxJQUFBSCxDQUFBLFFBQUFOLElBQUEsSUFBQU0sQ0FBQSxRQUFBTCxZQUFBLElBQUFLLENBQUEsUUFBQUosWUFBQSxJQUFBSSxDQUFBLFFBQUFILFlBQUEsSUFBQUcsQ0FBQSxRQUFBRSxTQUFBO0lBWXJCQyxFQUFBLEdBQUFDLE1BQW9ELENBQUFDLEdBQUEsQ0FBcEQsNkJBQW1ELENBQUM7SUFBQUMsR0FBQTtNQVg3RCxNQUFBQyxLQUFBLEdBQWNuQixRQUFRLENBQUNjLFNBQVMsQ0FBQztNQUVqQyxNQUFBTSxZQUFBLEdBQXFCRCxLQUFLLENBQUNYLFlBQVksQ0FBQztNQUN4QyxNQUFBYSxlQUFBLEdBQXdCRixLQUFLLENBQUNWLFlBQVksQ0FBQztNQUUzQyxNQUFBYSxPQUFBLEdBQWdCRixZQUFZLEdBQUdqQixRQUFRLENBQUNpQixZQUFtQixDQUFDLEdBQTVDLElBQTRDO01BQzVELE1BQUFHLFVBQUEsR0FBbUJGLGVBQWUsR0FBR2xCLFFBQVEsQ0FBQ2tCLGVBQXNCLENBQUMsR0FBbEQsSUFBa0Q7TUFFckUsSUFBSUMsT0FBcUIsSUFBckJDLFVBQXFCO1FBRXZCLE1BQUFDLFlBQUEsR0FBcUJ0QixnQkFBZ0IsQ0FBQ29CLE9BQU8sRUFBRUMsVUFBVSxFQUFFaEIsWUFBWSxDQUFDO1FBQ2pFUSxFQUFBLElBQUMsSUFBSSxDQUFRLEtBQXdCLENBQXhCLENBQUFYLFVBQVUsQ0FBQ29CLFlBQVksRUFBQyxDQUFHbEIsS0FBRyxDQUFFLEVBQTVDLElBQUksQ0FBK0M7UUFBcEQsTUFBQVksR0FBQTtNQUFvRDtJQUM1RDtJQUFBTixDQUFBLE1BQUFOLElBQUE7SUFBQU0sQ0FBQSxNQUFBTCxZQUFBO0lBQUFLLENBQUEsTUFBQUosWUFBQTtJQUFBSSxDQUFBLE1BQUFILFlBQUE7SUFBQUcsQ0FBQSxNQUFBRSxTQUFBO0lBQUFGLENBQUEsTUFBQUcsRUFBQTtFQUFBO0lBQUFBLEVBQUEsR0FBQUgsQ0FBQTtFQUFBO0VBQUEsSUFBQUcsRUFBQSxLQUFBQyxNQUFBLENBQUFDLEdBQUE7SUFBQSxPQUFBRixFQUFBO0VBQUE7RUFHRCxNQUFBVSxnQkFBQSxHQUF5QmxCLFlBQVksR0FBRyxHQUFHO0VBRTVCLE1BQUFtQixFQUFBLEdBQUFELGdCQUFnQixHQUFoQmhCLFlBQThDLEdBQTlDRCxZQUE4QztFQUFBLElBQUFtQixFQUFBO0VBQUEsSUFBQWYsQ0FBQSxRQUFBTixJQUFBLElBQUFNLENBQUEsUUFBQWMsRUFBQTtJQUEzREMsRUFBQSxJQUFDLElBQUksQ0FBUSxLQUE4QyxDQUE5QyxDQUFBRCxFQUE2QyxDQUFDLENBQUdwQixLQUFHLENBQUUsRUFBbEUsSUFBSSxDQUFxRTtJQUFBTSxDQUFBLE1BQUFOLElBQUE7SUFBQU0sQ0FBQSxNQUFBYyxFQUFBO0lBQUFkLENBQUEsTUFBQWUsRUFBQTtFQUFBO0lBQUFBLEVBQUEsR0FBQWYsQ0FBQTtFQUFBO0VBQUEsT0FBMUVlLEVBQTBFO0FBQUEiLCJpZ25vcmVMaXN0IjpbXX0=
+328
View File
@@ -0,0 +1,328 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { stringWidth } from '../../ink/stringWidth.js';
import { Text, useTheme } from '../../ink.js';
import { getGraphemeSegmenter } from '../../utils/intl.js';
import { getTheme, type Theme } from '../../utils/theme.js';
import type { SpinnerMode } from './types.js';
import { interpolateColor, parseRGB, toRGBColor } from './utils.js';
type Props = {
message: string;
mode: SpinnerMode;
messageColor: keyof Theme;
glimmerIndex: number;
flashOpacity: number;
shimmerColor: keyof Theme;
stalledIntensity?: number;
};
const ERROR_RED = {
r: 171,
g: 43,
b: 63
};
export function GlimmerMessage(t0) {
const $ = _c(75);
const {
message,
mode,
messageColor,
glimmerIndex,
flashOpacity,
shimmerColor,
stalledIntensity: t1
} = t0;
const stalledIntensity = t1 === undefined ? 0 : t1;
const [themeName] = useTheme();
let messageWidth;
let segments;
let t2;
if ($[0] !== flashOpacity || $[1] !== message || $[2] !== messageColor || $[3] !== mode || $[4] !== shimmerColor || $[5] !== stalledIntensity || $[6] !== themeName) {
t2 = Symbol.for("react.early_return_sentinel");
bb0: {
const theme = getTheme(themeName);
let segs;
if ($[10] !== message) {
segs = [];
for (const {
segment
} of getGraphemeSegmenter().segment(message)) {
segs.push({
segment,
width: stringWidth(segment)
});
}
$[10] = message;
$[11] = segs;
} else {
segs = $[11];
}
let t3;
if ($[12] !== message) {
t3 = stringWidth(message);
$[12] = message;
$[13] = t3;
} else {
t3 = $[13];
}
let t4;
if ($[14] !== segs || $[15] !== t3) {
t4 = {
segments: segs,
messageWidth: t3
};
$[14] = segs;
$[15] = t3;
$[16] = t4;
} else {
t4 = $[16];
}
({
segments,
messageWidth
} = t4);
if (!message) {
t2 = null;
break bb0;
}
if (stalledIntensity > 0) {
const baseColorStr = theme[messageColor];
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null;
if (baseRGB) {
const interpolated = interpolateColor(baseRGB, ERROR_RED, stalledIntensity);
const color = toRGBColor(interpolated);
let t5;
if ($[17] !== color) {
t5 = <Text color={color}> </Text>;
$[17] = color;
$[18] = t5;
} else {
t5 = $[18];
}
t2 = <><Text color={color}>{message}</Text>{t5}</>;
break bb0;
}
const color_0 = stalledIntensity > 0.5 ? "error" : messageColor;
let t5;
if ($[19] !== color_0 || $[20] !== message) {
t5 = <Text color={color_0}>{message}</Text>;
$[19] = color_0;
$[20] = message;
$[21] = t5;
} else {
t5 = $[21];
}
let t6;
if ($[22] !== color_0) {
t6 = <Text color={color_0}> </Text>;
$[22] = color_0;
$[23] = t6;
} else {
t6 = $[23];
}
let t7;
if ($[24] !== t5 || $[25] !== t6) {
t7 = <>{t5}{t6}</>;
$[24] = t5;
$[25] = t6;
$[26] = t7;
} else {
t7 = $[26];
}
t2 = t7;
break bb0;
}
if (mode === "tool-use") {
const baseColorStr_0 = theme[messageColor];
const shimmerColorStr = theme[shimmerColor];
const baseRGB_0 = baseColorStr_0 ? parseRGB(baseColorStr_0) : null;
const shimmerRGB = shimmerColorStr ? parseRGB(shimmerColorStr) : null;
if (baseRGB_0 && shimmerRGB) {
const interpolated_0 = interpolateColor(baseRGB_0, shimmerRGB, flashOpacity);
const t5 = <Text color={toRGBColor(interpolated_0)}>{message}</Text>;
let t6;
if ($[27] !== messageColor) {
t6 = <Text color={messageColor}> </Text>;
$[27] = messageColor;
$[28] = t6;
} else {
t6 = $[28];
}
let t7;
if ($[29] !== t5 || $[30] !== t6) {
t7 = <>{t5}{t6}</>;
$[29] = t5;
$[30] = t6;
$[31] = t7;
} else {
t7 = $[31];
}
t2 = t7;
break bb0;
}
const color_1 = flashOpacity > 0.5 ? shimmerColor : messageColor;
let t5;
if ($[32] !== color_1 || $[33] !== message) {
t5 = <Text color={color_1}>{message}</Text>;
$[32] = color_1;
$[33] = message;
$[34] = t5;
} else {
t5 = $[34];
}
let t6;
if ($[35] !== messageColor) {
t6 = <Text color={messageColor}> </Text>;
$[35] = messageColor;
$[36] = t6;
} else {
t6 = $[36];
}
let t7;
if ($[37] !== t5 || $[38] !== t6) {
t7 = <>{t5}{t6}</>;
$[37] = t5;
$[38] = t6;
$[39] = t7;
} else {
t7 = $[39];
}
t2 = t7;
break bb0;
}
}
$[0] = flashOpacity;
$[1] = message;
$[2] = messageColor;
$[3] = mode;
$[4] = shimmerColor;
$[5] = stalledIntensity;
$[6] = themeName;
$[7] = messageWidth;
$[8] = segments;
$[9] = t2;
} else {
messageWidth = $[7];
segments = $[8];
t2 = $[9];
}
if (t2 !== Symbol.for("react.early_return_sentinel")) {
return t2;
}
const shimmerStart = glimmerIndex - 1;
const shimmerEnd = glimmerIndex + 1;
if (shimmerStart >= messageWidth || shimmerEnd < 0) {
let t3;
if ($[40] !== message || $[41] !== messageColor) {
t3 = <Text color={messageColor}>{message}</Text>;
$[40] = message;
$[41] = messageColor;
$[42] = t3;
} else {
t3 = $[42];
}
let t4;
if ($[43] !== messageColor) {
t4 = <Text color={messageColor}> </Text>;
$[43] = messageColor;
$[44] = t4;
} else {
t4 = $[44];
}
let t5;
if ($[45] !== t3 || $[46] !== t4) {
t5 = <>{t3}{t4}</>;
$[45] = t3;
$[46] = t4;
$[47] = t5;
} else {
t5 = $[47];
}
return t5;
}
const clampedStart = Math.max(0, shimmerStart);
let colPos = 0;
let before = "";
let shim = "";
let after = "";
if ($[48] !== after || $[49] !== before || $[50] !== clampedStart || $[51] !== colPos || $[52] !== segments || $[53] !== shim || $[54] !== shimmerEnd) {
for (const {
segment: segment_0,
width
} of segments) {
if (colPos + width <= clampedStart) {
before = before + segment_0;
} else {
if (colPos > shimmerEnd) {
after = after + segment_0;
} else {
shim = shim + segment_0;
}
}
colPos = colPos + width;
}
$[48] = after;
$[49] = before;
$[50] = clampedStart;
$[51] = colPos;
$[52] = segments;
$[53] = shim;
$[54] = shimmerEnd;
$[55] = before;
$[56] = after;
$[57] = shim;
$[58] = colPos;
} else {
before = $[55];
after = $[56];
shim = $[57];
colPos = $[58];
}
let t3;
if ($[59] !== before || $[60] !== messageColor) {
t3 = before && <Text color={messageColor}>{before}</Text>;
$[59] = before;
$[60] = messageColor;
$[61] = t3;
} else {
t3 = $[61];
}
let t4;
if ($[62] !== shim || $[63] !== shimmerColor) {
t4 = <Text color={shimmerColor}>{shim}</Text>;
$[62] = shim;
$[63] = shimmerColor;
$[64] = t4;
} else {
t4 = $[64];
}
let t5;
if ($[65] !== after || $[66] !== messageColor) {
t5 = after && <Text color={messageColor}>{after}</Text>;
$[65] = after;
$[66] = messageColor;
$[67] = t5;
} else {
t5 = $[67];
}
let t6;
if ($[68] !== messageColor) {
t6 = <Text color={messageColor}> </Text>;
$[68] = messageColor;
$[69] = t6;
} else {
t6 = $[69];
}
let t7;
if ($[70] !== t3 || $[71] !== t4 || $[72] !== t5 || $[73] !== t6) {
t7 = <>{t3}{t4}{t5}{t6}</>;
$[70] = t3;
$[71] = t4;
$[72] = t5;
$[73] = t6;
$[74] = t7;
} else {
t7 = $[74];
}
return t7;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsInN0cmluZ1dpZHRoIiwiVGV4dCIsInVzZVRoZW1lIiwiZ2V0R3JhcGhlbWVTZWdtZW50ZXIiLCJnZXRUaGVtZSIsIlRoZW1lIiwiU3Bpbm5lck1vZGUiLCJpbnRlcnBvbGF0ZUNvbG9yIiwicGFyc2VSR0IiLCJ0b1JHQkNvbG9yIiwiUHJvcHMiLCJtZXNzYWdlIiwibW9kZSIsIm1lc3NhZ2VDb2xvciIsImdsaW1tZXJJbmRleCIsImZsYXNoT3BhY2l0eSIsInNoaW1tZXJDb2xvciIsInN0YWxsZWRJbnRlbnNpdHkiLCJFUlJPUl9SRUQiLCJyIiwiZyIsImIiLCJHbGltbWVyTWVzc2FnZSIsInQwIiwiJCIsIl9jIiwidDEiLCJ1bmRlZmluZWQiLCJ0aGVtZU5hbWUiLCJtZXNzYWdlV2lkdGgiLCJzZWdtZW50cyIsInQyIiwiU3ltYm9sIiwiZm9yIiwiYmIwIiwidGhlbWUiLCJzZWdzIiwic2VnbWVudCIsInB1c2giLCJ3aWR0aCIsInQzIiwidDQiLCJiYXNlQ29sb3JTdHIiLCJiYXNlUkdCIiwiaW50ZXJwb2xhdGVkIiwiY29sb3IiLCJ0NSIsImNvbG9yXzAiLCJ0NiIsInQ3IiwiYmFzZUNvbG9yU3RyXzAiLCJzaGltbWVyQ29sb3JTdHIiLCJiYXNlUkdCXzAiLCJzaGltbWVyUkdCIiwiaW50ZXJwb2xhdGVkXzAiLCJjb2xvcl8xIiwic2hpbW1lclN0YXJ0Iiwic2hpbW1lckVuZCIsImNsYW1wZWRTdGFydCIsIk1hdGgiLCJtYXgiLCJjb2xQb3MiLCJiZWZvcmUiLCJzaGltIiwiYWZ0ZXIiLCJzZWdtZW50XzAiXSwic291cmNlcyI6WyJHbGltbWVyTWVzc2FnZS50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBzdHJpbmdXaWR0aCB9IGZyb20gJy4uLy4uL2luay9zdHJpbmdXaWR0aC5qcydcbmltcG9ydCB7IFRleHQsIHVzZVRoZW1lIH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHsgZ2V0R3JhcGhlbWVTZWdtZW50ZXIgfSBmcm9tICcuLi8uLi91dGlscy9pbnRsLmpzJ1xuaW1wb3J0IHsgZ2V0VGhlbWUsIHR5cGUgVGhlbWUgfSBmcm9tICcuLi8uLi91dGlscy90aGVtZS5qcydcbmltcG9ydCB0eXBlIHsgU3Bpbm5lck1vZGUgfSBmcm9tICcuL3R5cGVzLmpzJ1xuaW1wb3J0IHsgaW50ZXJwb2xhdGVDb2xvciwgcGFyc2VSR0IsIHRvUkdCQ29sb3IgfSBmcm9tICcuL3V0aWxzLmpzJ1xuXG50eXBlIFByb3BzID0ge1xuICBtZXNzYWdlOiBzdHJpbmdcbiAgbW9kZTogU3Bpbm5lck1vZGVcbiAgbWVzc2FnZUNvbG9yOiBrZXlvZiBUaGVtZVxuICBnbGltbWVySW5kZXg6IG51bWJlclxuICBmbGFzaE9wYWNpdHk6IG51bWJlclxuICBzaGltbWVyQ29sb3I6IGtleW9mIFRoZW1lXG4gIHN0YWxsZWRJbnRlbnNpdHk/OiBudW1iZXJcbn1cblxuY29uc3QgRVJST1JfUkVEID0geyByOiAxNzEsIGc6IDQzLCBiOiA2MyB9XG5cbmV4cG9ydCBmdW5jdGlvbiBHbGltbWVyTWVzc2FnZSh7XG4gIG1lc3NhZ2UsXG4gIG1vZGUsXG4gIG1lc3NhZ2VDb2xvcixcbiAgZ2xpbW1lckluZGV4LFxuICBmbGFzaE9wYWNpdHksXG4gIHNoaW1tZXJDb2xvcixcbiAgc3RhbGxlZEludGVuc2l0eSA9IDAsXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IFt0aGVtZU5hbWVdID0gdXNlVGhlbWUoKVxuICBjb25zdCB0aGVtZSA9IGdldFRoZW1lKHRoZW1lTmFtZSlcblxuICAvLyBUaGlzIGNvbXBvbmVudCByZS1yZW5kZXJzIGF0IDIwZnBzIChnbGltbWVySW5kZXggY2hhbmdlcyBldmVyeSA1MG1zKSBidXRcbiAgLy8gbWVzc2FnZSBpcyBzdGFibGUgd2l0aGluIGEgdHVybi4gUHJlY29tcHV0ZSBncmFwaGVtZSBzZWdtZW50YXRpb24gKyB3aWR0aHNcbiAgLy8gb25jZSBwZXIgbWVzc2FnZSBpbnN0ZWFkIG9mIHBlciBmcmFtZS4gTWVhc3VyZWQgLTgxJSBvbiB0aGUgc2hpbW1lciBwYXRoLlxuICBjb25zdCB7IHNlZ21lbnRzLCBtZXNzYWdlV2lkdGggfSA9IFJlYWN0LnVzZU1lbW8oKCkgPT4ge1xuICAgIGNvbnN0IHNlZ3M6IHsgc2VnbWVudDogc3RyaW5nOyB3aWR0aDogbnVtYmVyIH1bXSA9IFtdXG4gICAgZm9yIChjb25zdCB7IHNlZ21lbnQgfSBvZiBnZXRHcmFwaGVtZVNlZ21lbnRlcigpLnNlZ21lbnQobWVzc2FnZSkpIHtcbiAgICAgIHNlZ3MucHVzaCh7IHNlZ21lbnQsIHdpZHRoOiBzdHJpbmdXaWR0aChzZWdtZW50KSB9KVxuICAgIH1cbiAgICByZXR1cm4geyBzZWdtZW50czogc2VncywgbWVzc2FnZVdpZHRoOiBzdHJpbmdXaWR0aChtZXNzYWdlKSB9XG4gIH0sIFttZXNzYWdlXSlcblxuICBpZiAoIW1lc3NhZ2UpIHJldHVybiBudWxsXG5cbiAgLy8gV2hlbiBzdGFsbGVkLCBzaG93IHRleHQgdGhhdCBzbW9vdGhseSB0cmFuc2l0aW9ucyB0byByZWRcbiAgaWYgKHN0YWxsZWRJbnRlbnNpdHkgPiAwKSB7XG4gICAgY29uc3QgYmFzZUNvbG9yU3RyID0gdGhlbWVbbWVzc2FnZUNvbG9yXVxuICAgIGNvbnN0IGJhc2VSR0IgPSBiYXNlQ29sb3JTdHIgPyBwYXJzZVJHQihiYXNlQ29sb3JTdHIpIDogbnVsbFxuXG4gICAgaWYgKGJhc2VSR0IpIHtcbiAgICAgIGNvbnN0IGludGVycG9sYXRlZCA9IGludGVycG9sYXRlQ29sb3IoXG4gICAgICAgIGJhc2VSR0IsXG4gICAgICAgIEVSUk9SX1JFRCxcbiAgICAgICAgc3RhbGxlZEludGVuc2l0eSxcbiAgICAgIClcbiAgICAgIGNvbnN0IGNvbG9yID0gdG9SR0JDb2xvcihpbnRlcnBvbGF0ZWQpXG4gICAgICByZXR1cm4gKFxuICAgICAgICA8PlxuICAgICAgICAgIDxUZXh0IGNvbG9yPXtjb2xvcn0+e21lc3NhZ2V9PC9UZXh0PlxuICAgICAgICAgIDxUZXh0IGNvbG9yPXtjb2xvcn0+IDwvVGV4dD5cbiAgICAgICAgPC8+XG4gICAgICApXG4gICAgfVxuXG4gICAgLy8gRmFsbGJhY2sgZm9yIEFOU0kgdGhlbWVzOiB1c2UgbWVzc2FnZUNvbG9yIHVudGlsIGZ1bGx5IHN0YWxsZWQsIHRoZW4gZXJyb3JcbiAgICBjb25zdCBjb2xvciA9IHN0YWxsZWRJbnRlbnNpdHkgPiAwLjUgPyAnZXJyb3InIDogbWVzc2FnZUNvbG9yXG4gICAgcmV0dXJuIChcbiAgICAgIDw+XG4gICAgICAgIDxUZXh0IGNvbG9yPXtjb2xvcn0+e21lc3NhZ2V9PC9UZXh0PlxuICAgICAgICA8VGV4dCBjb2xvcj17Y29sb3J9PiA8L1RleHQ+XG4gICAgICA8Lz5cbiAgICApXG4gIH1cblxuICAvLyB0b29sLXVzZSBtb2RlOiBhbGwgY2hhcnMgZmxhc2ggd2l0aCB0aGUgc2FtZSBvcGFjaXR5LCBzbyByZW5kZXIgYXMgYVxuICAvLyBzaW5nbGUgPFRleHQ+IGluc3RlYWQgb2YgTiBpbmRpdmlkdWFsIEZsYXNoaW5nQ2hhciBjb21wb25lbnRzLlxuICBpZiAobW9kZSA9PT0gJ3Rvb2wtdXNlJykge1xuICAgIGNvbnN0IGJhc2VDb2xvclN0ciA9IHRoZW1lW21lc3NhZ2VDb2xvcl1cbiAgICBjb25zdCBzaGltbWVyQ29sb3JTdHIgPSB0aGVtZVtzaGltbWVyQ29sb3JdXG4gICAgY29uc3QgYmFzZVJHQiA9IGJhc2VDb2xvclN0ciA/IHBhcnNlUkdCKGJhc2VDb2xvclN0cikgOiBudWxsXG4gICAgY29uc3Qgc2hpbW1lclJHQiA9IHNoaW1tZXJDb2xvclN0ciA/IHBhcnNlUkdCKHNoaW1tZXJDb2xvclN0cikgOiBudWxsXG5cbiAgICBpZiAoYmFzZVJHQiAmJiBzaGltbWVyUkdCKSB7XG4gICAgICBjb25zdCBpbnRlcnBvbGF0ZWQgPSBpbnRlcnBvbGF0ZUNvbG9yKGJhc2VSR0IsIHNoaW1tZXJSR0IsIGZsYXNoT3BhY2l0eSlcbiAgICAgIHJldHVybiAoXG4gICAgICAgIDw+XG4gICAgICAgICAgPFRleHQgY29sb3I9e3RvUkdCQ29sb3IoaW50ZXJwb2xhdGVkKX0+e21lc3NhZ2V9PC9UZXh0PlxuICAgICAgICA Line truncated
+36
View File
@@ -0,0 +1,36 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { Text } from '../../ink.js';
import type { Theme } from '../../utils/theme.js';
type Props = {
char: string;
index: number;
glimmerIndex: number;
messageColor: keyof Theme;
shimmerColor: keyof Theme;
};
export function ShimmerChar(t0) {
const $ = _c(3);
const {
char,
index,
glimmerIndex,
messageColor,
shimmerColor
} = t0;
const isHighlighted = index === glimmerIndex;
const isNearHighlight = Math.abs(index - glimmerIndex) === 1;
const shouldUseShimmer = isHighlighted || isNearHighlight;
const t1 = shouldUseShimmer ? shimmerColor : messageColor;
let t2;
if ($[0] !== char || $[1] !== t1) {
t2 = <Text color={t1}>{char}</Text>;
$[0] = char;
$[1] = t1;
$[2] = t2;
} else {
t2 = $[2];
}
return t2;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIlRleHQiLCJUaGVtZSIsIlByb3BzIiwiY2hhciIsImluZGV4IiwiZ2xpbW1lckluZGV4IiwibWVzc2FnZUNvbG9yIiwic2hpbW1lckNvbG9yIiwiU2hpbW1lckNoYXIiLCJ0MCIsIiQiLCJfYyIsImlzSGlnaGxpZ2h0ZWQiLCJpc05lYXJIaWdobGlnaHQiLCJNYXRoIiwiYWJzIiwic2hvdWxkVXNlU2hpbW1lciIsInQxIiwidDIiXSwic291cmNlcyI6WyJTaGltbWVyQ2hhci50c3giXSwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBUZXh0IH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHR5cGUgeyBUaGVtZSB9IGZyb20gJy4uLy4uL3V0aWxzL3RoZW1lLmpzJ1xuXG50eXBlIFByb3BzID0ge1xuICBjaGFyOiBzdHJpbmdcbiAgaW5kZXg6IG51bWJlclxuICBnbGltbWVySW5kZXg6IG51bWJlclxuICBtZXNzYWdlQ29sb3I6IGtleW9mIFRoZW1lXG4gIHNoaW1tZXJDb2xvcjoga2V5b2YgVGhlbWVcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFNoaW1tZXJDaGFyKHtcbiAgY2hhcixcbiAgaW5kZXgsXG4gIGdsaW1tZXJJbmRleCxcbiAgbWVzc2FnZUNvbG9yLFxuICBzaGltbWVyQ29sb3IsXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IGlzSGlnaGxpZ2h0ZWQgPSBpbmRleCA9PT0gZ2xpbW1lckluZGV4XG4gIGNvbnN0IGlzTmVhckhpZ2hsaWdodCA9IE1hdGguYWJzKGluZGV4IC0gZ2xpbW1lckluZGV4KSA9PT0gMVxuICBjb25zdCBzaG91bGRVc2VTaGltbWVyID0gaXNIaWdobGlnaHRlZCB8fCBpc05lYXJIaWdobGlnaHRcblxuICByZXR1cm4gKFxuICAgIDxUZXh0IGNvbG9yPXtzaG91bGRVc2VTaGltbWVyID8gc2hpbW1lckNvbG9yIDogbWVzc2FnZUNvbG9yfT57Y2hhcn08L1RleHQ+XG4gIClcbn1cbiJdLCJtYXBwaW5ncyI6IjtBQUFBLE9BQU8sS0FBS0EsS0FBSyxNQUFNLE9BQU87QUFDOUIsU0FBU0MsSUFBSSxRQUFRLGNBQWM7QUFDbkMsY0FBY0MsS0FBSyxRQUFRLHNCQUFzQjtBQUVqRCxLQUFLQyxLQUFLLEdBQUc7RUFDWEMsSUFBSSxFQUFFLE1BQU07RUFDWkMsS0FBSyxFQUFFLE1BQU07RUFDYkMsWUFBWSxFQUFFLE1BQU07RUFDcEJDLFlBQVksRUFBRSxNQUFNTCxLQUFLO0VBQ3pCTSxZQUFZLEVBQUUsTUFBTU4sS0FBSztBQUMzQixDQUFDO0FBRUQsT0FBTyxTQUFBTyxZQUFBQyxFQUFBO0VBQUEsTUFBQUMsQ0FBQSxHQUFBQyxFQUFBO0VBQXFCO0lBQUFSLElBQUE7SUFBQUMsS0FBQTtJQUFBQyxZQUFBO0lBQUFDLFlBQUE7SUFBQUM7RUFBQSxJQUFBRSxFQU1wQjtFQUNOLE1BQUFHLGFBQUEsR0FBc0JSLEtBQUssS0FBS0MsWUFBWTtFQUM1QyxNQUFBUSxlQUFBLEdBQXdCQyxJQUFJLENBQUFDLEdBQUksQ0FBQ1gsS0FBSyxHQUFHQyxZQUFZLENBQUMsS0FBSyxDQUFDO0VBQzVELE1BQUFXLGdCQUFBLEdBQXlCSixhQUFnQyxJQUFoQ0MsZUFBZ0M7RUFHMUMsTUFBQUksRUFBQSxHQUFBRCxnQkFBZ0IsR0FBaEJULFlBQThDLEdBQTlDRCxZQUE4QztFQUFBLElBQUFZLEVBQUE7RUFBQSxJQUFBUixDQUFBLFFBQUFQLElBQUEsSUFBQU8sQ0FBQSxRQUFBTyxFQUFBO0lBQTNEQyxFQUFBLElBQUMsSUFBSSxDQUFRLEtBQThDLENBQTlDLENBQUFELEVBQTZDLENBQUMsQ0FBR2QsS0FBRyxDQUFFLEVBQWxFLElBQUksQ0FBcUU7SUFBQU8sQ0FBQSxNQUFBUCxJQUFBO0lBQUFPLENBQUEsTUFBQU8sRUFBQTtJQUFBUCxDQUFBLE1BQUFRLEVBQUE7RUFBQTtJQUFBQSxFQUFBLEdBQUFSLENBQUE7RUFBQTtFQUFBLE9BQTFFUSxFQUEwRTtBQUFBIiwiaWdub3JlTGlzdCI6W119
@@ -0,0 +1,265 @@
import { c as _c } from "react/compiler-runtime";
import figures from 'figures';
import * as React from 'react';
import { useMemo, useRef } from 'react';
import { stringWidth } from '../../ink/stringWidth.js';
import { Box, Text, useAnimationFrame } from '../../ink.js';
import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js';
import { formatDuration, formatNumber } from '../../utils/format.js';
import { toInkColor } from '../../utils/ink.js';
import type { Theme } from '../../utils/theme.js';
import { Byline } from '../design-system/Byline.js';
import { GlimmerMessage } from './GlimmerMessage.js';
import { SpinnerGlyph } from './SpinnerGlyph.js';
import type { SpinnerMode } from './types.js';
import { useStalledAnimation } from './useStalledAnimation.js';
import { interpolateColor, toRGBColor } from './utils.js';
const SEP_WIDTH = stringWidth(' · ');
const THINKING_BARE_WIDTH = stringWidth('thinking');
const SHOW_TOKENS_AFTER_MS = 30_000;
// Thinking shimmer constants. Previously lived in a separate ThinkingShimmerText
// component with its own useAnimationFrame(50) — inlined here to reuse our
// existing 50ms clock and eliminate the redundant subscriber.
const THINKING_INACTIVE = {
r: 153,
g: 153,
b: 153
};
const THINKING_INACTIVE_SHIMMER = {
r: 185,
g: 185,
b: 185
};
const THINKING_DELAY_MS = 3000;
const THINKING_GLOW_PERIOD_S = 2;
export type SpinnerAnimationRowProps = {
// Animation inputs
mode: SpinnerMode;
reducedMotion: boolean;
hasActiveTools: boolean;
responseLengthRef: React.RefObject<number>;
// Message (stable within a turn)
message: string;
messageColor: keyof Theme;
shimmerColor: keyof Theme;
overrideColor?: keyof Theme | null;
// Timer refs (stable references)
loadingStartTimeRef: React.RefObject<number>;
totalPausedMsRef: React.RefObject<number>;
pauseStartTimeRef: React.RefObject<number | null>;
// Display flags
spinnerSuffix?: string | null;
verbose: boolean;
columns: number;
// Teammate-derived (computed by parent from tasks)
hasRunningTeammates: boolean;
teammateTokens: number;
foregroundedTeammate: InProcessTeammateTaskState | undefined;
/** Leader's turn has completed. Suppresses stall-red since responseLengthRef/hasActiveTools track leader state only. */
leaderIsIdle?: boolean;
// Thinking (state owned by parent, mode-dependent)
thinkingStatus: 'thinking' | number | null;
effortSuffix: string;
};
/**
* The 50ms-animated portion of SpinnerWithVerb. Owns useAnimationFrame(50)
* and all values derived from the animation clock (frame, glimmer, token
* counter animation, elapsed-time, stalled intensity, thinking shimmer).
*
* The parent SpinnerWithVerb is freed from the 50ms render loop and only
* re-renders when its props/app state change (~25x/turn instead of ~383x).
* That keeps the outer Box shells, useAppState selectors, task filtering,
* and tip/tree subtrees out of the hot animation path.
*/
export function SpinnerAnimationRow({
mode,
reducedMotion,
hasActiveTools,
responseLengthRef,
message,
messageColor,
shimmerColor,
overrideColor,
loadingStartTimeRef,
totalPausedMsRef,
pauseStartTimeRef,
spinnerSuffix,
verbose,
columns,
hasRunningTeammates,
teammateTokens,
foregroundedTeammate,
leaderIsIdle = false,
thinkingStatus,
effortSuffix
}: SpinnerAnimationRowProps): React.ReactNode {
const [viewportRef, time] = useAnimationFrame(reducedMotion ? null : 50);
// === Elapsed time (wall-clock, derived from refs each frame) ===
const now = Date.now();
const elapsedTimeMs = pauseStartTimeRef.current !== null ? pauseStartTimeRef.current - loadingStartTimeRef.current - totalPausedMsRef.current : now - loadingStartTimeRef.current - totalPausedMsRef.current;
// Track wall-clock turn start for teammates. While a swarm is running the
// leader's elapsedTimeMs may jump around (new API calls reset
// loadingStartTimeRef; pauses freeze it), so we anchor to the earliest
// derived start seen so far. When no teammates are running this just tracks
// derivedStart every frame, effectively resetting for the next swarm.
const derivedStart = now - elapsedTimeMs;
const turnStartRef = useRef(derivedStart);
if (!hasRunningTeammates || derivedStart < turnStartRef.current) {
turnStartRef.current = derivedStart;
}
// === Animation derivations from `time` ===
const currentResponseLength = responseLengthRef.current;
// Suppress stall detection when leader is idle — responseLengthRef and
// hasActiveTools both track leader state. When viewing an active teammate
// while leader is idle, they'd otherwise flag a false stall after 3s.
// Treating leaderIsIdle like hasActiveTools resets the stall timer.
const {
isStalled,
stalledIntensity
} = useStalledAnimation(time, currentResponseLength, hasActiveTools || leaderIsIdle, reducedMotion);
const frame = reducedMotion ? 0 : Math.floor(time / 120);
const glimmerSpeed = mode === 'requesting' ? 50 : 200;
// message is stable within a turn; stringWidth is expensive enough (Bun native
// call per code point) to memoize explicitly across the 50ms loop.
const glimmerMessageWidth = useMemo(() => stringWidth(message), [message]);
const cycleLength = glimmerMessageWidth + 20;
const cyclePosition = Math.floor(time / glimmerSpeed);
const glimmerIndex = reducedMotion ? -100 : isStalled ? -100 : mode === 'requesting' ? cyclePosition % cycleLength - 10 : glimmerMessageWidth + 10 - cyclePosition % cycleLength;
const flashOpacity = reducedMotion ? 0 : mode === 'tool-use' ? (Math.sin(time / 1000 * Math.PI) + 1) / 2 : 0;
// === Token counter animation (smooth increment, driven by 50ms clock) ===
const tokenCounterRef = useRef(currentResponseLength);
if (reducedMotion) {
tokenCounterRef.current = currentResponseLength;
} else {
const gap = currentResponseLength - tokenCounterRef.current;
if (gap > 0) {
let increment;
if (gap < 70) {
increment = 3;
} else if (gap < 200) {
increment = Math.max(8, Math.ceil(gap * 0.15));
} else {
increment = 50;
}
tokenCounterRef.current = Math.min(tokenCounterRef.current + increment, currentResponseLength);
}
}
const displayedResponseLength = tokenCounterRef.current;
const leaderTokens = Math.round(displayedResponseLength / 4);
const effectiveElapsedMs = hasRunningTeammates ? Math.max(elapsedTimeMs, now - turnStartRef.current) : elapsedTimeMs;
const timerText = formatDuration(effectiveElapsedMs);
const timerWidth = stringWidth(timerText);
// === Token count (leader + teammates, or foregrounded teammate) ===
const totalTokens = foregroundedTeammate && !foregroundedTeammate.isIdle ? foregroundedTeammate.progress?.tokenCount ?? 0 : leaderTokens + teammateTokens;
const tokenCount = formatNumber(totalTokens);
const tokensText = hasRunningTeammates ? `${tokenCount} tokens` : `${figures.arrowDown} ${tokenCount} tokens`;
const tokensWidth = stringWidth(tokensText);
// === Thinking text (may shrink to fit) ===
let thinkingText = thinkingStatus === 'thinking' ? `thinking${effortSuffix}` : typeof thinkingStatus === 'number' ? `thought for ${Math.max(1, Math.round(thinkingStatus / 1000))}s` : null;
let thinkingWidthValue = thinkingText ? stringWidth(thinkingText) : 0;
// === Progressive width gating ===
const messageWidth = glimmerMessageWidth + 2;
const sep = SEP_WIDTH;
const wantsThinking = thinkingStatus !== null;
const wantsTimerAndTokens = verbose || hasRunningTeammates || effectiveElapsedMs > SHOW_TOKENS_AFTER_MS;
const availableSpace = columns - messageWidth - 5;
let showThinking = wantsThinking && availableSpace > thinkingWidthValue;
if (!showThinking && wantsThinking && thinkingStatus === 'thinking' && effortSuffix) {
if (availableSpace > THINKING_BARE_WIDTH) {
thinkingText = 'thinking';
thinkingWidthValue = THINKING_BARE_WIDTH;
showThinking = true;
}
}
const usedAfterThinking = showThinking ? thinkingWidthValue + sep : 0;
const showTimer = wantsTimerAndTokens && availableSpace > usedAfterThinking + timerWidth;
const usedAfterTimer = usedAfterThinking + (showTimer ? timerWidth + sep : 0);
const showTokens = wantsTimerAndTokens && totalTokens > 0 && availableSpace > usedAfterTimer + tokensWidth;
const thinkingOnly = showThinking && thinkingStatus === 'thinking' && !spinnerSuffix && !showTimer && !showTokens && true;
// === Thinking shimmer color (formerly ThinkingShimmerText's own timer) ===
// Same sine-wave opacity, but derived from our shared `time` instead of a
// second useAnimationFrame(50) subscription.
const thinkingElapsedSec = (time - THINKING_DELAY_MS) / 1000;
const thinkingOpacity = time < THINKING_DELAY_MS ? 0 : (Math.sin(thinkingElapsedSec * Math.PI * 2 / THINKING_GLOW_PERIOD_S) + 1) / 2;
const thinkingShimmerColor = toRGBColor(interpolateColor(THINKING_INACTIVE, THINKING_INACTIVE_SHIMMER, thinkingOpacity));
// === Build status parts ===
const parts = [...(spinnerSuffix ? [<Text dimColor key="suffix">
{spinnerSuffix}
</Text>] : []), ...(showTimer ? [<Text dimColor key="elapsedTime">
{timerText}
</Text>] : []), ...(showTokens ? [<Box flexDirection="row" key="tokens">
{!hasRunningTeammates && <SpinnerModeGlyph mode={mode} />}
<Text dimColor>{tokenCount} tokens</Text>
</Box>] : []), ...(showThinking && thinkingText ? [thinkingStatus === 'thinking' && !reducedMotion ? <Text key="thinking" color={thinkingShimmerColor}>
{thinkingOnly ? `(${thinkingText})` : thinkingText}
</Text> : <Text dimColor key="thinking">
{thinkingText}
</Text>] : [])];
const status = foregroundedTeammate && !foregroundedTeammate.isIdle ? <>
<Text dimColor>(esc to interrupt </Text>
<Text color={toInkColor(foregroundedTeammate.identity.color)}>
{foregroundedTeammate.identity.agentName}
</Text>
<Text dimColor>)</Text>
</> : !foregroundedTeammate && parts.length > 0 ? thinkingOnly ? <Byline>{parts}</Byline> : <>
<Text dimColor>(</Text>
<Byline>{parts}</Byline>
<Text dimColor>)</Text>
</> : null;
return <Box ref={viewportRef} flexDirection="row" flexWrap="wrap" marginTop={1} width="100%">
<SpinnerGlyph frame={frame} messageColor={messageColor} stalledIntensity={overrideColor ? 0 : stalledIntensity} reducedMotion={reducedMotion} time={time} />
<GlimmerMessage message={message} mode={mode} messageColor={messageColor} glimmerIndex={glimmerIndex} flashOpacity={flashOpacity} shimmerColor={shimmerColor} stalledIntensity={overrideColor ? 0 : stalledIntensity} />
{status}
</Box>;
}
function SpinnerModeGlyph(t0) {
const $ = _c(2);
const {
mode
} = t0;
switch (mode) {
case "tool-input":
case "tool-use":
case "responding":
case "thinking":
{
let t1;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Box width={2}><Text dimColor={true}>{figures.arrowDown}</Text></Box>;
$[0] = t1;
} else {
t1 = $[0];
}
return t1;
}
case "requesting":
{
let t1;
if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <Box width={2}><Text dimColor={true}>{figures.arrowUp}</Text></Box>;
$[1] = t1;
} else {
t1 = $[1];
}
return t1;
}
}
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJ1c2VNZW1vIiwidXNlUmVmIiwic3RyaW5nV2lkdGgiLCJCb3giLCJUZXh0IiwidXNlQW5pbWF0aW9uRnJhbWUiLCJJblByb2Nlc3NUZWFtbWF0ZVRhc2tTdGF0ZSIsImZvcm1hdER1cmF0aW9uIiwiZm9ybWF0TnVtYmVyIiwidG9JbmtDb2xvciIsIlRoZW1lIiwiQnlsaW5lIiwiR2xpbW1lck1lc3NhZ2UiLCJTcGlubmVyR2x5cGgiLCJTcGlubmVyTW9kZSIsInVzZVN0YWxsZWRBbmltYXRpb24iLCJpbnRlcnBvbGF0ZUNvbG9yIiwidG9SR0JDb2xvciIsIlNFUF9XSURUSCIsIlRISU5LSU5HX0JBUkVfV0lEVEgiLCJTSE9XX1RPS0VOU19BRlRFUl9NUyIsIlRISU5LSU5HX0lOQUNUSVZFIiwiciIsImciLCJiIiwiVEhJTktJTkdfSU5BQ1RJVkVfU0hJTU1FUiIsIlRISU5LSU5HX0RFTEFZX01TIiwiVEhJTktJTkdfR0xPV19QRVJJT0RfUyIsIlNwaW5uZXJBbmltYXRpb25Sb3dQcm9wcyIsIm1vZGUiLCJyZWR1Y2VkTW90aW9uIiwiaGFzQWN0aXZlVG9vbHMiLCJyZXNwb25zZUxlbmd0aFJlZiIsIlJlZk9iamVjdCIsIm1lc3NhZ2UiLCJtZXNzYWdlQ29sb3IiLCJzaGltbWVyQ29sb3IiLCJvdmVycmlkZUNvbG9yIiwibG9hZGluZ1N0YXJ0VGltZVJlZiIsInRvdGFsUGF1c2VkTXNSZWYiLCJwYXVzZVN0YXJ0VGltZVJlZiIsInNwaW5uZXJTdWZmaXgiLCJ2ZXJib3NlIiwiY29sdW1ucyIsImhhc1J1bm5pbmdUZWFtbWF0ZXMiLCJ0ZWFtbWF0ZVRva2VucyIsImZvcmVncm91bmRlZFRlYW1tYXRlIiwibGVhZGVySXNJZGxlIiwidGhpbmtpbmdTdGF0dXMiLCJlZmZvcnRTdWZmaXgiLCJTcGlubmVyQW5pbWF0aW9uUm93IiwiUmVhY3ROb2RlIiwidmlld3BvcnRSZWYiLCJ0aW1lIiwibm93IiwiRGF0ZSIsImVsYXBzZWRUaW1lTXMiLCJjdXJyZW50IiwiZGVyaXZlZFN0YXJ0IiwidHVyblN0YXJ0UmVmIiwiY3VycmVudFJlc3BvbnNlTGVuZ3RoIiwiaXNTdGFsbGVkIiwic3RhbGxlZEludGVuc2l0eSIsImZyYW1lIiwiTWF0aCIsImZsb29yIiwiZ2xpbW1lclNwZWVkIiwiZ2xpbW1lck1lc3NhZ2VXaWR0aCIsImN5Y2xlTGVuZ3RoIiwiY3ljbGVQb3NpdGlvbiIsImdsaW1tZXJJbmRleCIsImZsYXNoT3BhY2l0eSIsInNpbiIsIlBJIiwidG9rZW5Db3VudGVyUmVmIiwiZ2FwIiwiaW5jcmVtZW50IiwibWF4IiwiY2VpbCIsIm1pbiIsImRpc3BsYXllZFJlc3BvbnNlTGVuZ3RoIiwibGVhZGVyVG9rZW5zIiwicm91bmQiLCJlZmZlY3RpdmVFbGFwc2VkTXMiLCJ0aW1lclRleHQiLCJ0aW1lcldpZHRoIiwidG90YWxUb2tlbnMiLCJpc0lkbGUiLCJwcm9ncmVzcyIsInRva2VuQ291bnQiLCJ0b2tlbnNUZXh0IiwiYXJyb3dEb3duIiwidG9rZW5zV2lkdGgiLCJ0aGlua2luZ1RleHQiLCJ0aGlua2luZ1dpZHRoVmFsdWUiLCJtZXNzYWdlV2lkdGgiLCJzZXAiLCJ3YW50c1RoaW5raW5nIiwid2FudHNUaW1lckFuZFRva2VucyIsImF2YWlsYWJsZVNwYWNlIiwic2hvd1RoaW5raW5nIiwidXNlZEFmdGVyVGhpbmtpbmciLCJzaG93VGltZXIiLCJ1c2VkQWZ0ZXJUaW1lciIsInNob3dUb2tlbnMiLCJ0aGlua2luZ09ubHkiLCJ0aGlua2luZ0VsYXBzZWRTZWMiLCJ0aGlua2luZ09wYWNpdHkiLCJ0aGlua2luZ1NoaW1tZXJDb2xvciIsInBhcnRzIiwic3RhdHVzIiwiaWRlbnRpdHkiLCJjb2xvciIsImFnZW50TmFtZSIsImxlbmd0aCIsIlNwaW5uZXJNb2RlR2x5cGgiLCJ0MCIsIiQiLCJfYyIsInQxIiwiU3ltYm9sIiwiZm9yIiwiYXJyb3dVcCJdLCJzb3VyY2VzIjpbIlNwaW5uZXJBbmltYXRpb25Sb3cudHN4Il0sInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBmaWd1cmVzIGZyb20gJ2ZpZ3VyZXMnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IHVzZU1lbW8sIHVzZVJlZiB9IGZyb20gJ3JlYWN0J1xuaW1wb3J0IHsgc3RyaW5nV2lkdGggfSBmcm9tICcuLi8uLi9pbmsvc3RyaW5nV2lkdGguanMnXG5pbXBvcnQgeyBCb3gsIFRleHQsIHVzZUFuaW1hdGlvbkZyYW1lIH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHR5cGUgeyBJblByb2Nlc3NUZWFtbWF0ZVRhc2tTdGF0ZSB9IGZyb20gJy4uLy4uL3Rhc2tzL0luUHJvY2Vzc1RlYW1tYXRlVGFzay90eXBlcy5qcydcbmltcG9ydCB7IGZvcm1hdER1cmF0aW9uLCBmb3JtYXROdW1iZXIgfSBmcm9tICcuLi8uLi91dGlscy9mb3JtYXQuanMnXG5pbXBvcnQgeyB0b0lua0NvbG9yIH0gZnJvbSAnLi4vLi4vdXRpbHMvaW5rLmpzJ1xuaW1wb3J0IHR5cGUgeyBUaGVtZSB9IGZyb20gJy4uLy4uL3V0aWxzL3RoZW1lLmpzJ1xuaW1wb3J0IHsgQnlsaW5lIH0gZnJvbSAnLi4vZGVzaWduLXN5c3RlbS9CeWxpbmUuanMnXG5pbXBvcnQgeyBHbGltbWVyTWVzc2FnZSB9IGZyb20gJy4vR2xpbW1lck1lc3NhZ2UuanMnXG5pbXBvcnQgeyBTcGlubmVyR2x5cGggfSBmcm9tICcuL1NwaW5uZXJHbHlwaC5qcydcbmltcG9ydCB0eXBlIHsgU3Bpbm5lck1vZGUgfSBmcm9tICcuL3R5cGVzLmpzJ1xuaW1wb3J0IHsgdXNlU3RhbGxlZEFuaW1hdGlvbiB9IGZyb20gJy4vdXNlU3RhbGxlZEFuaW1hdGlvbi5qcydcbmltcG9ydCB7IGludGVycG9sYXRlQ29sb3IsIHRvUkdCQ29sb3IgfSBmcm9tICcuL3V0aWxzLmpzJ1xuXG5jb25zdCBTRVBfV0lEVEggPSBzdHJpbmdXaWR0aCgnIMK3ICcpXG5jb25zdCBUSElOS0lOR19CQVJFX1dJRFRIID0gc3RyaW5nV2lkdGgoJ3RoaW5raW5nJylcbmNvbnN0IFNIT1dfVE9LRU5TX0FGVEVSX01TID0gMzBfMDAwXG5cbi8vIFRoaW5raW5nIHNoaW1tZXIgY29uc3RhbnRzLiBQcmV2aW91c2x5IGxpdmVkIGluIGEgc2VwYXJhdGUgVGhpbmtpbmdTaGltbWVyVGV4dFxuLy8gY29tcG9uZW50IHdpdGggaXRzIG93biB1c2VBbmltYXRpb25GcmFtZSg1MCkg4oCUIGlubGluZWQgaGVyZSB0byByZXVzZSBvdXJcbi8vIGV4aXN0aW5nIDUwbXMgY2xvY2sgYW5kIGVsaW1pbmF0ZSB0aGUgcmVkdW5kYW50IHN1YnNjcmliZXIuXG5jb25zdCBUSElOS0lOR19JTkFDVElWRSA9IHsgcjogMTUzLCBnOiAxNTMsIGI6IDE1MyB9XG5jb25zdCBUSElOS0lOR19JTkFDVElWRV9TSElNTUVSID0geyByOiAxODUsIGc6IDE4NSwgYjogMTg1IH1cbmNvbnN0IFRISU5LSU5HX0RFTEFZX01TID0gMzAwMFxuY29uc3QgVEhJTktJTkdfR0xPV19QRVJJT0RfUyA9IDJcblxuZXhwb3J0IHR5cGUgU3Bpbm5lckFuaW1hdGlvblJvd1Byb3BzID0ge1xuICAvLyBBbmltYXRpb24gaW5wdXRzXG4gIG1vZGU6IFNwaW5uZXJNb2RlXG4gIHJlZHVjZWRNb3Rpb246IGJvb2xlYW5cbiAgaGFzQWN0aXZlVG9vbHM6IGJvb2xlYW5cbiAgcmVzcG9uc2VMZW5ndGhSZWY6IFJlYWN0LlJlZk9iamVjdDxudW1iZXI+XG5cbiAgLy8gTWVzc2FnZSAoc3RhYmxlIHdpdGhpbiBhIHR1cm4pXG4gIG1lc3NhZ2U6IHN0cmluZ1xuICBtZXNzYWdlQ29sb3I6IGtleW9mIFRoZW1lXG4gIHNoaW1tZXJDb2xvcjoga2V5b2YgVGhlbWVcbiAgb3ZlcnJpZGVDb2xvcj86IGtleW9mIFRoZW1lIHwgbnVsbFxuXG4gIC8vIFRpbWVyIHJlZnMgKHN0YWJsZSByZWZlcmVuY2VzKVxuICBsb2FkaW5nU3RhcnRUaW1lUmVmOiBSZWFjdC5SZWZPYmplY3Q8bnVtYmVyPlxuICB0b3RhbFBhdXNlZE1zUmVmOiBSZWFjdC5SZWZPYmplY3Q8bnVtYmVyPlxuICBwYXVzZVN0YXJ0VGltZVJlZjogUmVhY3QuUmVmT2JqZWN0PG51bWJlciB8IG51bGw+XG5cbiAgLy8gRGlzcGx Line truncated
+80
View File
@@ -0,0 +1,80 @@
import { c as _c } from "react/compiler-runtime";
import * as React from 'react';
import { Box, Text, useTheme } from '../../ink.js';
import { getTheme, type Theme } from '../../utils/theme.js';
import { getDefaultCharacters, interpolateColor, parseRGB, toRGBColor } from './utils.js';
const DEFAULT_CHARACTERS = getDefaultCharacters();
const SPINNER_FRAMES = [...DEFAULT_CHARACTERS, ...[...DEFAULT_CHARACTERS].reverse()];
const REDUCED_MOTION_DOT = '●';
const REDUCED_MOTION_CYCLE_MS = 2000; // 2-second cycle: 1s visible, 1s dim
const ERROR_RED = {
r: 171,
g: 43,
b: 63
};
type Props = {
frame: number;
messageColor: keyof Theme;
stalledIntensity?: number;
reducedMotion?: boolean;
time?: number;
};
export function SpinnerGlyph(t0) {
const $ = _c(9);
const {
frame,
messageColor,
stalledIntensity: t1,
reducedMotion: t2,
time: t3
} = t0;
const stalledIntensity = t1 === undefined ? 0 : t1;
const reducedMotion = t2 === undefined ? false : t2;
const time = t3 === undefined ? 0 : t3;
const [themeName] = useTheme();
const theme = getTheme(themeName);
if (reducedMotion) {
const isDim = Math.floor(time / (REDUCED_MOTION_CYCLE_MS / 2)) % 2 === 1;
let t4;
if ($[0] !== isDim || $[1] !== messageColor) {
t4 = <Box flexWrap="wrap" height={1} width={2}><Text color={messageColor} dimColor={isDim}>{REDUCED_MOTION_DOT}</Text></Box>;
$[0] = isDim;
$[1] = messageColor;
$[2] = t4;
} else {
t4 = $[2];
}
return t4;
}
const spinnerChar = SPINNER_FRAMES[frame % SPINNER_FRAMES.length];
if (stalledIntensity > 0) {
const baseColorStr = theme[messageColor];
const baseRGB = baseColorStr ? parseRGB(baseColorStr) : null;
if (baseRGB) {
const interpolated = interpolateColor(baseRGB, ERROR_RED, stalledIntensity);
return <Box flexWrap="wrap" height={1} width={2}><Text color={toRGBColor(interpolated)}>{spinnerChar}</Text></Box>;
}
const color = stalledIntensity > 0.5 ? "error" : messageColor;
let t4;
if ($[3] !== color || $[4] !== spinnerChar) {
t4 = <Box flexWrap="wrap" height={1} width={2}><Text color={color}>{spinnerChar}</Text></Box>;
$[3] = color;
$[4] = spinnerChar;
$[5] = t4;
} else {
t4 = $[5];
}
return t4;
}
let t4;
if ($[6] !== messageColor || $[7] !== spinnerChar) {
t4 = <Box flexWrap="wrap" height={1} width={2}><Text color={messageColor}>{spinnerChar}</Text></Box>;
$[6] = messageColor;
$[7] = spinnerChar;
$[8] = t4;
} else {
t4 = $[8];
}
return t4;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJSZWFjdCIsIkJveCIsIlRleHQiLCJ1c2VUaGVtZSIsImdldFRoZW1lIiwiVGhlbWUiLCJnZXREZWZhdWx0Q2hhcmFjdGVycyIsImludGVycG9sYXRlQ29sb3IiLCJwYXJzZVJHQiIsInRvUkdCQ29sb3IiLCJERUZBVUxUX0NIQVJBQ1RFUlMiLCJTUElOTkVSX0ZSQU1FUyIsInJldmVyc2UiLCJSRURVQ0VEX01PVElPTl9ET1QiLCJSRURVQ0VEX01PVElPTl9DWUNMRV9NUyIsIkVSUk9SX1JFRCIsInIiLCJnIiwiYiIsIlByb3BzIiwiZnJhbWUiLCJtZXNzYWdlQ29sb3IiLCJzdGFsbGVkSW50ZW5zaXR5IiwicmVkdWNlZE1vdGlvbiIsInRpbWUiLCJTcGlubmVyR2x5cGgiLCJ0MCIsIiQiLCJfYyIsInQxIiwidDIiLCJ0MyIsInVuZGVmaW5lZCIsInRoZW1lTmFtZSIsInRoZW1lIiwiaXNEaW0iLCJNYXRoIiwiZmxvb3IiLCJ0NCIsInNwaW5uZXJDaGFyIiwibGVuZ3RoIiwiYmFzZUNvbG9yU3RyIiwiYmFzZVJHQiIsImludGVycG9sYXRlZCIsImNvbG9yIl0sInNvdXJjZXMiOlsiU3Bpbm5lckdseXBoLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IEJveCwgVGV4dCwgdXNlVGhlbWUgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgeyBnZXRUaGVtZSwgdHlwZSBUaGVtZSB9IGZyb20gJy4uLy4uL3V0aWxzL3RoZW1lLmpzJ1xuaW1wb3J0IHtcbiAgZ2V0RGVmYXVsdENoYXJhY3RlcnMsXG4gIGludGVycG9sYXRlQ29sb3IsXG4gIHBhcnNlUkdCLFxuICB0b1JHQkNvbG9yLFxufSBmcm9tICcuL3V0aWxzLmpzJ1xuXG5jb25zdCBERUZBVUxUX0NIQVJBQ1RFUlMgPSBnZXREZWZhdWx0Q2hhcmFjdGVycygpXG5cbmNvbnN0IFNQSU5ORVJfRlJBTUVTID0gW1xuICAuLi5ERUZBVUxUX0NIQVJBQ1RFUlMsXG4gIC4uLlsuLi5ERUZBVUxUX0NIQVJBQ1RFUlNdLnJldmVyc2UoKSxcbl1cblxuY29uc3QgUkVEVUNFRF9NT1RJT05fRE9UID0gJ+KXjydcbmNvbnN0IFJFRFVDRURfTU9USU9OX0NZQ0xFX01TID0gMjAwMCAvLyAyLXNlY29uZCBjeWNsZTogMXMgdmlzaWJsZSwgMXMgZGltXG5jb25zdCBFUlJPUl9SRUQgPSB7IHI6IDE3MSwgZzogNDMsIGI6IDYzIH1cblxudHlwZSBQcm9wcyA9IHtcbiAgZnJhbWU6IG51bWJlclxuICBtZXNzYWdlQ29sb3I6IGtleW9mIFRoZW1lXG4gIHN0YWxsZWRJbnRlbnNpdHk/OiBudW1iZXJcbiAgcmVkdWNlZE1vdGlvbj86IGJvb2xlYW5cbiAgdGltZT86IG51bWJlclxufVxuXG5leHBvcnQgZnVuY3Rpb24gU3Bpbm5lckdseXBoKHtcbiAgZnJhbWUsXG4gIG1lc3NhZ2VDb2xvcixcbiAgc3RhbGxlZEludGVuc2l0eSA9IDAsXG4gIHJlZHVjZWRNb3Rpb24gPSBmYWxzZSxcbiAgdGltZSA9IDAsXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IFt0aGVtZU5hbWVdID0gdXNlVGhlbWUoKVxuICBjb25zdCB0aGVtZSA9IGdldFRoZW1lKHRoZW1lTmFtZSlcblxuICAvLyBSZWR1Y2VkIG1vdGlvbjogc2xvd2x5IGZsYXNoaW5nIG9yYW5nZSBkb3RcbiAgaWYgKHJlZHVjZWRNb3Rpb24pIHtcbiAgICBjb25zdCBpc0RpbSA9IE1hdGguZmxvb3IodGltZSAvIChSRURVQ0VEX01PVElPTl9DWUNMRV9NUyAvIDIpKSAlIDIgPT09IDFcbiAgICByZXR1cm4gKFxuICAgICAgPEJveCBmbGV4V3JhcD1cIndyYXBcIiBoZWlnaHQ9ezF9IHdpZHRoPXsyfT5cbiAgICAgICAgPFRleHQgY29sb3I9e21lc3NhZ2VDb2xvcn0gZGltQ29sb3I9e2lzRGltfT5cbiAgICAgICAgICB7UkVEVUNFRF9NT1RJT05fRE9UfVxuICAgICAgICA8L1RleHQ+XG4gICAgICA8L0JveD5cbiAgICApXG4gIH1cblxuICBjb25zdCBzcGlubmVyQ2hhciA9IFNQSU5ORVJfRlJBTUVTW2ZyYW1lICUgU1BJTk5FUl9GUkFNRVMubGVuZ3RoXVxuXG4gIC8vIFNtb290aGx5IGludGVycG9sYXRlIGZyb20gY3VycmVudCBjb2xvciB0byByZWQgd2hlbiBzdGFsbGVkXG4gIGlmIChzdGFsbGVkSW50ZW5zaXR5ID4gMCkge1xuICAgIGNvbnN0IGJhc2VDb2xvclN0ciA9IHRoZW1lW21lc3NhZ2VDb2xvcl1cbiAgICBjb25zdCBiYXNlUkdCID0gYmFzZUNvbG9yU3RyID8gcGFyc2VSR0IoYmFzZUNvbG9yU3RyKSA6IG51bGxcblxuICAgIGlmIChiYXNlUkdCKSB7XG4gICAgICBjb25zdCBpbnRlcnBvbGF0ZWQgPSBpbnRlcnBvbGF0ZUNvbG9yKFxuICAgICAgICBiYXNlUkdCLFxuICAgICAgICBFUlJPUl9SRUQsXG4gICAgICAgIHN0YWxsZWRJbnRlbnNpdHksXG4gICAgICApXG4gICAgICByZXR1cm4gKFxuICAgICAgICA8Qm94IGZsZXhXcmFwPVwid3JhcFwiIGhlaWdodD17MX0gd2lkdGg9ezJ9PlxuICAgICAgICAgIDxUZXh0IGNvbG9yPXt0b1JHQkNvbG9yKGludGVycG9sYXRlZCl9PntzcGlubmVyQ2hhcn08L1RleHQ+XG4gICAgICAgIDwvQm94PlxuICAgICAgKVxuICAgIH1cblxuICAgIC8vIEZhbGxiYWNrIGZvciBBTlNJIHRoZW1lc1xuICAgIGNvbnN0IGNvbG9yID0gc3RhbGxlZEludGVuc2l0eSA+IDAuNSA/ICdlcnJvcicgOiBtZXNzYWdlQ29sb3JcbiAgICByZXR1cm4gKFxuICAgICAgPEJveCBmbGV4V3JhcD1cIndyYXBcIiBoZWlnaHQ9ezF9IHdpZHRoPXsyfT5cbiAgICAgICAgPFRleHQgY29sb3I9e2NvbG9yfT57c3Bpbm5lckNoYXJ9PC9UZXh0PlxuICAgICAgPC9Cb3g+XG4gICAgKVxuICB9XG5cbiAgcmV0dXJuIChcbiAgICA8Qm94IGZsZXhXcmFwPVwid3JhcFwiIGhlaWdodD17MX0gd2lkdGg9ezJ9PlxuICAgICAgPFRleHQgY29sb3I9e21lc3NhZ2VDb2xvcn0+e3NwaW5uZXJDaGFyfTwvVGV4dD5cbiAgICA8L0JveD5cbiAgKVxufVxuIl0sIm1hcHBpbmdzIjoiO0FBQUEsT0FBTyxLQUFLQSxLQUFLLE1BQU0sT0FBTztBQUM5QixTQUFTQyxHQUFHLEVBQUVDLElBQUksRUFBRUMsUUFBUSxRQUFRLGNBQWM7QUFDbEQsU0FBU0MsUUFBUSxFQUFFLEtBQUtDLEtBQUssUUFBUSxzQkFBc0I7QUFDM0QsU0FDRUMsb0JBQW9CLEVBQ3BCQyxnQkFBZ0IsRUFDaEJDLFFBQVEsRUFDUkMsVUFBVSxRQUNMLFlBQVk7QUFFbkIsTUFBTUMsa0JBQWtCLEdBQUdKLG9CQUFvQixDQUFDLENBQUM7QUFFakQsTUFBTUssY0FBYyxHQUFHLENBQ3JCLEdBQUdELGtCQUFrQixFQUNyQixHQUFHLENBQUMsR0FBR0Esa0JBQWtCLENBQUMsQ0FBQ0UsT0FBTyxDQUFDLENBQUMsQ0FDckM7QUFFRCxNQUFNQyxrQkFBa0IsR0FBRyxHQUFHO0FBQzlCLE1BQU1DLHVCQUF1QixHQUFHLElBQUksRUFBQztBQUNyQyxNQUFNQyxTQUFTLEdBQUc7RUFBRUMsQ0FBQyxFQUFFLEdBQUc7RUFBRUMsQ0FBQyxFQUFFLEVBQUU7RUFBRUMsQ0FBQyxFQUFFO0FBQUcsQ0FBQztBQUUxQyxLQUFLQyxLQUFLLEdBQUc7RUFDWEMsS0FBSyxFQUFFLE1BQU07RUFDYkMsWUFBWSxFQUFFLE1BQU1oQixLQUFLO0VBQ3pCaUIsZ0JBQWdCLENBQUMsRUFBRSxNQUFNO0VBQ3pCQyxhQUFhLENBQUMsRUFBRSxPQUFPO0VBQ3ZCQyxJQUFJLENBQUMsRUFBRSxNQUFNO0FBQ2YsQ0FBQztBQUVELE9BQU8sU0FBQUMsYUFBQUMsRUFBQTtFQUFBLE1BQUFDLENBQUEsR0FBQUMsRUFBQTtFQUFzQjtJQUFBUixLQUFBO0lBQUFDLFlBQUE7SUFBQUMsZ0JBQUEsRUFBQU8sRUFBQTtJQUFBTixhQUFBLEVBQUFPLEVBQUE7SUFBQU4sSUFBQSxFQUFBTztFQUFBLElBQUFMLEVBTXJCO0VBSE4sTUFBQUosZ0JBQUEsR0FBQU8sRUFBb0IsS0FBcEJHLFNBQW9CLEdBQXBCLENBQW9CLEdBQXBCSCxFQUF Line truncated
@@ -0,0 +1,233 @@
import figures from 'figures';
import sample from 'lodash-es/sample.js';
import * as React from 'react';
import { useRef, useState } from 'react';
import { getSpinnerVerbs } from '../../constants/spinnerVerbs.js';
import { TURN_COMPLETION_VERBS } from '../../constants/turnCompletionVerbs.js';
import { useElapsedTime } from '../../hooks/useElapsedTime.js';
import { useTerminalSize } from '../../hooks/useTerminalSize.js';
import { stringWidth } from '../../ink/stringWidth.js';
import { Box, Text } from '../../ink.js';
import type { InProcessTeammateTaskState } from '../../tasks/InProcessTeammateTask/types.js';
import { summarizeRecentActivities } from '../../utils/collapseReadSearch.js';
import { formatDuration, formatNumber, truncateToWidth } from '../../utils/format.js';
import { toInkColor } from '../../utils/ink.js';
import { TEAMMATE_SELECT_HINT } from './teammateSelectHint.js';
type Props = {
teammate: InProcessTeammateTaskState;
isLast: boolean;
isSelected?: boolean;
isForegrounded?: boolean;
allIdle?: boolean;
showPreview?: boolean;
};
/**
* Extract the last 3 lines of content from a teammate's conversation.
* Shows recent activity from any message type (user or assistant).
*/
function getMessagePreview(messages: InProcessTeammateTaskState['messages']): string[] {
if (!messages?.length) return [];
const allLines: string[] = [];
const maxLineLength = 80;
// Collect lines from recent messages (newest first)
for (let i = messages.length - 1; i >= 0 && allLines.length < 3; i--) {
const msg = messages[i];
// Only process messages that have content (user/assistant messages)
if (!msg || msg.type !== 'user' && msg.type !== 'assistant' || !msg.message?.content?.length) {
continue;
}
const content = msg.message.content;
for (const block of content) {
if (allLines.length >= 3) break;
if (!block || typeof block !== 'object') continue;
if ('type' in block && block.type === 'tool_use' && 'name' in block) {
// Try to show meaningful info from tool input
const input = 'input' in block ? block.input as Record<string, unknown> : null;
let toolLine = `Using ${block.name}…`;
if (input) {
// Look for common descriptive fields
const desc = input.description as string | undefined || input.prompt as string | undefined || input.command as string | undefined || input.query as string | undefined || input.pattern as string | undefined;
if (desc) {
toolLine = desc.split('\n')[0] ?? toolLine;
}
}
allLines.push(truncateToWidth(toolLine, maxLineLength));
} else if ('type' in block && block.type === 'text' && 'text' in block) {
const textLines = (block.text as string).split('\n').filter(l => l.trim());
// Take from end of text (most recent lines)
for (let j = textLines.length - 1; j >= 0 && allLines.length < 3; j--) {
const line = textLines[j];
if (!line) continue;
allLines.push(truncateToWidth(line, maxLineLength));
}
}
}
}
// Reverse so oldest of the 3 is first (reading order)
return allLines.reverse();
}
export function TeammateSpinnerLine({
teammate,
isLast,
isSelected,
isForegrounded,
allIdle,
showPreview
}: Props): React.ReactNode {
const [randomVerb] = useState(() => teammate.spinnerVerb ?? sample(getSpinnerVerbs()));
const [pastTenseVerb] = useState(() => teammate.pastTenseVerb ?? sample(TURN_COMPLETION_VERBS));
const isHighlighted = isSelected || isForegrounded;
const treeChar = isHighlighted ? isLast ? '╘═' : '╞═' : isLast ? '└─' : '├─';
const nameColor = toInkColor(teammate.identity.color);
const {
columns
} = useTerminalSize();
// Track when teammate became idle (for "Idle for X..." display)
const idleStartRef = useRef<number | null>(null);
// Freeze elapsed time when entering all-idle state
const frozenDurationRef = useRef<string | null>(null);
// Track idle start time
if (teammate.isIdle && idleStartRef.current === null) {
idleStartRef.current = Date.now();
} else if (!teammate.isIdle) {
idleStartRef.current = null;
}
// Reset frozen duration when leaving all-idle state
if (!allIdle && frozenDurationRef.current !== null) {
frozenDurationRef.current = null;
}
// Get elapsed idle time (how long they've been idle) - for "Idle for X..." display
const idleElapsedTime = useElapsedTime(idleStartRef.current ?? Date.now(), teammate.isIdle && !allIdle);
// Freeze the duration when we first detect all idle
// Use the teammate's actual work time (since task started) for the past-tense display
if (allIdle && frozenDurationRef.current === null) {
frozenDurationRef.current = formatDuration(Math.max(0, Date.now() - teammate.startTime - (teammate.totalPausedMs ?? 0)));
}
// Use frozen work duration when all idle, otherwise use idle elapsed time
const displayTime = allIdle ? frozenDurationRef.current ?? (() => {
throw new Error(`frozenDurationRef is null for idle teammate ${teammate.identity.agentName}`);
})() : idleElapsedTime;
// Layout: paddingLeft(3) + pointer(1) + space(1) + treeChar(2) + space(1) = 8 fixed chars
// Then optionally: @name + ": " OR just ": "
// Then: activity text + optional extras (stats, hints)
const basePrefix = 8;
const fullAgentName = `@${teammate.identity.agentName}`;
const fullNameWidth = stringWidth(fullAgentName);
// Get stats from progress
const toolUseCount = teammate.progress?.toolUseCount ?? 0;
const tokenCount = teammate.progress?.tokenCount ?? 0;
const statsText = ` · ${toolUseCount} tool ${toolUseCount === 1 ? 'use' : 'uses'} · ${formatNumber(tokenCount)} tokens`;
const statsWidth = stringWidth(statsText);
const selectHintText = ` · ${TEAMMATE_SELECT_HINT}`;
const selectHintWidth = stringWidth(selectHintText);
const viewHintText = ' · enter to view';
const viewHintWidth = stringWidth(viewHintText);
// Progressive responsive layout:
// Wide (80+): full name + activity + stats + hint
// Medium (60-80): full name + activity
// Narrow (<60): hide name, just show activity
const minActivityWidth = 25;
// Hide name on narrow terminals (< 60 cols) or if there's not enough room
const spaceWithFullName = columns - basePrefix - fullNameWidth - 2;
const showName = columns >= 60 && spaceWithFullName >= minActivityWidth;
const nameWidth = showName ? fullNameWidth + 2 : 0; // +2 for ": " when name shown
const availableForActivity = columns - basePrefix - nameWidth;
// Progressive hiding: view hint → select hint → stats
// Stats always visible (dimmed when not selected); hints only when highlighted/selected
const showViewHint = isSelected && !isForegrounded && availableForActivity > viewHintWidth + statsWidth + minActivityWidth + 5;
const showSelectHint = isHighlighted && availableForActivity > selectHintWidth + (showViewHint ? viewHintWidth : 0) + statsWidth + minActivityWidth + 5;
const showStats = availableForActivity > statsWidth + minActivityWidth + 5;
// Activity text gets remaining space
const extrasCost = (showStats ? statsWidth : 0) + (showSelectHint ? selectHintWidth : 0) + (showViewHint ? viewHintWidth : 0);
const activityMaxWidth = Math.max(minActivityWidth, availableForActivity - extrasCost - 1);
// Format the activity text for active teammates, rolling up search/read ops
const activityText = (() => {
const activities = teammate.progress?.recentActivities;
if (activities && activities.length > 0) {
const summary = summarizeRecentActivities(activities);
if (summary) return truncateToWidth(summary, activityMaxWidth);
}
const desc = teammate.progress?.lastActivity?.activityDescription;
if (desc) return truncateToWidth(desc, activityMaxWidth);
return randomVerb;
})();
// Status rendering logic
const renderStatus = (): React.ReactNode => {
if (teammate.shutdownRequested) {
return <Text dimColor>[stopping]</Text>;
}
if (teammate.awaitingPlanApproval) {
return <Text color="warning">[awaiting approval]</Text>;
}
if (teammate.isIdle) {
if (allIdle) {
return <Text dimColor>
{pastTenseVerb} for {displayTime}
</Text>;
}
return <Text dimColor>Idle for {idleElapsedTime}</Text>;
}
// Active - show spinner glyph + activity description (only when not highlighted;
// when highlighted, the main spinner above already shows the verb)
if (isHighlighted) {
return null;
}
return <Text dimColor>
{activityText?.endsWith('…') ? activityText : `${activityText}…`}
</Text>;
};
// Get preview lines if enabled
const previewLines = showPreview ? getMessagePreview(teammate.messages) : [];
// Tree continuation character for preview lines
const previewTreeChar = isLast ? ' ' : '│ ';
return <Box flexDirection="column">
<Box paddingLeft={3}>
{/* Selection indicator: pointer when selected, otherwise space */}
<Text color={isSelected ? 'suggestion' : undefined} bold={isSelected}>
{isSelected ? figures.pointer : ' '}
</Text>
<Text dimColor={!isSelected}>{treeChar} </Text>
{/* Agent name: hidden on very narrow screens */}
{showName && <Text color={isSelected ? 'suggestion' : nameColor}>
@{teammate.identity.agentName}
</Text>}
{showName && <Text dimColor={!isSelected}>: </Text>}
{renderStatus()}
{/* Stats: only shown when selected and terminal is wide enough */}
{showStats && <Text dimColor>
{' '}
· {toolUseCount} tool {toolUseCount === 1 ? 'use' : 'uses'} ·{' '}
{formatNumber(tokenCount)} tokens
</Text>}
{/* Hints: select hint when highlighted, view hint when selected but not foregrounded */}
{showSelectHint && <Text dimColor> · {TEAMMATE_SELECT_HINT}</Text>}
{showViewHint && <Text dimColor> · enter to view</Text>}
</Box>
{/* Preview lines */}
{previewLines.map((line, idx) => <Box key={idx} paddingLeft={3}>
<Text dimColor> </Text>
<Text dimColor>{previewTreeChar} </Text>
<Text dimColor>{line}</Text>
</Box>)}
</Box>;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwic2FtcGxlIiwiUmVhY3QiLCJ1c2VSZWYiLCJ1c2VTdGF0ZSIsImdldFNwaW5uZXJWZXJicyIsIlRVUk5fQ09NUExFVElPTl9WRVJCUyIsInVzZUVsYXBzZWRUaW1lIiwidXNlVGVybWluYWxTaXplIiwic3RyaW5nV2lkdGgiLCJCb3giLCJUZXh0IiwiSW5Qcm9jZXNzVGVhbW1hdGVUYXNrU3RhdGUiLCJzdW1tYXJpemVSZWNlbnRBY3Rpdml0aWVzIiwiZm9ybWF0RHVyYXRpb24iLCJmb3JtYXROdW1iZXIiLCJ0cnVuY2F0ZVRvV2lkdGgiLCJ0b0lua0NvbG9yIiwiVEVBTU1BVEVfU0VMRUNUX0hJTlQiLCJQcm9wcyIsInRlYW1tYXRlIiwiaXNMYXN0IiwiaXNTZWxlY3RlZCIsImlzRm9yZWdyb3VuZGVkIiwiYWxsSWRsZSIsInNob3dQcmV2aWV3IiwiZ2V0TWVzc2FnZVByZXZpZXciLCJtZXNzYWdlcyIsImxlbmd0aCIsImFsbExpbmVzIiwibWF4TGluZUxlbmd0aCIsImkiLCJtc2ciLCJ0eXBlIiwibWVzc2FnZSIsImNvbnRlbnQiLCJibG9jayIsImlucHV0IiwiUmVjb3JkIiwidG9vbExpbmUiLCJuYW1lIiwiZGVzYyIsImRlc2NyaXB0aW9uIiwicHJvbXB0IiwiY29tbWFuZCIsInF1ZXJ5IiwicGF0dGVybiIsInNwbGl0IiwicHVzaCIsInRleHRMaW5lcyIsInRleHQiLCJmaWx0ZXIiLCJsIiwidHJpbSIsImoiLCJsaW5lIiwicmV2ZXJzZSIsIlRlYW1tYXRlU3Bpbm5lckxpbmUiLCJSZWFjdE5vZGUiLCJyYW5kb21WZXJiIiwic3Bpbm5lclZlcmIiLCJwYXN0VGVuc2VWZXJiIiwiaXNIaWdobGlnaHRlZCIsInRyZWVDaGFyIiwibmFtZUNvbG9yIiwiaWRlbnRpdHkiLCJjb2xvciIsImNvbHVtbnMiLCJpZGxlU3RhcnRSZWYiLCJmcm96ZW5EdXJhdGlvblJlZiIsImlzSWRsZSIsImN1cnJlbnQiLCJEYXRlIiwibm93IiwiaWRsZUVsYXBzZWRUaW1lIiwiTWF0aCIsIm1heCIsInN0YXJ0VGltZSIsInRvdGFsUGF1c2VkTXMiLCJkaXNwbGF5VGltZSIsIkVycm9yIiwiYWdlbnROYW1lIiwiYmFzZVByZWZpeCIsImZ1bGxBZ2VudE5hbWUiLCJmdWxsTmFtZVdpZHRoIiwidG9vbFVzZUNvdW50IiwicHJvZ3Jlc3MiLCJ0b2tlbkNvdW50Iiwic3RhdHNUZXh0Iiwic3RhdHNXaWR0aCIsInNlbGVjdEhpbnRUZXh0Iiwic2VsZWN0SGludFdpZHRoIiwidmlld0hpbnRUZXh0Iiwidmlld0hpbnRXaWR0aCIsIm1pbkFjdGl2aXR5V2lkdGgiLCJzcGFjZVdpdGhGdWxsTmFtZSIsInNob3dOYW1lIiwibmFtZVdpZHRoIiwiYXZhaWxhYmxlRm9yQWN0aXZpdHkiLCJzaG93Vmlld0hpbnQiLCJzaG93U2VsZWN0SGludCIsInNob3dTdGF0cyIsImV4dHJhc0Nvc3QiLCJhY3Rpdml0eU1heFdpZHRoIiwiYWN0aXZpdHlUZXh0IiwiYWN0aXZpdGllcyIsInJlY2VudEFjdGl2aXRpZXMiLCJzdW1tYXJ5IiwibGFzdEFjdGl2aXR5IiwiYWN0aXZpdHlEZXNjcmlwdGlvbiIsInJlbmRlclN0YXR1cyIsInNodXRkb3duUmVxdWVzdGVkIiwiYXdhaXRpbmdQbGFuQXBwcm92YWwiLCJlbmRzV2l0aCIsInByZXZpZXdMaW5lcyIsInByZXZpZXdUcmVlQ2hhciIsInVuZGVmaW5lZCIsInBvaW50ZXIiLCJtYXAiLCJpZHgiXSwic291cmNlcyI6WyJUZWFtbWF0ZVNwaW5uZXJMaW5lLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZmlndXJlcyBmcm9tICdmaWd1cmVzJ1xuaW1wb3J0IHNhbXBsZSBmcm9tICdsb2Rhc2gtZXMvc2FtcGxlLmpzJ1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyB1c2VSZWYsIHVzZVN0YXRlIH0gZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBnZXRTcGlubmVyVmVyYnMgfSBmcm9tICcuLi8uLi9jb25zdGFudHMvc3Bpbm5lclZlcmJzLmpzJ1xuaW1wb3J0IHsgVFVSTl9DT01QTEVUSU9OX1ZFUkJTIH0gZnJvbSAnLi4vLi4vY29uc3RhbnRzL3R1cm5Db21wbGV0aW9uVmVyYnMuanMnXG5pbXBvcnQgeyB1c2VFbGFwc2VkVGltZSB9IGZyb20gJy4uLy4uL2hvb2tzL3VzZUVsYXBzZWRUaW1lLmpzJ1xuaW1wb3J0IHsgdXNlVGVybWluYWxTaXplIH0gZnJvbSAnLi4vLi4vaG9va3MvdXNlVGVybWluYWxTaXplLmpzJ1xuaW1wb3J0IHsgc3RyaW5nV2lkdGggfSBmcm9tICcuLi8uLi9pbmsvc3RyaW5nV2lkdGguanMnXG5pbXBvcnQgeyBCb3gsIFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5pbXBvcnQgdHlwZSB7IEluUHJvY2Vzc1RlYW1tYXRlVGFza1N0YXRlIH0gZnJvbSAnLi4vLi4vdGFza3MvSW5Qcm9jZXNzVGVhbW1hdGVUYXNrL3R5cGVzLmpzJ1xuaW1wb3J0IHsgc3VtbWFyaXplUmVjZW50QWN0aXZpdGllcyB9IGZyb20gJy4uLy4uL3V0aWxzL2NvbGxhcHNlUmVhZFNlYXJjaC5qcydcbmltcG9ydCB7XG4gIGZvcm1hdER1cmF0aW9uLFxuICBmb3JtYXROdW1iZXIsXG4gIHRydW5jYXRlVG9XaWR0aCxcbn0gZnJvbSAnLi4vLi4vdXRpbHMvZm9ybWF0LmpzJ1xuaW1wb3J0IHsgdG9JbmtDb2xvciB9IGZyb20gJy4uLy4uL3V0aWxzL2luay5qcydcbmltcG9ydCB7IFRFQU1NQVRFX1NFTEVDVF9ISU5UIH0gZnJvbSAnLi90ZWFtbWF0ZVNlbGVjdEhpbnQuanMnXG5cbnR5cGUgUHJvcHMgPSB7XG4gIHRlYW1tYXRlOiBJblByb2Nlc3NUZWFtbWF0ZVRhc2tTdGF0ZVxuICBpc0xhc3Q6IGJvb2xlYW5cbiAgaXNTZWxlY3RlZD86IGJvb2xlYW5cbiAgaXNGb3JlZ3JvdW5kZWQ/OiBib29sZWFuXG4gIGFsbElkbGU/OiBib29sZWFuXG4gIHNob3dQcmV2aWV3PzogYm9vbGVhblxufVxuXG4vKipcbiAqIEV4dHJhY3QgdGhlIGxhc3QgMyBsaW5lcyBvZiBjb250ZW50IGZyb20gYSB0ZWFtbWF0ZSdzIGNvbnZlcnNhdGlvbi5cbiAqIFNob3dzIHJlY2VudCBhY3Rpdml0eSBmcm9tIGFueSBtZXNzYWdlIHR5cGUgKHVzZXIgb3IgYXNzaXN0YW50KS5cbiAqL1xuZnVuY3Rpb24gZ2V0TWVzc2FnZVByZXZpZXcoXG4gIG1lc3NhZ2VzOiBJblByb2Nlc3NUZWFtbWF0ZVRhc2tTdGF0ZVsnbWVzc2FnZXMnXSxcbik6IHN0cmluZ1tdIHtcbiAgaWYgKCFtZXNzYWdlcz8ubGVuZ3RoKSByZXR1cm4gW11cblxuICBjb25zdCBhbGxMaW5lczogc3RyaW5nW10gPSBbXVxuICBjb25zdCBtYXhMaW5lTGVuZ3RoID0gODBcblxuICAvLyBDb2xsZWN0IGxpbmVzIGZyb20gcmVjZW50IG1lc3NhZ2VzIChuZXdlc3QgZmlyc3QpXG4gIGZvciAobGV0IGkgPSBtZXNzYWdlcy5sZW5ndGggLSAxOyBpID49IDAgJiYgYWxsTGluZXMubGVuZ3RoIDwgMzsgaS0tKSB7XG4gICAgY29uc3QgbXNnID0gbWVzc2FnZXNbaV1cbiAgICAvLyBPbmx5IHByb2Nlc3MgbWVzc2FnZXMgdGhhdCBoYXZlIGNvbnRlbnQgKHVzZXIvYXNzaXN0YW50IG1lc3NhZ2VzKVxuICAgIGlmIChcbiAgICAgICFtc2cgfHxcbiAgICAgIChtc2cudHlwZSAhPT0gJ3VzZXInICYmIG1zZy50eXBlICE9PSAnYXNzaXN0YW50JykgfHxcbiAgICAgICFtc2cubWVzc2FnZT8uY29udGVudD8ubGVuZ3RoXG4gICAgKSB7XG4gICAgICBjb250aW51ZVxuICAgIH1cbiAgICBjb25zdCBjb250ZW50ID0gbXNnLm1lc3NhZ2UuY29udGVudFxuXG4gICAgZm9yIChjb25zdCBibG9jayBvZiBjb250ZW50KSB7XG4gICAgICBpZiAoYWxsTGluZXMubGVuZ3RoID49IDMpIGJyZWFrXG4gICAgICBpZiAoIWJsb2NrIHx8IHR5cGVvZiBibG9jayAhPT0gJ29iamVjdCcpIGNvbnRpbnVlXG5cbiAgICAgIGlmICgndHlwZScgaW4gYmxvY2sgJiYgYmxvY2sudHlwZSA9PT0gJ3Rvb2xfdXNlJyAmJiAnbmFtZScgaW4gYmxvY2spIHtcbiAgICAgICAgLy8gVHJ5IHR Line truncated
@@ -0,0 +1,272 @@
import { c as _c } from "react/compiler-runtime";
import figures from 'figures';
import * as React from 'react';
import { Box, Text, type TextProps } from '../../ink.js';
import { useAppState } from '../../state/AppState.js';
import { getRunningTeammatesSorted } from '../../tasks/InProcessTeammateTask/InProcessTeammateTask.js';
import { formatNumber } from '../../utils/format.js';
import { TeammateSpinnerLine } from './TeammateSpinnerLine.js';
import { TEAMMATE_SELECT_HINT } from './teammateSelectHint.js';
type Props = {
selectedIndex?: number;
isInSelectionMode?: boolean;
allIdle?: boolean;
/** Leader's active verb (when leader is actively processing) */
leaderVerb?: string;
/** Leader's token count (when leader is actively processing) */
leaderTokenCount?: number;
/** Leader's idle status text (when leader is idle, e.g. "✻ Idle for 3s") */
leaderIdleText?: string;
};
export function TeammateSpinnerTree(t0) {
const $ = _c(61);
const {
selectedIndex,
isInSelectionMode,
allIdle,
leaderVerb,
leaderTokenCount,
leaderIdleText
} = t0;
const tasks = useAppState(_temp);
const viewingAgentTaskId = useAppState(_temp2);
const showTeammateMessagePreview = useAppState(_temp3);
let T0;
let isHideSelected;
let t1;
let t2;
let t3;
let t4;
let t5;
if ($[0] !== allIdle || $[1] !== isInSelectionMode || $[2] !== leaderIdleText || $[3] !== leaderTokenCount || $[4] !== leaderVerb || $[5] !== selectedIndex || $[6] !== showTeammateMessagePreview || $[7] !== tasks || $[8] !== viewingAgentTaskId) {
t5 = Symbol.for("react.early_return_sentinel");
bb0: {
const teammateTasks = getRunningTeammatesSorted(tasks);
if (teammateTasks.length === 0) {
t5 = null;
break bb0;
}
const isLeaderForegrounded = viewingAgentTaskId === undefined;
const isLeaderSelected = isInSelectionMode && selectedIndex === -1;
const isLeaderHighlighted = isLeaderForegrounded || isLeaderSelected;
isHideSelected = isInSelectionMode === true && selectedIndex === teammateTasks.length;
T0 = Box;
t1 = "column";
t2 = 1;
const t6 = isLeaderSelected ? "suggestion" : undefined;
const t7 = isLeaderSelected ? figures.pointer : " ";
let t8;
if ($[16] !== isLeaderHighlighted || $[17] !== t6 || $[18] !== t7) {
t8 = <Text color={t6} bold={isLeaderHighlighted}>{t7}</Text>;
$[16] = isLeaderHighlighted;
$[17] = t6;
$[18] = t7;
$[19] = t8;
} else {
t8 = $[19];
}
const t9 = !isLeaderHighlighted;
const t10 = isLeaderHighlighted ? "\u2552\u2550" : "\u250C\u2500";
let t11;
if ($[20] !== isLeaderHighlighted || $[21] !== t10 || $[22] !== t9) {
t11 = <Text dimColor={t9} bold={isLeaderHighlighted}>{t10}{" "}</Text>;
$[20] = isLeaderHighlighted;
$[21] = t10;
$[22] = t9;
$[23] = t11;
} else {
t11 = $[23];
}
const t12 = isLeaderSelected ? "suggestion" : "cyan_FOR_SUBAGENTS_ONLY";
let t13;
if ($[24] !== isLeaderHighlighted || $[25] !== t12) {
t13 = <Text bold={isLeaderHighlighted} color={t12}>team-lead</Text>;
$[24] = isLeaderHighlighted;
$[25] = t12;
$[26] = t13;
} else {
t13 = $[26];
}
let t14;
if ($[27] !== isLeaderForegrounded || $[28] !== leaderVerb) {
t14 = !isLeaderForegrounded && leaderVerb && <Text dimColor={true}>: {leaderVerb}…</Text>;
$[27] = isLeaderForegrounded;
$[28] = leaderVerb;
$[29] = t14;
} else {
t14 = $[29];
}
let t15;
if ($[30] !== isLeaderForegrounded || $[31] !== leaderIdleText || $[32] !== leaderVerb) {
t15 = !isLeaderForegrounded && !leaderVerb && leaderIdleText && <Text dimColor={true}>: {leaderIdleText}</Text>;
$[30] = isLeaderForegrounded;
$[31] = leaderIdleText;
$[32] = leaderVerb;
$[33] = t15;
} else {
t15 = $[33];
}
let t16;
if ($[34] !== isLeaderHighlighted || $[35] !== leaderTokenCount) {
t16 = leaderTokenCount !== undefined && leaderTokenCount > 0 && <Text dimColor={!isLeaderHighlighted}>{" "}· {formatNumber(leaderTokenCount)} tokens</Text>;
$[34] = isLeaderHighlighted;
$[35] = leaderTokenCount;
$[36] = t16;
} else {
t16 = $[36];
}
let t17;
if ($[37] !== isLeaderHighlighted) {
t17 = isLeaderHighlighted && <Text dimColor={true}> · {TEAMMATE_SELECT_HINT}</Text>;
$[37] = isLeaderHighlighted;
$[38] = t17;
} else {
t17 = $[38];
}
let t18;
if ($[39] !== isLeaderForegrounded || $[40] !== isLeaderSelected) {
t18 = isLeaderSelected && !isLeaderForegrounded && <Text dimColor={true}> · enter to view</Text>;
$[39] = isLeaderForegrounded;
$[40] = isLeaderSelected;
$[41] = t18;
} else {
t18 = $[41];
}
if ($[42] !== t11 || $[43] !== t13 || $[44] !== t14 || $[45] !== t15 || $[46] !== t16 || $[47] !== t17 || $[48] !== t18 || $[49] !== t8) {
t3 = <Box paddingLeft={3}>{t8}{t11}{t13}{t14}{t15}{t16}{t17}{t18}</Box>;
$[42] = t11;
$[43] = t13;
$[44] = t14;
$[45] = t15;
$[46] = t16;
$[47] = t17;
$[48] = t18;
$[49] = t8;
$[50] = t3;
} else {
t3 = $[50];
}
t4 = teammateTasks.map((teammate, index) => <TeammateSpinnerLine key={teammate.id} teammate={teammate} isLast={!isInSelectionMode && index === teammateTasks.length - 1} isSelected={isInSelectionMode && selectedIndex === index} isForegrounded={viewingAgentTaskId === teammate.id} allIdle={allIdle} showPreview={showTeammateMessagePreview} />);
}
$[0] = allIdle;
$[1] = isInSelectionMode;
$[2] = leaderIdleText;
$[3] = leaderTokenCount;
$[4] = leaderVerb;
$[5] = selectedIndex;
$[6] = showTeammateMessagePreview;
$[7] = tasks;
$[8] = viewingAgentTaskId;
$[9] = T0;
$[10] = isHideSelected;
$[11] = t1;
$[12] = t2;
$[13] = t3;
$[14] = t4;
$[15] = t5;
} else {
T0 = $[9];
isHideSelected = $[10];
t1 = $[11];
t2 = $[12];
t3 = $[13];
t4 = $[14];
t5 = $[15];
}
if (t5 !== Symbol.for("react.early_return_sentinel")) {
return t5;
}
let t6;
if ($[51] !== isHideSelected || $[52] !== isInSelectionMode) {
t6 = isInSelectionMode && <HideRow isSelected={isHideSelected} />;
$[51] = isHideSelected;
$[52] = isInSelectionMode;
$[53] = t6;
} else {
t6 = $[53];
}
let t7;
if ($[54] !== T0 || $[55] !== t1 || $[56] !== t2 || $[57] !== t3 || $[58] !== t4 || $[59] !== t6) {
t7 = <T0 flexDirection={t1} marginTop={t2}>{t3}{t4}{t6}</T0>;
$[54] = T0;
$[55] = t1;
$[56] = t2;
$[57] = t3;
$[58] = t4;
$[59] = t6;
$[60] = t7;
} else {
t7 = $[60];
}
return t7;
}
function _temp3(s_1) {
return s_1.showTeammateMessagePreview;
}
function _temp2(s_0) {
return s_0.viewingAgentTaskId;
}
function _temp(s) {
return s.tasks;
}
function HideRow(t0) {
const $ = _c(18);
const {
isSelected
} = t0;
const t1 = isSelected ? "suggestion" : undefined;
const t2 = isSelected ? figures.pointer : " ";
let t3;
if ($[0] !== isSelected || $[1] !== t1 || $[2] !== t2) {
t3 = <Text color={t1} bold={isSelected}>{t2}</Text>;
$[0] = isSelected;
$[1] = t1;
$[2] = t2;
$[3] = t3;
} else {
t3 = $[3];
}
const t4 = !isSelected;
const t5 = isSelected ? "\u2558\u2550" : "\u2514\u2500";
let t6;
if ($[4] !== isSelected || $[5] !== t4 || $[6] !== t5) {
t6 = <Text dimColor={t4} bold={isSelected}>{t5}{" "}</Text>;
$[4] = isSelected;
$[5] = t4;
$[6] = t5;
$[7] = t6;
} else {
t6 = $[7];
}
const t7 = !isSelected;
let t8;
if ($[8] !== isSelected || $[9] !== t7) {
t8 = <Text dimColor={t7} bold={isSelected}>hide</Text>;
$[8] = isSelected;
$[9] = t7;
$[10] = t8;
} else {
t8 = $[10];
}
let t9;
if ($[11] !== isSelected) {
t9 = isSelected && <Text dimColor={true}> · enter to collapse</Text>;
$[11] = isSelected;
$[12] = t9;
} else {
t9 = $[12];
}
let t10;
if ($[13] !== t3 || $[14] !== t6 || $[15] !== t8 || $[16] !== t9) {
t10 = <Box paddingLeft={3}>{t3}{t6}{t8}{t9}</Box>;
$[13] = t3;
$[14] = t6;
$[15] = t8;
$[16] = t9;
$[17] = t10;
} else {
t10 = $[17];
}
return t10;
}
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJmaWd1cmVzIiwiUmVhY3QiLCJCb3giLCJUZXh0IiwiVGV4dFByb3BzIiwidXNlQXBwU3RhdGUiLCJnZXRSdW5uaW5nVGVhbW1hdGVzU29ydGVkIiwiZm9ybWF0TnVtYmVyIiwiVGVhbW1hdGVTcGlubmVyTGluZSIsIlRFQU1NQVRFX1NFTEVDVF9ISU5UIiwiUHJvcHMiLCJzZWxlY3RlZEluZGV4IiwiaXNJblNlbGVjdGlvbk1vZGUiLCJhbGxJZGxlIiwibGVhZGVyVmVyYiIsImxlYWRlclRva2VuQ291bnQiLCJsZWFkZXJJZGxlVGV4dCIsIlRlYW1tYXRlU3Bpbm5lclRyZWUiLCJ0MCIsIiQiLCJfYyIsInRhc2tzIiwiX3RlbXAiLCJ2aWV3aW5nQWdlbnRUYXNrSWQiLCJfdGVtcDIiLCJzaG93VGVhbW1hdGVNZXNzYWdlUHJldmlldyIsIl90ZW1wMyIsIlQwIiwiaXNIaWRlU2VsZWN0ZWQiLCJ0MSIsInQyIiwidDMiLCJ0NCIsInQ1IiwiU3ltYm9sIiwiZm9yIiwiYmIwIiwidGVhbW1hdGVUYXNrcyIsImxlbmd0aCIsImlzTGVhZGVyRm9yZWdyb3VuZGVkIiwidW5kZWZpbmVkIiwiaXNMZWFkZXJTZWxlY3RlZCIsImlzTGVhZGVySGlnaGxpZ2h0ZWQiLCJ0NiIsInQ3IiwicG9pbnRlciIsInQ4IiwidDkiLCJ0MTAiLCJ0MTEiLCJ0MTIiLCJ0MTMiLCJ0MTQiLCJ0MTUiLCJ0MTYiLCJ0MTciLCJ0MTgiLCJtYXAiLCJ0ZWFtbWF0ZSIsImluZGV4IiwiaWQiLCJzXzEiLCJzIiwic18wIiwiSGlkZVJvdyIsImlzU2VsZWN0ZWQiXSwic291cmNlcyI6WyJUZWFtbWF0ZVNwaW5uZXJUcmVlLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgZmlndXJlcyBmcm9tICdmaWd1cmVzJ1xuaW1wb3J0ICogYXMgUmVhY3QgZnJvbSAncmVhY3QnXG5pbXBvcnQgeyBCb3gsIFRleHQsIHR5cGUgVGV4dFByb3BzIH0gZnJvbSAnLi4vLi4vaW5rLmpzJ1xuaW1wb3J0IHsgdXNlQXBwU3RhdGUgfSBmcm9tICcuLi8uLi9zdGF0ZS9BcHBTdGF0ZS5qcydcbmltcG9ydCB7IGdldFJ1bm5pbmdUZWFtbWF0ZXNTb3J0ZWQgfSBmcm9tICcuLi8uLi90YXNrcy9JblByb2Nlc3NUZWFtbWF0ZVRhc2svSW5Qcm9jZXNzVGVhbW1hdGVUYXNrLmpzJ1xuaW1wb3J0IHsgZm9ybWF0TnVtYmVyIH0gZnJvbSAnLi4vLi4vdXRpbHMvZm9ybWF0LmpzJ1xuaW1wb3J0IHsgVGVhbW1hdGVTcGlubmVyTGluZSB9IGZyb20gJy4vVGVhbW1hdGVTcGlubmVyTGluZS5qcydcbmltcG9ydCB7IFRFQU1NQVRFX1NFTEVDVF9ISU5UIH0gZnJvbSAnLi90ZWFtbWF0ZVNlbGVjdEhpbnQuanMnXG5cbnR5cGUgUHJvcHMgPSB7XG4gIHNlbGVjdGVkSW5kZXg/OiBudW1iZXJcbiAgaXNJblNlbGVjdGlvbk1vZGU/OiBib29sZWFuXG4gIGFsbElkbGU/OiBib29sZWFuXG4gIC8qKiBMZWFkZXIncyBhY3RpdmUgdmVyYiAod2hlbiBsZWFkZXIgaXMgYWN0aXZlbHkgcHJvY2Vzc2luZykgKi9cbiAgbGVhZGVyVmVyYj86IHN0cmluZ1xuICAvKiogTGVhZGVyJ3MgdG9rZW4gY291bnQgKHdoZW4gbGVhZGVyIGlzIGFjdGl2ZWx5IHByb2Nlc3NpbmcpICovXG4gIGxlYWRlclRva2VuQ291bnQ/OiBudW1iZXJcbiAgLyoqIExlYWRlcidzIGlkbGUgc3RhdHVzIHRleHQgKHdoZW4gbGVhZGVyIGlzIGlkbGUsIGUuZy4gXCLinLsgSWRsZSBmb3IgM3NcIikgKi9cbiAgbGVhZGVySWRsZVRleHQ/OiBzdHJpbmdcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIFRlYW1tYXRlU3Bpbm5lclRyZWUoe1xuICBzZWxlY3RlZEluZGV4LFxuICBpc0luU2VsZWN0aW9uTW9kZSxcbiAgYWxsSWRsZSxcbiAgbGVhZGVyVmVyYixcbiAgbGVhZGVyVG9rZW5Db3VudCxcbiAgbGVhZGVySWRsZVRleHQsXG59OiBQcm9wcyk6IFJlYWN0LlJlYWN0Tm9kZSB7XG4gIGNvbnN0IHRhc2tzID0gdXNlQXBwU3RhdGUocyA9PiBzLnRhc2tzKVxuICBjb25zdCB2aWV3aW5nQWdlbnRUYXNrSWQgPSB1c2VBcHBTdGF0ZShzID0+IHMudmlld2luZ0FnZW50VGFza0lkKVxuICBjb25zdCBzaG93VGVhbW1hdGVNZXNzYWdlUHJldmlldyA9IHVzZUFwcFN0YXRlKFxuICAgIHMgPT4gcy5zaG93VGVhbW1hdGVNZXNzYWdlUHJldmlldyxcbiAgKVxuXG4gIGNvbnN0IHRlYW1tYXRlVGFza3MgPSBnZXRSdW5uaW5nVGVhbW1hdGVzU29ydGVkKHRhc2tzKVxuXG4gIC8vIERvbid0IHJlbmRlciBpZiBubyBydW5uaW5nIHRlYW1tYXRlc1xuICBpZiAodGVhbW1hdGVUYXNrcy5sZW5ndGggPT09IDApIHtcbiAgICByZXR1cm4gbnVsbFxuICB9XG5cbiAgLy8gTGVhZGVyIGhpZ2hsaWdodGluZyBmb2xsb3dzIHNhbWUgcGF0dGVybiBhcyB0ZWFtbWF0ZXM6XG4gIC8vIGlzSGlnaGxpZ2h0ZWQgPSBpc0ZvcmVncm91bmRlZCB8fCBpc1NlbGVjdGVkXG4gIGNvbnN0IGlzTGVhZGVyRm9yZWdyb3VuZGVkID0gdmlld2luZ0FnZW50VGFza0lkID09PSB1bmRlZmluZWRcbiAgY29uc3QgaXNMZWFkZXJTZWxlY3RlZCA9IGlzSW5TZWxlY3Rpb25Nb2RlICYmIHNlbGVjdGVkSW5kZXggPT09IC0xXG4gIGNvbnN0IGlzTGVhZGVySGlnaGxpZ2h0ZWQgPSBpc0xlYWRlckZvcmVncm91bmRlZCB8fCBpc0xlYWRlclNlbGVjdGVkXG4gIGNvbnN0IGxlYWRlckNvbG9yOiBUZXh0UHJvcHNbJ2NvbG9yJ10gPSAnY3lhbl9GT1JfU1VCQUdFTlRTX09OTFknXG5cbiAgLy8gSXMgdGhlIFwiaGlkZVwiIHJvdyBzZWxlY3RlZD8gKGluZGV4ID09PSB0ZWFtbWF0ZUNvdW50IGluIHNlbGVjdGlvbiBtb2RlKVxuICBjb25zdCBpc0hpZGVTZWxlY3RlZCA9XG4gICAgaXNJblNlbGVjdGlvbk1vZGUgPT09IHRydWUgJiYgc2VsZWN0ZWRJbmRleCA9PT0gdGVhbW1hdGVUYXNrcy5sZW5ndGhcblxuICByZXR1cm4gKFxuICAgIDxCb3ggZmxleERpcmVjdGlvbj1cImNvbHVtblwiIG1hcmdpblRvcD17MX0+XG4gICAgICB7LyogTGVhZGVyIHJvdyAtIGFsd2F5cyB2aXNpYmxlLCB1c2VzIOKUjOKUgCB0byBlbmNsb3NlIHRoZSB0cmVlICovfVxuICAgICAge1xuICAgICAgICA8Qm94IHBhZGRpbmdMZWZ0PXszfT5cbiAgICAgICAgICA8VGV4dFxuICAgICAgICAgICAgY29sb3I9e2lzTGVhZGVyU2VsZWN0ZWQgPyAnc3VnZ2VzdGlvbicgOiB1bmRlZmluZWR9XG4gICAgICAgICAgICBib2xkPXtpc0xlYWRlckhpZ2hsaWdodGVkfVxuICAgICAgICAgID5cbiAgICAgICAgICAgIHtpc0xlYWRlclNlbGVjdGVkID8gZmlndXJlcy5wb2ludGVyIDogJyAnfVxuICAgICAgICAgIDwvVGV4dD5cbiAgICAgICAgICA8VGV4dCBkaW1Db2xvcj17IWlzTGVhZGVySGlnaGxpZ2h0ZWR9IGJvbGQ9e2lzTGVhZGVySGlnaGxpZ2h0ZWR9PlxuICAgICAgICAgICAge2lzTGVhZGVySGlnaGxpZ2h0ZWQgPyAn4pWS4pWQJyA6ICfilIzilIAnfXsnICd9XG4gICAgICAgICAgPC9UZXh0PlxuICAgICAgICAgIDxUZXh0XG4gICAgICAgICAgICBib2xkPXtpc0xlYWRlckhpZ2hsaWdodGVkfVxuICAgICAgICAgICAgY29sb3I9e2lzTGVhZGVyU2VsZWN0ZWQgPyAnc3VnZ2VzdGlvbicgOiBsZWFkZXJDb2xvcn1cbiAgICAgICAgICA+XG4gICAgICAgICAgICB0ZWFtLWxlYWRcbiAgICAgICAgICA8L1RleHQ+XG4gICAgICAgICAgey8qIFdoZW4gYmFja2dyb3VuZGVkIGFuZCBhY3RpdmU6IHNob3cgc3Bpbm5lciArIHZlcmIgKi99XG4gICAgICAgICAgeyFpc0xlYWRlckZvcmVncm91bmRlZCAmJiBsZWFkZXJWZXJiICYmIChcbiAgICAgICAgICAgIDxUZXh0IGRpbUNvbG9yPjoge2xlYWRlclZlcmJ94oCmPC9UZXh0PlxuICAgICAgICAgICl9XG4gICAgICAgICAgey8qIFdoZW4gYmFja2dyb3VuZGVkIGF Line truncated
+10
View File
@@ -0,0 +1,10 @@
export { FlashingChar } from './FlashingChar.js'
export { GlimmerMessage } from './GlimmerMessage.js'
export { ShimmerChar } from './ShimmerChar.js'
export { SpinnerGlyph } from './SpinnerGlyph.js'
export type { SpinnerMode } from './types.js'
export { useShimmerAnimation } from './useShimmerAnimation.js'
export { useStalledAnimation } from './useStalledAnimation.js'
export { getDefaultCharacters, interpolateColor } from './utils.js'
// Teammate components are NOT exported here - use dynamic require() to enable dead code elimination
// See REPL.tsx and Spinner.tsx for the correct import pattern
@@ -0,0 +1 @@
export const TEAMMATE_SELECT_HINT = 'shift + ↑/↓ to select'
@@ -0,0 +1,31 @@
import { useMemo } from 'react'
import { stringWidth } from '../../ink/stringWidth.js'
import { type DOMElement, useAnimationFrame } from '../../ink.js'
import type { SpinnerMode } from './types.js'
export function useShimmerAnimation(
mode: SpinnerMode,
message: string,
isStalled: boolean,
): [ref: (element: DOMElement | null) => void, glimmerIndex: number] {
const glimmerSpeed = mode === 'requesting' ? 50 : 200
// Pass null when stalled to unsubscribe from the clock — otherwise the
// setInterval keeps firing at 20fps even when the shimmer isn't visible.
// Notably, if the caller never attaches `ref` (e.g. conditional JSX),
// useTerminalViewport stays at its initial isVisible:true and the
// viewport-pause never kicks in, so this is the only stop mechanism.
const [ref, time] = useAnimationFrame(isStalled ? null : glimmerSpeed)
const messageWidth = useMemo(() => stringWidth(message), [message])
if (isStalled) {
return [ref, -100]
}
const cyclePosition = Math.floor(time / glimmerSpeed)
const cycleLength = messageWidth + 20
if (mode === 'requesting') {
return [ref, (cyclePosition % cycleLength) - 10]
}
return [ref, messageWidth + 10 - (cyclePosition % cycleLength)]
}
@@ -0,0 +1,75 @@
import { useRef } from 'react'
// Hook to handle the transition to red when tokens stop flowing.
// Driven by the parent's animation clock time instead of independent intervals,
// so it slows down when the terminal is blurred.
export function useStalledAnimation(
time: number,
currentResponseLength: number,
hasActiveTools = false,
reducedMotion = false,
): {
isStalled: boolean
stalledIntensity: number
} {
const lastTokenTime = useRef(time)
const lastResponseLength = useRef(currentResponseLength)
const mountTime = useRef(time)
const stalledIntensityRef = useRef(0)
const lastSmoothTime = useRef(time)
// Reset timer when new tokens arrive (check actual length change)
if (currentResponseLength > lastResponseLength.current) {
lastTokenTime.current = time
lastResponseLength.current = currentResponseLength
stalledIntensityRef.current = 0
lastSmoothTime.current = time
}
// Derive time since last token from animation clock
let timeSinceLastToken: number
if (hasActiveTools) {
timeSinceLastToken = 0
lastTokenTime.current = time
} else if (currentResponseLength > 0) {
timeSinceLastToken = time - lastTokenTime.current
} else {
timeSinceLastToken = time - mountTime.current
}
// Calculate stalled intensity based on time since last token
// Start showing red after 3 seconds of no new tokens (only when no tools are active)
const isStalled = timeSinceLastToken > 3000 && !hasActiveTools
const intensity = isStalled
? Math.min((timeSinceLastToken - 3000) / 2000, 1) // Fade over 2 seconds
: 0
// Smooth intensity transition driven by animation frame ticks
if (!reducedMotion && (intensity > 0 || stalledIntensityRef.current > 0)) {
const dt = time - lastSmoothTime.current
if (dt >= 50) {
const steps = Math.floor(dt / 50)
let current = stalledIntensityRef.current
for (let i = 0; i < steps; i++) {
const diff = intensity - current
if (Math.abs(diff) < 0.01) {
current = intensity
break
}
current += diff * 0.1
}
stalledIntensityRef.current = current
lastSmoothTime.current = time
}
} else {
stalledIntensityRef.current = intensity
lastSmoothTime.current = time
}
// When reducedMotion is enabled, use instant intensity change
const effectiveIntensity = reducedMotion
? intensity
: stalledIntensityRef.current
return { isStalled, stalledIntensity: effectiveIntensity }
}
+84
View File
@@ -0,0 +1,84 @@
import type { RGBColor as RGBColorString } from '../../ink/styles.js'
import type { RGBColor as RGBColorType } from './types.js'
export function getDefaultCharacters(): string[] {
if (process.env.TERM === 'xterm-ghostty') {
return ['·', '✢', '✳', '✶', '✻', '*'] // Use * instead of ✽ for Ghostty because the latter renders in a way that's slightly offset
}
return process.platform === 'darwin'
? ['·', '✢', '✳', '✶', '✻', '✽']
: ['·', '✢', '*', '✶', '✻', '✽']
}
// Interpolate between two RGB colors
export function interpolateColor(
color1: RGBColorType,
color2: RGBColorType,
t: number, // 0 to 1
): RGBColorType {
return {
r: Math.round(color1.r + (color2.r - color1.r) * t),
g: Math.round(color1.g + (color2.g - color1.g) * t),
b: Math.round(color1.b + (color2.b - color1.b) * t),
}
}
// Convert RGB object to rgb() color string for Text component
export function toRGBColor(color: RGBColorType): RGBColorString {
return `rgb(${color.r},${color.g},${color.b})`
}
// HSL hue (0-360) to RGB, using voice-mode waveform parameters (s=0.7, l=0.6).
export function hueToRgb(hue: number): RGBColorType {
const h = ((hue % 360) + 360) % 360
const s = 0.7
const l = 0.6
const c = (1 - Math.abs(2 * l - 1)) * s
const x = c * (1 - Math.abs(((h / 60) % 2) - 1))
const m = l - c / 2
let r = 0
let g = 0
let b = 0
if (h < 60) {
r = c
g = x
} else if (h < 120) {
r = x
g = c
} else if (h < 180) {
g = c
b = x
} else if (h < 240) {
g = x
b = c
} else if (h < 300) {
r = x
b = c
} else {
r = c
b = x
}
return {
r: Math.round((r + m) * 255),
g: Math.round((g + m) * 255),
b: Math.round((b + m) * 255),
}
}
const RGB_CACHE = new Map<string, RGBColorType | null>()
export function parseRGB(colorStr: string): RGBColorType | null {
const cached = RGB_CACHE.get(colorStr)
if (cached !== undefined) return cached
const match = colorStr.match(/rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/)
const result = match
? {
r: parseInt(match[1]!, 10),
g: parseInt(match[2]!, 10),
b: parseInt(match[3]!, 10),
}
: null
RGB_CACHE.set(colorStr, result)
return result
}