pi-one-ui 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -1
- package/README.en.md +30 -26
- package/README.md +6 -2
- package/extensions/app/commands/settings-previews.ts +1 -13
- package/extensions/app/config/shell.ts +11 -133
- package/extensions/app/panel.ts +1 -6
- package/extensions/app/runtime/tui-runtime.ts +1 -26
- package/extensions/layouts/editor/controller.ts +0 -54
- package/extensions/layouts/editor/factory.ts +2 -20
- package/extensions/layouts/editor/index.ts +0 -6
- package/extensions/layouts/editor/minimalist-editor.ts +1 -52
- package/extensions/layouts/editor/ui.ts +18 -250
- package/extensions/layouts/working-line/interaction-summary.ts +47 -7
- package/extensions/layouts/working-line/working-line.ts +24 -3
- package/extensions/shared/icons.ts +0 -4
- package/package.json +2 -3
- package/extensions/layouts/editor/accent-rail-editor.ts +0 -112
- package/extensions/layouts/editor/accent-rail-layout-patch.ts +0 -530
|
@@ -7,7 +7,6 @@ import type {
|
|
|
7
7
|
import type { EditorTheme, TUI } from "@earendil-works/pi-tui";
|
|
8
8
|
import type { PolishedTuiConfig } from "../../app/config/shell.ts";
|
|
9
9
|
import { type FooterState, modelLabelFor } from "../footer/index.ts";
|
|
10
|
-
import { markAccentRailLayoutEditor } from "./accent-rail-layout-patch.ts";
|
|
11
10
|
import {
|
|
12
11
|
type EditorFactory,
|
|
13
12
|
markEditorFactory,
|
|
@@ -22,14 +21,11 @@ export type EditorFactoryRuntime = {
|
|
|
22
21
|
readonly getConfig: () => PolishedTuiConfig;
|
|
23
22
|
readonly getState: () => FooterState;
|
|
24
23
|
readonly getThinkingLevel: () => ReturnType<ExtensionAPI["getThinkingLevel"]>;
|
|
25
|
-
readonly getContextWindow: (ctx: ExtensionContext) => number | undefined;
|
|
26
|
-
readonly getContextPercent: (ctx: ExtensionContext) => number | undefined;
|
|
27
24
|
readonly getAgentDurationMs: () => number;
|
|
28
25
|
readonly isAgentActive: () => boolean;
|
|
29
26
|
readonly getProjectRoot: () => string | undefined;
|
|
30
27
|
readonly onRender: (requestRender: () => void) => void;
|
|
31
28
|
readonly onDecorationActive: (active: boolean) => void;
|
|
32
|
-
readonly isAccentRailActive: () => boolean;
|
|
33
29
|
};
|
|
34
30
|
|
|
35
31
|
/**
|
|
@@ -49,7 +45,7 @@ export function createEditorFactory(
|
|
|
49
45
|
keybindings: KeybindingsManager,
|
|
50
46
|
) => {
|
|
51
47
|
runtime.onRender(() => tui.requestRender());
|
|
52
|
-
|
|
48
|
+
return new PolishedEditor(
|
|
53
49
|
tui,
|
|
54
50
|
theme,
|
|
55
51
|
keybindings,
|
|
@@ -60,12 +56,6 @@ export function createEditorFactory(
|
|
|
60
56
|
() => editorDecoration(ctx, runtime),
|
|
61
57
|
runtime.onDecorationActive,
|
|
62
58
|
);
|
|
63
|
-
markAccentRailLayoutEditor(
|
|
64
|
-
editor,
|
|
65
|
-
runtime.ownerToken,
|
|
66
|
-
runtime.isAccentRailActive,
|
|
67
|
-
);
|
|
68
|
-
return editor;
|
|
69
59
|
}) as ZentuiEditorFactory;
|
|
70
60
|
return markEditorFactory(factory, runtime.ownerToken);
|
|
71
61
|
}
|
|
@@ -89,7 +79,7 @@ export function createWrappedEditorFactory(
|
|
|
89
79
|
keybindings: KeybindingsManager,
|
|
90
80
|
) => {
|
|
91
81
|
runtime.onRender(() => tui.requestRender());
|
|
92
|
-
|
|
82
|
+
return new WrappedPolishedEditor(
|
|
93
83
|
baseFactory(tui, theme, keybindings),
|
|
94
84
|
runtime.sessionTheme,
|
|
95
85
|
runtime.getConfig,
|
|
@@ -98,12 +88,6 @@ export function createWrappedEditorFactory(
|
|
|
98
88
|
() => editorDecoration(ctx, runtime),
|
|
99
89
|
runtime.onDecorationActive,
|
|
100
90
|
);
|
|
101
|
-
markAccentRailLayoutEditor(
|
|
102
|
-
editor,
|
|
103
|
-
runtime.ownerToken,
|
|
104
|
-
runtime.isAccentRailActive,
|
|
105
|
-
);
|
|
106
|
-
return editor;
|
|
107
91
|
}) as ZentuiEditorFactory;
|
|
108
92
|
return markWrappedEditorFactory(factory, baseFactory, runtime.ownerToken);
|
|
109
93
|
}
|
|
@@ -150,8 +134,6 @@ function editorDecoration(
|
|
|
150
134
|
costLabel: state.costLabel,
|
|
151
135
|
modelLabel: modelLabelFor(state, config.components.editor.modelLabel),
|
|
152
136
|
thinkingLevel: runtime.getThinkingLevel(),
|
|
153
|
-
contextPercent: runtime.getContextPercent(ctx),
|
|
154
|
-
contextWindow: runtime.getContextWindow(ctx),
|
|
155
137
|
sessionName: ctx.sessionManager.getSessionName() ?? "",
|
|
156
138
|
agentDurationMs: runtime.getAgentDurationMs(),
|
|
157
139
|
agentActive: runtime.isAgentActive(),
|
|
@@ -2,13 +2,7 @@ import { basename, isAbsolute, relative, sep } from "node:path";
|
|
|
2
2
|
import type { Theme } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
4
4
|
import type { ZentuiConfig } from "../../app/config/shell.ts";
|
|
5
|
-
import {
|
|
6
|
-
buildContextGauge,
|
|
7
|
-
contextColorTier,
|
|
8
|
-
formatCount,
|
|
9
|
-
formatCwdLabel,
|
|
10
|
-
formatElapsedDuration,
|
|
11
|
-
} from "../../shared/format.ts";
|
|
5
|
+
import { formatCwdLabel, formatElapsedDuration } from "../../shared/format.ts";
|
|
12
6
|
import { sanitizeEditorMetadataText } from "./editor-metadata-format.ts";
|
|
13
7
|
|
|
14
8
|
export { formatElapsedDuration } from "../../shared/format.ts";
|
|
@@ -53,8 +47,6 @@ export type MinimalistEditorMetadata = {
|
|
|
53
47
|
costLabel?: string;
|
|
54
48
|
modelLabel?: string;
|
|
55
49
|
thinkingLevel?: string;
|
|
56
|
-
contextPercent?: number;
|
|
57
|
-
contextWindow?: number;
|
|
58
50
|
sessionName?: string;
|
|
59
51
|
agentDurationMs?: number;
|
|
60
52
|
agentActive?: boolean;
|
|
@@ -201,7 +193,6 @@ function renderTopRight(
|
|
|
201
193
|
metadata: MinimalistEditorMetadata,
|
|
202
194
|
uiTheme: Theme,
|
|
203
195
|
config: ZentuiConfig,
|
|
204
|
-
availableWidth: number,
|
|
205
196
|
renderBorder: (text: string) => string,
|
|
206
197
|
renderThinking: (text: string) => string,
|
|
207
198
|
): string {
|
|
@@ -235,46 +226,6 @@ function renderTopRight(
|
|
|
235
226
|
if (thinking && thinking.toLowerCase() !== "off") {
|
|
236
227
|
parts.push(renderThinking(thinking));
|
|
237
228
|
}
|
|
238
|
-
if (
|
|
239
|
-
metadata.contextPercent !== undefined &&
|
|
240
|
-
Number.isFinite(metadata.contextPercent)
|
|
241
|
-
) {
|
|
242
|
-
const percent = Math.round(
|
|
243
|
-
Math.max(0, Math.min(999, metadata.contextPercent)),
|
|
244
|
-
);
|
|
245
|
-
const tier = contextColorTier(
|
|
246
|
-
percent,
|
|
247
|
-
config.components.editor.styles.minimalist.contextThresholds,
|
|
248
|
-
);
|
|
249
|
-
const style =
|
|
250
|
-
tier === "error"
|
|
251
|
-
? config.colors.contextError
|
|
252
|
-
: tier === "warning"
|
|
253
|
-
? config.colors.contextWarning
|
|
254
|
-
: config.colors.contextNormal;
|
|
255
|
-
const total =
|
|
256
|
-
config.components.editor.styles.minimalist.contextFormat ===
|
|
257
|
-
"percent-total" &&
|
|
258
|
-
metadata.contextWindow !== undefined &&
|
|
259
|
-
Number.isFinite(metadata.contextWindow) &&
|
|
260
|
-
metadata.contextWindow > 0
|
|
261
|
-
? `/${formatCount(metadata.contextWindow)}`
|
|
262
|
-
: "";
|
|
263
|
-
const text = `${percent}%${total}`;
|
|
264
|
-
let context = renderStyleForSource(uiTheme, source, style, text);
|
|
265
|
-
if (config.components.editor.styles.minimalist.contextGauge) {
|
|
266
|
-
for (const gaugeWidth of [5, 3]) {
|
|
267
|
-
const gauge = `[${buildContextGauge(percent, gaugeWidth, config.icons.mode === "ascii")}] ${text}`;
|
|
268
|
-
const styledGauge = renderStyleForSource(uiTheme, source, style, gauge);
|
|
269
|
-
const candidate = joinParts([...parts, styledGauge]);
|
|
270
|
-
if (visibleWidth(candidate) <= availableWidth) {
|
|
271
|
-
context = styledGauge;
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
parts.push(context);
|
|
277
|
-
}
|
|
278
229
|
return joinParts(parts);
|
|
279
230
|
}
|
|
280
231
|
|
|
@@ -500,7 +451,6 @@ export function renderMinimalistFrame({
|
|
|
500
451
|
);
|
|
501
452
|
const topViewport = viewportLabel("above", viewport?.above);
|
|
502
453
|
const topLeft = joinStyled([topViewport, topMetadata], separator);
|
|
503
|
-
const topRightBudget = Math.max(0, width - 8 - visibleWidth(topLeft));
|
|
504
454
|
const topFallbacks = [
|
|
505
455
|
joinStyled([topViewport, topOperational], separator),
|
|
506
456
|
topOperational,
|
|
@@ -516,7 +466,6 @@ export function renderMinimalistFrame({
|
|
|
516
466
|
metadata,
|
|
517
467
|
uiTheme,
|
|
518
468
|
config,
|
|
519
|
-
topRightBudget,
|
|
520
469
|
renderBorder,
|
|
521
470
|
renderThinking,
|
|
522
471
|
),
|
|
@@ -12,17 +12,13 @@ import {
|
|
|
12
12
|
truncateToWidth,
|
|
13
13
|
visibleWidth,
|
|
14
14
|
} from "@earendil-works/pi-tui";
|
|
15
|
-
import type {
|
|
15
|
+
import type { ZentuiConfig } from "../../app/config/shell.ts";
|
|
16
16
|
import {
|
|
17
17
|
EDITOR_ACCENT_FALLBACK,
|
|
18
18
|
EDITOR_BORDER_FALLBACK,
|
|
19
19
|
renderStyleForSourceOrFallback,
|
|
20
20
|
safeThemeFg,
|
|
21
21
|
} from "../../shared/style.ts";
|
|
22
|
-
import {
|
|
23
|
-
ACCENT_RAIL_CHROME_WIDTH,
|
|
24
|
-
renderAccentRailEditorFrame,
|
|
25
|
-
} from "./accent-rail-editor.ts";
|
|
26
22
|
import { renderCompletionPalette } from "./completion-menu.ts";
|
|
27
23
|
import { renderEditorMetadataFormat } from "./editor-metadata-format.ts";
|
|
28
24
|
import {
|
|
@@ -128,17 +124,6 @@ type PolishedFrameResult = {
|
|
|
128
124
|
decorated: boolean;
|
|
129
125
|
};
|
|
130
126
|
|
|
131
|
-
type AccentRailFrameAdapterOptions = {
|
|
132
|
-
width: number;
|
|
133
|
-
baseRendered: string[];
|
|
134
|
-
autocompleteSource: AutocompleteEditorInternals;
|
|
135
|
-
autocompleteCapture?: AutocompleteCapture;
|
|
136
|
-
uiTheme: Theme;
|
|
137
|
-
config: ZentuiConfig;
|
|
138
|
-
ownedFrame?: PolishedFrameSplit;
|
|
139
|
-
trustedBaseFrame?: boolean;
|
|
140
|
-
};
|
|
141
|
-
|
|
142
127
|
type MinimalistFrameAdapterOptions = {
|
|
143
128
|
width: number;
|
|
144
129
|
baseRendered: string[];
|
|
@@ -342,61 +327,19 @@ function fillLine(content: string, width: number): string {
|
|
|
342
327
|
return `${truncated}${pad}`;
|
|
343
328
|
}
|
|
344
329
|
|
|
345
|
-
function isLowRailPolishedStyle(style: EditorStyle): boolean {
|
|
346
|
-
return style === "opencode-copy-friendly";
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function selectedPolishedConfig(config: ZentuiConfig) {
|
|
350
|
-
switch (config.components.editor.style) {
|
|
351
|
-
case "opencode":
|
|
352
|
-
return config.components.editor.styles.opencode;
|
|
353
|
-
case "opencode-copy-friendly":
|
|
354
|
-
return config.components.editor.styles["opencode-copy-friendly"];
|
|
355
|
-
case "accent-rail":
|
|
356
|
-
case "minimalist":
|
|
357
|
-
return undefined;
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function lowRailPrompt(
|
|
362
|
-
config: ZentuiConfig,
|
|
363
|
-
uiTheme: Theme,
|
|
364
|
-
reset: string,
|
|
365
|
-
): string {
|
|
366
|
-
const promptIcon = config.icons.editorPrompt;
|
|
367
|
-
return promptIcon
|
|
368
|
-
? `${renderStyleForSourceOrFallback(
|
|
369
|
-
uiTheme,
|
|
370
|
-
config.components.editor.colorSource,
|
|
371
|
-
config.colors.editorPrompt ?? config.colors.editorAccent,
|
|
372
|
-
EDITOR_ACCENT_FALLBACK,
|
|
373
|
-
promptIcon,
|
|
374
|
-
)}${reset} `
|
|
375
|
-
: "";
|
|
376
|
-
}
|
|
377
|
-
|
|
378
330
|
function getEditorChromeWidths(
|
|
379
331
|
config: ZentuiConfig,
|
|
380
332
|
uiTheme: Theme,
|
|
381
333
|
reset: string,
|
|
382
334
|
) {
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
EDITOR_ACCENT_FALLBACK,
|
|
392
|
-
config.icons.rail,
|
|
393
|
-
)}${reset} `;
|
|
394
|
-
return {
|
|
395
|
-
prompt,
|
|
396
|
-
promptWidth: visibleWidth(prompt),
|
|
397
|
-
rail,
|
|
398
|
-
railWidth: lowRail ? visibleWidth(prompt) : visibleWidth(rail),
|
|
399
|
-
};
|
|
335
|
+
const rail = `${renderStyleForSourceOrFallback(
|
|
336
|
+
uiTheme,
|
|
337
|
+
config.components.editor.colorSource,
|
|
338
|
+
config.colors.editorAccent,
|
|
339
|
+
EDITOR_ACCENT_FALLBACK,
|
|
340
|
+
config.icons.rail,
|
|
341
|
+
)}${reset} `;
|
|
342
|
+
return { rail, railWidth: visibleWidth(rail) };
|
|
400
343
|
}
|
|
401
344
|
|
|
402
345
|
function composeMetadataLine(
|
|
@@ -464,31 +407,6 @@ function unwrapPolishedFrameOnly(
|
|
|
464
407
|
const interior = lines.slice(1, -1);
|
|
465
408
|
if (interior.length < 3) return undefined;
|
|
466
409
|
|
|
467
|
-
if (isLowRailPolishedStyle(config.components.editor.style)) {
|
|
468
|
-
if (
|
|
469
|
-
plainRenderedText(interior[0] ?? "").trim() !== "" ||
|
|
470
|
-
plainRenderedText(interior.at(-2) ?? "").trim() !== "" ||
|
|
471
|
-
!(interior.at(-1) ?? "").startsWith(" ")
|
|
472
|
-
)
|
|
473
|
-
return undefined;
|
|
474
|
-
|
|
475
|
-
const { prompt, promptWidth } = getEditorChromeWidths(
|
|
476
|
-
config,
|
|
477
|
-
uiTheme,
|
|
478
|
-
"\x1b[0m",
|
|
479
|
-
);
|
|
480
|
-
const continuation = " ".repeat(promptWidth);
|
|
481
|
-
const content = interior.slice(1, -2);
|
|
482
|
-
const unwrapped: string[] = [];
|
|
483
|
-
for (let index = 0; index < content.length; index++) {
|
|
484
|
-
const prefix = index === 0 ? prompt : continuation;
|
|
485
|
-
const line = content[index] ?? "";
|
|
486
|
-
if (prefix && !line.startsWith(prefix)) return undefined;
|
|
487
|
-
unwrapped.push(prefix ? line.slice(prefix.length) : line);
|
|
488
|
-
}
|
|
489
|
-
return { editorLines: unwrapped, viewport };
|
|
490
|
-
}
|
|
491
|
-
|
|
492
410
|
const { rail } = getEditorChromeWidths(config, uiTheme, "\x1b[0m");
|
|
493
411
|
if (!rail || interior.some((line) => !line.startsWith(rail)))
|
|
494
412
|
return undefined;
|
|
@@ -569,65 +487,6 @@ function readVimStatus(
|
|
|
569
487
|
return safeThemeFg(uiTheme, vimModeColor(normalized), label);
|
|
570
488
|
}
|
|
571
489
|
|
|
572
|
-
function renderAccentRailFrameFromBase({
|
|
573
|
-
width,
|
|
574
|
-
baseRendered,
|
|
575
|
-
autocompleteSource,
|
|
576
|
-
autocompleteCapture,
|
|
577
|
-
uiTheme,
|
|
578
|
-
config,
|
|
579
|
-
ownedFrame,
|
|
580
|
-
trustedBaseFrame = false,
|
|
581
|
-
}: AccentRailFrameAdapterOptions): PolishedFrameResult {
|
|
582
|
-
if (width < ACCENT_RAIL_CHROME_WIDTH + 1 || baseRendered.length < 2) {
|
|
583
|
-
return { lines: clampRenderedLines(baseRendered, width), decorated: false };
|
|
584
|
-
}
|
|
585
|
-
if (ownedFrame && !isPolishedFrameSplit(ownedFrame, baseRendered.length)) {
|
|
586
|
-
return { lines: clampRenderedLines(baseRendered, width), decorated: false };
|
|
587
|
-
}
|
|
588
|
-
const autocomplete = ownedFrame
|
|
589
|
-
? { known: true as const, count: ownedFrame.trailingLines.length }
|
|
590
|
-
: autocompleteCount(autocompleteSource, autocompleteCapture, baseRendered);
|
|
591
|
-
if (!autocomplete.known) {
|
|
592
|
-
return { lines: clampRenderedLines(baseRendered, width), decorated: false };
|
|
593
|
-
}
|
|
594
|
-
const editorFrame =
|
|
595
|
-
!ownedFrame && autocomplete.count > 0
|
|
596
|
-
? baseRendered.slice(0, -autocomplete.count)
|
|
597
|
-
: baseRendered;
|
|
598
|
-
const autocompleteLines = ownedFrame
|
|
599
|
-
? ownedFrame.trailingLines
|
|
600
|
-
: autocomplete.count > 0
|
|
601
|
-
? baseRendered.slice(-autocomplete.count)
|
|
602
|
-
: [];
|
|
603
|
-
if (editorFrame.length < 2) {
|
|
604
|
-
return { lines: clampRenderedLines(baseRendered, width), decorated: false };
|
|
605
|
-
}
|
|
606
|
-
const parsedTop = parseEditorBorder(editorFrame[0] ?? "", "above");
|
|
607
|
-
const parsedBottom = parseEditorBorder(editorFrame.at(-1) ?? "", "below");
|
|
608
|
-
if (!ownedFrame && !trustedBaseFrame && (!parsedTop || !parsedBottom)) {
|
|
609
|
-
return { lines: clampRenderedLines(baseRendered, width), decorated: false };
|
|
610
|
-
}
|
|
611
|
-
const editorLines = ownedFrame?.editorLines ?? editorFrame.slice(1, -1);
|
|
612
|
-
const viewport = ownedFrame?.viewport ?? {
|
|
613
|
-
above: parsedTop?.count,
|
|
614
|
-
below: parsedBottom?.count,
|
|
615
|
-
};
|
|
616
|
-
const lines = renderAccentRailEditorFrame({
|
|
617
|
-
width,
|
|
618
|
-
editorLines,
|
|
619
|
-
autocompleteLines,
|
|
620
|
-
viewport,
|
|
621
|
-
uiTheme,
|
|
622
|
-
config,
|
|
623
|
-
});
|
|
624
|
-
POLISHED_FRAME_SPLITS.set(lines, {
|
|
625
|
-
rows: Object.freeze([...lines]),
|
|
626
|
-
split: { editorLines, trailingLines: autocompleteLines, viewport },
|
|
627
|
-
});
|
|
628
|
-
return { lines, decorated: true };
|
|
629
|
-
}
|
|
630
|
-
|
|
631
490
|
function renderMinimalistFrameFromBase({
|
|
632
491
|
width,
|
|
633
492
|
baseRendered,
|
|
@@ -784,16 +643,10 @@ export function renderPolishedEditorFrame({
|
|
|
784
643
|
if (width <= 2) return clampRenderedLines(editorLines, width);
|
|
785
644
|
const reset = "\x1b[0m";
|
|
786
645
|
const colorSource = config.components.editor.colorSource;
|
|
787
|
-
const {
|
|
788
|
-
config,
|
|
789
|
-
uiTheme,
|
|
790
|
-
reset,
|
|
791
|
-
);
|
|
646
|
+
const { rail, railWidth } = getEditorChromeWidths(config, uiTheme, reset);
|
|
792
647
|
const innerWidth = Math.max(0, width - railWidth);
|
|
793
|
-
const lowRailContinuation = " ".repeat(promptWidth);
|
|
794
648
|
const meta = renderEditorMetadataFormat(
|
|
795
|
-
|
|
796
|
-
config.components.editor.styles.opencode.metadataFormat,
|
|
649
|
+
config.components.editor.styles.opencode.metadataFormat,
|
|
797
650
|
{
|
|
798
651
|
model: modelMeta.modelLabel,
|
|
799
652
|
modelId: modelMeta.modelId ?? "",
|
|
@@ -805,11 +658,6 @@ export function renderPolishedEditorFrame({
|
|
|
805
658
|
uiTheme,
|
|
806
659
|
config,
|
|
807
660
|
);
|
|
808
|
-
const lowRailMeta = composeMetadataLine(
|
|
809
|
-
meta,
|
|
810
|
-
rightStatus,
|
|
811
|
-
Math.max(0, width - 1),
|
|
812
|
-
);
|
|
813
661
|
const railedMeta = composeMetadataLine(meta, rightStatus, innerWidth);
|
|
814
662
|
|
|
815
663
|
const renderStaticBorder = (text: string) =>
|
|
@@ -849,7 +697,7 @@ export function renderPolishedEditorFrame({
|
|
|
849
697
|
),
|
|
850
698
|
);
|
|
851
699
|
const completionLines =
|
|
852
|
-
|
|
700
|
+
config.components.editor.styles.opencode.completionMenu === "palette"
|
|
853
701
|
? renderCompletionPalette({
|
|
854
702
|
lines: autocompleteLines,
|
|
855
703
|
width,
|
|
@@ -859,25 +707,12 @@ export function renderPolishedEditorFrame({
|
|
|
859
707
|
})
|
|
860
708
|
: autocompleteLines;
|
|
861
709
|
const lines = ["", ...editorLines, "", railedMeta];
|
|
862
|
-
const renderedLines =
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
`${index === 0 ? prompt : lowRailContinuation}${fillLine(line, innerWidth)}`,
|
|
869
|
-
),
|
|
870
|
-
"",
|
|
871
|
-
` ${truncateToWidth(lowRailMeta, Math.max(0, width - 1), "")}`,
|
|
872
|
-
bottom,
|
|
873
|
-
...completionLines,
|
|
874
|
-
]
|
|
875
|
-
: [
|
|
876
|
-
top,
|
|
877
|
-
...lines.map((line) => `${rail}${fillLine(line, innerWidth)}`),
|
|
878
|
-
bottom,
|
|
879
|
-
...completionLines,
|
|
880
|
-
];
|
|
710
|
+
const renderedLines = [
|
|
711
|
+
top,
|
|
712
|
+
...lines.map((line) => `${rail}${fillLine(line, innerWidth)}`),
|
|
713
|
+
bottom,
|
|
714
|
+
...completionLines,
|
|
715
|
+
];
|
|
881
716
|
|
|
882
717
|
return clampRenderedLines(renderedLines, width);
|
|
883
718
|
}
|
|
@@ -921,36 +756,6 @@ export class PolishedEditor extends CustomEditor {
|
|
|
921
756
|
this.reportMinimalistDecoration(false);
|
|
922
757
|
return clampRenderedLines(super.render(width), width);
|
|
923
758
|
}
|
|
924
|
-
if (config.components.editor.style === "accent-rail") {
|
|
925
|
-
this.reportMinimalistDecoration(false);
|
|
926
|
-
if (width < ACCENT_RAIL_CHROME_WIDTH + 1) {
|
|
927
|
-
return clampRenderedLines(super.render(width), width);
|
|
928
|
-
}
|
|
929
|
-
let captured: { value: string[]; capture?: AutocompleteCapture };
|
|
930
|
-
try {
|
|
931
|
-
captured = renderWithAutocompleteCapture(
|
|
932
|
-
this as unknown as AutocompleteEditorInternals,
|
|
933
|
-
() => super.render(width - ACCENT_RAIL_CHROME_WIDTH),
|
|
934
|
-
);
|
|
935
|
-
} catch {
|
|
936
|
-
return clampRenderedLines(super.render(width), width);
|
|
937
|
-
}
|
|
938
|
-
try {
|
|
939
|
-
const result = renderAccentRailFrameFromBase({
|
|
940
|
-
width,
|
|
941
|
-
baseRendered: captured.value,
|
|
942
|
-
autocompleteSource: this as unknown as AutocompleteEditorInternals,
|
|
943
|
-
autocompleteCapture: captured.capture,
|
|
944
|
-
uiTheme: this.uiTheme,
|
|
945
|
-
config,
|
|
946
|
-
trustedBaseFrame: true,
|
|
947
|
-
});
|
|
948
|
-
if (result.decorated) return result.lines;
|
|
949
|
-
} catch {
|
|
950
|
-
// Decoration is optional; preserve the completed same-render rows below.
|
|
951
|
-
}
|
|
952
|
-
return clampRenderedLines(captured.value, width);
|
|
953
|
-
}
|
|
954
759
|
if (config.components.editor.style === "minimalist") {
|
|
955
760
|
if (width <= 4) {
|
|
956
761
|
this.reportMinimalistDecoration(false);
|
|
@@ -1155,43 +960,6 @@ export class WrappedPolishedEditor implements EditorComponent {
|
|
|
1155
960
|
this.reportMinimalistDecoration(false);
|
|
1156
961
|
return clampRenderedLines(this.base.render(width), width);
|
|
1157
962
|
}
|
|
1158
|
-
if (config.components.editor.style === "accent-rail") {
|
|
1159
|
-
this.reportMinimalistDecoration(false);
|
|
1160
|
-
if (width < ACCENT_RAIL_CHROME_WIDTH + 1) {
|
|
1161
|
-
return clampRenderedLines(this.base.render(width), width);
|
|
1162
|
-
}
|
|
1163
|
-
let captured: { value: string[]; capture?: AutocompleteCapture };
|
|
1164
|
-
try {
|
|
1165
|
-
captured = renderWithAutocompleteCapture(this.base, () =>
|
|
1166
|
-
this.base.render(width - ACCENT_RAIL_CHROME_WIDTH),
|
|
1167
|
-
);
|
|
1168
|
-
} catch {
|
|
1169
|
-
return clampRenderedLines(this.base.render(width), width);
|
|
1170
|
-
}
|
|
1171
|
-
try {
|
|
1172
|
-
const provenance = inspectPolishedFrameProvenance(
|
|
1173
|
-
this.base,
|
|
1174
|
-
captured.value,
|
|
1175
|
-
config,
|
|
1176
|
-
this.uiTheme,
|
|
1177
|
-
);
|
|
1178
|
-
if (provenance.safe) {
|
|
1179
|
-
const result = renderAccentRailFrameFromBase({
|
|
1180
|
-
width,
|
|
1181
|
-
baseRendered: captured.value,
|
|
1182
|
-
autocompleteSource: this.base,
|
|
1183
|
-
autocompleteCapture: captured.capture,
|
|
1184
|
-
uiTheme: this.uiTheme,
|
|
1185
|
-
config,
|
|
1186
|
-
ownedFrame: provenance.ownedFrame,
|
|
1187
|
-
});
|
|
1188
|
-
if (result.decorated) return result.lines;
|
|
1189
|
-
}
|
|
1190
|
-
} catch {
|
|
1191
|
-
// Decoration is optional; preserve the completed same-render rows below.
|
|
1192
|
-
}
|
|
1193
|
-
return clampRenderedLines(captured.value, width);
|
|
1194
|
-
}
|
|
1195
963
|
if (config.components.editor.style === "minimalist") {
|
|
1196
964
|
if (width <= 4) {
|
|
1197
965
|
this.reportMinimalistDecoration(false);
|
|
@@ -10,6 +10,7 @@ import { isSafeSgrStylePrefix } from "../../shared/style.ts";
|
|
|
10
10
|
import { renderWorkingLineHigh } from "./working-line.ts";
|
|
11
11
|
|
|
12
12
|
export const TURN_SUMMARY_ENTRY_TYPE = "zentui-turn-summary";
|
|
13
|
+
export const MIN_OUTPUT_RATE_WINDOW_MS = 500;
|
|
13
14
|
const TURN_SUMMARY_VERSION = 3;
|
|
14
15
|
const SGR_RESET = "\x1b[0m";
|
|
15
16
|
|
|
@@ -18,6 +19,7 @@ export type LiveTokenDisplay = Readonly<{
|
|
|
18
19
|
input: number;
|
|
19
20
|
output: number;
|
|
20
21
|
outputApproximate: boolean;
|
|
22
|
+
outputTokensPerSecond?: number;
|
|
21
23
|
}>;
|
|
22
24
|
export type ThoughtSnapshot = Readonly<{ durationMs: number; active: boolean }>;
|
|
23
25
|
|
|
@@ -70,6 +72,8 @@ type ResponseState = {
|
|
|
70
72
|
outputAnchor: number;
|
|
71
73
|
outputAnchorCodePoints: number;
|
|
72
74
|
liveOutputFloor: number;
|
|
75
|
+
startedAt: number;
|
|
76
|
+
outputTokensPerSecond?: number;
|
|
73
77
|
lastDisplay?: LiveTokenDisplay;
|
|
74
78
|
estimateIncomplete: boolean;
|
|
75
79
|
estimateEnabled: boolean;
|
|
@@ -515,7 +519,7 @@ export class InteractionMetricsTracker {
|
|
|
515
519
|
this.commitSnapshot(run, run.response);
|
|
516
520
|
this.closeResponse(run, run.response, now);
|
|
517
521
|
}
|
|
518
|
-
this.openResponse(run);
|
|
522
|
+
this.openResponse(run, now);
|
|
519
523
|
}
|
|
520
524
|
|
|
521
525
|
messageUpdate(
|
|
@@ -549,7 +553,7 @@ export class InteractionMetricsTracker {
|
|
|
549
553
|
contentChanged = this.applyContentEvent(response, validated);
|
|
550
554
|
}
|
|
551
555
|
const usageChanged = tokens ? this.applyLiveUsage(response, tokens) : false;
|
|
552
|
-
const display = this.liveDisplay(response);
|
|
556
|
+
const display = this.liveDisplay(response, now);
|
|
553
557
|
const displayChanged = !this.sameDisplay(response.lastDisplay, display);
|
|
554
558
|
response.lastDisplay = display;
|
|
555
559
|
return {
|
|
@@ -570,20 +574,31 @@ export class InteractionMetricsTracker {
|
|
|
570
574
|
if (response.responseIdKey && key && response.responseIdKey !== key)
|
|
571
575
|
return { status: "rejected" };
|
|
572
576
|
if (!response.responseIdKey && key) response.responseIdKey = key;
|
|
573
|
-
const preCloseDisplay = this.liveDisplay(response);
|
|
574
577
|
let source: "final" | "last-snapshot" | "no-snapshot";
|
|
575
578
|
if (tokens) {
|
|
576
579
|
response.snapshot = tokens;
|
|
577
580
|
response.hasSnapshot = true;
|
|
581
|
+
this.updateOutputRate(response, tokens.output, now);
|
|
578
582
|
source = "final";
|
|
579
583
|
} else source = response.hasSnapshot ? "last-snapshot" : "no-snapshot";
|
|
584
|
+
const preCloseDisplay = this.liveDisplay(
|
|
585
|
+
response,
|
|
586
|
+
tokens ? undefined : now,
|
|
587
|
+
);
|
|
580
588
|
this.commitSnapshot(run, response);
|
|
581
589
|
const committed = this.totalTokens();
|
|
582
590
|
const preserveEstimate =
|
|
583
591
|
source !== "final" && response.lastDisplay?.outputApproximate === true;
|
|
592
|
+
const outputRate = preCloseDisplay.outputTokensPerSecond;
|
|
584
593
|
const displayTokens = preserveEstimate
|
|
585
594
|
? { ...preCloseDisplay, outputApproximate: true }
|
|
586
|
-
: {
|
|
595
|
+
: {
|
|
596
|
+
...committed,
|
|
597
|
+
outputApproximate: false,
|
|
598
|
+
...(outputRate === undefined
|
|
599
|
+
? {}
|
|
600
|
+
: { outputTokensPerSecond: outputRate }),
|
|
601
|
+
};
|
|
587
602
|
run.lastClosedDisplay = preserveEstimate ? displayTokens : undefined;
|
|
588
603
|
this.closeResponse(run, response, now);
|
|
589
604
|
return { status: "accepted", tokens: committed, displayTokens, source };
|
|
@@ -770,7 +785,11 @@ export class InteractionMetricsTracker {
|
|
|
770
785
|
);
|
|
771
786
|
}
|
|
772
787
|
|
|
773
|
-
private openResponse(
|
|
788
|
+
private openResponse(
|
|
789
|
+
run: RunState,
|
|
790
|
+
now: number,
|
|
791
|
+
key?: string,
|
|
792
|
+
): ResponseState {
|
|
774
793
|
run.responseOrdinal = safeAdd(run.responseOrdinal, 1);
|
|
775
794
|
run.lastClosedDisplay = undefined;
|
|
776
795
|
const estimateEnabled =
|
|
@@ -787,6 +806,7 @@ export class InteractionMetricsTracker {
|
|
|
787
806
|
outputAnchor: 0,
|
|
788
807
|
outputAnchorCodePoints: 0,
|
|
789
808
|
liveOutputFloor: 0,
|
|
809
|
+
startedAt: safeTime(now),
|
|
790
810
|
estimateIncomplete: !estimateEnabled,
|
|
791
811
|
estimateEnabled,
|
|
792
812
|
};
|
|
@@ -996,7 +1016,7 @@ export class InteractionMetricsTracker {
|
|
|
996
1016
|
return true;
|
|
997
1017
|
}
|
|
998
1018
|
|
|
999
|
-
private liveDisplay(response: ResponseState): LiveTokenDisplay {
|
|
1019
|
+
private liveDisplay(response: ResponseState, now?: number): LiveTokenDisplay {
|
|
1000
1020
|
const committed = this.committedTokens();
|
|
1001
1021
|
const postAnchorCodePoints = Math.max(
|
|
1002
1022
|
0,
|
|
@@ -1013,6 +1033,8 @@ export class InteractionMetricsTracker {
|
|
|
1013
1033
|
responseOutput === response.outputAnchor &&
|
|
1014
1034
|
!response.estimateIncomplete;
|
|
1015
1035
|
response.liveOutputFloor = responseOutput;
|
|
1036
|
+
if (now !== undefined) this.updateOutputRate(response, responseOutput, now);
|
|
1037
|
+
const outputRate = response.outputTokensPerSecond;
|
|
1016
1038
|
return {
|
|
1017
1039
|
input: safeAdd(
|
|
1018
1040
|
committed.input,
|
|
@@ -1020,9 +1042,26 @@ export class InteractionMetricsTracker {
|
|
|
1020
1042
|
),
|
|
1021
1043
|
output: safeAdd(committed.output, responseOutput),
|
|
1022
1044
|
outputApproximate: !exactCoverage,
|
|
1045
|
+
...(outputRate === undefined
|
|
1046
|
+
? {}
|
|
1047
|
+
: { outputTokensPerSecond: outputRate }),
|
|
1023
1048
|
};
|
|
1024
1049
|
}
|
|
1025
1050
|
|
|
1051
|
+
private updateOutputRate(
|
|
1052
|
+
response: ResponseState,
|
|
1053
|
+
responseOutput: number,
|
|
1054
|
+
now: number,
|
|
1055
|
+
): void {
|
|
1056
|
+
const elapsedMs = durationMs(response.startedAt, now);
|
|
1057
|
+
const rate =
|
|
1058
|
+
elapsedMs >= MIN_OUTPUT_RATE_WINDOW_MS && responseOutput > 0
|
|
1059
|
+
? Math.min(Number.MAX_SAFE_INTEGER, (responseOutput * 1000) / elapsedMs)
|
|
1060
|
+
: undefined;
|
|
1061
|
+
response.outputTokensPerSecond =
|
|
1062
|
+
rate !== undefined && rate >= 0.1 ? rate : undefined;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1026
1065
|
private runTokens(run: RunState): InteractionTokens {
|
|
1027
1066
|
return run.response?.hasSnapshot
|
|
1028
1067
|
? addTokens(run.committed, run.response.snapshot)
|
|
@@ -1104,7 +1143,8 @@ export class InteractionMetricsTracker {
|
|
|
1104
1143
|
return (
|
|
1105
1144
|
left?.input === right?.input &&
|
|
1106
1145
|
left?.output === right?.output &&
|
|
1107
|
-
left?.outputApproximate === right?.outputApproximate
|
|
1146
|
+
left?.outputApproximate === right?.outputApproximate &&
|
|
1147
|
+
left?.outputTokensPerSecond === right?.outputTokensPerSecond
|
|
1108
1148
|
);
|
|
1109
1149
|
}
|
|
1110
1150
|
}
|