@linxiraos/pi-tui 1.1.26 → 1.2.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 +51 -491
- package/README.md +13 -7
- package/dist/types/active-terminal.d.ts +30 -0
- package/dist/types/app-keybindings.d.ts +7 -2
- package/dist/types/apps/cleanse-board.d.ts +1 -1
- package/dist/types/apps/debug/log-viewer.d.ts +4 -0
- package/dist/types/apps/debug/raw-sse-buffer.d.ts +2 -0
- package/dist/types/apps/git/git-tui.d.ts +5 -4
- package/dist/types/apps/git/help.d.ts +3 -1
- package/dist/types/apps/if-bench-board.d.ts +2 -2
- package/dist/types/apps/setup-model-picker.d.ts +1 -1
- package/dist/types/autocomplete.d.ts +5 -2
- package/dist/types/charts/chart-plan.d.ts +674 -0
- package/dist/types/charts/chart-svg.d.ts +59 -0
- package/dist/types/charts/table-data.d.ts +175 -0
- package/dist/types/chat/assistant-message.d.ts +11 -1
- package/dist/types/chat/chat-transcript-builder.d.ts +3 -0
- package/dist/types/chat/compaction-summary-message.d.ts +1 -0
- package/dist/types/chat/execution-shared.d.ts +6 -5
- package/dist/types/chat/fence-figure.d.ts +42 -0
- package/dist/types/chat/figure-markdown.d.ts +42 -0
- package/dist/types/chat/messages.d.ts +1 -1
- package/dist/types/chat/svg-figure.d.ts +33 -0
- package/dist/types/chat/svg-source.d.ts +49 -0
- package/dist/types/chat/table-chart.d.ts +89 -0
- package/dist/types/chat/tool-execution.d.ts +2 -0
- package/dist/types/chat/user-message.d.ts +17 -4
- package/dist/types/chrome/shared.d.ts +9 -0
- package/dist/types/chrome/status-notice.d.ts +15 -3
- package/dist/types/chrome/transcript-container.d.ts +6 -0
- package/dist/types/components/box.d.ts +1 -0
- package/dist/types/components/disclosure.d.ts +1 -0
- package/dist/types/components/editor.d.ts +23 -5
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +26 -44
- package/dist/types/components/input.d.ts +2 -1
- package/dist/types/components/key-value-list.d.ts +1 -0
- package/dist/types/components/layout/row.d.ts +1 -0
- package/dist/types/components/layout/stack.d.ts +1 -0
- package/dist/types/components/loader.d.ts +5 -6
- package/dist/types/components/markdown.d.ts +47 -5
- package/dist/types/components/scroll-view.d.ts +1 -0
- package/dist/types/components/section.d.ts +1 -0
- package/dist/types/components/table.d.ts +1 -0
- package/dist/types/components/text.d.ts +1 -0
- package/dist/types/components/tree-view.d.ts +7 -0
- package/dist/types/components/truncated-text.d.ts +1 -0
- package/dist/types/fuzzy.d.ts +29 -1
- package/dist/types/key-hint-format.d.ts +9 -1
- package/dist/types/keybinding-matchers.d.ts +3 -0
- package/dist/types/keybindings.d.ts +1 -1
- package/dist/types/kitty-graphics.d.ts +2 -2
- package/dist/types/loop-watchdog.d.ts +8 -7
- package/dist/types/multiplexers/cmux.d.ts +16 -0
- package/dist/types/multiplexers/herdr.d.ts +26 -0
- package/dist/types/multiplexers/notify.d.ts +13 -0
- package/dist/types/multiplexers/orca.d.ts +12 -0
- package/dist/types/multiplexers/screen.d.ts +9 -0
- package/dist/types/multiplexers/tmux.d.ts +29 -0
- package/dist/types/multiplexers/types.d.ts +100 -0
- package/dist/types/multiplexers/wmux.d.ts +12 -0
- package/dist/types/multiplexers/zellij.d.ts +12 -0
- package/dist/types/native/apply.d.ts +4 -3
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +8 -6
- package/dist/types/native/node.d.ts +36 -5
- package/dist/types/native/overlay.d.ts +8 -3
- package/dist/types/native/settle.d.ts +4 -3
- package/dist/types/overlays/agent-hub-projection.d.ts +6 -5
- package/dist/types/overlays/agent-hub-types.d.ts +9 -0
- package/dist/types/overlays/agent-hub.d.ts +3 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +12 -2
- package/dist/types/overlays/agents-hub.d.ts +9 -0
- package/dist/types/overlays/annotation-overlay.d.ts +19 -2
- package/dist/types/overlays/annotation-types.d.ts +2 -0
- package/dist/types/overlays/ask-dialog.d.ts +5 -2
- package/dist/types/overlays/btw-history-panel.d.ts +13 -1
- package/dist/types/overlays/copy-selector.d.ts +1 -0
- package/dist/types/overlays/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- package/dist/types/overlays/hub-frame.d.ts +4 -1
- package/dist/types/overlays/jobs-panel.d.ts +1 -7
- package/dist/types/overlays/model-browser.d.ts +54 -7
- package/dist/types/overlays/model-hub.d.ts +37 -0
- package/dist/types/overlays/oauth-selector.d.ts +0 -1
- package/dist/types/overlays/plan-review-overlay.d.ts +3 -2
- package/dist/types/overlays/report-panel.d.ts +68 -0
- package/dist/types/overlays/rewind-selector.d.ts +1 -0
- package/dist/types/overlays/session-observer-registry.d.ts +2 -0
- package/dist/types/overlays/session-selector.d.ts +6 -2
- package/dist/types/overlays/settings-defs.d.ts +1 -0
- package/dist/types/overlays/thinking-selector.d.ts +2 -2
- package/dist/types/overlays/usage-dashboard.d.ts +12 -8
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer-cache.d.ts +0 -16
- package/dist/types/prompt/composer.d.ts +18 -3
- package/dist/types/prompt/custom-editor.d.ts +25 -13
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +5 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +7 -2
- package/dist/types/render/composite.d.ts +2 -0
- package/dist/types/render/output-pane.d.ts +1 -0
- package/dist/types/render/render-utils.d.ts +1 -0
- package/dist/types/render/tool-card.d.ts +6 -0
- package/dist/types/render/width-aware-text.d.ts +2 -0
- package/dist/types/setup/scenes/types.d.ts +1 -0
- package/dist/types/space-hold.d.ts +40 -25
- package/dist/types/status-line/component.d.ts +3 -0
- package/dist/types/status-line/context-usage.d.ts +7 -5
- package/dist/types/status-line/host.d.ts +4 -1
- package/dist/types/status-line/segments.d.ts +2 -0
- package/dist/types/status-line/types.d.ts +14 -3
- package/dist/types/terminal-capabilities.d.ts +41 -36
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +23 -16
- package/dist/types/theme/active-symbols.d.ts +1 -1
- package/dist/types/theme/schema.d.ts +11 -2
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme-class.d.ts +7 -0
- package/dist/types/theme/theme.d.ts +1 -1
- package/dist/types/theme/tui-adapters.d.ts +3 -0
- package/dist/types/thinking.d.ts +1 -1
- package/dist/types/tmux.d.ts +11 -4
- package/dist/types/tools/bash.d.ts +7 -2
- package/dist/types/tools/daemon.d.ts +1 -1
- package/dist/types/tools/eval.d.ts +22 -3
- package/dist/types/tools/glob.d.ts +4 -0
- package/dist/types/tools/native-view.d.ts +47 -0
- package/dist/types/tools/output-meta.d.ts +11 -1
- package/dist/types/tools/renderer.d.ts +37 -2
- package/dist/types/tools/streaming-output.d.ts +20 -1
- package/dist/types/tools/task.d.ts +2 -3
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +36 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/dist/types/windows-input-mode.d.ts +33 -1
- package/package.json +13 -9
- package/src/active-terminal.d.ts +30 -0
- package/src/active-terminal.ts +80 -0
- package/src/app-keybindings.d.ts +7 -2
- package/src/app-keybindings.ts +7 -2
- package/src/apps/autoresearch-dashboard.ts +7 -3
- package/src/apps/cleanse-board.ts +2 -2
- package/src/apps/cleanse-picker.ts +1 -1
- package/src/apps/debug/log-viewer.ts +188 -72
- package/src/apps/debug/raw-sse-buffer.ts +8 -0
- package/src/apps/debug/raw-sse.ts +39 -5
- package/src/apps/git/diff-pane.ts +96 -23
- package/src/apps/git/git-tui.ts +23 -16
- package/src/apps/git/help.ts +29 -20
- package/src/apps/git/sidebar.ts +8 -3
- package/src/apps/if-bench-board.ts +2 -2
- package/src/apps/ps-top.ts +2 -2
- package/src/apps/setup-model-picker.ts +1 -1
- package/src/autocomplete.d.ts +5 -2
- package/src/autocomplete.ts +67 -43
- package/src/bracketed-paste.ts +10 -3
- package/src/charts/chart-plan.d.ts +674 -0
- package/src/charts/chart-plan.ts +5081 -0
- package/src/charts/chart-svg.d.ts +59 -0
- package/src/charts/chart-svg.ts +4657 -0
- package/src/charts/table-data.d.ts +175 -0
- package/src/charts/table-data.ts +1478 -0
- package/src/chat/advisor-message.ts +11 -0
- package/src/chat/assistant-message.d.ts +11 -1
- package/src/chat/assistant-message.ts +245 -54
- package/src/chat/chat-transcript-builder.d.ts +3 -0
- package/src/chat/chat-transcript-builder.ts +9 -3
- package/src/chat/compaction-summary-message.d.ts +1 -0
- package/src/chat/compaction-summary-message.ts +8 -0
- package/src/chat/execution-shared.d.ts +6 -5
- package/src/chat/execution-shared.ts +42 -25
- package/src/chat/fence-figure.d.ts +42 -0
- package/src/chat/fence-figure.ts +80 -0
- package/src/chat/figure-markdown.d.ts +42 -0
- package/src/chat/figure-markdown.ts +174 -0
- package/src/chat/messages.d.ts +1 -1
- package/src/chat/messages.ts +1 -1
- package/src/chat/read-tool-group.ts +6 -4
- package/src/chat/skill-message.ts +6 -0
- package/src/chat/svg-figure.d.ts +33 -0
- package/src/chat/svg-figure.ts +399 -0
- package/src/chat/svg-source.d.ts +49 -0
- package/src/chat/svg-source.ts +188 -0
- package/src/chat/table-chart.d.ts +89 -0
- package/src/chat/table-chart.ts +294 -0
- package/src/chat/tool-execution.d.ts +2 -0
- package/src/chat/tool-execution.ts +86 -7
- package/src/chat/user-message.d.ts +17 -4
- package/src/chat/user-message.ts +71 -8
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/status-notice.ts +25 -7
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +162 -19
- package/src/components/box.d.ts +1 -0
- package/src/components/box.ts +5 -0
- package/src/components/disclosure.d.ts +1 -0
- package/src/components/disclosure.ts +8 -0
- package/src/components/editor.d.ts +23 -5
- package/src/components/editor.ts +316 -47
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +26 -44
- package/src/components/image.ts +115 -152
- package/src/components/input.d.ts +2 -1
- package/src/components/input.ts +17 -7
- package/src/components/key-value-list.d.ts +1 -0
- package/src/components/key-value-list.ts +4 -0
- package/src/components/layout/row.d.ts +1 -0
- package/src/components/layout/row.ts +7 -0
- package/src/components/layout/stack.d.ts +1 -0
- package/src/components/layout/stack.ts +7 -0
- package/src/components/loader.d.ts +5 -6
- package/src/components/loader.ts +10 -17
- package/src/components/markdown.d.ts +47 -5
- package/src/components/markdown.ts +786 -303
- package/src/components/scroll-view.d.ts +1 -0
- package/src/components/scroll-view.ts +5 -0
- package/src/components/section.d.ts +1 -0
- package/src/components/section.ts +5 -0
- package/src/components/settings-list.ts +2 -0
- package/src/components/table.d.ts +1 -0
- package/src/components/table.ts +4 -0
- package/src/components/text.d.ts +1 -0
- package/src/components/text.ts +7 -0
- package/src/components/tree-view.d.ts +7 -0
- package/src/components/tree-view.ts +38 -8
- package/src/components/truncated-text.d.ts +1 -0
- package/src/components/truncated-text.ts +5 -0
- package/src/desktop-notify.ts +3 -2
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/hotkeys-markdown.ts +9 -2
- package/src/key-hint-format.d.ts +9 -1
- package/src/key-hint-format.ts +26 -11
- package/src/keybinding-matchers.d.ts +3 -0
- package/src/keybinding-matchers.ts +6 -2
- package/src/keybindings.d.ts +1 -1
- package/src/keybindings.ts +1 -1
- package/src/kitty-graphics.d.ts +2 -2
- package/src/kitty-graphics.ts +11 -5
- package/src/loop-watchdog.d.ts +8 -7
- package/src/loop-watchdog.ts +18 -13
- package/src/multiplexers/cmux.d.ts +16 -0
- package/src/multiplexers/cmux.ts +29 -0
- package/src/multiplexers/herdr.d.ts +26 -0
- package/src/multiplexers/herdr.ts +64 -0
- package/src/multiplexers/notify.d.ts +13 -0
- package/src/multiplexers/notify.ts +24 -0
- package/src/multiplexers/orca.d.ts +12 -0
- package/src/multiplexers/orca.ts +16 -0
- package/src/multiplexers/screen.d.ts +9 -0
- package/src/multiplexers/screen.ts +15 -0
- package/src/multiplexers/tmux.d.ts +29 -0
- package/src/multiplexers/tmux.ts +40 -0
- package/src/multiplexers/types.d.ts +100 -0
- package/src/multiplexers/types.ts +110 -0
- package/src/multiplexers/wmux.d.ts +12 -0
- package/src/multiplexers/wmux.ts +16 -0
- package/src/multiplexers/zellij.d.ts +12 -0
- package/src/multiplexers/zellij.ts +24 -0
- package/src/native/apply.d.ts +4 -3
- package/src/native/apply.ts +4 -3
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +199 -22
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +8 -6
- package/src/native/encode.ts +76 -34
- package/src/native/node.d.ts +36 -5
- package/src/native/node.ts +31 -5
- package/src/native/overlay.d.ts +8 -3
- package/src/native/overlay.ts +17 -4
- package/src/native/reconcile.ts +16 -4
- package/src/native/settle.d.ts +4 -3
- package/src/native/settle.ts +4 -3
- package/src/overlays/agent-hub-projection.d.ts +6 -5
- package/src/overlays/agent-hub-projection.ts +124 -13
- package/src/overlays/agent-hub-types.d.ts +9 -0
- package/src/overlays/agent-hub-types.ts +6 -0
- package/src/overlays/agent-hub.ts +48 -29
- package/src/overlays/agent-transcript-viewer.ts +93 -23
- package/src/overlays/agents-hub.ts +28 -8
- package/src/overlays/annotation-overlay.ts +293 -90
- package/src/overlays/annotation-types.ts +2 -0
- package/src/overlays/ask-dialog.ts +10 -17
- package/src/overlays/btw-history-panel.ts +136 -39
- package/src/overlays/copy-selector.d.ts +1 -0
- package/src/overlays/copy-selector.ts +32 -8
- package/src/overlays/extensions/extension-dashboard.ts +4 -4
- package/src/overlays/extensions/state-manager.ts +52 -12
- package/src/overlays/history-search.ts +100 -7
- package/src/overlays/hook-selector.ts +12 -5
- package/src/overlays/hub-frame.ts +19 -1
- package/src/overlays/jobs-panel.ts +33 -52
- package/src/overlays/model-browser.ts +367 -122
- package/src/overlays/model-hub.ts +435 -59
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/pause-screen.ts +2 -2
- package/src/overlays/plan-review-overlay.ts +208 -70
- package/src/overlays/plugin-settings.ts +3 -3
- package/src/overlays/report-panel.ts +191 -0
- package/src/overlays/rewind-selector.ts +88 -17
- package/src/overlays/session-observer-registry.d.ts +2 -0
- package/src/overlays/session-observer-registry.ts +10 -2
- package/src/overlays/session-selector.ts +64 -26
- package/src/overlays/settings-defs.ts +5 -4
- package/src/overlays/settings-selector.ts +33 -8
- package/src/overlays/snapcompact-shape-preview.ts +2 -3
- package/src/overlays/stats-notice.ts +1 -1
- package/src/overlays/thinking-selector.ts +11 -10
- package/src/overlays/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +117 -58
- package/src/overlays/usage-display.ts +10 -4
- package/src/overlays/usage-row.ts +22 -8
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer-cache.ts +7 -86
- package/src/prompt/composer.ts +37 -26
- package/src/prompt/custom-editor.ts +282 -105
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +32 -18
- package/src/prompt/macos-spelling.ts +7 -0
- package/src/prompt/magic-keywords.ts +11 -2
- package/src/prompt/model-mention-autocomplete.ts +14 -14
- package/src/prompt/queued-messages.ts +6 -4
- package/src/prompt/tips.txt +13 -9
- package/src/prompt/welcome.ts +64 -46
- package/src/prompt/word-completion.ts +38 -5
- package/src/render/composite.d.ts +2 -0
- package/src/render/composite.ts +64 -0
- package/src/render/output-pane.d.ts +1 -0
- package/src/render/output-pane.ts +6 -0
- package/src/render/render-utils.d.ts +1 -0
- package/src/render/render-utils.ts +16 -1
- package/src/render/tool-card.d.ts +6 -0
- package/src/render/tool-card.ts +15 -0
- package/src/render/width-aware-text.d.ts +2 -0
- package/src/render/width-aware-text.ts +5 -0
- package/src/setup/scenes/model.ts +2 -1
- package/src/setup/scenes/types.ts +1 -0
- package/src/space-hold.d.ts +40 -25
- package/src/space-hold.ts +156 -52
- package/src/status-line/component.ts +292 -78
- package/src/status-line/context-usage.ts +21 -46
- package/src/status-line/host.ts +4 -1
- package/src/status-line/segments.ts +40 -22
- package/src/status-line/types.ts +15 -3
- package/src/terminal-capabilities.d.ts +41 -36
- package/src/terminal-capabilities.ts +137 -213
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +23 -16
- package/src/terminal.ts +203 -99
- package/src/theme/active-symbols.d.ts +1 -1
- package/src/theme/active-symbols.ts +1 -1
- package/src/theme/schema-validation.ts +1 -0
- package/src/theme/schema.d.ts +11 -2
- package/src/theme/schema.ts +14 -1
- package/src/theme/session-color.ts +1 -7
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +11 -4
- package/src/theme/theme-class.d.ts +7 -0
- package/src/theme/theme-class.ts +26 -1
- package/src/theme/theme-schema.json +5 -1
- package/src/theme/theme.d.ts +1 -1
- package/src/theme/theme.ts +8 -4
- package/src/theme/tui-adapters.d.ts +3 -0
- package/src/theme/tui-adapters.ts +68 -0
- package/src/thinking.d.ts +1 -1
- package/src/thinking.ts +1 -1
- package/src/tmux.d.ts +11 -4
- package/src/tmux.ts +47 -11
- package/src/tools/ast-edit.ts +59 -35
- package/src/tools/ast-grep.ts +35 -16
- package/src/tools/bash.d.ts +7 -2
- package/src/tools/bash.ts +116 -47
- package/src/tools/daemon.d.ts +1 -1
- package/src/tools/daemon.ts +1 -1
- package/src/tools/default-renderer.ts +35 -19
- package/src/tools/edit.ts +142 -120
- package/src/tools/eval-format/index.ts +28 -2
- package/src/tools/eval-format/javascript.ts +7 -5
- package/src/tools/eval.d.ts +22 -3
- package/src/tools/eval.ts +364 -208
- package/src/tools/fetch.ts +5 -6
- package/src/tools/glob.d.ts +4 -0
- package/src/tools/glob.ts +33 -65
- package/src/tools/grep.ts +81 -48
- package/src/tools/index.ts +2 -0
- package/src/tools/lsp.ts +81 -59
- package/src/tools/mcp.ts +111 -68
- package/src/tools/native-view.d.ts +47 -0
- package/src/tools/native-view.ts +102 -1
- package/src/tools/output-meta.d.ts +11 -1
- package/src/tools/output-meta.ts +23 -3
- package/src/tools/read.ts +7 -5
- package/src/tools/renderer.d.ts +37 -2
- package/src/tools/renderer.ts +42 -2
- package/src/tools/streaming-output.d.ts +20 -1
- package/src/tools/streaming-output.ts +154 -50
- package/src/tools/task.d.ts +2 -3
- package/src/tools/task.ts +115 -62
- package/src/tools/todo.ts +119 -30
- package/src/tools/wait.ts +34 -24
- package/src/tools/web-search.ts +1 -1
- package/src/tools/write.d.ts +9 -2
- package/src/tools/write.ts +147 -95
- package/src/tools/yield.d.ts +22 -0
- package/src/tools/yield.ts +225 -0
- package/src/tui.d.ts +36 -0
- package/src/tui.ts +666 -217
- package/src/utils.d.ts +6 -0
- package/src/utils.ts +19 -0
- package/src/vim.d.ts +19 -5
- package/src/vim.ts +333 -18
- package/src/windows-input-mode.d.ts +33 -1
- package/src/windows-input-mode.ts +212 -6
|
@@ -2,7 +2,8 @@ import { ThinkingLevel } from "@linxiraos/pi-agent-core";
|
|
|
2
2
|
import {
|
|
3
3
|
type Component,
|
|
4
4
|
Container,
|
|
5
|
-
|
|
5
|
+
FuzzyQuery,
|
|
6
|
+
FuzzyText,
|
|
6
7
|
Input,
|
|
7
8
|
matchesKey,
|
|
8
9
|
Spacer,
|
|
@@ -53,7 +54,7 @@ import { shortenPath } from "../render/render-utils";
|
|
|
53
54
|
import { canonicalizeMessage } from "../chat/thinking-display";
|
|
54
55
|
import { resolveAssistantErrorPresentation } from "../chat/transcript-render-helpers";
|
|
55
56
|
import { OverlayPanel, PanelDivider } from "../chrome/overlay-box";
|
|
56
|
-
import { TreeView, type TreeRow } from "../components/tree-view";
|
|
57
|
+
import { TreeView, type TreeAncestor, type TreeRow } from "../components/tree-view";
|
|
57
58
|
import { formatKeyHint, formatKeyHints } from "../app-keybindings";
|
|
58
59
|
import { boundKeys, editorKeys, interruptKey } from "../chrome/keybinding-hints";
|
|
59
60
|
import type { TspPickerItem, TspPickerProps, TspSpan, TspText } from "@linxiraos/pi-wire";
|
|
@@ -210,6 +211,8 @@ class TreeList implements Component {
|
|
|
210
211
|
#multipleRoots = false;
|
|
211
212
|
#activePathIds: Set<string> = new Set();
|
|
212
213
|
#containsActive: Map<TreeSelectorNode, boolean> = new Map();
|
|
214
|
+
/** Prepared search text per node; rebuilt when the node's label changes. */
|
|
215
|
+
#searchTextCache = new WeakMap<TreeSelectorNode, { label: string | undefined; text: FuzzyText }>();
|
|
213
216
|
|
|
214
217
|
onSelect?: (entryId: string, options: { summarize: boolean }) => void;
|
|
215
218
|
onCancel?: () => void;
|
|
@@ -253,6 +256,7 @@ class TreeList implements Component {
|
|
|
253
256
|
getChildren: node => this.#orderedChildren(node),
|
|
254
257
|
getChildDepth: (_node, row, children) =>
|
|
255
258
|
children.length > 1 || (this.#multipleRoots && row.parentKey === undefined) ? row.depth + 1 : row.depth,
|
|
259
|
+
compactSameDepthAncestors: true,
|
|
256
260
|
theme,
|
|
257
261
|
filter: this.#buildFilter(),
|
|
258
262
|
maxRows: maxVisibleLines,
|
|
@@ -373,7 +377,11 @@ class TreeList implements Component {
|
|
|
373
377
|
#buildFilter(): (node: TreeSelectorNode, row: TreeRow<TreeSelectorNode, string>) => boolean {
|
|
374
378
|
const filterMode = this.#filterMode;
|
|
375
379
|
const leafId = this.currentLeafId;
|
|
376
|
-
const searchTokens = this.getSearchQuery()
|
|
380
|
+
const searchTokens = this.getSearchQuery()
|
|
381
|
+
.toLowerCase()
|
|
382
|
+
.split(/\s+/)
|
|
383
|
+
.filter(Boolean)
|
|
384
|
+
.map(token => new FuzzyQuery(token));
|
|
377
385
|
return node => {
|
|
378
386
|
const entry = node.entry;
|
|
379
387
|
const isCurrentLeaf = entry.id === leafId;
|
|
@@ -435,14 +443,23 @@ class TreeList implements Component {
|
|
|
435
443
|
|
|
436
444
|
// Apply fuzzy search filter
|
|
437
445
|
if (searchTokens.length > 0) {
|
|
438
|
-
const nodeText = this.#
|
|
439
|
-
return searchTokens.every(token =>
|
|
446
|
+
const nodeText = this.#getFuzzyText(node);
|
|
447
|
+
return searchTokens.every(token => token.match(nodeText).matches);
|
|
440
448
|
}
|
|
441
449
|
|
|
442
450
|
return true;
|
|
443
451
|
};
|
|
444
452
|
}
|
|
445
453
|
|
|
454
|
+
/** Prepared fuzzy text for a node's searchable content, built once per label. */
|
|
455
|
+
#getFuzzyText(node: TreeSelectorNode): FuzzyText {
|
|
456
|
+
const cached = this.#searchTextCache.get(node);
|
|
457
|
+
if (cached !== undefined && cached.label === node.label) return cached.text;
|
|
458
|
+
const text = new FuzzyText(this.#getSearchableText(node));
|
|
459
|
+
this.#searchTextCache.set(node, { label: node.label, text });
|
|
460
|
+
return text;
|
|
461
|
+
}
|
|
462
|
+
|
|
446
463
|
/** Get searchable text content from a node */
|
|
447
464
|
#getSearchableText(node: TreeSelectorNode): string {
|
|
448
465
|
const entry = node.entry;
|
|
@@ -877,23 +894,26 @@ class TreeList implements Component {
|
|
|
877
894
|
// Linear rows reuse their branch head's depth. Existing sibling
|
|
878
895
|
// gutters remain visible; terminal gutters remain terminated.
|
|
879
896
|
|
|
897
|
+
// Branch points that can draw a gutter, indexed by rendered level: the
|
|
898
|
+
// ancestor list is as deep as the conversation, so it is scanned once
|
|
899
|
+
// per row rather than once per prefix cell.
|
|
900
|
+
// oxlint-disable-next-line unicorn/no-new-array -- length preallocation
|
|
901
|
+
const gutterByLevel: (TreeAncestor<string> | undefined)[] = new Array(renderedIndent);
|
|
902
|
+
for (const ancestor of row.ancestors) {
|
|
903
|
+
if (ancestor.siblingCount <= 1) continue;
|
|
904
|
+
if (this.#multipleRoots && this.#rootIds.has(ancestor.key)) continue;
|
|
905
|
+
const level = ancestor.depth - 1 - scrollOffset;
|
|
906
|
+
if (level >= 0 && level < renderedIndent && gutterByLevel[level] === undefined) {
|
|
907
|
+
gutterByLevel[level] = ancestor;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
880
910
|
// Build prefix char by char, placing gutters and connector at their positions
|
|
881
911
|
const totalChars = renderedIndent * 3;
|
|
882
912
|
const prefixChars: string[] = [];
|
|
883
913
|
for (let i = 0; i < totalChars; i++) {
|
|
884
914
|
const level = Math.floor(i / 3);
|
|
885
|
-
const originalDepth = level + scrollOffset;
|
|
886
915
|
const posInLevel = i % 3;
|
|
887
|
-
|
|
888
|
-
// An ancestor branch point draws its gutter at its own depth minus
|
|
889
|
-
// one — the level its connector occupied. Session roots never emit
|
|
890
|
-
// gutters; their connectors are suppressed.
|
|
891
|
-
const gutterAncestor = row.ancestors.find(
|
|
892
|
-
ancestor =>
|
|
893
|
-
ancestor.depth - 1 === originalDepth &&
|
|
894
|
-
ancestor.siblingCount > 1 &&
|
|
895
|
-
!(this.#multipleRoots && this.#rootIds.has(ancestor.key)),
|
|
896
|
-
);
|
|
916
|
+
const gutterAncestor = gutterByLevel[level];
|
|
897
917
|
if (gutterAncestor) {
|
|
898
918
|
// Gutters follow standard tree semantics: `│` only while more
|
|
899
919
|
// siblings continue below, space below a `└─`.
|
|
@@ -1036,12 +1056,12 @@ class TreeList implements Component {
|
|
|
1036
1056
|
}
|
|
1037
1057
|
|
|
1038
1058
|
#extractContent(content: unknown): string {
|
|
1039
|
-
return this.#joinTextContent(content
|
|
1059
|
+
return this.#joinTextContent(content, SEARCH_TEXT_LIMIT);
|
|
1040
1060
|
}
|
|
1041
1061
|
|
|
1042
|
-
/** Concatenate
|
|
1043
|
-
#joinTextContent(content: unknown): string {
|
|
1044
|
-
if (typeof content === "string") return content;
|
|
1062
|
+
/** Concatenate text blocks, stopping as soon as `limit` characters exist. */
|
|
1063
|
+
#joinTextContent(content: unknown, limit = Number.POSITIVE_INFINITY): string {
|
|
1064
|
+
if (typeof content === "string") return content.length > limit ? content.slice(0, limit) : content;
|
|
1045
1065
|
if (Array.isArray(content)) {
|
|
1046
1066
|
let result = "";
|
|
1047
1067
|
for (const c of content) {
|
|
@@ -1054,6 +1074,7 @@ class TreeList implements Component {
|
|
|
1054
1074
|
typeof c.text === "string"
|
|
1055
1075
|
) {
|
|
1056
1076
|
result += c.text;
|
|
1077
|
+
if (result.length >= limit) return result.slice(0, limit);
|
|
1057
1078
|
}
|
|
1058
1079
|
}
|
|
1059
1080
|
return result;
|
|
@@ -43,7 +43,7 @@ import { editorKey, editorKeys } from "../chrome/keybinding-hints";
|
|
|
43
43
|
import type { TspSpan, TspTableColumn, TspText, TspTone } from "@linxiraos/pi-wire";
|
|
44
44
|
import { col, elapsed, node, span, text } from "../native/describe";
|
|
45
45
|
import { type DescribeContext, leafKey, type NativeChild, type NativeNode, type NativeUiEvent } from "../native/node";
|
|
46
|
-
import { actionButton } from "../native/overlay";
|
|
46
|
+
import { actionBar, actionButton } from "../native/overlay";
|
|
47
47
|
|
|
48
48
|
/** Local calendar-day activity consumed by the usage heatmap. */
|
|
49
49
|
export interface DailyActivityPoint {
|
|
@@ -84,10 +84,12 @@ export interface ProviderCard {
|
|
|
84
84
|
/** Number of represented accounts, including unavailable usage lookups. */
|
|
85
85
|
accounts: number;
|
|
86
86
|
unavailableAccounts: string[];
|
|
87
|
-
/** Window rows
|
|
87
|
+
/** Window rows in provider-declared order (e.g. 5h → weekly → monthly); the fullest CARD_MAX_WINDOWS lead. */
|
|
88
88
|
windows: CardWindowRow[];
|
|
89
|
-
/** True when every account reports no limits (e.g. enterprise plans). */
|
|
89
|
+
/** True when every account reports no limits (e.g. enterprise plans, providers without a quota API). */
|
|
90
90
|
unlimited: boolean;
|
|
91
|
+
/** Distinct report notes across accounts (e.g. why a provider exposes no quota windows). */
|
|
92
|
+
notes: string[];
|
|
91
93
|
/** True when nothing is used anywhere (or there are no limits): collapses to a tick. */
|
|
92
94
|
idle: boolean;
|
|
93
95
|
resetCredits?: {
|
|
@@ -146,8 +148,10 @@ function compactWindowTag(window: NonNullable<UsageLimit["window"]>): string {
|
|
|
146
148
|
* Collapse usage reports into one compact card per provider: limits grouped by
|
|
147
149
|
* quota bucket (label + window), each bucket showing the mean used fraction
|
|
148
150
|
* across accounts (matching the classic report's aggregate "% free") with the
|
|
149
|
-
* most-used account's reset countdown.
|
|
150
|
-
*
|
|
151
|
+
* most-used account's reset countdown. Rows keep the provider's declared window
|
|
152
|
+
* order so 5h → week → month reads the same every time; cards sort by their
|
|
153
|
+
* fullest window so what's burning is on top-left; fully idle providers
|
|
154
|
+
* collapse into a tick.
|
|
151
155
|
*/
|
|
152
156
|
export function buildProviderCards(
|
|
153
157
|
reports: UsageReport[],
|
|
@@ -181,7 +185,7 @@ export function buildProviderCards(
|
|
|
181
185
|
}
|
|
182
186
|
}
|
|
183
187
|
|
|
184
|
-
const
|
|
188
|
+
const declared: CardWindowRow[] = [...buckets.values()].map(bucket => {
|
|
185
189
|
const fractions = bucket.limits
|
|
186
190
|
.map(limit => resolveUsedFraction(limit))
|
|
187
191
|
.filter((value): value is number => value !== undefined);
|
|
@@ -200,7 +204,13 @@ export function buildProviderCards(
|
|
|
200
204
|
usedText: fraction === undefined ? formatAbsoluteOnlyAmount(bucket.limits) : undefined,
|
|
201
205
|
};
|
|
202
206
|
});
|
|
203
|
-
|
|
207
|
+
// Cards render at most CARD_MAX_WINDOWS rows; when a provider declares
|
|
208
|
+
// more, keep the fullest ones visible (still in declared order) so an
|
|
209
|
+
// exhausted bucket never hides behind "+N more".
|
|
210
|
+
const visible = new Set(
|
|
211
|
+
[...declared].sort((a, b) => (b.fraction ?? -1) - (a.fraction ?? -1)).slice(0, CARD_MAX_WINDOWS),
|
|
212
|
+
);
|
|
213
|
+
const windows = [...declared.filter(row => visible.has(row)), ...declared.filter(row => !visible.has(row))];
|
|
204
214
|
// The window tag earns its columns only when sibling rows would otherwise
|
|
205
215
|
// be indistinguishable (e.g. Antigravity's daily vs weekly "Usage (Google)").
|
|
206
216
|
for (const window of windows) {
|
|
@@ -254,6 +264,7 @@ export function buildProviderCards(
|
|
|
254
264
|
unavailableAccounts: unavailable,
|
|
255
265
|
windows,
|
|
256
266
|
unlimited: windows.length === 0 && unavailable.length === 0,
|
|
267
|
+
notes: [...new Set(providerReports.flatMap(report => report.notes ?? []))],
|
|
257
268
|
idle:
|
|
258
269
|
unavailable.length === 0 &&
|
|
259
270
|
!resetCredits &&
|
|
@@ -264,9 +275,12 @@ export function buildProviderCards(
|
|
|
264
275
|
});
|
|
265
276
|
}
|
|
266
277
|
|
|
278
|
+
const worstFraction = new Map(
|
|
279
|
+
cards.map(card => [card, card.windows.reduce((max, window) => Math.max(max, window.fraction ?? -1), -1)]),
|
|
280
|
+
);
|
|
267
281
|
cards.sort((a, b) => {
|
|
268
|
-
const aWorst = a
|
|
269
|
-
const bWorst = b
|
|
282
|
+
const aWorst = worstFraction.get(a)!;
|
|
283
|
+
const bWorst = worstFraction.get(b)!;
|
|
270
284
|
if (aWorst !== bWorst) return bWorst - aWorst;
|
|
271
285
|
return a.name.localeCompare(b.name);
|
|
272
286
|
});
|
|
@@ -440,6 +454,25 @@ function mutedText(content: string, wrap = false): NativeNode {
|
|
|
440
454
|
return text([span(content, "muted")], wrap ? { wrap: "word" } : { truncate: "end" });
|
|
441
455
|
}
|
|
442
456
|
|
|
457
|
+
/**
|
|
458
|
+
* A quiet line under the provider grid naming the providers that get no
|
|
459
|
+
* frame (`Untouched: Kimi Code, Zai`); `title` says why on hover.
|
|
460
|
+
*/
|
|
461
|
+
function footnote(key: string, label: string, cards: readonly ProviderCard[], title?: string): NativeNode {
|
|
462
|
+
const names = cards.map(card => card.name).join(", ");
|
|
463
|
+
return node(
|
|
464
|
+
"text",
|
|
465
|
+
{
|
|
466
|
+
spans: [span(`${label}: `, "dim"), span(names)],
|
|
467
|
+
wrap: "word",
|
|
468
|
+
role: "zeta.usage.footnote",
|
|
469
|
+
...(title ? { title: sanitizeDisplayLine(title) } : {}),
|
|
470
|
+
},
|
|
471
|
+
undefined,
|
|
472
|
+
key,
|
|
473
|
+
);
|
|
474
|
+
}
|
|
475
|
+
|
|
443
476
|
/** Stable, human account name for a report in the detail view. */
|
|
444
477
|
function reportAccountLabel(report: UsageReport, limit: UsageLimit | undefined, index: number): string {
|
|
445
478
|
const metadata = report.metadata;
|
|
@@ -463,16 +496,14 @@ function detailWindowLabel(label: string, limit: UsageLimit): string | undefined
|
|
|
463
496
|
return sanitizeDisplayLine(windowLabel);
|
|
464
497
|
}
|
|
465
498
|
|
|
499
|
+
const WHOLE_DOLLARS = new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 });
|
|
500
|
+
const COMPACT_COUNT = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 });
|
|
501
|
+
|
|
466
502
|
/** `$1,234 · 5.6K requests` totals for the activity summary. */
|
|
467
503
|
function formatActivityTotals(layout: HeatmapLayout): string {
|
|
468
504
|
const cost =
|
|
469
|
-
layout.totalCost >= 1
|
|
470
|
-
|
|
471
|
-
: `$${layout.totalCost.toFixed(2)}`;
|
|
472
|
-
const requests = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 }).format(
|
|
473
|
-
layout.totalRequests,
|
|
474
|
-
);
|
|
475
|
-
return `${cost} · ${requests} requests`;
|
|
505
|
+
layout.totalCost >= 1 ? `$${WHOLE_DOLLARS.format(layout.totalCost)}` : `$${layout.totalCost.toFixed(2)}`;
|
|
506
|
+
return `${cost} · ${COMPACT_COUNT.format(layout.totalRequests)} requests`;
|
|
476
507
|
}
|
|
477
508
|
|
|
478
509
|
// =============================================================================
|
|
@@ -547,6 +578,8 @@ export class UsageDashboardComponent implements Component {
|
|
|
547
578
|
#activityError: string | null = null;
|
|
548
579
|
#syncing = true;
|
|
549
580
|
#detailCache: { width: number; lines: string[] } | null = null;
|
|
581
|
+
/** ANSI overview rows; rebuilt when the revision or width changes. */
|
|
582
|
+
#overviewCache: { revision: number; width: number; lines: string[] } | null = null;
|
|
550
583
|
#lastViewportRows = 10;
|
|
551
584
|
#closed = false;
|
|
552
585
|
readonly #panel: OverlayPanel;
|
|
@@ -592,6 +625,7 @@ export class UsageDashboardComponent implements Component {
|
|
|
592
625
|
|
|
593
626
|
invalidate(): void {
|
|
594
627
|
this.#detailCache = null;
|
|
628
|
+
this.#overviewCache = null;
|
|
595
629
|
this.#panel.invalidate();
|
|
596
630
|
}
|
|
597
631
|
|
|
@@ -836,15 +870,8 @@ export class UsageDashboardComponent implements Component {
|
|
|
836
870
|
const ramp = this.#heatRamp();
|
|
837
871
|
const reset = "\x1b[39m";
|
|
838
872
|
|
|
839
|
-
const cost =
|
|
840
|
-
layout.totalCost >= 1
|
|
841
|
-
? `$${new Intl.NumberFormat("en-US", { maximumFractionDigits: 0 }).format(layout.totalCost)}`
|
|
842
|
-
: `$${layout.totalCost.toFixed(2)}`;
|
|
843
|
-
const requests = new Intl.NumberFormat("en-US", { notation: "compact", maximumFractionDigits: 1 }).format(
|
|
844
|
-
layout.totalRequests,
|
|
845
|
-
);
|
|
846
873
|
summary.push(
|
|
847
|
-
`${theme.bold(theme.fg("accent", "Activity"))} ${theme.fg("dim", `${
|
|
874
|
+
`${theme.bold(theme.fg("accent", "Activity"))} ${theme.fg("dim", `${formatActivityTotals(layout)} · last ${weeks} weeks`)}${this.#syncing ? theme.fg("dim", " · syncing…") : ""}`,
|
|
848
875
|
);
|
|
849
876
|
summary.push("");
|
|
850
877
|
|
|
@@ -876,10 +903,13 @@ export class UsageDashboardComponent implements Component {
|
|
|
876
903
|
// ---------------------------------------------------------------------------
|
|
877
904
|
|
|
878
905
|
#overviewLines(innerWidth: number): string[] {
|
|
906
|
+
const cached = this.#overviewCache;
|
|
907
|
+
if (cached?.revision === this.#revision && cached.width === innerWidth) return cached.lines;
|
|
879
908
|
const lines: string[] = [];
|
|
880
909
|
lines.push(...this.#renderCardsGrid(innerWidth));
|
|
881
910
|
lines.push("");
|
|
882
911
|
lines.push(...this.#renderHeatmap(innerWidth));
|
|
912
|
+
this.#overviewCache = { revision: this.#revision, width: innerWidth, lines };
|
|
883
913
|
return lines;
|
|
884
914
|
}
|
|
885
915
|
|
|
@@ -935,9 +965,9 @@ export class UsageDashboardComponent implements Component {
|
|
|
935
965
|
* The sheet body (the terminal's `lg` overlay is the frame): a head row
|
|
936
966
|
* (checked ago, Overview/Details tabs, Refresh), then either the provider
|
|
937
967
|
* grid of frames with window meters and the activity heatmap, or the
|
|
938
|
-
* per-provider detail tables
|
|
939
|
-
* `progress`/`table` on terminals without
|
|
940
|
-
* inputs change.
|
|
968
|
+
* per-provider detail tables, then Close like the other report sheets.
|
|
969
|
+
* `meter`/`chart` fall back to `progress`/`table` on terminals without
|
|
970
|
+
* them. Rebuilt only when its inputs change.
|
|
941
971
|
*/
|
|
942
972
|
describe(cx: DescribeContext): NativeNode {
|
|
943
973
|
const meter = cx.supports("meter");
|
|
@@ -947,15 +977,20 @@ export class UsageDashboardComponent implements Component {
|
|
|
947
977
|
return cache.node;
|
|
948
978
|
}
|
|
949
979
|
const body = this.#view === "detail" ? this.#describeDetail() : this.#describeOverview(meter, chart);
|
|
950
|
-
|
|
980
|
+
// Esc leaves Details for Overview first, so only Overview's Close wears its keycap.
|
|
981
|
+
const close = actionButton("Close", "close", this.#view === "overview" ? { keys: "escape" } : {});
|
|
982
|
+
const root = col([this.#describeHead(), node("col", { gap: "lg" }, body, this.#view), actionBar([null, close])], {
|
|
983
|
+
gap: "lg",
|
|
984
|
+
});
|
|
951
985
|
this.#nativeCache = { revision: this.#revision, meter, chart, node: root };
|
|
952
986
|
return root;
|
|
953
987
|
}
|
|
954
988
|
|
|
955
|
-
/** Tab clicks switch views like Enter/Esc; the Refresh button runs `r
|
|
989
|
+
/** Tab clicks switch views like Enter/Esc; the Refresh button runs `r`; Close closes from either view. */
|
|
956
990
|
handleNativeEvent(event: NativeUiEvent): void {
|
|
957
991
|
if (event.type === "action") {
|
|
958
992
|
if (event.act === "refresh") void this.#refresh();
|
|
993
|
+
else if (event.act === "close") this.#close();
|
|
959
994
|
return;
|
|
960
995
|
}
|
|
961
996
|
if (event.type !== "select" && event.type !== "activate") return;
|
|
@@ -1007,41 +1042,59 @@ export class UsageDashboardComponent implements Component {
|
|
|
1007
1042
|
const children: NativeChild[] = [];
|
|
1008
1043
|
if (this.#cards.length === 0) {
|
|
1009
1044
|
children.push(
|
|
1010
|
-
node(
|
|
1011
|
-
"text",
|
|
1012
|
-
{ spans: [span("No usage data available.")], role: "zeta.usage.untouched" },
|
|
1013
|
-
undefined,
|
|
1014
|
-
"none",
|
|
1015
|
-
),
|
|
1045
|
+
node("text", { spans: [span("No usage data available.")], role: "zeta.usage.footnote" }, undefined, "none"),
|
|
1016
1046
|
);
|
|
1017
1047
|
} else {
|
|
1018
|
-
//
|
|
1019
|
-
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1048
|
+
// Only providers with something to show get a frame: the rest are
|
|
1049
|
+
// named in footnotes, whether untouched, unreadable or quota-less.
|
|
1050
|
+
const framed: ProviderCard[] = [];
|
|
1051
|
+
const untouched: ProviderCard[] = [];
|
|
1052
|
+
const unreported: ProviderCard[] = [];
|
|
1053
|
+
const unmetered: ProviderCard[] = [];
|
|
1054
|
+
for (const entry of this.#cards) {
|
|
1055
|
+
const shown =
|
|
1056
|
+
entry.windows.length > 0 || entry.resetCredits !== undefined || entry.daybreakAccounts !== undefined;
|
|
1057
|
+
if (!shown) (entry.unavailableAccounts.length > 0 ? unreported : unmetered).push(entry);
|
|
1058
|
+
else if (entry.idle) untouched.push(entry);
|
|
1059
|
+
else framed.push(entry);
|
|
1060
|
+
}
|
|
1061
|
+
if (framed.length > 0) {
|
|
1022
1062
|
children.push(
|
|
1023
1063
|
node(
|
|
1024
1064
|
"row",
|
|
1025
1065
|
{ wrap: true, gap: "md", role: "zeta.usage.grid" },
|
|
1026
|
-
|
|
1066
|
+
framed.map(entry => this.#describeCard(entry, meter)),
|
|
1027
1067
|
"providers",
|
|
1028
1068
|
),
|
|
1029
1069
|
);
|
|
1030
1070
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1071
|
+
const notes: NativeNode[] = [];
|
|
1072
|
+
if (untouched.length > 0) notes.push(footnote("idle", "Untouched", untouched));
|
|
1073
|
+
if (unreported.length > 0) {
|
|
1074
|
+
const accounts = unreported.flatMap(entry =>
|
|
1075
|
+
entry.unavailableAccounts.map(account => `${entry.name} (${account})`),
|
|
1076
|
+
);
|
|
1077
|
+
notes.push(
|
|
1078
|
+
footnote(
|
|
1079
|
+
"unreported",
|
|
1080
|
+
"No usage data",
|
|
1081
|
+
unreported,
|
|
1082
|
+
`No usage report came back for ${accounts.join(", ")}: the sign-in expired, the request failed, or the plan has no quotas`,
|
|
1083
|
+
),
|
|
1084
|
+
);
|
|
1085
|
+
}
|
|
1086
|
+
if (unmetered.length > 0) {
|
|
1087
|
+
const why = unmetered.flatMap(entry => entry.notes.map(note => `${entry.name}: ${note}`));
|
|
1088
|
+
notes.push(
|
|
1089
|
+
footnote(
|
|
1090
|
+
"unmetered",
|
|
1091
|
+
"No quotas reported",
|
|
1092
|
+
unmetered,
|
|
1093
|
+
why.length > 0 ? why.join(" • ") : "These providers report no quota windows to track",
|
|
1042
1094
|
),
|
|
1043
1095
|
);
|
|
1044
1096
|
}
|
|
1097
|
+
if (notes.length > 0) children.push(node("col", { gap: "xs" }, notes, "footnotes"));
|
|
1045
1098
|
}
|
|
1046
1099
|
children.push(this.#describeActivity(chart));
|
|
1047
1100
|
return children;
|
|
@@ -1092,10 +1145,10 @@ export class UsageDashboardComponent implements Component {
|
|
|
1092
1145
|
);
|
|
1093
1146
|
}
|
|
1094
1147
|
for (const account of entry.unavailableAccounts) {
|
|
1095
|
-
children.push(mutedText(`${sanitizeDisplayLine(account)}: usage
|
|
1148
|
+
children.push(mutedText(`${sanitizeDisplayLine(account)}: no usage data`, true));
|
|
1096
1149
|
}
|
|
1097
1150
|
if (entry.unlimited) {
|
|
1098
|
-
children.push(mutedText("No
|
|
1151
|
+
children.push(mutedText("No quotas reported"));
|
|
1099
1152
|
} else {
|
|
1100
1153
|
for (const [index, window] of entry.windows.slice(0, CARD_MAX_WINDOWS).entries()) {
|
|
1101
1154
|
const label: TspSpan[] = [span(sanitizeDisplayLine(window.label))];
|
|
@@ -1104,7 +1157,9 @@ export class UsageDashboardComponent implements Component {
|
|
|
1104
1157
|
text(label, { role: "zeta.usage.label", truncate: "middle", title: sanitizeDisplayLine(window.label) }),
|
|
1105
1158
|
];
|
|
1106
1159
|
if (window.fraction === undefined) {
|
|
1107
|
-
cells.push(
|
|
1160
|
+
cells.push(
|
|
1161
|
+
text([span(window.usedText ?? "No data", "muted")], { role: "zeta.usage.amount", truncate: "end" }),
|
|
1162
|
+
);
|
|
1108
1163
|
} else {
|
|
1109
1164
|
const token =
|
|
1110
1165
|
window.status === "exhausted" ? "error" : window.status === "warning" ? "warning" : undefined;
|
|
@@ -1221,7 +1276,7 @@ export class UsageDashboardComponent implements Component {
|
|
|
1221
1276
|
const children: NativeChild[] = [];
|
|
1222
1277
|
for (const account of unavailable) {
|
|
1223
1278
|
if (account.provider !== entry.provider) continue;
|
|
1224
|
-
children.push(mutedText(`${sanitizeDisplayLine(account.label)}: usage
|
|
1279
|
+
children.push(mutedText(`${sanitizeDisplayLine(account.label)}: no usage data`, true));
|
|
1225
1280
|
}
|
|
1226
1281
|
|
|
1227
1282
|
const facts: { k: TspText; v: TspText }[] = [];
|
|
@@ -1313,7 +1368,7 @@ export class UsageDashboardComponent implements Component {
|
|
|
1313
1368
|
children.push(node("table", { cols, rows }, undefined, "limits"));
|
|
1314
1369
|
for (const note of new Set(limitNotes)) children.push(mutedText(note, true));
|
|
1315
1370
|
} else if (unavailable.every(account => account.provider !== entry.provider)) {
|
|
1316
|
-
children.push(mutedText("No
|
|
1371
|
+
children.push(mutedText("No quotas reported"));
|
|
1317
1372
|
}
|
|
1318
1373
|
sections.push(
|
|
1319
1374
|
node(
|
|
@@ -1384,8 +1439,7 @@ export class UsageDashboardComponent implements Component {
|
|
|
1384
1439
|
this.#setView("overview");
|
|
1385
1440
|
return;
|
|
1386
1441
|
}
|
|
1387
|
-
this
|
|
1388
|
-
this.#options.onClose();
|
|
1442
|
+
this.#close();
|
|
1389
1443
|
return;
|
|
1390
1444
|
}
|
|
1391
1445
|
if (matchesKey(data, "r")) {
|
|
@@ -1408,4 +1462,9 @@ export class UsageDashboardComponent implements Component {
|
|
|
1408
1462
|
this.#options.requestRender();
|
|
1409
1463
|
} else if (matchesKey(data, "end")) this.#scrollBy(Number.MAX_SAFE_INTEGER);
|
|
1410
1464
|
}
|
|
1465
|
+
|
|
1466
|
+
#close(): void {
|
|
1467
|
+
this.dispose();
|
|
1468
|
+
this.#options.onClose();
|
|
1469
|
+
}
|
|
1411
1470
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { UsageLimit, UsageReport, UsageResetCredits } from "@linxiraos/pi-ai";
|
|
1
|
+
import type { UsageLimit, UsageReport, UsageResetCreditDetail, UsageResetCredits } from "@linxiraos/pi-ai";
|
|
2
2
|
|
|
3
3
|
/** Include the usage tier in a limit title unless its label already names it. */
|
|
4
4
|
export function formatLimitTitle(limit: UsageLimit): string {
|
|
@@ -51,6 +51,13 @@ export interface UsageResetSummary {
|
|
|
51
51
|
unavailableReason?: string;
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
+
/** Expiry (epoch ms) of a credit that still banks a reset, whether or not it can be spent now. */
|
|
55
|
+
export function bankedResetCreditExpiryMs(credit: UsageResetCreditDetail): number | undefined {
|
|
56
|
+
if (!credit.expiresAt || credit.remainingCount === 0 || credit.status === "redeemed") return undefined;
|
|
57
|
+
const expiryMs = Date.parse(credit.expiresAt);
|
|
58
|
+
return Number.isFinite(expiryMs) ? expiryMs : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
54
61
|
/**
|
|
55
62
|
* Normalize old count-only and current provider reset metadata for display.
|
|
56
63
|
* `availableCount` is banked inventory; `redeemableCount` is the subset that
|
|
@@ -68,9 +75,8 @@ export function summarizeUsageResetCredits(
|
|
|
68
75
|
let latestExpired: string | undefined;
|
|
69
76
|
let latestExpiredMs = Number.NEGATIVE_INFINITY;
|
|
70
77
|
for (const credit of reset.credits ?? []) {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (!Number.isFinite(expiryMs)) continue;
|
|
78
|
+
const expiryMs = bankedResetCreditExpiryMs(credit);
|
|
79
|
+
if (expiryMs === undefined) continue;
|
|
74
80
|
if (expiryMs > nowMs && expiryMs < soonestExpiryMs) {
|
|
75
81
|
soonestExpiryMs = expiryMs;
|
|
76
82
|
soonestExpiry = credit.expiresAt;
|
|
@@ -5,17 +5,19 @@ import { formatDuration, formatNumber } from "@linxiraos/pi-utils";
|
|
|
5
5
|
import { theme } from "../theme/theme";
|
|
6
6
|
import { formatMetricRow, MetricRow, type MetricSpec } from "../components/metric";
|
|
7
7
|
import { node, row, span, text } from "../native/describe";
|
|
8
|
-
import type { NativeChild, NativeNode } from "../native/node";
|
|
8
|
+
import type { DescribeContext, NativeChild, NativeNode } from "../native/node";
|
|
9
9
|
|
|
10
10
|
/** Below this the rate is nonsense (cached/instant responses yield absurd tok/s). */
|
|
11
11
|
const MIN_DURATION_MS = 100;
|
|
12
12
|
|
|
13
|
-
/** Local
|
|
14
|
-
function formatUsageTimestamp(ms: number): string {
|
|
13
|
+
/** Local ISO date with a wall-clock time; only the hour cycle follows the terminal. */
|
|
14
|
+
function formatUsageTimestamp(ms: number, hour12 = false): string {
|
|
15
15
|
const d = new Date(ms);
|
|
16
16
|
const pad = (n: number): string => String(n).padStart(2, "0");
|
|
17
17
|
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
18
|
-
const time =
|
|
18
|
+
const time = hour12
|
|
19
|
+
? d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12 })
|
|
20
|
+
: `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
19
21
|
return `${date} ${time}`;
|
|
20
22
|
}
|
|
21
23
|
|
|
@@ -161,6 +163,8 @@ class UsageRowBlock extends Container {
|
|
|
161
163
|
readonly #timestamp: number | undefined;
|
|
162
164
|
readonly #turnElapsedMs: number | undefined;
|
|
163
165
|
#nativeNode: NativeNode | undefined;
|
|
166
|
+
/** The terminal's clock {@link #nativeNode} was described with. */
|
|
167
|
+
#nativeHour12: boolean | undefined;
|
|
164
168
|
|
|
165
169
|
constructor(usage: Usage, durationMs?: number, ttftMs?: number, timestamp?: number, turnElapsedMs?: number) {
|
|
166
170
|
super();
|
|
@@ -186,13 +190,23 @@ class UsageRowBlock extends Container {
|
|
|
186
190
|
* ttft 0.9s · 96 tok/s`): worded metrics instead of the ANSI icons, the
|
|
187
191
|
* full timestamp in the tooltip, and throughput as a `rate`.
|
|
188
192
|
*/
|
|
189
|
-
override describe(): NativeNode {
|
|
190
|
-
|
|
193
|
+
override describe(cx?: DescribeContext): NativeNode {
|
|
194
|
+
// The terminal's clock: the process locale doesn't reliably carry the user's 12/24-hour choice.
|
|
195
|
+
const hour12 = cx?.hour12;
|
|
196
|
+
if (this.#nativeNode && this.#nativeHour12 === hour12) return this.#nativeNode;
|
|
197
|
+
this.#nativeHour12 = hour12;
|
|
191
198
|
const usage = this.#usage;
|
|
192
199
|
const parts: string[] = [];
|
|
193
200
|
const timestamp = this.#timestamp;
|
|
194
201
|
const stamped = timestamp !== undefined && Number.isFinite(timestamp) && timestamp > 0;
|
|
195
|
-
|
|
202
|
+
// Only a 12-hour clock goes through the locale; 24-hour keeps the tooltip's own `HH:mm`.
|
|
203
|
+
if (stamped) {
|
|
204
|
+
parts.push(
|
|
205
|
+
hour12
|
|
206
|
+
? new Date(timestamp).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", hour12 })
|
|
207
|
+
: formatUsageTimestamp(timestamp).slice(11, 16),
|
|
208
|
+
);
|
|
209
|
+
}
|
|
196
210
|
if (this.#turnElapsedMs !== undefined && this.#turnElapsedMs > 0) {
|
|
197
211
|
parts.push(`Δ ${formatDuration(Math.round(this.#turnElapsedMs))}`);
|
|
198
212
|
}
|
|
@@ -211,7 +225,7 @@ class UsageRowBlock extends Container {
|
|
|
211
225
|
role: "zeta.usage.turn",
|
|
212
226
|
gap: "none",
|
|
213
227
|
align: "baseline",
|
|
214
|
-
...(stamped ? { title: formatUsageTimestamp(timestamp) } : {}),
|
|
228
|
+
...(stamped ? { title: formatUsageTimestamp(timestamp, hour12) } : {}),
|
|
215
229
|
});
|
|
216
230
|
return this.#nativeNode;
|
|
217
231
|
}
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
visibleWidth,
|
|
13
13
|
} from "../index";
|
|
14
14
|
import { fileHyperlink } from "../render/hyperlink";
|
|
15
|
-
import {
|
|
15
|
+
import { nativeImageNode } from "../native/blobs";
|
|
16
16
|
import { node, row, span } from "../native/describe";
|
|
17
17
|
import { plainText } from "../native/spans";
|
|
18
18
|
import type { DescribeContext, NativeNode } from "../native/node";
|
|
@@ -33,12 +33,9 @@ const RESET_FG = "\x1b[39m";
|
|
|
33
33
|
* (pastes are usually re-encoded JPEG/WebP) convert before transmit — the same pipeline
|
|
34
34
|
* the transcript uses. `null` = conversion in flight or failed. */
|
|
35
35
|
const kImagePng = Symbol("zeta.imagePng");
|
|
36
|
-
/** Content address of the draft image's decoded bytes, registered once for TSP `image` nodes. */
|
|
37
|
-
const kImageBlob = Symbol("zeta.imageBlob");
|
|
38
36
|
|
|
39
37
|
interface ImageContentWithPng extends ImageContent {
|
|
40
38
|
[kImagePng]?: ImageContent | null;
|
|
41
|
-
[kImageBlob]?: string;
|
|
42
39
|
}
|
|
43
40
|
|
|
44
41
|
/**
|
|
@@ -57,7 +54,9 @@ export class AttachmentChipsBand implements Component {
|
|
|
57
54
|
private readonly requestRender: () => void,
|
|
58
55
|
) {}
|
|
59
56
|
|
|
60
|
-
#native:
|
|
57
|
+
#native:
|
|
58
|
+
| { chips: readonly ComposerChipDescriptor[]; node: NativeNode; images: ReadonlyMap<ImageContent, NativeNode> }
|
|
59
|
+
| undefined;
|
|
61
60
|
|
|
62
61
|
/**
|
|
63
62
|
* A wrapping `row` of chip `card`s (`zeta.composer.chip`) titled with the
|
|
@@ -69,6 +68,7 @@ export class AttachmentChipsBand implements Component {
|
|
|
69
68
|
const chips = this.editor.composerChips();
|
|
70
69
|
if (this.#native?.chips === chips) return this.#native.node;
|
|
71
70
|
const cards: NativeNode[] = [];
|
|
71
|
+
const images = new Map<ImageContent, NativeNode>();
|
|
72
72
|
for (const chip of chips) {
|
|
73
73
|
const icon = theme.symbol(
|
|
74
74
|
chip.kind === "paste" ? "chip.paste" : chip.kind === "video" ? "chip.video" : "chip.image",
|
|
@@ -86,11 +86,14 @@ export class AttachmentChipsBand implements Component {
|
|
|
86
86
|
} else {
|
|
87
87
|
const dims = this.#imageDims(chip.image);
|
|
88
88
|
caption = dims ? `${dims.width}x${dims.height}` : "";
|
|
89
|
-
const image = chip.image
|
|
90
|
-
const
|
|
91
|
-
|
|
89
|
+
const image = chip.image;
|
|
90
|
+
const native =
|
|
91
|
+
images.get(image) ??
|
|
92
|
+
this.#native?.images.get(image) ??
|
|
93
|
+
nativeImageNode(Buffer.from(image.data, "base64"), image.mimeType);
|
|
94
|
+
images.set(image, native);
|
|
92
95
|
content = node("image", {
|
|
93
|
-
|
|
96
|
+
...native.p,
|
|
94
97
|
alt: title,
|
|
95
98
|
w: dims?.width,
|
|
96
99
|
h: dims?.height,
|
|
@@ -106,7 +109,7 @@ export class AttachmentChipsBand implements Component {
|
|
|
106
109
|
);
|
|
107
110
|
}
|
|
108
111
|
const described = row(cards, { gap: "sm", wrap: true, role: "zeta.composer.chips", hidden: cards.length === 0 });
|
|
109
|
-
this.#native = { chips, node: described };
|
|
112
|
+
this.#native = { chips, node: described, images };
|
|
110
113
|
return described;
|
|
111
114
|
}
|
|
112
115
|
|
|
@@ -249,7 +252,8 @@ export class AttachmentChipsBand implements Component {
|
|
|
249
252
|
|
|
250
253
|
/** Leading 4 rows x 12 cols of the pasted text, muted. */
|
|
251
254
|
#textInterior(entry: TextAttachment): string[] {
|
|
252
|
-
|
|
255
|
+
// Only the leading rows are drawn; don't split a multi-KB paste every frame.
|
|
256
|
+
const lines = entry.content.split("\n", INNER_ROWS);
|
|
253
257
|
const rows: string[] = [];
|
|
254
258
|
for (let r = 0; r < INNER_ROWS; r++) {
|
|
255
259
|
const cut = truncateToWidth(replaceTabs(lines[r] ?? ""), INNER_COLS);
|