@oh-my-pi/pi-tui 18.3.2 → 18.3.3
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 +22 -0
- package/dist/types/app-keybindings.d.ts +2 -17
- package/dist/types/autocomplete.d.ts +18 -3
- package/dist/types/chrome/keybinding-hints.d.ts +16 -10
- package/dist/types/components/editor.d.ts +11 -0
- package/dist/types/key-hint-format.d.ts +29 -0
- package/dist/types/keys.d.ts +2 -1
- package/dist/types/overlays/agent-hub.d.ts +1 -1
- package/dist/types/overlays/agent-transcript-viewer.d.ts +1 -1
- package/dist/types/overlays/model-picker.d.ts +1 -3
- package/dist/types/prompt/composer-hints.d.ts +42 -0
- package/dist/types/prompt/composer.d.ts +2 -1
- package/dist/types/prompt/custom-editor.d.ts +8 -2
- package/dist/types/prompt/macos-spelling.d.ts +7 -16
- package/dist/types/prompt/prompt-action-autocomplete.d.ts +4 -1
- package/dist/types/prompt/prose-gate.d.ts +24 -0
- package/dist/types/prompt/word-completion.d.ts +46 -0
- package/dist/types/render/render-utils.d.ts +1 -1
- package/dist/types/theme/active-symbols.d.ts +12 -0
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +0 -1
- package/dist/types/tools/eval.d.ts +1 -1
- package/dist/types/tools/mcp.d.ts +3 -1
- package/dist/types/tools/task-yield-assembly.d.ts +10 -2
- package/dist/types/tools/task.d.ts +4 -0
- package/package.json +11 -10
- package/src/app-keybindings.ts +11 -104
- package/src/apps/autoresearch-dashboard.ts +10 -3
- package/src/apps/debug/log-viewer.ts +3 -2
- package/src/apps/debug/raw-sse.ts +2 -1
- package/src/apps/git/git-tui.ts +9 -5
- package/src/apps/live-visualizer.ts +2 -1
- package/src/apps/ps-top.ts +13 -4
- package/src/autocomplete.ts +42 -7
- package/src/chat/compaction-summary-message.ts +2 -1
- package/src/chat/execution-shared.ts +4 -2
- package/src/chat/tool-execution.ts +3 -1
- package/src/chat/ttsr-notification.ts +4 -3
- package/src/chat/user-message.ts +2 -2
- package/src/chrome/keybinding-hints.ts +38 -13
- package/src/components/editor.ts +180 -58
- package/src/components/settings-list.ts +10 -3
- package/src/components/tab-bar.ts +2 -1
- package/src/debug-server.ts +4 -1
- package/src/hotkeys-markdown.ts +14 -17
- package/src/key-hint-format.ts +122 -0
- package/src/keys.ts +2 -1
- package/src/overlays/advisor-config.ts +28 -6
- package/src/overlays/agent-hub.ts +8 -5
- package/src/overlays/agent-transcript-viewer.ts +6 -4
- package/src/overlays/agents-hub.ts +22 -8
- package/src/overlays/annotation-overlay.ts +31 -17
- package/src/overlays/ask-dialog.ts +14 -19
- package/src/overlays/bordered-loader.ts +2 -1
- package/src/overlays/btw-history-panel.ts +15 -12
- package/src/overlays/btw-panel.ts +12 -7
- package/src/overlays/cleanse-panel.ts +9 -6
- package/src/overlays/codex-reset-fireworks.ts +5 -3
- package/src/overlays/copy-selector.ts +13 -5
- package/src/overlays/extensions/extension-dashboard.ts +6 -1
- package/src/overlays/history-search.ts +3 -2
- package/src/overlays/hook-editor.ts +7 -2
- package/src/overlays/hook-input.ts +2 -1
- package/src/overlays/hook-selector.ts +5 -1
- package/src/overlays/login-dialog.ts +17 -4
- package/src/overlays/logout-account-selector.ts +10 -1
- package/src/overlays/mcp-add-wizard.ts +32 -20
- package/src/overlays/model-hub.ts +55 -28
- package/src/overlays/model-picker.ts +27 -12
- package/src/overlays/move-overlay.ts +5 -1
- package/src/overlays/omfg-panel.ts +8 -5
- package/src/overlays/pause-screen.ts +7 -3
- package/src/overlays/plan-review-overlay.ts +40 -14
- package/src/overlays/plan-save-overlay.ts +3 -1
- package/src/overlays/plugin-settings.ts +15 -8
- package/src/overlays/reset-usage-selector.ts +10 -3
- package/src/overlays/rewind-selector.ts +11 -5
- package/src/overlays/session-info-overlay.ts +4 -3
- package/src/overlays/session-selector.ts +12 -2
- package/src/overlays/settings-selector.ts +18 -11
- package/src/overlays/tree-selector.ts +27 -4
- package/src/overlays/usage-dashboard.ts +8 -2
- package/src/prompt/composer-cache.ts +2 -1
- package/src/prompt/composer-hints.ts +75 -0
- package/src/prompt/composer.ts +3 -2
- package/src/prompt/custom-editor.ts +22 -4
- package/src/prompt/macos-spelling.ts +17 -158
- package/src/prompt/prompt-action-autocomplete.ts +9 -2
- package/src/prompt/prose-gate.ts +87 -0
- package/src/prompt/tips.txt +6 -6
- package/src/prompt/welcome.ts +35 -2
- package/src/prompt/word-completion.ts +205 -0
- package/src/render/render-utils.ts +4 -4
- package/src/setup/scenes/composer.ts +5 -1
- package/src/setup/scenes/model.ts +3 -1
- package/src/setup/scenes/providers.ts +4 -1
- package/src/setup/scenes/sign-in.ts +4 -2
- package/src/setup/scenes/splash.ts +9 -4
- package/src/setup/scenes/theme.ts +18 -5
- package/src/setup/wizard-overlay.ts +5 -4
- package/src/theme/active-symbols.ts +20 -0
- package/src/theme/glyph-bundle.json +28 -1
- package/src/theme/symbols.ts +88 -6
- package/src/theme/theme-class.ts +0 -1
- package/src/theme/theme.ts +2 -0
- package/src/tools/bash-interactive.ts +2 -1
- package/src/tools/eval.ts +3 -3
- package/src/tools/mcp.ts +3 -1
- package/src/tools/task-yield-assembly.ts +34 -8
- package/src/tools/task.ts +14 -5
- package/dist/types/overlays/tiny-title-download-progress.d.ts +0 -21
- package/src/overlays/tiny-title-download-progress.ts +0 -102
package/src/theme/theme-class.ts
CHANGED
|
@@ -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"],
|
package/src/theme/theme.ts
CHANGED
|
@@ -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", "
|
|
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
|
|
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 (
|
|
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}) (
|
|
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
|
-
/**
|
|
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
|
-
|
|
64
|
+
shape: "array" | "scalar" | undefined,
|
|
56
65
|
): void {
|
|
57
66
|
const count = sectionCounts.get(label) ?? 0;
|
|
58
67
|
const existing = sections[label];
|
|
59
|
-
if (
|
|
60
|
-
sections[label] =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
102
|
-
const
|
|
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,
|
|
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 / #
|
|
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
|
+
/** Caller's terse rationale for why the work is simple or complex; required by the schema and fed to the child's `auto` thinking classifier. */
|
|
1735
|
+
complexity?: 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
|
+
/** Caller's difficulty rationale (flat form); see {@link TaskItem.complexity}. */
|
|
1762
|
+
complexity?: 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
|
-
}
|