@oh-my-pi/pi-tui 18.3.2 → 18.3.4

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.
Files changed (112) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/types/app-keybindings.d.ts +2 -17
  3. package/dist/types/autocomplete.d.ts +18 -3
  4. package/dist/types/chrome/keybinding-hints.d.ts +16 -10
  5. package/dist/types/components/editor.d.ts +11 -0
  6. package/dist/types/key-hint-format.d.ts +29 -0
  7. package/dist/types/keys.d.ts +2 -1
  8. package/dist/types/overlays/agent-hub.d.ts +1 -1
  9. package/dist/types/overlays/agent-transcript-viewer.d.ts +1 -1
  10. package/dist/types/overlays/model-picker.d.ts +1 -3
  11. package/dist/types/prompt/composer-hints.d.ts +42 -0
  12. package/dist/types/prompt/composer.d.ts +2 -1
  13. package/dist/types/prompt/custom-editor.d.ts +8 -2
  14. package/dist/types/prompt/macos-spelling.d.ts +7 -16
  15. package/dist/types/prompt/prompt-action-autocomplete.d.ts +4 -1
  16. package/dist/types/prompt/prose-gate.d.ts +24 -0
  17. package/dist/types/prompt/word-completion.d.ts +46 -0
  18. package/dist/types/render/render-utils.d.ts +1 -1
  19. package/dist/types/theme/active-symbols.d.ts +12 -0
  20. package/dist/types/theme/symbols.d.ts +1 -1
  21. package/dist/types/theme/theme-class.d.ts +0 -1
  22. package/dist/types/tools/eval.d.ts +1 -1
  23. package/dist/types/tools/mcp.d.ts +3 -1
  24. package/dist/types/tools/task-yield-assembly.d.ts +10 -2
  25. package/dist/types/tools/task.d.ts +4 -0
  26. package/package.json +11 -10
  27. package/src/app-keybindings.ts +11 -104
  28. package/src/apps/autoresearch-dashboard.ts +10 -3
  29. package/src/apps/debug/log-viewer.ts +3 -2
  30. package/src/apps/debug/raw-sse.ts +2 -1
  31. package/src/apps/git/git-tui.ts +9 -5
  32. package/src/apps/live-visualizer.ts +2 -1
  33. package/src/apps/ps-top.ts +13 -4
  34. package/src/autocomplete.ts +42 -7
  35. package/src/chat/compaction-summary-message.ts +2 -1
  36. package/src/chat/execution-shared.ts +4 -2
  37. package/src/chat/tool-execution.ts +3 -1
  38. package/src/chat/ttsr-notification.ts +4 -3
  39. package/src/chat/user-message.ts +2 -2
  40. package/src/chrome/keybinding-hints.ts +38 -13
  41. package/src/components/editor.ts +180 -58
  42. package/src/components/settings-list.ts +10 -3
  43. package/src/components/tab-bar.ts +2 -1
  44. package/src/debug-server.ts +4 -1
  45. package/src/hotkeys-markdown.ts +14 -17
  46. package/src/key-hint-format.ts +122 -0
  47. package/src/keys.ts +2 -1
  48. package/src/overlays/advisor-config.ts +28 -6
  49. package/src/overlays/agent-hub.ts +8 -5
  50. package/src/overlays/agent-transcript-viewer.ts +6 -4
  51. package/src/overlays/agents-hub.ts +22 -8
  52. package/src/overlays/annotation-overlay.ts +31 -17
  53. package/src/overlays/ask-dialog.ts +14 -19
  54. package/src/overlays/bordered-loader.ts +2 -1
  55. package/src/overlays/btw-history-panel.ts +15 -12
  56. package/src/overlays/btw-panel.ts +12 -7
  57. package/src/overlays/cleanse-panel.ts +9 -6
  58. package/src/overlays/codex-reset-fireworks.ts +5 -3
  59. package/src/overlays/copy-selector.ts +13 -5
  60. package/src/overlays/extensions/extension-dashboard.ts +6 -1
  61. package/src/overlays/history-search.ts +3 -2
  62. package/src/overlays/hook-editor.ts +7 -2
  63. package/src/overlays/hook-input.ts +2 -1
  64. package/src/overlays/hook-selector.ts +5 -1
  65. package/src/overlays/login-dialog.ts +17 -4
  66. package/src/overlays/logout-account-selector.ts +10 -1
  67. package/src/overlays/mcp-add-wizard.ts +32 -20
  68. package/src/overlays/model-hub.ts +55 -28
  69. package/src/overlays/model-picker.ts +27 -12
  70. package/src/overlays/move-overlay.ts +5 -1
  71. package/src/overlays/omfg-panel.ts +8 -5
  72. package/src/overlays/pause-screen.ts +7 -3
  73. package/src/overlays/plan-review-overlay.ts +40 -14
  74. package/src/overlays/plan-save-overlay.ts +3 -1
  75. package/src/overlays/plugin-settings.ts +15 -8
  76. package/src/overlays/reset-usage-selector.ts +10 -3
  77. package/src/overlays/rewind-selector.ts +11 -5
  78. package/src/overlays/session-info-overlay.ts +4 -3
  79. package/src/overlays/session-selector.ts +12 -2
  80. package/src/overlays/settings-selector.ts +18 -11
  81. package/src/overlays/tree-selector.ts +27 -4
  82. package/src/overlays/usage-dashboard.ts +8 -2
  83. package/src/prompt/composer-cache.ts +2 -1
  84. package/src/prompt/composer-hints.ts +75 -0
  85. package/src/prompt/composer.ts +3 -2
  86. package/src/prompt/custom-editor.ts +22 -4
  87. package/src/prompt/macos-spelling.ts +17 -158
  88. package/src/prompt/prompt-action-autocomplete.ts +9 -2
  89. package/src/prompt/prose-gate.ts +87 -0
  90. package/src/prompt/tips.txt +6 -6
  91. package/src/prompt/welcome.ts +35 -2
  92. package/src/prompt/word-completion.ts +205 -0
  93. package/src/render/render-utils.ts +4 -4
  94. package/src/setup/scenes/composer.ts +5 -1
  95. package/src/setup/scenes/model.ts +3 -1
  96. package/src/setup/scenes/providers.ts +4 -1
  97. package/src/setup/scenes/sign-in.ts +4 -2
  98. package/src/setup/scenes/splash.ts +9 -4
  99. package/src/setup/scenes/theme.ts +18 -5
  100. package/src/setup/wizard-overlay.ts +5 -4
  101. package/src/theme/active-symbols.ts +20 -0
  102. package/src/theme/glyph-bundle.json +28 -1
  103. package/src/theme/symbols.ts +88 -6
  104. package/src/theme/theme-class.ts +0 -1
  105. package/src/theme/theme.ts +2 -0
  106. package/src/tools/bash-interactive.ts +2 -1
  107. package/src/tools/eval.ts +3 -3
  108. package/src/tools/mcp.ts +3 -1
  109. package/src/tools/task-yield-assembly.ts +34 -8
  110. package/src/tools/task.ts +14 -5
  111. package/dist/types/overlays/tiny-title-download-progress.d.ts +0 -21
  112. package/src/overlays/tiny-title-download-progress.ts +0 -102
@@ -592,7 +592,6 @@ export class Theme {
592
592
  advisorClosed: this.#symbols["icon.advisorClosed"],
593
593
  time: this.#symbols["icon.time"],
594
594
  omp: this.#symbols["icon.omp"],
595
- esc: this.#symbols["icon.esc"],
596
595
  ghost: this.#symbols["icon.ghost"],
597
596
  agents: this.#symbols["icon.agents"],
598
597
  job: this.#symbols["icon.job"],
@@ -5,6 +5,7 @@ import type { Terminal, TerminalAppearance } from "../terminal";
5
5
  import { colorLuma } from "@oh-my-pi/pi-utils/color";
6
6
  import { getCustomThemesDir } from "@oh-my-pi/pi-utils/dirs";
7
7
  import * as logger from "@oh-my-pi/pi-utils/logger";
8
+ import { setActiveSymbolTheme } from "./active-symbols";
8
9
  import { ansi256ToHex, resolveThemeColors, resolveVarRefs } from "./color";
9
10
  import { type CreateThemeOptions, getBuiltinThemes, loadTheme, loadThemeJson, loadThemeSync } from "./loader";
10
11
  import type { ThemeColor, ThemeJson } from "./schema";
@@ -99,6 +100,7 @@ export function bindTheme(binding: ThemeBinding): () => void {
99
100
 
100
101
  function assignTheme(value: Theme): void {
101
102
  theme = value;
103
+ setActiveSymbolTheme(value);
102
104
  if (themeBindings) {
103
105
  for (const binding of themeBindings) binding(value);
104
106
  }
@@ -4,6 +4,7 @@ import type { Component } from "../tui";
4
4
  import { extractPrintableText, matchesKey, parseKey, parseKittySequence } from "../keys";
5
5
  import { padding, truncateToWidth, visibleWidth } from "../utils";
6
6
  import type { Theme } from "../theme/theme";
7
+ import { formatKeyHint } from "../app-keybindings";
7
8
  import { formatStatusIcon, replaceTabs } from "../render/render-utils";
8
9
  import { readTerminalRows, styleTerminalRow } from "./terminal-output";
9
10
 
@@ -267,7 +268,7 @@ export class BashInteractiveOverlayComponent implements Component {
267
268
  const footer =
268
269
  this.#state === "running"
269
270
  ? truncateToWidth(
270
- `${this.#uiTheme.fg("warning", "esc")} ${this.#uiTheme.fg("dim", "force-kill")} ${this.#uiTheme.fg("dim", "· input forwarded to PTY")}`,
271
+ `${this.#uiTheme.fg("warning", formatKeyHint("escape"))} ${this.#uiTheme.fg("dim", "force-kill")} ${this.#uiTheme.fg("dim", "· input forwarded to PTY")}`,
271
272
  innerWidth,
272
273
  )
273
274
  : truncateToWidth(this.#uiTheme.fg("dim", "session finished"), innerWidth);
package/src/tools/eval.ts CHANGED
@@ -30,7 +30,7 @@ import {
30
30
  } from "../render/render-utils";
31
31
  import type { ImageContent } from "@oh-my-pi/pi-ai";
32
32
  import type { OutputMeta } from "./output-meta";
33
- import type { ConfiguredThinkingLevel } from "../render/render-utils";
33
+ import { type ConfiguredThinkingLevel, expandKeyHint } from "../render/render-utils";
34
34
 
35
35
  /** Runtime backend that an eval cell dispatches to. */
36
36
  export type EvalLanguage = "python" | "js";
@@ -677,7 +677,7 @@ export const evalToolRenderer = {
677
677
  const outputLines = [...outputContent.lines];
678
678
  if (!expanded && outputContent.hiddenCount > 0) {
679
679
  outputLines.push(
680
- uiTheme.fg("dim", `… ${outputContent.hiddenCount} more lines (ctrl+o to expand)`),
680
+ uiTheme.fg("dim", `… ${outputContent.hiddenCount} more lines (${expandKeyHint()} to expand)`),
681
681
  );
682
682
  }
683
683
  if (statusLines.length > 0) {
@@ -811,7 +811,7 @@ export const evalToolRenderer = {
811
811
  outputLines.push("");
812
812
  const skippedLine = uiTheme.fg(
813
813
  "dim",
814
- `… (${cachedSkipped} earlier lines, showing ${cachedLines.length} of ${cachedSkipped + cachedLines.length}) (ctrl+o to expand)`,
814
+ `… (${cachedSkipped} earlier lines, showing ${cachedLines.length} of ${cachedSkipped + cachedLines.length}) (${expandKeyHint()} to expand)`,
815
815
  );
816
816
  outputLines.push(truncateToWidth(skippedLine, width));
817
817
  }
package/src/tools/mcp.ts CHANGED
@@ -266,7 +266,7 @@ export interface MCPResourceContent {
266
266
  /** Supported MCP result content blocks retained in display metadata. */
267
267
  export type MCPContent = MCPTextContent | MCPImageContent | MCPResourceContent;
268
268
 
269
- /** Details included in MCP tool results for rendering */
269
+ /** MCP result details shared by renderers and programmatic tool consumers. */
270
270
  export interface MCPToolDetails {
271
271
  /** Server name */
272
272
  serverName: string;
@@ -276,6 +276,8 @@ export interface MCPToolDetails {
276
276
  isError?: boolean;
277
277
  /** Raw content from MCP response */
278
278
  rawContent?: MCPContent[];
279
+ /** Server-supplied structured data, independent of the model-facing text rendering. */
280
+ structuredContent?: Record<string, unknown>;
279
281
  /** Structured metadata from the MCP response */
280
282
  mcpMeta?: Record<string, unknown>;
281
283
  /** Provider ID (e.g., "claude", "mcp-json") */
@@ -1,4 +1,13 @@
1
1
  import type { YieldItem } from "./task";
2
+
3
+ /**
4
+ * Output-schema shape of each declared top-level property, keyed by incremental yield label.
5
+ * `array` sections accumulate into a list (even a lone yield); `scalar` sections keep the
6
+ * latest yield, since the schema admits exactly one value. Undeclared labels accumulate
7
+ * into a list only once repeated.
8
+ */
9
+ export type YieldSectionShapes = ReadonlyMap<string, "array" | "scalar">;
10
+
2
11
  /** Outcome of folding a run's yield calls into one payload, with provenance flags. */
3
12
  interface AssembledYieldResult {
4
13
  data: unknown;
@@ -52,12 +61,14 @@ function appendYieldSection(
52
61
  sectionCounts: Map<string, number>,
53
62
  label: string,
54
63
  value: unknown,
55
- forceArray: boolean,
64
+ shape: "array" | "scalar" | undefined,
56
65
  ): void {
57
66
  const count = sectionCounts.get(label) ?? 0;
58
67
  const existing = sections[label];
59
- if (count === 0) {
60
- sections[label] = forceArray ? [value] : value;
68
+ if (shape === "scalar") {
69
+ sections[label] = value;
70
+ } else if (count === 0) {
71
+ sections[label] = shape === "array" ? [value] : value;
61
72
  } else if (Array.isArray(existing)) {
62
73
  existing.push(value);
63
74
  } else {
@@ -74,12 +85,13 @@ function appendYieldSection(
74
85
  * assistant turn the raw terminal result. Other string-typed yields contribute
75
86
  * the terminal labelled section. Untyped terminal yields keep the historical
76
87
  * "last yield wins" behavior unless no terminal yield exists, in which case
77
- * accumulated typed sections finalize on idle.
88
+ * accumulated typed sections finalize on idle. Repeated sections merge per
89
+ * `sectionShapes` (see {@link YieldSectionShapes}).
78
90
  */
79
91
  export function assembleYieldResult(
80
92
  yieldItems: YieldItem[],
81
93
  lastAssistantText?: string,
82
- arrayLabels?: ReadonlySet<string>,
94
+ sectionShapes?: YieldSectionShapes,
83
95
  ): AssembledYieldResult | undefined {
84
96
  if (yieldItems.length === 0) return undefined;
85
97
 
@@ -101,18 +113,27 @@ export function assembleYieldResult(
101
113
  // level deep and made output-schema validation report every field missing.
102
114
  const sections: Record<string, unknown> = {};
103
115
  const sectionCounts = new Map<string, number>();
116
+ const overriddenScalars = new Set<string>();
104
117
  let schemaOverridden = false;
105
118
  let missingData = false;
106
119
  let hasSections = false;
107
120
  for (const item of yieldItems) {
108
121
  if (item.status === "aborted") continue;
109
122
  if (!isIncrementalYieldType(item.type)) continue;
110
- schemaOverridden ||= item.schemaOverridden === true;
123
+ const overridden = item.schemaOverridden === true;
111
124
  const labels = getYieldLabels(item.type);
112
125
  const resolved = resolveYieldPayload(item, lastAssistantText, labels);
113
126
  missingData ||= resolved.missingData;
127
+ if (labels.length === 0) schemaOverridden ||= overridden;
114
128
  for (const label of labels) {
115
- appendYieldSection(sections, sectionCounts, label, resolved.value, arrayLabels?.has(label) ?? false);
129
+ const shape = sectionShapes?.get(label);
130
+ appendYieldSection(sections, sectionCounts, label, resolved.value, shape);
131
+ if (shape === "scalar") {
132
+ if (overridden) overriddenScalars.add(label);
133
+ else overriddenScalars.delete(label);
134
+ } else {
135
+ schemaOverridden ||= overridden;
136
+ }
116
137
  hasSections = true;
117
138
  }
118
139
  }
@@ -133,7 +154,12 @@ export function assembleYieldResult(
133
154
  // A data-less terminal finalize keeps accumulated sections; only when none
134
155
  // exist does the last assistant turn become the raw result.
135
156
  if (hasSections) {
136
- return { data: sections, schemaOverridden, rawText: false, missingData };
157
+ return {
158
+ data: sections,
159
+ schemaOverridden: schemaOverridden || overriddenScalars.size > 0,
160
+ rawText: false,
161
+ missingData,
162
+ };
137
163
  }
138
164
 
139
165
  if (!terminalItem) return undefined;
package/src/tools/task.ts CHANGED
@@ -42,7 +42,7 @@ import { framedToolCard } from "../render/tool-card";
42
42
  import { formatOutputInline, renderJsonTreeLines } from "./json-tree";
43
43
  import { repairDoubleEncodedJsonString } from "./task-repair-args";
44
44
  import { getSubprocessToolRenderer } from "./subprocess";
45
- import { assembleYieldResult } from "./task-yield-assembly";
45
+ import { assembleYieldResult, type YieldSectionShapes } from "./task-yield-assembly";
46
46
 
47
47
  /** Render context threaded in from `ToolExecutionComponent.#buildRenderContext`. */
48
48
  interface TaskRenderContext {
@@ -98,8 +98,13 @@ function normalizeFindings(value: unknown): FindingDetails[] {
98
98
  return findings;
99
99
  }
100
100
 
101
- /** Reviewer output declares `findings` as an array, so a lone finding section still assembles as a list. */
102
- const REVIEWER_ARRAY_LABELS: ReadonlySet<string> = new Set(["findings"]);
101
+ /** Reviewer output shapes: `findings` is an array (a lone finding still assembles as a list); the verdict fields are scalars. */
102
+ const REVIEWER_SECTION_SHAPES: YieldSectionShapes = new Map([
103
+ ["findings", "array"],
104
+ ["overall_correctness", "scalar"],
105
+ ["explanation", "scalar"],
106
+ ["confidence", "scalar"],
107
+ ]);
103
108
 
104
109
  function extractIncrementalReviewResult(
105
110
  items: RenderYieldItem[],
@@ -110,7 +115,7 @@ function extractIncrementalReviewResult(
110
115
  status: item.status === "aborted" ? "aborted" : item.status === "success" ? "success" : undefined,
111
116
  useLastTurn: item.useLastTurn,
112
117
  }));
113
- const assembled = assembleYieldResult(yieldItems, undefined, REVIEWER_ARRAY_LABELS);
118
+ const assembled = assembleYieldResult(yieldItems, undefined, REVIEWER_SECTION_SHAPES);
114
119
  const data = assembled?.data;
115
120
  if (!data || typeof data !== "object" || Array.isArray(data)) return undefined;
116
121
  const record = data as Record<string, unknown>;
@@ -553,7 +558,7 @@ function createAssignmentSectionRenderer(
553
558
  }
554
559
 
555
560
  /**
556
- * Build the shared-context section (the `# Goal / # Constraints` background a
561
+ * Build the shared-context section (the `# Goal / # Contract` background a
557
562
  * batch call hands every subagent). Rendered like the assignment brief so the
558
563
  * shared background stays visible for the whole task lifecycle.
559
564
  */
@@ -1726,6 +1731,8 @@ export interface TaskItem {
1726
1731
  agent?: string;
1727
1732
  /** The work; required by the schema. */
1728
1733
  task?: string;
1734
+ /** How open-ended the work is; required by the schema and the child's sole `auto` thinking classification input. */
1735
+ solutionSpace?: string;
1729
1736
  /** Per-spawn thinking effort: lowest/middle/highest level the resolved model supports. Overrides the agent's default selector (e.g. `auto`). */
1730
1737
  effort?: "lo" | "med" | "hi";
1731
1738
  /** Caller-provided output schema; its presence overrides the selected agent's schema. */
@@ -1751,6 +1758,8 @@ export interface TaskParams {
1751
1758
  agent?: string;
1752
1759
  /** The work (flat form). */
1753
1760
  task?: string;
1761
+ /** How open-ended the work is (flat form); see {@link TaskItem.solutionSpace}. */
1762
+ solutionSpace?: string;
1754
1763
  /** Per-spawn thinking effort (flat form): lowest/middle/highest level the resolved model supports. */
1755
1764
  effort?: "lo" | "med" | "hi";
1756
1765
  /** Caller-provided output schema; its presence overrides the selected agent's schema. */
@@ -1,21 +0,0 @@
1
- import { type Component } from "../index.js";
2
- /** Download progress fields consumed by the status display. */
3
- export interface TinyTitleDownloadProgress {
4
- status: "initiate" | "download" | "progress" | "progress_total" | "done" | "ready" | "error";
5
- file?: string;
6
- progress?: number;
7
- loaded?: number;
8
- total?: number;
9
- files?: Record<string, {
10
- loaded: number;
11
- total: number;
12
- }>;
13
- }
14
- export declare class TinyTitleDownloadProgressComponent implements Component {
15
- #private;
16
- constructor(modelLabel: string);
17
- update(event: TinyTitleDownloadProgress): void;
18
- isComplete(): boolean;
19
- invalidate(): void;
20
- render(width: number): readonly string[];
21
- }
@@ -1,102 +0,0 @@
1
- import { type Component, truncateToWidth, visibleWidth } from "../index";
2
- import { formatBytes } from "@oh-my-pi/pi-utils";
3
- import { ProgressBar } from "../components/progress-bar";
4
- /** Download progress fields consumed by the status display. */
5
- export interface TinyTitleDownloadProgress {
6
- status: "initiate" | "download" | "progress" | "progress_total" | "done" | "ready" | "error";
7
- file?: string;
8
- progress?: number;
9
- loaded?: number;
10
- total?: number;
11
- files?: Record<string, { loaded: number; total: number }>;
12
- }
13
- import { theme } from "../theme/theme";
14
-
15
- const DEFAULT_BAR_WIDTH = 24;
16
-
17
- function padLine(line: string, width: number): string {
18
- const visible = visibleWidth(line);
19
- return visible >= width ? truncateToWidth(line, width) : `${line}${" ".repeat(width - visible)}`;
20
- }
21
-
22
- function currentFile(event: TinyTitleDownloadProgress | undefined): string | undefined {
23
- if (!event) return undefined;
24
- if (event.file) return event.file.split("/").at(-1) ?? event.file;
25
- if (event.files) {
26
- let largestFile: string | undefined;
27
- let largestLoaded = -1;
28
- for (const file in event.files) {
29
- const state = event.files[file];
30
- if (state.loaded <= largestLoaded || state.loaded >= state.total) continue;
31
- largestFile = file;
32
- largestLoaded = state.loaded;
33
- }
34
- return largestFile?.split("/").at(-1) ?? largestFile;
35
- }
36
- return undefined;
37
- }
38
-
39
- function statusLabel(event: TinyTitleDownloadProgress | undefined): string {
40
- if (!event) return "Preparing";
41
- if (event.status === "error") return "Failed";
42
- if (event.status === "ready") return "Ready";
43
- if (event.status === "done") return "Downloaded";
44
- if (event.status === "download") return "Downloading";
45
- if (event.status === "progress" || event.status === "progress_total") return "Downloading";
46
- return "Preparing";
47
- }
48
-
49
- function byteLabel(event: TinyTitleDownloadProgress | undefined): string | undefined {
50
- if (!event?.loaded || !event.total) return undefined;
51
- return `${formatBytes(event.loaded)} / ${formatBytes(event.total)}`;
52
- }
53
-
54
- export class TinyTitleDownloadProgressComponent implements Component {
55
- #modelLabel: string;
56
- #event: TinyTitleDownloadProgress | undefined;
57
- #bar = new ProgressBar(undefined, {
58
- min: 0,
59
- max: 100,
60
- minWidth: 8,
61
- maxWidth: DEFAULT_BAR_WIDTH,
62
- style: {
63
- filled: "█",
64
- empty: "░",
65
- styleFilled: text => theme.fg("accent", text),
66
- styleEmpty: text => theme.fg("muted", text),
67
- styleIndeterminate: text => theme.fg("muted", text),
68
- },
69
- });
70
-
71
- constructor(modelLabel: string) {
72
- this.#modelLabel = modelLabel;
73
- }
74
-
75
- update(event: TinyTitleDownloadProgress): void {
76
- this.#event = event;
77
- this.#bar.setValue(event.progress);
78
- }
79
-
80
- isComplete(): boolean {
81
- return this.#event?.status === "ready" || this.#event?.status === "error";
82
- }
83
-
84
- invalidate(): void {
85
- this.#bar.invalidate();
86
- }
87
-
88
- render(width: number): readonly string[] {
89
- width = Math.max(1, width);
90
- const border = theme.fg("border", theme.boxRound.horizontal.repeat(width));
91
- const status = statusLabel(this.#event);
92
- const file = currentFile(this.#event);
93
- const pct =
94
- this.#event?.progress === undefined ? "" : `${Math.floor(this.#event.progress).toString().padStart(3, " ")}%`;
95
- const bytes = byteLabel(this.#event);
96
- const title = `${theme.fg("accent", "Tiny model")} ${theme.fg("muted", status)} ${this.#modelLabel}`;
97
- const bar = this.#bar.render(Math.max(8, width - 36))[0] ?? "";
98
- const details = [bar, pct, bytes, file].filter((part): part is string => Boolean(part)).join(" ");
99
-
100
- return [border, padLine(` ${title}`, width), padLine(` ${details}`, width), border];
101
- }
102
- }