@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
|
@@ -11,10 +11,9 @@ import { ThinkingLevel } from "@linxiraos/pi-agent-core";
|
|
|
11
11
|
import type { Model } from "@linxiraos/pi-ai";
|
|
12
12
|
import { buildModel } from "@linxiraos/pi-catalog/build";
|
|
13
13
|
import { getModelPricingStatus, modelsAreEqual } from "@linxiraos/pi-catalog/models";
|
|
14
|
-
import type
|
|
14
|
+
import { modelKind, type ModelKind, type ModelPricingStatus } from "@linxiraos/pi-catalog/types";
|
|
15
15
|
import type { Component } from "../tui";
|
|
16
|
-
import {
|
|
17
|
-
import { fuzzyRank } from "../fuzzy";
|
|
16
|
+
import { FuzzyCorpus, fuzzyRank } from "../fuzzy";
|
|
18
17
|
import { Input } from "../components/input";
|
|
19
18
|
import { ScrollView } from "../components/scroll-view";
|
|
20
19
|
import { matchesKey } from "../keys";
|
|
@@ -28,7 +27,7 @@ import {
|
|
|
28
27
|
parseConfiguredThinkingLevel,
|
|
29
28
|
} from "../thinking";
|
|
30
29
|
import { thinkingLevelGlyph } from "../render/render-utils";
|
|
31
|
-
import { type ThemeColor, theme } from "../theme/theme";
|
|
30
|
+
import { type Theme, type ThemeColor, theme } from "../theme/theme";
|
|
32
31
|
import {
|
|
33
32
|
matchesSelectCancel,
|
|
34
33
|
matchesSelectDown,
|
|
@@ -123,6 +122,18 @@ export interface ResolvedModelRoleValue {
|
|
|
123
122
|
warning?: string;
|
|
124
123
|
}
|
|
125
124
|
|
|
125
|
+
/** Where auto-compaction triggers for one model, as the model hub's preview shows it (edited from the Roles view). */
|
|
126
|
+
export interface ModelCompactionPoint {
|
|
127
|
+
/** Context tokens that trigger auto-compaction; undefined when auto-compaction is off or the window is unknown. */
|
|
128
|
+
tokens: number | undefined;
|
|
129
|
+
/** Why it triggers there, in a few words: `fixed`, `85% of 400K base`, `80% of window`. */
|
|
130
|
+
basis?: string;
|
|
131
|
+
/** What sets it: the matching `compaction.modelThresholds` key, `global`, or `default`. */
|
|
132
|
+
source: string;
|
|
133
|
+
/** The model's own exact entry as editable text (`90k`, `80%`); absent when it has none. */
|
|
134
|
+
draft?: string;
|
|
135
|
+
}
|
|
136
|
+
|
|
126
137
|
/** Host-provided preferences and model-role resolution for the browser. */
|
|
127
138
|
export interface ModelBrowserSource extends ModelRoleLookup {
|
|
128
139
|
/**
|
|
@@ -135,11 +146,17 @@ export interface ModelBrowserSource extends ModelRoleLookup {
|
|
|
135
146
|
readonly knownRoleIds: readonly string[];
|
|
136
147
|
readonly mruOrder: readonly string[];
|
|
137
148
|
readonly modelPerf: ReadonlyMap<string, ModelBrowserPerf>;
|
|
149
|
+
/**
|
|
150
|
+
* Service tier the host would send for `model`, when it runs one. Rows with a
|
|
151
|
+
* measured tier aggregate (`selector@tier`) then display that tier's numbers
|
|
152
|
+
* instead of the standard aggregate.
|
|
153
|
+
*/
|
|
154
|
+
serviceTierFor?(model: Model): string | undefined;
|
|
138
155
|
getRoleInfo(role: string): ModelBrowserRoleInfo;
|
|
139
|
-
/** True when the provider was injected from the upstream OMP model config. */
|
|
140
|
-
isOmpOriginProvider?(provider: string): boolean;
|
|
141
156
|
defaultRoleChain(role: string): string[];
|
|
142
157
|
resolveRoleValue(value: string | undefined, models: Model[], roleLookup?: ModelRoleLookup): ResolvedModelRoleValue;
|
|
158
|
+
/** Auto-compaction point for `model`; absent hosts show no compaction row in the preview. */
|
|
159
|
+
compactionPointFor?(model: Model): ModelCompactionPoint | undefined;
|
|
143
160
|
}
|
|
144
161
|
|
|
145
162
|
/** Read-only catalog surface consumed by model browsers. */
|
|
@@ -246,6 +263,22 @@ export function resolveRoleAssignments(
|
|
|
246
263
|
return roles;
|
|
247
264
|
}
|
|
248
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Role-resolution pool for a `--models`/`enabledModels` scope: the scoped
|
|
268
|
+
* models plus every available non-chat runner (judge, search, image, …). The
|
|
269
|
+
* scope only filters chat models (it feeds Ctrl+P cycling) and runtime role
|
|
270
|
+
* resolution ignores it for other kinds, so displayed role assignments must too.
|
|
271
|
+
* Shared by the /models hub and the session picker.
|
|
272
|
+
*/
|
|
273
|
+
export function scopedRolePool(scoped: ReadonlyArray<Model>, registry: ModelBrowserRegistry): Model[] {
|
|
274
|
+
const pool = [...scoped];
|
|
275
|
+
for (const model of registry.getAvailable("all")) {
|
|
276
|
+
if (modelKind(model) === "chat" || scoped.some(entry => modelsAreEqual(entry, model))) continue;
|
|
277
|
+
pool.push(model);
|
|
278
|
+
}
|
|
279
|
+
return pool;
|
|
280
|
+
}
|
|
281
|
+
|
|
249
282
|
/** Wrap raw models into browser items. */
|
|
250
283
|
export function buildBrowserItems(models: ReadonlyArray<Model>): ModelBrowserItem[] {
|
|
251
284
|
return models.map(model => ({
|
|
@@ -300,13 +333,17 @@ export interface SortModelItemsOptions {
|
|
|
300
333
|
* then per provider by priority, version, and recency.
|
|
301
334
|
*/
|
|
302
335
|
export function sortModelItems(items: ModelBrowserItem[], options: SortModelItemsOptions = {}): void {
|
|
336
|
+
items.sort(modelItemComparator(options));
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function modelItemComparator(options: SortModelItemsOptions): (a: ModelBrowserItem, b: ModelBrowserItem) => number {
|
|
303
340
|
const { roles = {}, mruOrder = [], skipRoleRank = false } = options;
|
|
304
341
|
const mruIndex = new Map(mruOrder.map((key, i) => [key, i]));
|
|
305
342
|
|
|
306
343
|
const dateRe = /-(\d{8})$/;
|
|
307
344
|
const latestRe = /-latest$/;
|
|
308
345
|
|
|
309
|
-
|
|
346
|
+
return (a, b) => {
|
|
310
347
|
if (!skipRoleRank) {
|
|
311
348
|
const aRank = computeModelRank(a.model, roles);
|
|
312
349
|
const bRank = computeModelRank(b.model, roles);
|
|
@@ -351,9 +388,9 @@ export function sortModelItems(items: ModelBrowserItem[], options: SortModelItem
|
|
|
351
388
|
// Both have dates — descending (newest first)
|
|
352
389
|
if (aDate && bDate) return bDate.localeCompare(aDate);
|
|
353
390
|
|
|
354
|
-
//
|
|
355
|
-
return
|
|
356
|
-
}
|
|
391
|
+
// Both -latest — alphabetical (a fixed -1 here made the order depend on input order)
|
|
392
|
+
return a.id.localeCompare(b.id);
|
|
393
|
+
};
|
|
357
394
|
}
|
|
358
395
|
|
|
359
396
|
/** Picker candidates and ordering inputs shared with composer model mentions. */
|
|
@@ -366,8 +403,16 @@ export interface SessionModelScope {
|
|
|
366
403
|
|
|
367
404
|
/** Catalog inputs a {@link SessionModelScope} is derived from. */
|
|
368
405
|
interface SessionModelScopeInputs {
|
|
406
|
+
/** Chat models listed as picker rows. */
|
|
369
407
|
models: ReadonlyArray<Model>;
|
|
408
|
+
/** Catalog that configured role values resolve against. */
|
|
370
409
|
allModels: ReadonlyArray<Model>;
|
|
410
|
+
/**
|
|
411
|
+
* Available models of every kind that unconfigured roles auto-select from,
|
|
412
|
+
* as at runtime; a chat-only pool would pin `judge` on a chat model while
|
|
413
|
+
* the session judges with an available native judge.
|
|
414
|
+
*/
|
|
415
|
+
roleModels: ReadonlyArray<Model>;
|
|
371
416
|
error: string | undefined;
|
|
372
417
|
}
|
|
373
418
|
|
|
@@ -375,21 +420,32 @@ function readSessionModelScopeInputs(
|
|
|
375
420
|
registry: ModelBrowserRegistry,
|
|
376
421
|
scopedModels: ReadonlyArray<Model>,
|
|
377
422
|
): SessionModelScopeInputs {
|
|
378
|
-
if (scopedModels.length > 0)
|
|
423
|
+
if (scopedModels.length > 0) {
|
|
424
|
+
let roleModels: ReadonlyArray<Model>;
|
|
425
|
+
let error: string | undefined;
|
|
426
|
+
try {
|
|
427
|
+
roleModels = scopedRolePool(scopedModels, registry);
|
|
428
|
+
} catch (cause) {
|
|
429
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
430
|
+
roleModels = scopedModels;
|
|
431
|
+
}
|
|
432
|
+
return { models: scopedModels, allModels: roleModels, roleModels, error };
|
|
433
|
+
}
|
|
379
434
|
const loadError = registry.getError();
|
|
380
435
|
let error = loadError ? String(loadError) : undefined;
|
|
381
|
-
let
|
|
436
|
+
let roleModels: ReadonlyArray<Model>;
|
|
382
437
|
try {
|
|
383
|
-
|
|
438
|
+
roleModels = registry.getAvailable("all");
|
|
384
439
|
} catch (cause) {
|
|
385
440
|
error = cause instanceof Error ? cause.message : String(cause);
|
|
386
|
-
|
|
441
|
+
roleModels = [];
|
|
387
442
|
}
|
|
388
|
-
|
|
443
|
+
const models = roleModels.filter(model => modelKind(model) === "chat");
|
|
444
|
+
return { models, allModels: registry.getAll("all"), roleModels, error };
|
|
389
445
|
}
|
|
390
446
|
|
|
391
447
|
function scopeFromInputs(settings: ModelBrowserSource, inputs: SessionModelScopeInputs): SessionModelScope {
|
|
392
|
-
const roles = resolveRoleAssignments(settings, inputs.allModels, inputs.
|
|
448
|
+
const roles = resolveRoleAssignments(settings, inputs.allModels, inputs.roleModels);
|
|
393
449
|
const mruOrder = settings.mruOrder;
|
|
394
450
|
const items = buildBrowserItems(inputs.models);
|
|
395
451
|
sortModelItems(items, { roles, mruOrder });
|
|
@@ -434,6 +490,7 @@ export class SessionModelScopeCache {
|
|
|
434
490
|
inputs.error === cachedInputs.error &&
|
|
435
491
|
sameItems(inputs.models, cachedInputs.models) &&
|
|
436
492
|
sameItems(inputs.allModels, cachedInputs.allModels) &&
|
|
493
|
+
sameItems(inputs.roleModels, cachedInputs.roleModels) &&
|
|
437
494
|
sameItems(this.#settings.mruOrder, cached.mruOrder)
|
|
438
495
|
) {
|
|
439
496
|
return cached;
|
|
@@ -533,58 +590,100 @@ function compactModelSearchText(value: string): string {
|
|
|
533
590
|
return value.toLowerCase().replace(/[^\p{Letter}\p{Mark}\p{Number}]+/gu, "");
|
|
534
591
|
}
|
|
535
592
|
|
|
536
|
-
/** Exact id/selector → contiguous literal → fuzzy-only. */
|
|
537
|
-
function modelSearchTier(query: string,
|
|
593
|
+
/** Exact id/selector → contiguous literal → fuzzy-only, against precomputed compact forms. */
|
|
594
|
+
function modelSearchTier(query: string, id: string, selector: string): number {
|
|
538
595
|
if (!query) return 2;
|
|
539
|
-
const id = compactModelSearchText(item.id);
|
|
540
|
-
const selector = compactModelSearchText(item.selector);
|
|
541
596
|
if (query === id || query === selector) return 0;
|
|
542
597
|
if (id.includes(query) || selector.includes(query)) return 1;
|
|
543
598
|
return 2;
|
|
544
599
|
}
|
|
545
600
|
|
|
601
|
+
/** Inputs to {@link rankModelItems} / {@link ModelItemRanker}. */
|
|
602
|
+
export interface ModelRankOptions {
|
|
603
|
+
roles: RoleAssignments;
|
|
604
|
+
mruOrder: ReadonlyArray<string>;
|
|
605
|
+
affinity: SearchAffinity;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/** Query-independent sort keys of one candidate, computed once per ranker. */
|
|
609
|
+
interface ModelRankKeys {
|
|
610
|
+
compactId: string;
|
|
611
|
+
compactSelector: string;
|
|
612
|
+
modelAffinity: number;
|
|
613
|
+
providerAffinity: number;
|
|
614
|
+
/** Position class in the MRU/version order; comparator-equal items share it. */
|
|
615
|
+
orderGroup: number;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* {@link rankModelItems} prepared for one candidate list: the fuzzy index, the
|
|
620
|
+
* MRU/version order, and the affinity ranks depend only on the items and options,
|
|
621
|
+
* so per-keystroke callers (composer mentions, `/switch` arguments, the picker)
|
|
622
|
+
* hold one ranker and pay only the per-query fuzzy scan.
|
|
623
|
+
*/
|
|
624
|
+
export class ModelItemRanker {
|
|
625
|
+
readonly items: ReadonlyArray<ModelBrowserItem>;
|
|
626
|
+
readonly options: ModelRankOptions;
|
|
627
|
+
readonly #corpus: FuzzyCorpus<ModelBrowserItem>;
|
|
628
|
+
readonly #keys = new Map<ModelBrowserItem, ModelRankKeys>();
|
|
629
|
+
|
|
630
|
+
constructor(items: ReadonlyArray<ModelBrowserItem>, options: ModelRankOptions) {
|
|
631
|
+
this.items = items;
|
|
632
|
+
this.options = options;
|
|
633
|
+
this.#corpus = new FuzzyCorpus(items, modelSearchText);
|
|
634
|
+
const compare = modelItemComparator({ roles: options.roles, mruOrder: options.mruOrder, skipRoleRank: true });
|
|
635
|
+
const ordered = [...items].sort(compare);
|
|
636
|
+
let group = 0;
|
|
637
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
638
|
+
const item = ordered[i]!;
|
|
639
|
+
if (i > 0 && compare(ordered[i - 1]!, item) !== 0) group++;
|
|
640
|
+
this.#keys.set(item, {
|
|
641
|
+
compactId: compactModelSearchText(item.id),
|
|
642
|
+
compactSelector: compactModelSearchText(item.selector),
|
|
643
|
+
modelAffinity: options.affinity.models.get(item.selector.toLowerCase()) ?? Number.MAX_SAFE_INTEGER,
|
|
644
|
+
providerAffinity: options.affinity.providers.get(item.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER,
|
|
645
|
+
orderGroup: group,
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
/** Rank by text relevance, user affinity, and MRU/version order. */
|
|
651
|
+
rank(query: string): ModelBrowserItem[] {
|
|
652
|
+
if (!query.trim()) return [...this.items];
|
|
653
|
+
const queryKey = compactModelSearchText(query);
|
|
654
|
+
// Exact and contiguous matches stay ahead of fuzzy-only candidates; affinity
|
|
655
|
+
// breaks ties before fuzzy quality and the normal MRU/version ordering, and
|
|
656
|
+
// fuzzy order settles items that ordering cannot tell apart.
|
|
657
|
+
const rows = this.#corpus.rank(query).map((result, fuzzyIndex) => {
|
|
658
|
+
const keys = this.#keys.get(result.item)!;
|
|
659
|
+
return {
|
|
660
|
+
item: result.item,
|
|
661
|
+
keys,
|
|
662
|
+
tier: modelSearchTier(queryKey, keys.compactId, keys.compactSelector),
|
|
663
|
+
bucket: Math.round(result.score / 10),
|
|
664
|
+
fuzzyIndex,
|
|
665
|
+
};
|
|
666
|
+
});
|
|
667
|
+
rows.sort(
|
|
668
|
+
(a, b) =>
|
|
669
|
+
a.tier - b.tier ||
|
|
670
|
+
a.keys.modelAffinity - b.keys.modelAffinity ||
|
|
671
|
+
a.keys.providerAffinity - b.keys.providerAffinity ||
|
|
672
|
+
a.bucket - b.bucket ||
|
|
673
|
+
a.keys.orderGroup - b.keys.orderGroup ||
|
|
674
|
+
a.fuzzyIndex - b.fuzzyIndex,
|
|
675
|
+
);
|
|
676
|
+
return rows.map(row => row.item);
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
546
680
|
/** Rank picker and mention candidates by text relevance, user affinity, and MRU/version order. */
|
|
547
681
|
export function rankModelItems(
|
|
548
682
|
query: string,
|
|
549
683
|
items: ReadonlyArray<ModelBrowserItem>,
|
|
550
|
-
options:
|
|
684
|
+
options: ModelRankOptions,
|
|
551
685
|
): ModelBrowserItem[] {
|
|
552
|
-
|
|
553
|
-
const ranked = fuzzyRank(items, query, modelSearchText);
|
|
554
|
-
const matches = ranked.map(result => result.item);
|
|
555
|
-
// Exact and contiguous matches stay ahead of fuzzy-only candidates; affinity
|
|
556
|
-
// breaks ties before fuzzy quality and the normal MRU/version ordering.
|
|
557
|
-
sortModelItems(matches, { roles: options.roles, mruOrder: options.mruOrder, skipRoleRank: true });
|
|
558
|
-
const fallbackRanks = new Map(matches.map((item, index) => [item, index]));
|
|
559
|
-
const queryKey = compactModelSearchText(query);
|
|
560
|
-
const searchRanks = new Map<ModelBrowserItem, { tier: number; bucket: number }>();
|
|
561
|
-
for (const result of ranked) {
|
|
562
|
-
searchRanks.set(result.item, {
|
|
563
|
-
tier: modelSearchTier(queryKey, result.item),
|
|
564
|
-
bucket: Math.round(result.score / 10),
|
|
565
|
-
});
|
|
566
|
-
}
|
|
567
|
-
matches.sort((a, b) => {
|
|
568
|
-
const aSearch = searchRanks.get(a);
|
|
569
|
-
const bSearch = searchRanks.get(b);
|
|
570
|
-
const tierCmp = (aSearch?.tier ?? Number.MAX_SAFE_INTEGER) - (bSearch?.tier ?? Number.MAX_SAFE_INTEGER);
|
|
571
|
-
if (tierCmp !== 0) return tierCmp;
|
|
572
|
-
|
|
573
|
-
const modelCmp =
|
|
574
|
-
(options.affinity.models.get(a.selector.toLowerCase()) ?? Number.MAX_SAFE_INTEGER) -
|
|
575
|
-
(options.affinity.models.get(b.selector.toLowerCase()) ?? Number.MAX_SAFE_INTEGER);
|
|
576
|
-
if (modelCmp !== 0) return modelCmp;
|
|
577
|
-
|
|
578
|
-
const providerCmp =
|
|
579
|
-
(options.affinity.providers.get(a.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER) -
|
|
580
|
-
(options.affinity.providers.get(b.provider.toLowerCase()) ?? Number.MAX_SAFE_INTEGER);
|
|
581
|
-
if (providerCmp !== 0) return providerCmp;
|
|
582
|
-
|
|
583
|
-
const bucketCmp = (aSearch?.bucket ?? Number.MAX_SAFE_INTEGER) - (bSearch?.bucket ?? Number.MAX_SAFE_INTEGER);
|
|
584
|
-
if (bucketCmp !== 0) return bucketCmp;
|
|
585
|
-
return (fallbackRanks.get(a) ?? Number.MAX_SAFE_INTEGER) - (fallbackRanks.get(b) ?? Number.MAX_SAFE_INTEGER);
|
|
586
|
-
});
|
|
587
|
-
return matches;
|
|
686
|
+
return new ModelItemRanker(items, options).rank(query);
|
|
588
687
|
}
|
|
589
688
|
|
|
590
689
|
/**
|
|
@@ -710,7 +809,7 @@ function previewPrice(model: Model): string {
|
|
|
710
809
|
return withCreditBadge(model, parts.join(" · "));
|
|
711
810
|
}
|
|
712
811
|
|
|
713
|
-
/** The
|
|
812
|
+
/** The omp theme token of a thinking level's dot (`thinkingHigh`); none for inherit and auto. */
|
|
714
813
|
export function thinkingDotToken(level: ConfiguredThinkingLevel): string | undefined {
|
|
715
814
|
if (level === ThinkingLevel.Inherit || level === AUTO_THINKING) return undefined;
|
|
716
815
|
return `thinking${level.charAt(0).toUpperCase()}${level.slice(1)}`;
|
|
@@ -820,9 +919,42 @@ const DETAIL_ROWS = 3;
|
|
|
820
919
|
const PERF_TPS_MIN_WIDTH = 76;
|
|
821
920
|
/** Row width from which the perf column also includes TTFT. */
|
|
822
921
|
const PERF_FULL_MIN_WIDTH = 96;
|
|
922
|
+
/** Narrowest model-name cell retained before cost and context are dropped. */
|
|
923
|
+
const MIN_NAME_WIDTH = 16;
|
|
924
|
+
|
|
925
|
+
/** Total width of present metadata columns, joined by two-space gaps. */
|
|
926
|
+
function metaColumnsWidth(widths: readonly number[]): number {
|
|
927
|
+
let total = 0;
|
|
928
|
+
let count = 0;
|
|
929
|
+
for (const width of widths) {
|
|
930
|
+
if (width <= 0) continue;
|
|
931
|
+
total += width;
|
|
932
|
+
count++;
|
|
933
|
+
}
|
|
934
|
+
return count > 0 ? total + 2 * (count - 1) : 0;
|
|
935
|
+
}
|
|
936
|
+
|
|
823
937
|
/** What the per-row perf column shows at the current width. */
|
|
824
938
|
type PerfMode = "off" | "tps" | "full";
|
|
825
939
|
|
|
940
|
+
/** A measured perf aggregate and, when it came from one, its service tier. */
|
|
941
|
+
interface MeasuredPerf {
|
|
942
|
+
perf: ModelBrowserPerf;
|
|
943
|
+
tier?: string;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/** Plain catalog metric cells of one row and their terminal widths, valid for one model and theme. */
|
|
947
|
+
interface MetricCells {
|
|
948
|
+
model: Model;
|
|
949
|
+
theme: Theme;
|
|
950
|
+
intelligence: string;
|
|
951
|
+
intelligenceWidth: number;
|
|
952
|
+
ctx: string;
|
|
953
|
+
ctxWidth: number;
|
|
954
|
+
cost: string;
|
|
955
|
+
costWidth: number;
|
|
956
|
+
}
|
|
957
|
+
|
|
826
958
|
/**
|
|
827
959
|
* The reusable browser component. Renders a fixed-height block
|
|
828
960
|
* (`maxVisible + LIST_ROW_START + DETAIL_ROWS` rows) so host mouse geometry
|
|
@@ -840,7 +972,17 @@ export class ModelBrowser implements Component {
|
|
|
840
972
|
#roles: RoleAssignments = {};
|
|
841
973
|
#mruOrder: ReadonlyArray<string> = [];
|
|
842
974
|
#affinity: SearchAffinity = { models: new Map(), providers: new Map() };
|
|
975
|
+
/** Ranker for the menu's current item array; rebuilt when items or affinity change. */
|
|
976
|
+
#ranker?: { source: readonly ModelBrowserItem[]; ranker: ModelItemRanker };
|
|
843
977
|
#perf: ReadonlyMap<string, ModelBrowserPerf> = new Map();
|
|
978
|
+
/** Fastest tier aggregate per selector in {@link #perf}, built on the first lookup miss. */
|
|
979
|
+
#perfTiers:
|
|
980
|
+
| { perf: ReadonlyMap<string, ModelBrowserPerf>; size: number; best: Map<string, MeasuredPerf> }
|
|
981
|
+
| undefined;
|
|
982
|
+
/** Theme-formatted catalog metric cells per item; rebuilt when the item's model or the theme changes. */
|
|
983
|
+
#metricCells = new WeakMap<ModelBrowserItem, MetricCells>();
|
|
984
|
+
/** Non-separator items of the last ranked visible list, for {@link queryMatches}. */
|
|
985
|
+
#queryMatches: { visible: readonly ModelBrowserItem[]; matches: readonly ModelBrowserItem[] } | undefined;
|
|
844
986
|
#hoveredIndex: number | null = null;
|
|
845
987
|
#maxVisible = 10;
|
|
846
988
|
#showProvider: boolean;
|
|
@@ -1009,6 +1151,22 @@ export class ModelBrowser implements Component {
|
|
|
1009
1151
|
return this.#menu.visibleItems.length;
|
|
1010
1152
|
}
|
|
1011
1153
|
|
|
1154
|
+
/**
|
|
1155
|
+
* Base items matching the live query, best first, without the separator
|
|
1156
|
+
* row; undefined while the query is blank. Reuses the ranking the list
|
|
1157
|
+
* already ran, so hosts can derive match counts without ranking again.
|
|
1158
|
+
*/
|
|
1159
|
+
get queryMatches(): readonly ModelBrowserItem[] | undefined {
|
|
1160
|
+
if (!this.query.trim()) return undefined;
|
|
1161
|
+
const visible = this.#menu.visibleItems;
|
|
1162
|
+
let cached = this.#queryMatches;
|
|
1163
|
+
if (cached?.visible !== visible) {
|
|
1164
|
+
cached = { visible, matches: visible.filter(item => !this.#isDisabled(item)) };
|
|
1165
|
+
this.#queryMatches = cached;
|
|
1166
|
+
}
|
|
1167
|
+
return cached.matches;
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1012
1170
|
/** Move selection to `selector`; false when it is not in the current view. */
|
|
1013
1171
|
selectSelector(selector: string): boolean {
|
|
1014
1172
|
if (!this.#menu.setSelectedKey(selector)) return false;
|
|
@@ -1027,17 +1185,26 @@ export class ModelBrowser implements Component {
|
|
|
1027
1185
|
* unfiltered when the query is blank.
|
|
1028
1186
|
*/
|
|
1029
1187
|
#filterItems(items: readonly ModelBrowserItem[], query: string): readonly ModelBrowserItem[] {
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
? fuzzyRank(base, query, modelSearchText).map(result => result.item)
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1188
|
+
if (this.#preserveQueryOrder) {
|
|
1189
|
+
const base = items.filter(item => !this.#isDisabled(item));
|
|
1190
|
+
return this.#insertSeparator(
|
|
1191
|
+
query.trim() ? fuzzyRank(base, query, modelSearchText).map(result => result.item) : base,
|
|
1192
|
+
);
|
|
1193
|
+
}
|
|
1194
|
+
// The menu hands back the same array until `setItems`, and roles/MRU replace the
|
|
1195
|
+
// affinity object, so one ranker serves every keystroke of a search.
|
|
1196
|
+
let cached = this.#ranker;
|
|
1197
|
+
if (cached?.source !== items || cached.ranker.options.affinity !== this.#affinity) {
|
|
1198
|
+
cached = {
|
|
1199
|
+
source: items,
|
|
1200
|
+
ranker: new ModelItemRanker(
|
|
1201
|
+
items.filter(item => !this.#isDisabled(item)),
|
|
1202
|
+
{ roles: this.#roles, mruOrder: this.#mruOrder, affinity: this.#affinity },
|
|
1203
|
+
),
|
|
1204
|
+
};
|
|
1205
|
+
this.#ranker = cached;
|
|
1206
|
+
}
|
|
1207
|
+
return this.#insertSeparator(cached.ranker.rank(query));
|
|
1041
1208
|
}
|
|
1042
1209
|
|
|
1043
1210
|
/** True when `item`'s context window is smaller than the live session token count (grayed row; hosts compact before switching). */
|
|
@@ -1258,14 +1425,76 @@ export class ModelBrowser implements Component {
|
|
|
1258
1425
|
return index;
|
|
1259
1426
|
}
|
|
1260
1427
|
|
|
1428
|
+
/**
|
|
1429
|
+
* Measured perf for a row: the tier the host would send, else the standard
|
|
1430
|
+
* aggregate, else any measured tier (fastest first) so a model only ever run
|
|
1431
|
+
* on a non-default tier still shows its real speed. `tier` is set whenever
|
|
1432
|
+
* the numbers come from a tier aggregate, for the caller to label.
|
|
1433
|
+
*/
|
|
1434
|
+
#perfFor(item: ModelBrowserItem): MeasuredPerf | undefined {
|
|
1435
|
+
if (this.#perf.size === 0) return undefined;
|
|
1436
|
+
const tier = this.#settings.serviceTierFor?.(item.model);
|
|
1437
|
+
if (tier) {
|
|
1438
|
+
const tiered = this.#perf.get(`${item.selector}@${tier}`);
|
|
1439
|
+
if (tiered) return { perf: tiered, tier };
|
|
1440
|
+
}
|
|
1441
|
+
const standard = this.#perf.get(item.selector);
|
|
1442
|
+
if (standard) return { perf: standard };
|
|
1443
|
+
return this.#fastestTierPerf(item.selector);
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
/** Fastest `selector@tier` aggregate (first wins ties), from an index built once per perf map. */
|
|
1447
|
+
#fastestTierPerf(selector: string): MeasuredPerf | undefined {
|
|
1448
|
+
const perfMap = this.#perf;
|
|
1449
|
+
let index = this.#perfTiers;
|
|
1450
|
+
if (index?.perf !== perfMap || index.size !== perfMap.size) {
|
|
1451
|
+
const best = new Map<string, MeasuredPerf>();
|
|
1452
|
+
for (const [key, perf] of perfMap) {
|
|
1453
|
+
// Every `@` may end the selector: `a@b@t` is tier `b@t` of `a` and tier `t` of `a@b`.
|
|
1454
|
+
for (let at = key.indexOf("@"); at >= 0; at = key.indexOf("@", at + 1)) {
|
|
1455
|
+
const owner = key.slice(0, at);
|
|
1456
|
+
const current = best.get(owner);
|
|
1457
|
+
if (!current || perf.tps > current.perf.tps) best.set(owner, { perf, tier: key.slice(at + 1) });
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
index = { perf: perfMap, size: perfMap.size, best };
|
|
1461
|
+
this.#perfTiers = index;
|
|
1462
|
+
}
|
|
1463
|
+
return index.best.get(selector);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
/** Catalog metric cells of a row, formatted once per model and theme. */
|
|
1467
|
+
#metricCellsFor(item: ModelBrowserItem): MetricCells {
|
|
1468
|
+
const cached = this.#metricCells.get(item);
|
|
1469
|
+
if (cached?.model === item.model && cached.theme === theme) return cached;
|
|
1470
|
+
const intelligence = formatIntelligence(item.model);
|
|
1471
|
+
const ctx = formatContext(item.model);
|
|
1472
|
+
const cost = formatCostPair(item.model);
|
|
1473
|
+
const cells: MetricCells = {
|
|
1474
|
+
model: item.model,
|
|
1475
|
+
theme,
|
|
1476
|
+
intelligence,
|
|
1477
|
+
intelligenceWidth: visibleWidth(intelligence),
|
|
1478
|
+
ctx,
|
|
1479
|
+
ctxWidth: visibleWidth(ctx),
|
|
1480
|
+
cost,
|
|
1481
|
+
costWidth: visibleWidth(cost),
|
|
1482
|
+
};
|
|
1483
|
+
this.#metricCells.set(item, cells);
|
|
1484
|
+
return cells;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1261
1487
|
/** Measured TPS/TTFT, falling back to the catalog TPS as an estimated `~118t/s`. */
|
|
1262
1488
|
#perfCell(item: ModelBrowserItem, mode: PerfMode): string {
|
|
1263
1489
|
if (mode === "off") return "";
|
|
1264
|
-
const
|
|
1265
|
-
if (
|
|
1266
|
-
const tps = formatTps(perf.tps);
|
|
1267
|
-
|
|
1268
|
-
|
|
1490
|
+
const measured = this.#perfFor(item);
|
|
1491
|
+
if (measured) {
|
|
1492
|
+
const tps = formatTps(measured.perf.tps);
|
|
1493
|
+
const tier = measured.tier ? ` ${measured.tier}` : "";
|
|
1494
|
+
if (mode === "full" && measured.perf.ttftMs !== null) {
|
|
1495
|
+
return `${formatTtft(measured.perf.ttftMs)} ${tps}${tier}`;
|
|
1496
|
+
}
|
|
1497
|
+
return `${tps}${tier}`;
|
|
1269
1498
|
}
|
|
1270
1499
|
const tps = item.model.tps;
|
|
1271
1500
|
return tps != null && Number.isFinite(tps) && tps > 0 ? `~${formatTps(tps)}` : "";
|
|
@@ -1280,7 +1509,8 @@ export class ModelBrowser implements Component {
|
|
|
1280
1509
|
costWidth: number,
|
|
1281
1510
|
intelligenceWidth: number,
|
|
1282
1511
|
perfWidth: number,
|
|
1283
|
-
|
|
1512
|
+
metrics: MetricCells,
|
|
1513
|
+
perfCell: string,
|
|
1284
1514
|
): string {
|
|
1285
1515
|
if (item.id === "separator") {
|
|
1286
1516
|
const dashCount = Math.max(0, width - 4);
|
|
@@ -1297,33 +1527,23 @@ export class ModelBrowser implements Component {
|
|
|
1297
1527
|
: item.id;
|
|
1298
1528
|
const currentMark =
|
|
1299
1529
|
item.selector === this.#currentSelector ? ` ${theme.fg("success", theme.status.enabled)}` : "";
|
|
1300
|
-
const ompBadge = this.#settings.isOmpOriginProvider?.(item.provider)
|
|
1301
|
-
? ` ${theme.fg("dim", tuiText("models.originOmp", "from OMP"))}`
|
|
1302
|
-
: "";
|
|
1303
1530
|
const overLimit = overContext
|
|
1304
1531
|
? ` ${theme.status.disabled} context>${formatNumber(item.model.contextWindow ?? 0).toLowerCase()}`
|
|
1305
1532
|
: "";
|
|
1306
|
-
let left = `${prefix}${providerPrefix}${name}${
|
|
1307
|
-
|
|
1308
|
-
// Metric columns collapse
|
|
1309
|
-
const
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
const
|
|
1316
|
-
const metaWidth =
|
|
1317
|
-
ctxWidth +
|
|
1318
|
-
costWidth +
|
|
1319
|
-
2 +
|
|
1320
|
-
(intelligenceWidth > 0 ? intelligenceWidth + 2 : 0) +
|
|
1321
|
-
(perfWidth > 0 ? perfWidth + 2 : 0);
|
|
1322
|
-
const available = Math.max(1, width - metaWidth - 1);
|
|
1533
|
+
let left = `${prefix}${providerPrefix}${name}${currentMark}${overLimit}`;
|
|
1534
|
+
|
|
1535
|
+
// Metric columns collapse when empty or when the row needs room for its name.
|
|
1536
|
+
const cols: string[] = [];
|
|
1537
|
+
if (intelligenceWidth > 0) cols.push(theme.fg("dim", padLeftVisible(metrics.intelligence, intelligenceWidth)));
|
|
1538
|
+
if (perfWidth > 0) cols.push(theme.fg("dim", padLeftVisible(perfCell, perfWidth)));
|
|
1539
|
+
if (ctxWidth > 0) cols.push(theme.fg("dim", padLeftVisible(metrics.ctx, ctxWidth)));
|
|
1540
|
+
if (costWidth > 0) cols.push(theme.fg("dim", padLeftVisible(metrics.cost, costWidth)));
|
|
1541
|
+
const metaWidth = metaColumnsWidth([intelligenceWidth, perfWidth, ctxWidth, costWidth]);
|
|
1542
|
+
const available = Math.max(1, width - metaWidth - (cols.length > 0 ? 1 : 0));
|
|
1323
1543
|
left = truncateToWidth(left, available);
|
|
1324
1544
|
const gap = Math.max(0, available - visibleWidth(left));
|
|
1325
1545
|
|
|
1326
|
-
let line = `${left}${" ".repeat(gap)} ${
|
|
1546
|
+
let line = cols.length > 0 ? `${left}${" ".repeat(gap)} ${cols.join(" ")}` : `${left}${" ".repeat(gap)}`;
|
|
1327
1547
|
if (overContext) {
|
|
1328
1548
|
// Gray the whole row but keep the selection cursor visible: over-context
|
|
1329
1549
|
// models stay selectable (the host compacts before switching).
|
|
@@ -1356,10 +1576,10 @@ export class ModelBrowser implements Component {
|
|
|
1356
1576
|
if (model.input.includes("image")) facts.push("vision");
|
|
1357
1577
|
const intelligence = formatIntelligence(model);
|
|
1358
1578
|
if (intelligence) facts.push(intelligence);
|
|
1359
|
-
const
|
|
1360
|
-
if (
|
|
1361
|
-
facts.push(`~${formatTps(perf.tps)}`);
|
|
1362
|
-
if (perf.ttftMs !== null) facts.push(`${formatTtft(perf.ttftMs)} ttft`);
|
|
1579
|
+
const measured = this.#perfFor(selected);
|
|
1580
|
+
if (measured) {
|
|
1581
|
+
facts.push(`~${formatTps(measured.perf.tps)}${measured.tier ? ` ${measured.tier}` : ""}`);
|
|
1582
|
+
if (measured.perf.ttftMs !== null) facts.push(`${formatTtft(measured.perf.ttftMs)} ttft`);
|
|
1363
1583
|
} else if (model.tps != null && Number.isFinite(model.tps) && model.tps > 0) {
|
|
1364
1584
|
facts.push(`~${formatTps(model.tps)}`);
|
|
1365
1585
|
}
|
|
@@ -1412,10 +1632,7 @@ export class ModelBrowser implements Component {
|
|
|
1412
1632
|
|
|
1413
1633
|
if (total === 0) {
|
|
1414
1634
|
const message =
|
|
1415
|
-
this.#emptyText?.() ??
|
|
1416
|
-
(this.query.trim()
|
|
1417
|
-
? tuiText("mbNoMatchingModels", " No matching models")
|
|
1418
|
-
: tuiText("mbNoModelsInScope", " No models available in this scope"));
|
|
1635
|
+
this.#emptyText?.() ?? (this.query.trim() ? " No matching models" : " No models available in this scope");
|
|
1419
1636
|
lines.push(truncateToWidth(theme.fg("muted", message), width));
|
|
1420
1637
|
for (let i = 1; i < this.#maxVisible; i++) lines.push("");
|
|
1421
1638
|
} else {
|
|
@@ -1426,20 +1643,32 @@ export class ModelBrowser implements Component {
|
|
|
1426
1643
|
const perfMode: PerfMode = width >= PERF_FULL_MIN_WIDTH ? "full" : width >= PERF_TPS_MIN_WIDTH ? "tps" : "off";
|
|
1427
1644
|
let intelligenceWidth = 0;
|
|
1428
1645
|
let perfWidth = 0;
|
|
1646
|
+
const visible = this.#menu.visibleItems;
|
|
1647
|
+
const windowMetrics: MetricCells[] = [];
|
|
1648
|
+
const windowPerf: string[] = [];
|
|
1429
1649
|
for (let i = startIndex; i < endIndex; i++) {
|
|
1430
|
-
const item =
|
|
1650
|
+
const item = visible[i];
|
|
1431
1651
|
if (!item) continue;
|
|
1432
|
-
|
|
1433
|
-
|
|
1652
|
+
const metrics = this.#metricCellsFor(item);
|
|
1653
|
+
const perfCell = this.#perfCell(item, perfMode);
|
|
1654
|
+
windowMetrics[i - startIndex] = metrics;
|
|
1655
|
+
windowPerf[i - startIndex] = perfCell;
|
|
1656
|
+
ctxWidth = Math.max(ctxWidth, metrics.ctxWidth);
|
|
1657
|
+
costWidth = Math.max(costWidth, metrics.costWidth);
|
|
1434
1658
|
if (perfMode !== "off") {
|
|
1435
|
-
intelligenceWidth = Math.max(intelligenceWidth,
|
|
1659
|
+
intelligenceWidth = Math.max(intelligenceWidth, metrics.intelligenceWidth);
|
|
1436
1660
|
}
|
|
1437
|
-
perfWidth = Math.max(perfWidth, visibleWidth(
|
|
1661
|
+
perfWidth = Math.max(perfWidth, visibleWidth(perfCell));
|
|
1438
1662
|
}
|
|
1663
|
+
// Preserve at least a readable name by dropping cost, then context.
|
|
1664
|
+
let nameRoom = width - 2 - metaColumnsWidth([intelligenceWidth, perfWidth, ctxWidth, costWidth]);
|
|
1665
|
+
if (nameRoom < MIN_NAME_WIDTH) costWidth = 0;
|
|
1666
|
+
nameRoom = width - 2 - metaColumnsWidth([intelligenceWidth, perfWidth, ctxWidth, costWidth]);
|
|
1667
|
+
if (nameRoom < MIN_NAME_WIDTH) ctxWidth = 0;
|
|
1439
1668
|
|
|
1440
1669
|
const rows: string[] = [];
|
|
1441
1670
|
for (let i = startIndex; i < endIndex; i++) {
|
|
1442
|
-
const item =
|
|
1671
|
+
const item = visible[i];
|
|
1443
1672
|
if (!item) continue;
|
|
1444
1673
|
rows.push(
|
|
1445
1674
|
this.#renderRow(
|
|
@@ -1451,7 +1680,8 @@ export class ModelBrowser implements Component {
|
|
|
1451
1680
|
costWidth,
|
|
1452
1681
|
intelligenceWidth,
|
|
1453
1682
|
perfWidth,
|
|
1454
|
-
|
|
1683
|
+
windowMetrics[i - startIndex]!,
|
|
1684
|
+
windowPerf[i - startIndex]!,
|
|
1455
1685
|
),
|
|
1456
1686
|
);
|
|
1457
1687
|
}
|
|
@@ -1622,10 +1852,10 @@ export class ModelBrowser implements Component {
|
|
|
1622
1852
|
if (model.input.includes("image")) facts.push("vision");
|
|
1623
1853
|
const intelligence = formatIntelligence(model);
|
|
1624
1854
|
if (intelligence) facts.push(intelligence);
|
|
1625
|
-
const
|
|
1626
|
-
if (
|
|
1627
|
-
facts.push(`~${formatTps(perf.tps)}`);
|
|
1628
|
-
if (perf.ttftMs !== null) facts.push(`${formatTtft(perf.ttftMs)} ttft`);
|
|
1855
|
+
const measured = this.#perfFor(selected);
|
|
1856
|
+
if (measured) {
|
|
1857
|
+
facts.push(`~${formatTps(measured.perf.tps)}${measured.tier ? ` ${measured.tier}` : ""}`);
|
|
1858
|
+
if (measured.perf.ttftMs !== null) facts.push(`${formatTtft(measured.perf.ttftMs)} ttft`);
|
|
1629
1859
|
} else if (model.tps != null && Number.isFinite(model.tps) && model.tps > 0) {
|
|
1630
1860
|
facts.push(`~${formatTps(model.tps)}`);
|
|
1631
1861
|
}
|
|
@@ -1737,7 +1967,9 @@ export class ModelBrowser implements Component {
|
|
|
1737
1967
|
const facts: Record<string, TspText | number> = {};
|
|
1738
1968
|
const int = model.int != null && Number.isFinite(model.int) ? model.int : undefined;
|
|
1739
1969
|
if (int !== undefined) facts.int = String(Math.round(int));
|
|
1740
|
-
const speed = this.#perfCell(item, "tps")
|
|
1970
|
+
const speed = this.#perfCell(item, "tps")
|
|
1971
|
+
.replace(/\s*t\/s\s*/, " ")
|
|
1972
|
+
.trim();
|
|
1741
1973
|
if (speed) facts.speed = speed;
|
|
1742
1974
|
if (model.contextWindow) facts.ctx = model.contextWindow;
|
|
1743
1975
|
facts.price = pickerPrice(model);
|
|
@@ -1879,7 +2111,7 @@ export class ModelBrowser implements Component {
|
|
|
1879
2111
|
pickerPreview(mode: "full" | "compact", current?: string): readonly NativeChild[] {
|
|
1880
2112
|
const selected = this.getSelected();
|
|
1881
2113
|
const item = selected && !this.#isDisabled(selected) ? selected : undefined;
|
|
1882
|
-
const key = `${mode}\0${current ?? ""}`;
|
|
2114
|
+
const key = `${mode}\0${current ?? ""}\0${this.#settings.revision}`;
|
|
1883
2115
|
const memo = this.#pickerPreview;
|
|
1884
2116
|
if (
|
|
1885
2117
|
memo !== undefined &&
|
|
@@ -1899,7 +2131,7 @@ export class ModelBrowser implements Component {
|
|
|
1899
2131
|
modelPreview(item: ModelBrowserItem, mode: "full" | "compact", current: string | undefined): NativeChild[] {
|
|
1900
2132
|
const model = item.model;
|
|
1901
2133
|
const selector = `${model.provider}/${model.id}`;
|
|
1902
|
-
const
|
|
2134
|
+
const measured = this.#perfFor(item);
|
|
1903
2135
|
const ctx = model.contextWindow ?? 0;
|
|
1904
2136
|
const out = model.maxTokens ?? 0;
|
|
1905
2137
|
const overContext = this.isOverContext(item);
|
|
@@ -1959,10 +2191,12 @@ export class ModelBrowser implements Component {
|
|
|
1959
2191
|
if (badges.length > 0) children.push(row(badges, { gap: "xs", wrap: true }));
|
|
1960
2192
|
|
|
1961
2193
|
const speed: string[] = [];
|
|
1962
|
-
if (
|
|
1963
|
-
speed.push(
|
|
1964
|
-
|
|
1965
|
-
|
|
2194
|
+
if (measured) {
|
|
2195
|
+
speed.push(
|
|
2196
|
+
`${formatTps(measured.perf.tps).replace("t/s", " t/s")}${measured.tier ? ` ${measured.tier}` : ""}`,
|
|
2197
|
+
);
|
|
2198
|
+
if (measured.perf.ttftMs !== null) speed.push(`${formatTtft(measured.perf.ttftMs).replace("s", " s")} TTFT`);
|
|
2199
|
+
speed.push(`${measured.perf.samples} ${measured.perf.samples === 1 ? "sample" : "samples"}`);
|
|
1966
2200
|
} else if (model.tps != null && Number.isFinite(model.tps) && model.tps > 0) {
|
|
1967
2201
|
speed.push(`~${formatTps(model.tps).replace("t/s", " t/s")} (catalog)`);
|
|
1968
2202
|
}
|
|
@@ -1971,6 +2205,17 @@ export class ModelBrowser implements Component {
|
|
|
1971
2205
|
if (v) facts.push({ k: [span(k, "muted")], v: [span(v, "mono")] });
|
|
1972
2206
|
};
|
|
1973
2207
|
fact("Context", ctx > 0 ? ctx.toLocaleString("en-US") : undefined);
|
|
2208
|
+
const compaction = this.#settings.compactionPointFor?.(model);
|
|
2209
|
+
if (compaction) {
|
|
2210
|
+
const value =
|
|
2211
|
+
compaction.tokens === undefined
|
|
2212
|
+
? "off"
|
|
2213
|
+
: `${compaction.tokens.toLocaleString("en-US")}${compaction.basis !== undefined ? ` · ${compaction.basis}` : ""}`;
|
|
2214
|
+
facts.push({
|
|
2215
|
+
k: [span("Compacts at", "muted")],
|
|
2216
|
+
v: [span(value, "mono"), span(` · ${compaction.source}`, "dim")],
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
1974
2219
|
fact("Max output", out > 0 ? out.toLocaleString("en-US") : undefined);
|
|
1975
2220
|
fact("Price", isFreeModel(model) ? "free" : `${previewPrice(model)} per M`);
|
|
1976
2221
|
fact("Speed", speed.length > 0 ? speed.join(" · ") : undefined);
|