@linxiraos/pi-tui 1.2.1 → 1.3.1
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 +245 -224
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +23 -20
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +26 -6
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +482 -12
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -1
- package/src/prompt/composer.ts +200 -74
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +97 -45
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +22 -8
- package/src/status-line/component.ts +79 -11
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +300 -62
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +190 -63
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search-types.d.ts +1 -0
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +160 -43
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
|
@@ -10,8 +10,10 @@ import { formatUsageResetWindow } from "./usage-display";
|
|
|
10
10
|
import { formatKeyHint } from "../app-keybindings";
|
|
11
11
|
import { editorKey, editorKeys } from "../chrome/keybinding-hints";
|
|
12
12
|
import { node, span, text } from "../native/describe";
|
|
13
|
-
import type { NativeNode, NativeUiEvent } from "../native/node";
|
|
13
|
+
import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node";
|
|
14
14
|
import { actionHint, hintsRow, overlayCard } from "../native/overlay";
|
|
15
|
+
import { CLOSE_ACTION, dockedPicker, PICKER_KEY, pickerAction, pickerEvent } from "../native/picker";
|
|
16
|
+
import type { TspPickerItem, TspPickerProps, TspSpan } from "@linxiraos/pi-wire";
|
|
15
17
|
|
|
16
18
|
const RESET_SELECTOR_MAX_VISIBLE = 10;
|
|
17
19
|
|
|
@@ -36,6 +38,37 @@ function accountCountLabel(account: ResetUsageAccount): string {
|
|
|
36
38
|
return countLabel;
|
|
37
39
|
}
|
|
38
40
|
|
|
41
|
+
/** The picker row of an account: provider mark, identity, its resets as a fact, unusable ones disabled with the reason. */
|
|
42
|
+
function accountPickerItem(account: ResetUsageAccount, id: string): TspPickerItem {
|
|
43
|
+
const redeemable = account.redeemableCount > 0;
|
|
44
|
+
const detail: TspSpan[] = [span(`${oneLine(account.providerLabel)} · #${account.target.credentialId}`)];
|
|
45
|
+
const reason = account.error ?? (redeemable ? undefined : (account.unavailableReason ?? "no resets usable now"));
|
|
46
|
+
if (reason) detail.push(span(` · ${oneLine(reason)}`, account.error ? "error" : "warning"));
|
|
47
|
+
let resets: TspSpan;
|
|
48
|
+
if (account.error) resets = span("unavailable", "error");
|
|
49
|
+
else if (!redeemable) resets = span(`${account.availableCount} saved`, "dim");
|
|
50
|
+
else if (account.redeemableCount === account.availableCount)
|
|
51
|
+
resets = span(`${account.redeemableCount} usable`, "success");
|
|
52
|
+
else resets = span(`${account.redeemableCount} of ${account.availableCount} usable`, "success");
|
|
53
|
+
const facts: Record<string, TspSpan[]> = { resets: [resets] };
|
|
54
|
+
const expiryMs = account.expiresAt ? Date.parse(account.expiresAt) : Number.NaN;
|
|
55
|
+
if (!Number.isNaN(expiryMs)) {
|
|
56
|
+
facts.expires = [
|
|
57
|
+
expiryMs > Date.now()
|
|
58
|
+
? span(`expires in ${formatDuration(expiryMs - Date.now())}`, "dim")
|
|
59
|
+
: span("expired", "warning"),
|
|
60
|
+
];
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
id,
|
|
64
|
+
label: oneLine(account.label),
|
|
65
|
+
detail,
|
|
66
|
+
mark: { text: account.providerLabel, seed: account.provider },
|
|
67
|
+
facts,
|
|
68
|
+
...(reason ? { disabled: oneLine(reason) } : {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
39
72
|
/** One account row with its redeemable rate-limit reset credits. */
|
|
40
73
|
export interface ResetUsageAccount {
|
|
41
74
|
label: string;
|
|
@@ -74,6 +107,8 @@ export class ResetUsageSelectorComponent extends OverlayPanel {
|
|
|
74
107
|
#nativeItems: readonly NativeNode[] | undefined;
|
|
75
108
|
#nativeHints: NativeNode | undefined;
|
|
76
109
|
#nativeRoot: NativeNode | undefined;
|
|
110
|
+
#pickerRoot: NativeNode | undefined;
|
|
111
|
+
#pickerItems: readonly TspPickerItem[] | undefined;
|
|
77
112
|
|
|
78
113
|
constructor(accounts: ResetUsageAccount[], onSelect: (account: ResetUsageAccount) => void, onCancel: () => void) {
|
|
79
114
|
super("Spend a saved rate-limit reset", "zeta.overlay.reset-usage");
|
|
@@ -99,6 +134,7 @@ export class ResetUsageSelectorComponent extends OverlayPanel {
|
|
|
99
134
|
|
|
100
135
|
#updateList(): void {
|
|
101
136
|
this.#nativeRoot = undefined;
|
|
137
|
+
this.#pickerRoot = undefined;
|
|
102
138
|
this.#listContainer.clear();
|
|
103
139
|
|
|
104
140
|
const items = this.#menu.visibleItems;
|
|
@@ -165,21 +201,28 @@ export class ResetUsageSelectorComponent extends OverlayPanel {
|
|
|
165
201
|
}
|
|
166
202
|
}
|
|
167
203
|
|
|
168
|
-
|
|
204
|
+
/** What a reset of `account` spends and covers, without key instructions. */
|
|
205
|
+
#confirmationNotes(account: ResetUsageAccount): { subject: string; notes: string[] } {
|
|
169
206
|
const subject = account.credit?.title ? `“${account.credit.title}”` : "1 saved reset";
|
|
170
|
-
const
|
|
171
|
-
`Press ${formatKeyHint("enter")} again to spend ${subject} for ${account.label} (${account.providerLabel}).`,
|
|
172
|
-
];
|
|
207
|
+
const notes: string[] = [];
|
|
173
208
|
if (account.credit?.program === "juniper_tide") {
|
|
174
|
-
|
|
209
|
+
notes.push("This resets Claude's 5h session limit only; weekly limits stay unchanged.");
|
|
175
210
|
} else if (account.credit?.clears?.length) {
|
|
176
|
-
|
|
211
|
+
notes.push(`Covers ${account.credit.clears.map(formatUsageResetWindow).join(", ")}.`);
|
|
177
212
|
}
|
|
178
213
|
if (account.credit?.requiresLimit === false) {
|
|
179
|
-
|
|
214
|
+
notes.push("Optional early use: this can be spent before the covered limit is reached.");
|
|
180
215
|
}
|
|
181
|
-
|
|
182
|
-
|
|
216
|
+
return { subject, notes };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
#confirmationMessage(account: ResetUsageAccount): string {
|
|
220
|
+
const { subject, notes } = this.#confirmationNotes(account);
|
|
221
|
+
return [
|
|
222
|
+
`Press ${formatKeyHint("enter")} again to spend ${subject} for ${account.label} (${account.providerLabel}).`,
|
|
223
|
+
...notes,
|
|
224
|
+
`${editorKey("tui.select.cancel")} cancels.`,
|
|
225
|
+
].join(" ");
|
|
183
226
|
}
|
|
184
227
|
|
|
185
228
|
handleInput(keyData: string): void {
|
|
@@ -239,7 +282,8 @@ export class ResetUsageSelectorComponent extends OverlayPanel {
|
|
|
239
282
|
}
|
|
240
283
|
}
|
|
241
284
|
|
|
242
|
-
override describe(): NativeNode {
|
|
285
|
+
override describe(cx: DescribeContext): NativeNode {
|
|
286
|
+
if (cx.supports("picker")) return this.#describePicker();
|
|
243
287
|
if (this.#nativeRoot) return this.#nativeRoot;
|
|
244
288
|
this.#nativeItems ??= this.#menu.visibleItems.map(account => {
|
|
245
289
|
const redeemable = account.redeemableCount > 0;
|
|
@@ -282,18 +326,83 @@ export class ResetUsageSelectorComponent extends OverlayPanel {
|
|
|
282
326
|
}
|
|
283
327
|
|
|
284
328
|
/**
|
|
285
|
-
*
|
|
286
|
-
*
|
|
287
|
-
* reset
|
|
329
|
+
* The docked md picker: accounts as provider-marked cards with their resets
|
|
330
|
+
* as facts, the active account checked, unusable ones disabled with the
|
|
331
|
+
* reason; the first `Spend reset` turns the selected row into a confirm strip.
|
|
332
|
+
*/
|
|
333
|
+
#describePicker(): NativeNode {
|
|
334
|
+
if (this.#pickerRoot) return this.#pickerRoot;
|
|
335
|
+
const items = (this.#pickerItems ??= this.#menu.visibleItems.map(account =>
|
|
336
|
+
accountPickerItem(account, this.#menu.adapter.getKey(account)),
|
|
337
|
+
));
|
|
338
|
+
const pending = this.#menu.visibleItems.find(item => this.#menu.isPending(item));
|
|
339
|
+
// The strip asks the question over the row; what the reset covers reads in the subtitle meanwhile.
|
|
340
|
+
let confirm: TspPickerProps["confirm"] = null;
|
|
341
|
+
let subtitle: TspPickerProps["subtitle"] = "Spend a saved reset to refresh an account's rate limit now";
|
|
342
|
+
if (pending) {
|
|
343
|
+
const { subject, notes } = this.#confirmationNotes(pending);
|
|
344
|
+
confirm = { text: `Spend ${subject} on ${oneLine(pending.label)}?`, act: "confirm", label: "Spend" };
|
|
345
|
+
if (notes.length > 0) subtitle = oneLine(notes.join(" "));
|
|
346
|
+
}
|
|
347
|
+
if (this.#statusMessage) subtitle = [span(oneLine(this.#statusMessage), "warning")];
|
|
348
|
+
this.#pickerRoot = dockedPicker({
|
|
349
|
+
title: "Rate-limit resets",
|
|
350
|
+
subtitle,
|
|
351
|
+
icon: "refresh",
|
|
352
|
+
noun: "accounts",
|
|
353
|
+
size: "md",
|
|
354
|
+
layout: "cards",
|
|
355
|
+
preview: "none",
|
|
356
|
+
query: null,
|
|
357
|
+
columns: [
|
|
358
|
+
{ id: "expires", priority: 1 },
|
|
359
|
+
{ id: "resets", priority: 2 },
|
|
360
|
+
],
|
|
361
|
+
items,
|
|
362
|
+
current: this.#menu.visibleItems
|
|
363
|
+
.filter(account => account.active)
|
|
364
|
+
.map(account => this.#menu.adapter.getKey(account)),
|
|
365
|
+
selected: this.#menu.selectedKey ?? null,
|
|
366
|
+
empty: "No provider accounts with saved resets",
|
|
367
|
+
actions: [pickerAction("confirm", "Spend reset", "enter", { primary: true }), CLOSE_ACTION],
|
|
368
|
+
confirm,
|
|
369
|
+
});
|
|
370
|
+
return this.#pickerRoot;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Point the menu at `key`; moving to another account drops a pending confirmation. */
|
|
374
|
+
#selectKey(key: string): boolean {
|
|
375
|
+
if (this.#menu.selectedKey === key) return true;
|
|
376
|
+
this.#menu.cancelConfirmation();
|
|
377
|
+
this.#menu.setSelectedKey(key);
|
|
378
|
+
if (this.#menu.selectedKey !== key) return false;
|
|
379
|
+
this.#statusMessage = undefined;
|
|
380
|
+
this.#updateList();
|
|
381
|
+
return true;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Picker: a row click selects, a double-click or `Spend reset` presses
|
|
386
|
+
* Enter (so spending still takes a second activation, on the confirm
|
|
387
|
+
* strip), the strip's `Keep` drops the confirmation and `Close` cancels.
|
|
388
|
+
* Card list: a click (or double-click) on an account highlights it and
|
|
389
|
+
* presses Enter.
|
|
288
390
|
*/
|
|
289
391
|
handleNativeEvent(event: NativeUiEvent): void {
|
|
290
|
-
|
|
291
|
-
if (
|
|
292
|
-
this.#
|
|
293
|
-
this.#menu.
|
|
294
|
-
|
|
295
|
-
|
|
392
|
+
const ev = pickerEvent(event, PICKER_KEY);
|
|
393
|
+
if (ev?.kind === "action") {
|
|
394
|
+
if (ev.act === "confirm") this.#activateSelection();
|
|
395
|
+
else if (ev.act === "cancel" && this.#menu.cancelConfirmation()) {
|
|
396
|
+
this.#statusMessage = undefined;
|
|
397
|
+
this.#updateList();
|
|
398
|
+
} else if (ev.act === "close" || ev.act === "cancel") this.#onCancelCallback();
|
|
399
|
+
return;
|
|
296
400
|
}
|
|
297
|
-
|
|
401
|
+
if (ev) {
|
|
402
|
+
if (this.#selectKey(ev.item) && ev.kind === "activate") this.#activateSelection();
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
if ((event.type !== "select" && event.type !== "activate") || event.key !== "list") return;
|
|
406
|
+
if (this.#selectKey(event.item)) this.#activateSelection();
|
|
298
407
|
}
|
|
299
408
|
}
|
|
@@ -2,6 +2,8 @@ import { type SelectItem, SelectList, type SgrMouseEvent } from "../index";
|
|
|
2
2
|
import { getSelectListTheme } from "../theme/theme";
|
|
3
3
|
import { OverlayPanel } from "../chrome/overlay-box";
|
|
4
4
|
import { routeSelectListMouseWithTopBorder } from "../chrome/select-list-mouse-routing";
|
|
5
|
+
import type { DescribeContext, NativeNode, NativeUiEvent } from "../native/node";
|
|
6
|
+
import { SelectListSheet } from "../native/picker";
|
|
5
7
|
|
|
6
8
|
const ACCOUNT_SELECTOR_MAX_VISIBLE = 10;
|
|
7
9
|
|
|
@@ -19,9 +21,18 @@ export interface SessionPinAccount {
|
|
|
19
21
|
label: string;
|
|
20
22
|
}
|
|
21
23
|
|
|
24
|
+
/** What tells two accounts of one provider apart besides their label: the organization, project or host. */
|
|
25
|
+
function accountDetail(account: SessionPinAccount): string | undefined {
|
|
26
|
+
const parts = [account.orgName ?? account.orgId, account.projectId, account.enterpriseUrl].filter(
|
|
27
|
+
(part): part is string => typeof part === "string" && part.trim().length > 0 && !account.label.includes(part),
|
|
28
|
+
);
|
|
29
|
+
return parts.length > 0 ? parts.join(" · ") : undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
/** Account picker opened by `/session pin` for the current model provider. */
|
|
23
33
|
export class SessionAccountSelectorComponent extends OverlayPanel {
|
|
24
34
|
#selectList: SelectList;
|
|
35
|
+
#sheet: SelectListSheet;
|
|
25
36
|
|
|
26
37
|
constructor(
|
|
27
38
|
providerName: string,
|
|
@@ -54,6 +65,31 @@ export class SessionAccountSelectorComponent extends OverlayPanel {
|
|
|
54
65
|
};
|
|
55
66
|
this.#selectList.onCancel = onCancel;
|
|
56
67
|
this.addChild(this.#selectList);
|
|
68
|
+
// Natively the active account carries the picker's current check, so the
|
|
69
|
+
// row's detail is what tells accounts apart instead of "active for this session".
|
|
70
|
+
this.#sheet = new SelectListSheet(this.#selectList, {
|
|
71
|
+
title: `${providerName} account`,
|
|
72
|
+
subtitle: "Pin the account this session sends its requests with",
|
|
73
|
+
icon: "key-round",
|
|
74
|
+
noun: "accounts",
|
|
75
|
+
current: accounts.filter(account => account.active).map(account => String(account.credentialId)),
|
|
76
|
+
confirm: "Pin",
|
|
77
|
+
decorate: item => {
|
|
78
|
+
const account = accountsByValue.get(item.value);
|
|
79
|
+
if (!account) return undefined;
|
|
80
|
+
const detail = accountDetail(account);
|
|
81
|
+
return { mark: { text: account.email ?? account.label, seed: account.label }, detail: detail ?? "" };
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
override describe(cx: DescribeContext): NativeNode | null {
|
|
87
|
+
return cx.supports("picker") ? this.#sheet.describe() : super.describe(cx);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Picker pointer events drive the list exactly as its keys do. */
|
|
91
|
+
handleNativeEvent(event: NativeUiEvent): void {
|
|
92
|
+
this.#sheet.handle(event);
|
|
57
93
|
}
|
|
58
94
|
|
|
59
95
|
/** Forward keyboard navigation and cancellation when the wrapper owns focus. */
|
|
@@ -4,9 +4,9 @@ import { matchesSelectCancel } from "../keybinding-matchers";
|
|
|
4
4
|
import { OverlayPanel, PanelDivider, PanelRows } from "../chrome/overlay-box";
|
|
5
5
|
import { formatKeyHints } from "../app-keybindings";
|
|
6
6
|
import { editorKey } from "../chrome/keybinding-hints";
|
|
7
|
-
import type {
|
|
7
|
+
import type { TspSpan } from "@linxiraos/pi-wire";
|
|
8
8
|
import { formatNumber } from "@linxiraos/pi-utils";
|
|
9
|
-
import { col, node,
|
|
9
|
+
import { col, node, span, text } from "../native/describe";
|
|
10
10
|
import type { DescribeContext, NativeChild, NativeNode, NativeUiEvent } from "../native/node";
|
|
11
11
|
import { actionBar, actionButton } from "../native/overlay";
|
|
12
12
|
import { plainText, styledSpans } from "../native/spans";
|
|
@@ -57,29 +57,50 @@ function joined(spans: readonly TspSpan[]): string {
|
|
|
57
57
|
return spans.map(part => part.t).join("");
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
/** A figure the stats grid can right-align: `182,440`, `0.4127`, `$1.20`, `21%`. */
|
|
61
|
+
const NUMERIC = /^[$€£]?\d[\d,]*(?:\.\d+)?%?$/;
|
|
62
|
+
|
|
63
|
+
/** A report key or heading in sentence case: `Tool Calls` → `Tool calls`; acronyms (`MCP`, `ACU`) stay. */
|
|
64
|
+
function sentenceCase(label: string): string {
|
|
65
|
+
let first = true;
|
|
66
|
+
return label.replace(/[A-Za-z]+/g, word => {
|
|
67
|
+
if (first) {
|
|
68
|
+
first = false;
|
|
69
|
+
return word;
|
|
70
|
+
}
|
|
71
|
+
return /^[A-Z][a-z]+$/.test(word) ? word.toLowerCase() : word;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
60
75
|
/**
|
|
61
|
-
*
|
|
62
|
-
*
|
|
76
|
+
* One line of the identity block: a sans label, then the value. With `copy`
|
|
77
|
+
* (its tooltip) the value and a copy glyph after it are one target: a click
|
|
78
|
+
* copies the value (the terminal-local `copy` action takes the leaf text; the
|
|
79
|
+
* icon has none).
|
|
63
80
|
*/
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
title: string,
|
|
67
|
-
key: string,
|
|
68
|
-
props: Omit<TspProps<"text">, "text" | "spans"> = {},
|
|
69
|
-
): NativeNode {
|
|
81
|
+
function identityField(label: string, value: TspSpan[], key: string, copy?: string): NativeNode {
|
|
82
|
+
const shown = text(value, { truncate: "middle", grow: 1, shrink: 1 });
|
|
70
83
|
return node(
|
|
71
84
|
"row",
|
|
72
|
-
{
|
|
73
|
-
gap: "sm",
|
|
74
|
-
align: "center",
|
|
75
|
-
grow: 1,
|
|
76
|
-
role: "zeta.info.copy",
|
|
77
|
-
actions: { click: "copy" },
|
|
78
|
-
title,
|
|
79
|
-
},
|
|
85
|
+
{ gap: "md", align: "center", role: "zeta.info.field" },
|
|
80
86
|
[
|
|
81
|
-
text(
|
|
82
|
-
|
|
87
|
+
text([span(label)], { role: "zeta.info.label", wrap: "none", shrink: 0 }),
|
|
88
|
+
copy
|
|
89
|
+
? node(
|
|
90
|
+
"row",
|
|
91
|
+
{
|
|
92
|
+
gap: "sm",
|
|
93
|
+
align: "center",
|
|
94
|
+
grow: 1,
|
|
95
|
+
shrink: 1,
|
|
96
|
+
role: "zeta.info.copy",
|
|
97
|
+
actions: { click: "copy" },
|
|
98
|
+
title: copy,
|
|
99
|
+
},
|
|
100
|
+
[shown, node("icon", { name: "copy", role: "zeta.info.copy.ic", aria: copy })],
|
|
101
|
+
"value",
|
|
102
|
+
)
|
|
103
|
+
: shown,
|
|
83
104
|
],
|
|
84
105
|
key,
|
|
85
106
|
);
|
|
@@ -165,83 +186,185 @@ export class SessionInfoOverlay implements Component {
|
|
|
165
186
|
}
|
|
166
187
|
|
|
167
188
|
/**
|
|
168
|
-
* The sheet body (the overlay is the frame): the session
|
|
169
|
-
* (
|
|
170
|
-
*
|
|
189
|
+
* The sheet body (the overlay is the frame), in three tiers: who the session
|
|
190
|
+
* is (the id and file as copyable mono fields), how full its context window
|
|
191
|
+
* is (a meter under a right-aligned figure), then the report's sections as
|
|
192
|
+
* headed groups. Groups of plain counts (Messages, Tokens, Cost) share a
|
|
193
|
+
* two-column stats grid with right-aligned figures and their Total set off
|
|
194
|
+
* last; the rest (Provider, servers) are key/value rows. A Close button (Esc)
|
|
195
|
+
* ends it.
|
|
171
196
|
*/
|
|
172
197
|
describe(cx: DescribeContext): NativeNode {
|
|
173
198
|
const meter = cx.supports("meter");
|
|
174
199
|
if (this.#nativeNode?.meter === meter) return this.#nativeNode.node;
|
|
200
|
+
// The report prints the append-only context mode after the message counts;
|
|
201
|
+
// it is a provider setting, so it joins the Provider group when there is one.
|
|
202
|
+
const parsed = parseKvReport(this.#infoSource);
|
|
203
|
+
const hasProvider = parsed.some(section => section.head === "Provider");
|
|
204
|
+
const moved: KvReportEntry[] = [];
|
|
205
|
+
const sections = parsed.map(section => ({
|
|
206
|
+
head: section.head,
|
|
207
|
+
entries: section.entries.filter(entry => {
|
|
208
|
+
if (!hasProvider || section.head === "Provider" || !("k" in entry) || entry.k !== "Append-Only")
|
|
209
|
+
return true;
|
|
210
|
+
moved.push(entry);
|
|
211
|
+
return false;
|
|
212
|
+
}),
|
|
213
|
+
}));
|
|
214
|
+
sections.find(section => section.head === "Provider")?.entries.push(...moved);
|
|
175
215
|
const body: NativeChild[] = [];
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
[
|
|
198
|
-
text([span("File", "muted")], { wrap: "none", shrink: 0 }),
|
|
199
|
-
copyLine([span(joined(entry.v), "mono")], "Copy file path", "path", { wrap: "char" }),
|
|
200
|
-
],
|
|
201
|
-
{
|
|
202
|
-
gap: "md",
|
|
203
|
-
align: "start",
|
|
204
|
-
role: "zeta.info.file",
|
|
205
|
-
},
|
|
206
|
-
),
|
|
207
|
-
);
|
|
208
|
-
} else {
|
|
209
|
-
items.push({ k: entry.k, v: entry.v });
|
|
216
|
+
const stats: { head: string; key: string; items: { k: string; v: TspSpan[] }[] }[] = [];
|
|
217
|
+
const flushStats = (): void => {
|
|
218
|
+
if (stats.length > 0) body.push(this.#describeStats(stats.splice(0)));
|
|
219
|
+
};
|
|
220
|
+
for (const [index, { head, entries }] of sections.entries()) {
|
|
221
|
+
if (head === undefined) {
|
|
222
|
+
// The session's identity: its id, then its file (copyable when it is one).
|
|
223
|
+
const identity: NativeChild[] = [];
|
|
224
|
+
const rest: NativeChild[] = [];
|
|
225
|
+
for (const entry of entries) {
|
|
226
|
+
if (!("k" in entry)) rest.push(text(entry.line, { wrap: "word", role: "zeta.info.note" }));
|
|
227
|
+
else if (entry.k === "ID")
|
|
228
|
+
identity.unshift(identityField("ID", [span(joined(entry.v), "mono")], "id", "Copy session ID"));
|
|
229
|
+
else if (entry.k === "File") {
|
|
230
|
+
const path = joined(entry.v);
|
|
231
|
+
identity.push(
|
|
232
|
+
path.includes("/")
|
|
233
|
+
? identityField("File", [span(path, "path")], "file", `Copy ${path}`)
|
|
234
|
+
: identityField("File", [span(path, "muted")], "file"),
|
|
235
|
+
);
|
|
236
|
+
} else rest.push(node("kv", { items: [{ k: entry.k, v: entry.v }], role: "zeta.info.kv" }));
|
|
210
237
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
if (section.head === undefined) {
|
|
214
|
-
body.push(node("col", { gap: "sm" }, children, `s${index}`));
|
|
238
|
+
if (identity.length > 0) body.push(node("col", { role: "zeta.info.identity" }, identity, "identity"));
|
|
239
|
+
body.push(...rest);
|
|
215
240
|
const context = this.#context;
|
|
216
241
|
if (context && context.contextWindow > 0) body.push(this.#describeContext(context, meter));
|
|
217
|
-
|
|
218
|
-
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
const pairs = entries.filter((entry): entry is { k: string; v: TspSpan[] } => "k" in entry);
|
|
245
|
+
if (
|
|
246
|
+
pairs.length > 0 &&
|
|
247
|
+
pairs.length === entries.length &&
|
|
248
|
+
pairs.every(p => NUMERIC.test(joined(p.v).trim()))
|
|
249
|
+
) {
|
|
250
|
+
const cost = head === "Cost";
|
|
251
|
+
stats.push({
|
|
252
|
+
head,
|
|
253
|
+
key: `s${index}`,
|
|
254
|
+
items: pairs.map(p => ({
|
|
255
|
+
k: sentenceCase(p.k),
|
|
256
|
+
v: [span(cost && p.k === "Total" ? `$${joined(p.v).trim()}` : joined(p.v).trim(), "mono")],
|
|
257
|
+
})),
|
|
258
|
+
});
|
|
259
|
+
continue;
|
|
219
260
|
}
|
|
261
|
+
flushStats();
|
|
262
|
+
body.push(
|
|
263
|
+
node(
|
|
264
|
+
"section",
|
|
265
|
+
{ head: sentenceCase(head), role: "zeta.info.group" },
|
|
266
|
+
this.#describeEntries(entries, head === "Provider"),
|
|
267
|
+
`s${index}`,
|
|
268
|
+
),
|
|
269
|
+
);
|
|
220
270
|
}
|
|
271
|
+
flushStats();
|
|
221
272
|
body.push(actionBar([null, actionButton("Close", "close", { keys: "escape" })]));
|
|
222
273
|
const described = col(body, { gap: "md" });
|
|
223
274
|
this.#nativeNode = { meter, node: described };
|
|
224
275
|
return described;
|
|
225
276
|
}
|
|
226
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Key/value rows, free lines as notes between them. The provider's values are
|
|
280
|
+
* machine strings (`mono`); elsewhere (LSP, MCP servers) the keys are names
|
|
281
|
+
* (role `zeta.info.names`, keys in mono).
|
|
282
|
+
*/
|
|
283
|
+
#describeEntries(entries: readonly KvReportEntry[], mono: boolean): NativeChild[] {
|
|
284
|
+
const children: NativeChild[] = [];
|
|
285
|
+
let items: { k: string; v: TspSpan[] }[] = [];
|
|
286
|
+
const flush = (): void => {
|
|
287
|
+
if (items.length === 0) return;
|
|
288
|
+
children.push(node("kv", { items, layout: "grid", role: mono ? "zeta.info.kv" : "zeta.info.names" }));
|
|
289
|
+
items = [];
|
|
290
|
+
};
|
|
291
|
+
for (const entry of entries) {
|
|
292
|
+
if (!("k" in entry)) {
|
|
293
|
+
flush();
|
|
294
|
+
children.push(text(entry.line, { wrap: "word", role: "zeta.info.note" }));
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
const v = mono ? entry.v.map(part => ({ ...part, s: part.s ? `${part.s} mono` : "mono" })) : entry.v;
|
|
298
|
+
items.push({ k: mono ? sentenceCase(entry.k) : entry.k, v });
|
|
299
|
+
}
|
|
300
|
+
flush();
|
|
301
|
+
return children;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Count groups as a two-column grid that stacks when narrow: each group goes
|
|
306
|
+
* to the shorter column (report order holds when stacked), its Total last
|
|
307
|
+
* over a hairline.
|
|
308
|
+
*/
|
|
309
|
+
#describeStats(groups: readonly { head: string; key: string; items: { k: string; v: TspSpan[] }[] }[]): NativeNode {
|
|
310
|
+
const columns: { lines: number; children: NativeChild[] }[] = [
|
|
311
|
+
{ lines: 0, children: [] },
|
|
312
|
+
{ lines: 0, children: [] },
|
|
313
|
+
];
|
|
314
|
+
for (const g of groups) {
|
|
315
|
+
const total = g.items.length > 1 ? g.items.find(item => item.k === "Total") : undefined;
|
|
316
|
+
const items = total ? g.items.filter(item => item !== total) : g.items;
|
|
317
|
+
const children: NativeChild[] = [node("kv", { items, layout: "grid", role: "zeta.info.nums" })];
|
|
318
|
+
if (total) children.push(node("kv", { items: [total], layout: "grid", role: "zeta.info.total" }));
|
|
319
|
+
// Ties go right, so the stacked order (left, then right) stays the report's.
|
|
320
|
+
const column = columns[0].lines === 0 || columns[0].lines < columns[1].lines ? columns[0] : columns[1];
|
|
321
|
+
column.lines += g.items.length + 2;
|
|
322
|
+
column.children.push(
|
|
323
|
+
node("section", { head: sentenceCase(g.head), role: "zeta.info.group" }, children, g.key),
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
return node(
|
|
327
|
+
"row",
|
|
328
|
+
{ align: "start", wrap: true, role: "zeta.info.stats" },
|
|
329
|
+
columns
|
|
330
|
+
.filter(column => column.children.length > 0)
|
|
331
|
+
.map((column, i) => node("col", { role: "zeta.info.column" }, column.children, `c${i}`)),
|
|
332
|
+
"stats",
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/** The context window: a head row (label, `41K / 200K`, the share) over a full-width meter. */
|
|
227
337
|
#describeContext(context: ContextUsage, meter: boolean): NativeNode {
|
|
228
338
|
const value = Math.min(1, Math.max(0, context.percent / 100));
|
|
229
|
-
const
|
|
339
|
+
const title = `${context.tokens.toLocaleString("en-US")} of ${context.contextWindow.toLocaleString("en-US")} tokens`;
|
|
230
340
|
return node(
|
|
231
|
-
"
|
|
232
|
-
{
|
|
341
|
+
"col",
|
|
342
|
+
{ role: "zeta.info.context", title },
|
|
233
343
|
[
|
|
234
|
-
|
|
344
|
+
node(
|
|
345
|
+
"row",
|
|
346
|
+
{ align: "baseline", role: "zeta.info.contextHead" },
|
|
347
|
+
[
|
|
348
|
+
text([span("Context window")], { grow: 1, shrink: 1, truncate: "end" }),
|
|
349
|
+
text(
|
|
350
|
+
[
|
|
351
|
+
span(`${formatNumber(context.tokens)} / ${formatNumber(context.contextWindow)}`, "mono muted"),
|
|
352
|
+
span(" "),
|
|
353
|
+
span(`${Math.round(context.percent)}%`, "mono strong"),
|
|
354
|
+
],
|
|
355
|
+
{ wrap: "none", shrink: 0 },
|
|
356
|
+
),
|
|
357
|
+
],
|
|
358
|
+
"head",
|
|
359
|
+
),
|
|
235
360
|
meter
|
|
236
|
-
? node(
|
|
237
|
-
|
|
238
|
-
style: "bar",
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
: node("progress", { value, grow: 1 }),
|
|
244
|
-
text([span(figure, "mono")]),
|
|
361
|
+
? node(
|
|
362
|
+
"meter",
|
|
363
|
+
{ value, style: "bar", size: "md", thresholds: { warn: 0.7, bad: 0.9 }, title },
|
|
364
|
+
undefined,
|
|
365
|
+
"meter",
|
|
366
|
+
)
|
|
367
|
+
: node("progress", { value }, undefined, "meter"),
|
|
245
368
|
],
|
|
246
369
|
"context",
|
|
247
370
|
);
|