@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
|
@@ -23,12 +23,11 @@ import type {
|
|
|
23
23
|
import type { KeysApi, Model } from "@linxiraos/pi-ai";
|
|
24
24
|
import { getOAuthProviders } from "@linxiraos/pi-ai/oauth";
|
|
25
25
|
import { getSupportedEfforts } from "@linxiraos/pi-catalog/model-thinking";
|
|
26
|
-
import { modelsAreEqual } from "@linxiraos/pi-catalog/models";
|
|
27
26
|
import { providerEntry } from "@linxiraos/pi-catalog/compat/providers";
|
|
28
27
|
import { MODEL_KINDS, modelKind, type ModelKind } from "@linxiraos/pi-catalog/types";
|
|
29
28
|
import type { Component, TUI } from "../tui";
|
|
30
29
|
import { extractPrintableText, matchesKey } from "../keys";
|
|
31
|
-
import {
|
|
30
|
+
import { FuzzyCorpus } from "../fuzzy";
|
|
32
31
|
import { formatKeyHint, formatKeyHints } from "../app-keybindings";
|
|
33
32
|
import { boundKeys, editorKey, editorKeys } from "../chrome/keybinding-hints";
|
|
34
33
|
import type { KeyName } from "../key-hint-format";
|
|
@@ -48,7 +47,7 @@ import type {
|
|
|
48
47
|
ResolvedModelRoleValue,
|
|
49
48
|
} from "./model-browser";
|
|
50
49
|
import { AUTO_THINKING, type ConfiguredThinkingLevel, getConfiguredThinkingLevelMetadata } from "../thinking";
|
|
51
|
-
import { thinkingLevelGlyph } from "../render/render-utils";
|
|
50
|
+
import { sanitizeDisplayWarning, thinkingLevelGlyph } from "../render/render-utils";
|
|
52
51
|
import { theme } from "../theme/theme";
|
|
53
52
|
import { matchesSelectCancel, matchesSelectDown, matchesSelectUp } from "../keybinding-matchers";
|
|
54
53
|
import {
|
|
@@ -60,6 +59,7 @@ import {
|
|
|
60
59
|
thinkingDotToken,
|
|
61
60
|
type RoleAssignments,
|
|
62
61
|
resolveRoleAssignments,
|
|
62
|
+
scopedRolePool,
|
|
63
63
|
sortModelItems,
|
|
64
64
|
} from "./model-browser";
|
|
65
65
|
import {
|
|
@@ -74,6 +74,8 @@ import {
|
|
|
74
74
|
} from "./hub-frame";
|
|
75
75
|
import { renderSegmentTrack } from "../chrome/segment-track";
|
|
76
76
|
|
|
77
|
+
const MODEL_HUB_BODY_MIN_WIDTH = 28;
|
|
78
|
+
|
|
77
79
|
/**
|
|
78
80
|
* A row of the Roles view: a role, a model/wildcard chain-key header, one of a
|
|
79
81
|
* chain's fallback entries, or the trailing "+ New role…". Fallback rows under
|
|
@@ -107,6 +109,11 @@ export interface ModelHubSource extends ModelBrowserSource {
|
|
|
107
109
|
getProjectModelRole(role: string): string | undefined;
|
|
108
110
|
getGlobalModelRole(role: string): string | undefined;
|
|
109
111
|
getModelRoleSource(role: string): "global" | "project" | "default";
|
|
112
|
+
/**
|
|
113
|
+
* Saved model presets (in switch order) and the one the current role setup
|
|
114
|
+
* matches, if any. Absent hosts have no presets to switch between.
|
|
115
|
+
*/
|
|
116
|
+
getModelPresets?(): { names: readonly string[]; active: string | undefined };
|
|
110
117
|
}
|
|
111
118
|
|
|
112
119
|
/** Catalog capabilities required by the model hub. */
|
|
@@ -155,11 +162,39 @@ export interface ModelHubCallbacks {
|
|
|
155
162
|
onLoginRequest?: (providerId: string) => void;
|
|
156
163
|
/** Save the current role assignments and default thinking level as a named model preset. */
|
|
157
164
|
onSavePreset?: (name: string) => void;
|
|
165
|
+
/**
|
|
166
|
+
* Apply a saved model preset (ctrl+←/→ or p/⇧P in the Roles view) and report
|
|
167
|
+
* the outcome itself. A switch that writes nothing (a refused preset) still
|
|
168
|
+
* advances the hub's preset cursor, so the next press moves past it.
|
|
169
|
+
*/
|
|
170
|
+
onSwitchPreset?: (name: string) => void | Promise<void>;
|
|
158
171
|
/** Persist a new quick-switch cycle order (the ctrl+p role cycle). */
|
|
159
172
|
onCycleOrderChange?: (order: string[]) => void;
|
|
173
|
+
/**
|
|
174
|
+
* Persist typed text as `model`'s own compaction limit: a token base the
|
|
175
|
+
* policy scales (`400k`), a fixed trigger (`f400k`), a percentage (`80%`), or
|
|
176
|
+
* empty to reset. Rejected input returns `{ kind: "error" }`; input that
|
|
177
|
+
* needs acknowledgement (one that opens the extended window) returns
|
|
178
|
+
* `{ kind: "confirm" }` until called again with the same text and
|
|
179
|
+
* `confirmed`. Either keeps the field open.
|
|
180
|
+
*/
|
|
181
|
+
onCompactionPointChange?: (
|
|
182
|
+
model: Model,
|
|
183
|
+
input: string,
|
|
184
|
+
confirmed: boolean,
|
|
185
|
+
) => CompactionPointChangeResult | undefined;
|
|
186
|
+
/**
|
|
187
|
+
* One short line on where `model` would compact with the typed limit
|
|
188
|
+
* (`compacts at 340K · 85% of 400K base`), shown beside the field while
|
|
189
|
+
* typing; undefined for input that does not parse or fit.
|
|
190
|
+
*/
|
|
191
|
+
previewCompactionPoint?: (model: Model, input: string) => string | undefined;
|
|
160
192
|
onCancel: () => void;
|
|
161
193
|
}
|
|
162
194
|
|
|
195
|
+
/** Why {@link ModelHubCallbacks.onCompactionPointChange} kept the field open. */
|
|
196
|
+
export type CompactionPointChangeResult = { kind: "error"; message: string } | { kind: "confirm"; message: string };
|
|
197
|
+
|
|
163
198
|
export interface ModelHubOptions {
|
|
164
199
|
/** Preselect this provider's sidebar entry (e.g. when reopening after /login). */
|
|
165
200
|
initialProviderId?: string;
|
|
@@ -211,10 +246,36 @@ type StripState =
|
|
|
211
246
|
kind: "name";
|
|
212
247
|
purpose: "role" | "preset";
|
|
213
248
|
input: Input;
|
|
249
|
+
}
|
|
250
|
+
| {
|
|
251
|
+
/**
|
|
252
|
+
* Footer text input setting `model`'s compaction limit; `error` is the last
|
|
253
|
+
* rejection, `confirm` the warning a second Enter on the same `value`
|
|
254
|
+
* accepts, `preview` the host's live line for the typed `value`.
|
|
255
|
+
*/
|
|
256
|
+
kind: "name";
|
|
257
|
+
purpose: "compaction";
|
|
258
|
+
model: Model;
|
|
259
|
+
input: Input;
|
|
260
|
+
error?: string;
|
|
261
|
+
confirm?: { value: string; message: string };
|
|
262
|
+
preview?: { value: string; text: string | undefined };
|
|
214
263
|
};
|
|
215
264
|
|
|
216
265
|
/** A Roles-view command; keys and the picker's action bar both run {@link ModelHubComponent}'s `#runRolesAction`. */
|
|
217
|
-
type RolesAction =
|
|
266
|
+
type RolesAction =
|
|
267
|
+
| "pick"
|
|
268
|
+
| "clear"
|
|
269
|
+
| "fallback"
|
|
270
|
+
| "cycle"
|
|
271
|
+
| "earlier"
|
|
272
|
+
| "later"
|
|
273
|
+
| "new"
|
|
274
|
+
| "thinking"
|
|
275
|
+
| "compaction"
|
|
276
|
+
| "save"
|
|
277
|
+
| "nextPreset"
|
|
278
|
+
| "prevPreset";
|
|
218
279
|
|
|
219
280
|
/** Printable keys of the Roles view and the command each runs. */
|
|
220
281
|
const ROLES_ACTION_KEYS: Record<string, RolesAction> = {
|
|
@@ -225,7 +286,11 @@ const ROLES_ACTION_KEYS: Record<string, RolesAction> = {
|
|
|
225
286
|
"]": "later",
|
|
226
287
|
n: "new",
|
|
227
288
|
t: "thinking",
|
|
289
|
+
k: "compaction",
|
|
228
290
|
s: "save",
|
|
291
|
+
// Letter twins of ctrl+←/→, which macOS reserves for switching Spaces.
|
|
292
|
+
p: "nextPreset",
|
|
293
|
+
P: "prevPreset",
|
|
229
294
|
};
|
|
230
295
|
|
|
231
296
|
/** Picker fact columns of the Roles view. */
|
|
@@ -259,6 +324,41 @@ function providerInitials(providerId: string): string {
|
|
|
259
324
|
|
|
260
325
|
const PROVIDER_REFRESH_DEBOUNCE_MS = 120;
|
|
261
326
|
const RECENT_LIMIT = 15;
|
|
327
|
+
/** Accepted compaction limit input, shown beside the field: a scaled base, a fixed trigger, or a percentage. */
|
|
328
|
+
const COMPACTION_INPUT_HINT = "400k base · f400k fixed · 80% · empty resets";
|
|
329
|
+
|
|
330
|
+
type CompactionStrip = Extract<StripState, { purpose: "compaction" }>;
|
|
331
|
+
|
|
332
|
+
/** Whether the compaction field still holds the text its pending warning was raised for. */
|
|
333
|
+
function compactionConfirmPending(strip: CompactionStrip): boolean {
|
|
334
|
+
return strip.confirm !== undefined && strip.confirm.value === strip.input.getValue();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* The note beside the compaction field: the last error, a pending warning, the
|
|
339
|
+
* host's live preview of the typed limit, or (empty or unparsed input) the
|
|
340
|
+
* input syntax. Host text embeds model ids and config text, so it is
|
|
341
|
+
* sanitized for every render path (terminal footer, Tern picker and strip).
|
|
342
|
+
*/
|
|
343
|
+
function compactionNotice(
|
|
344
|
+
strip: CompactionStrip,
|
|
345
|
+
preview: ((model: Model, input: string) => string | undefined) | undefined,
|
|
346
|
+
): { text: string; style: "error" | "warning" | "dim" } {
|
|
347
|
+
if (strip.error) return { text: sanitizeDisplayWarning(strip.error), style: "error" };
|
|
348
|
+
// The footer hint and the primary action already say Enter accepts; the
|
|
349
|
+
// notice stays short so it fits beside the field.
|
|
350
|
+
if (strip.confirm && compactionConfirmPending(strip)) {
|
|
351
|
+
return { text: sanitizeDisplayWarning(strip.confirm.message), style: "warning" };
|
|
352
|
+
}
|
|
353
|
+
const value = strip.input.getValue();
|
|
354
|
+
if (preview && value.trim().length > 0) {
|
|
355
|
+
// Renders repeat per frame; ask the host once per typed value.
|
|
356
|
+
if (strip.preview?.value !== value) strip.preview = { value, text: preview(strip.model, value) };
|
|
357
|
+
if (strip.preview.text) return { text: sanitizeDisplayWarning(strip.preview.text), style: "dim" };
|
|
358
|
+
}
|
|
359
|
+
return { text: COMPACTION_INPUT_HINT, style: "dim" };
|
|
360
|
+
}
|
|
361
|
+
|
|
262
362
|
const MODEL_KIND_TABS: ReadonlyArray<"all" | ModelKind> = ["all", ...MODEL_KINDS];
|
|
263
363
|
const ROLE_TABS = ["all", "chat", "kind"] as const;
|
|
264
364
|
type RoleTab = (typeof ROLE_TABS)[number];
|
|
@@ -307,7 +407,15 @@ export class ModelHubComponent implements Component {
|
|
|
307
407
|
#roles: RoleAssignments = {};
|
|
308
408
|
#availableItems: ModelBrowserItem[] = [];
|
|
309
409
|
#recentItems: ModelBrowserItem[] = [];
|
|
410
|
+
/** Selectors of {@link #recentItems}, rebuilt with it, for search hit counts. */
|
|
411
|
+
#recentSelectors: ReadonlySet<string> = new Set();
|
|
310
412
|
#candidateItems: ModelBrowserItem[] = [];
|
|
413
|
+
/** {@link #availableItems} when the candidates are that whole catalog (All scope, no role filter). */
|
|
414
|
+
#candidateCatalog: readonly ModelBrowserItem[] | undefined;
|
|
415
|
+
/** {@link #availableItems} when the browser's base items are that whole catalog; its query ranking then covers every count. */
|
|
416
|
+
#browserCatalog: readonly ModelBrowserItem[] | undefined;
|
|
417
|
+
/** Fuzzy index over {@link #availableItems}, for match counts while the browser holds a narrower scope. */
|
|
418
|
+
#catalogCorpus: { items: readonly ModelBrowserItem[]; corpus: FuzzyCorpus<ModelBrowserItem> } | undefined;
|
|
311
419
|
#modelKindTab: "all" | ModelKind = "all";
|
|
312
420
|
#roleTab: RoleTab = "all";
|
|
313
421
|
#configError: string | undefined;
|
|
@@ -343,6 +451,12 @@ export class ModelHubComponent implements Component {
|
|
|
343
451
|
#assigning: AssignTarget | null = null;
|
|
344
452
|
#strip: StripState | null = null;
|
|
345
453
|
#assignmentPending = false;
|
|
454
|
+
/**
|
|
455
|
+
* Last preset ctrl+←/→ tried in this hub, with the settings revision after the
|
|
456
|
+
* attempt; steps from it while nothing matches the setup or nothing changed since.
|
|
457
|
+
*/
|
|
458
|
+
#presetCursor: { name: string; revision: number } | undefined;
|
|
459
|
+
#presetsMemo: { revision: number; names: readonly string[]; active: string | undefined } | undefined;
|
|
346
460
|
#disposed = false;
|
|
347
461
|
/** Per-provider fuzzy match counts while a query is active; null when not searching. */
|
|
348
462
|
#searchCounts: Map<string, number> | null = null;
|
|
@@ -376,6 +490,7 @@ export class ModelHubComponent implements Component {
|
|
|
376
490
|
{ min: 18, max: 26 },
|
|
377
491
|
(width, rows) => this.#renderSidebar(width, rows),
|
|
378
492
|
this.#renderBodyPane,
|
|
493
|
+
{ bodyMinWidth: MODEL_HUB_BODY_MIN_WIDTH, preserveSidebar: true },
|
|
379
494
|
);
|
|
380
495
|
#lockedLoginLine: number | null = null;
|
|
381
496
|
#rolesRowStart = 1;
|
|
@@ -394,6 +509,7 @@ export class ModelHubComponent implements Component {
|
|
|
394
509
|
row: RolesRow | undefined;
|
|
395
510
|
roles: RoleAssignments;
|
|
396
511
|
rows: readonly RolesRow[];
|
|
512
|
+
revision: number;
|
|
397
513
|
children: readonly NativeChild[];
|
|
398
514
|
}
|
|
399
515
|
| undefined;
|
|
@@ -484,22 +600,6 @@ export class ModelHubComponent implements Component {
|
|
|
484
600
|
return this.#settings.knownRoleIds.filter(role => !this.#settings.getRoleInfo(role).hidden);
|
|
485
601
|
}
|
|
486
602
|
|
|
487
|
-
/**
|
|
488
|
-
* Models a `--models`/`enabledModels` scope exposes. The scope only resolves
|
|
489
|
-
* chat models (it feeds Ctrl+P cycling), so available non-chat runners
|
|
490
|
-
* (judge, search, image, …) join from the registry — runtime role
|
|
491
|
-
* resolution ignores the scope for them as well.
|
|
492
|
-
*/
|
|
493
|
-
#scopedPool(): Model[] {
|
|
494
|
-
const pool = this.#scopedModels.map(scoped => scoped.model);
|
|
495
|
-
for (const model of this.#registry.getAvailable("all")) {
|
|
496
|
-
if (modelKind(model) === "chat") continue;
|
|
497
|
-
if (this.#scopedModels.some(scoped => modelsAreEqual(scoped.model, model))) continue;
|
|
498
|
-
pool.push(model);
|
|
499
|
-
}
|
|
500
|
-
return pool;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
603
|
/** Resolve every known role: configured values first, auto-selection for the rest. */
|
|
504
604
|
#reloadRoles(autoCandidates: ReadonlyArray<Model>): void {
|
|
505
605
|
const allModels = this.#scopedModels.length > 0 ? autoCandidates : this.#registry.getAll("all");
|
|
@@ -518,7 +618,10 @@ export class ModelHubComponent implements Component {
|
|
|
518
618
|
if (this.#scopedModels.length > 0) {
|
|
519
619
|
this.#configError = undefined;
|
|
520
620
|
try {
|
|
521
|
-
allModels =
|
|
621
|
+
allModels = scopedRolePool(
|
|
622
|
+
this.#scopedModels.map(scoped => scoped.model),
|
|
623
|
+
this.#registry,
|
|
624
|
+
);
|
|
522
625
|
} catch (error) {
|
|
523
626
|
this.#configError = error instanceof Error ? error.message : String(error);
|
|
524
627
|
allModels = this.#scopedModels.map(scoped => scoped.model);
|
|
@@ -553,6 +656,7 @@ export class ModelHubComponent implements Component {
|
|
|
553
656
|
if (item) this.#recentItems.push(item);
|
|
554
657
|
if (this.#recentItems.length >= RECENT_LIMIT) break;
|
|
555
658
|
}
|
|
659
|
+
this.#recentSelectors = new Set(this.#recentItems.map(item => item.selector));
|
|
556
660
|
|
|
557
661
|
this.#buildSidebar(allModels, availableModels);
|
|
558
662
|
this.#restoreSidebarAnchor(anchor);
|
|
@@ -775,13 +879,27 @@ export class ModelHubComponent implements Component {
|
|
|
775
879
|
}
|
|
776
880
|
}
|
|
777
881
|
|
|
882
|
+
/**
|
|
883
|
+
* Push the active scope's items into the browser. While assigning a role,
|
|
884
|
+
* the role's `accepts` predicate is re-applied here so a scope hop
|
|
885
|
+
* (provider/all/recent) can never surface a model the role rejects — role
|
|
886
|
+
* resolution and the runtime candidate pool filter the same way, so an
|
|
887
|
+
* unaccepted pick would persist a selector that never resolves.
|
|
888
|
+
*/
|
|
778
889
|
#setCandidateItems(items: ReadonlyArray<ModelBrowserItem>): void {
|
|
779
|
-
|
|
890
|
+
const assigning = this.#assigning;
|
|
891
|
+
const scoped =
|
|
892
|
+
assigning?.kind === "role"
|
|
893
|
+
? items.filter(item => this.#settings.getRoleInfo(assigning.role).accepts(item.model))
|
|
894
|
+
: items;
|
|
895
|
+
this.#candidateItems = [...scoped];
|
|
896
|
+
this.#candidateCatalog = scoped === this.#availableItems ? this.#availableItems : undefined;
|
|
780
897
|
this.#applyModelKind();
|
|
781
898
|
}
|
|
782
899
|
|
|
783
900
|
#applyModelKind(): void {
|
|
784
901
|
const kind = this.#modelKindTab;
|
|
902
|
+
this.#browserCatalog = kind === "all" ? this.#candidateCatalog : undefined;
|
|
785
903
|
this.#browser.setItems(
|
|
786
904
|
kind === "all"
|
|
787
905
|
? [...this.#candidateItems]
|
|
@@ -890,16 +1008,14 @@ export class ModelHubComponent implements Component {
|
|
|
890
1008
|
this.#composeEntries();
|
|
891
1009
|
return;
|
|
892
1010
|
}
|
|
893
|
-
const matches =
|
|
1011
|
+
const matches = this.#catalogMatches(query);
|
|
894
1012
|
const counts = new Map<string, number>();
|
|
1013
|
+
let recentCount = 0;
|
|
895
1014
|
for (const item of matches) {
|
|
896
1015
|
counts.set(item.provider, (counts.get(item.provider) ?? 0) + 1);
|
|
1016
|
+
if (this.#recentSelectors.has(item.selector)) recentCount++;
|
|
897
1017
|
}
|
|
898
|
-
|
|
899
|
-
this.#recentSearchCount = matches.reduce(
|
|
900
|
-
(total, item) => total + (recentSelectors.has(item.selector) ? 1 : 0),
|
|
901
|
-
0,
|
|
902
|
-
);
|
|
1018
|
+
this.#recentSearchCount = recentCount;
|
|
903
1019
|
this.#searchTotal = matches.length;
|
|
904
1020
|
this.#searchCounts = counts;
|
|
905
1021
|
this.#composeEntries();
|
|
@@ -913,6 +1029,24 @@ export class ModelHubComponent implements Component {
|
|
|
913
1029
|
}
|
|
914
1030
|
}
|
|
915
1031
|
|
|
1032
|
+
/**
|
|
1033
|
+
* Catalog items matching a non-blank `query`. When the browser holds the
|
|
1034
|
+
* whole catalog its ranking for this query already ran, so reuse it;
|
|
1035
|
+
* otherwise scan a catalog index kept across keystrokes.
|
|
1036
|
+
*/
|
|
1037
|
+
#catalogMatches(query: string): readonly ModelBrowserItem[] {
|
|
1038
|
+
if (this.#browserCatalog === this.#availableItems && this.#browser.query === query) {
|
|
1039
|
+
const ranked = this.#browser.queryMatches;
|
|
1040
|
+
if (ranked) return ranked;
|
|
1041
|
+
}
|
|
1042
|
+
let cached = this.#catalogCorpus;
|
|
1043
|
+
if (cached?.items !== this.#availableItems) {
|
|
1044
|
+
cached = { items: this.#availableItems, corpus: new FuzzyCorpus(this.#availableItems, modelSearchText) };
|
|
1045
|
+
this.#catalogCorpus = cached;
|
|
1046
|
+
}
|
|
1047
|
+
return cached.corpus.rank(query).map(result => result.item);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
916
1050
|
/**
|
|
917
1051
|
* Entries the scope hop skips: separators always; while searching, also
|
|
918
1052
|
* the Roles view (not a model scope), an empty Recent, locked providers,
|
|
@@ -1103,7 +1237,13 @@ export class ModelHubComponent implements Component {
|
|
|
1103
1237
|
#roleForScope(role: string, scope: ModelRoleSelectionScope): ResolvedModelRoleValue {
|
|
1104
1238
|
const roleValue =
|
|
1105
1239
|
scope === "project" ? this.#settings.getProjectModelRole(role) : this.#settings.getGlobalModelRole(role);
|
|
1106
|
-
const allModels =
|
|
1240
|
+
const allModels =
|
|
1241
|
+
this.#scopedModels.length > 0
|
|
1242
|
+
? scopedRolePool(
|
|
1243
|
+
this.#scopedModels.map(scoped => scoped.model),
|
|
1244
|
+
this.#registry,
|
|
1245
|
+
)
|
|
1246
|
+
: this.#registry.getAll("all");
|
|
1107
1247
|
const roleLookup: ModelRoleLookup = {
|
|
1108
1248
|
getModelRole: scopedRole =>
|
|
1109
1249
|
scope === "project"
|
|
@@ -1521,14 +1661,15 @@ export class ModelHubComponent implements Component {
|
|
|
1521
1661
|
}
|
|
1522
1662
|
}
|
|
1523
1663
|
|
|
1524
|
-
/**
|
|
1664
|
+
/**
|
|
1665
|
+
* Switch the body into assign mode for `role`: full catalog, cleared query, current model preselected.
|
|
1666
|
+
* Arrows land on the model rows (the next step of the flow); ← still reaches the provider scopes.
|
|
1667
|
+
*/
|
|
1525
1668
|
#startAssign(role: string): void {
|
|
1526
1669
|
this.#assigning = { kind: "role", role };
|
|
1527
|
-
this.#focus = "
|
|
1670
|
+
this.#focus = "list";
|
|
1528
1671
|
this.#browser.setShowProvider(true);
|
|
1529
|
-
this.#setCandidateItems(
|
|
1530
|
-
this.#availableItems.filter(item => this.#settings.getRoleInfo(role).accepts(item.model)),
|
|
1531
|
-
);
|
|
1672
|
+
this.#setCandidateItems(this.#availableItems);
|
|
1532
1673
|
this.#browser.setQuery("");
|
|
1533
1674
|
const current = this.#roles[role];
|
|
1534
1675
|
if (current) {
|
|
@@ -1539,7 +1680,7 @@ export class ModelHubComponent implements Component {
|
|
|
1539
1680
|
/** Browse the catalog to fill a fallback-chain slot: `index` replaces an entry, `null` appends. */
|
|
1540
1681
|
#startAssignFallback(role: string, index: number | null): void {
|
|
1541
1682
|
this.#assigning = { kind: "fallback", role, index };
|
|
1542
|
-
this.#focus = "
|
|
1683
|
+
this.#focus = "list";
|
|
1543
1684
|
this.#browser.setShowProvider(true);
|
|
1544
1685
|
this.#setCandidateItems(this.#availableItems);
|
|
1545
1686
|
this.#browser.setQuery("");
|
|
@@ -1557,7 +1698,7 @@ export class ModelHubComponent implements Component {
|
|
|
1557
1698
|
/** Browse the catalog for the primary model a brand-new fallback chain protects. */
|
|
1558
1699
|
#startAssignFallbackKey(): void {
|
|
1559
1700
|
this.#assigning = { kind: "fallbackKey" };
|
|
1560
|
-
this.#focus = "
|
|
1701
|
+
this.#focus = "list";
|
|
1561
1702
|
this.#browser.setShowProvider(true);
|
|
1562
1703
|
this.#setCandidateItems(this.#availableItems);
|
|
1563
1704
|
this.#browser.setQuery("");
|
|
@@ -1684,10 +1825,46 @@ export class ModelHubComponent implements Component {
|
|
|
1684
1825
|
this.#strip = { kind: "name", purpose, input: new Input() };
|
|
1685
1826
|
}
|
|
1686
1827
|
|
|
1687
|
-
/**
|
|
1828
|
+
/** The model a Roles row resolves to (assigned role or resolvable fallback), whose compaction point `k` edits. */
|
|
1829
|
+
#roleRowModel(): Model | undefined {
|
|
1830
|
+
const row = this.#rolesRows[this.#roleIndex];
|
|
1831
|
+
if (row?.kind === "role") return this.#roles[row.role]?.model;
|
|
1832
|
+
if (row?.kind === "fallback") return this.#resolveFallbackEntry(row.role, row.chainIndex)?.item.model;
|
|
1833
|
+
return undefined;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
/** Open the footer input for the Roles row's model compaction point, prefilled with its own entry. */
|
|
1837
|
+
#openCompactionStrip(): void {
|
|
1838
|
+
if (!this.#callbacks.onCompactionPointChange) return;
|
|
1839
|
+
const model = this.#roleRowModel();
|
|
1840
|
+
if (!model) return;
|
|
1841
|
+
const input = new Input();
|
|
1842
|
+
input.setValue(this.#settings.compactionPointFor?.(model)?.draft ?? "");
|
|
1843
|
+
this.#strip = { kind: "name", purpose: "compaction", model, input };
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
/** Validate and commit the name strip: a new role, a preset name, or a compaction point. */
|
|
1688
1847
|
#submitNameStrip(): void {
|
|
1689
1848
|
const strip = this.#strip;
|
|
1690
1849
|
if (strip?.kind !== "name") return;
|
|
1850
|
+
if (strip.purpose === "compaction") {
|
|
1851
|
+
const value = strip.input.getValue();
|
|
1852
|
+
const result = this.#callbacks.onCompactionPointChange?.(strip.model, value, compactionConfirmPending(strip));
|
|
1853
|
+
if (result?.kind === "error") {
|
|
1854
|
+
strip.error = result.message;
|
|
1855
|
+
strip.confirm = undefined;
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
if (result?.kind === "confirm") {
|
|
1859
|
+
strip.error = undefined;
|
|
1860
|
+
strip.confirm = { value, message: result.message };
|
|
1861
|
+
return;
|
|
1862
|
+
}
|
|
1863
|
+
this.#strip = null;
|
|
1864
|
+
this.#frame.chipRanges = [];
|
|
1865
|
+
this.#refreshAfterMutation();
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1691
1868
|
if (strip.purpose === "preset") {
|
|
1692
1869
|
this.#submitPresetName();
|
|
1693
1870
|
return;
|
|
@@ -1739,6 +1916,52 @@ export class ModelHubComponent implements Component {
|
|
|
1739
1916
|
this.#buildRolesRows();
|
|
1740
1917
|
}
|
|
1741
1918
|
|
|
1919
|
+
/** Saved presets and the one the setup matches; empty unless the host can both list and switch. */
|
|
1920
|
+
#presets(): { names: readonly string[]; active: string | undefined } {
|
|
1921
|
+
if (!this.#callbacks.onSwitchPreset || !this.#settings.getModelPresets) return { names: [], active: undefined };
|
|
1922
|
+
const revision = this.#settings.revision;
|
|
1923
|
+
if (this.#presetsMemo?.revision !== revision) {
|
|
1924
|
+
const { names, active } = this.#settings.getModelPresets();
|
|
1925
|
+
this.#presetsMemo = { revision, names, active };
|
|
1926
|
+
}
|
|
1927
|
+
return this.#presetsMemo;
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
/**
|
|
1931
|
+
* The preset ctrl+←/→ steps from: the last one tried here while settings are
|
|
1932
|
+
* unchanged since (a refused switch writes nothing, so the next press moves
|
|
1933
|
+
* past it) or nothing matches; otherwise the preset the setup matches.
|
|
1934
|
+
*/
|
|
1935
|
+
#presetBase(active: string | undefined): string | undefined {
|
|
1936
|
+
const cursor = this.#presetCursor;
|
|
1937
|
+
if (cursor && (active === undefined || cursor.revision === this.#settings.revision)) return cursor.name;
|
|
1938
|
+
return active;
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
/** Apply the previous/next saved preset (wrapping), as `/modelpreset switch` does. */
|
|
1942
|
+
#switchPreset(delta: -1 | 1): void {
|
|
1943
|
+
const onSwitchPreset = this.#callbacks.onSwitchPreset;
|
|
1944
|
+
const { names, active } = this.#presets();
|
|
1945
|
+
if (!onSwitchPreset || names.length === 0) return;
|
|
1946
|
+
const base = this.#presetBase(active);
|
|
1947
|
+
const current = base === undefined ? -1 : names.indexOf(base);
|
|
1948
|
+
const next = current < 0 ? (delta > 0 ? 0 : names.length - 1) : (current + delta + names.length) % names.length;
|
|
1949
|
+
const name = names[next];
|
|
1950
|
+
if (name === undefined) return;
|
|
1951
|
+
const settle = () => {
|
|
1952
|
+
this.#presetCursor = { name, revision: this.#settings.revision };
|
|
1953
|
+
this.#refreshAfterMutation();
|
|
1954
|
+
};
|
|
1955
|
+
// Stepping onto the preset already in effect has nothing to apply.
|
|
1956
|
+
if (name === active) {
|
|
1957
|
+
settle();
|
|
1958
|
+
return;
|
|
1959
|
+
}
|
|
1960
|
+
// The host reports refusals itself, so the cursor moves once the switch
|
|
1961
|
+
// resolves, refused or not (`#presetBase` sees a refusal left the revision alone).
|
|
1962
|
+
this.#finishAssignment(onSwitchPreset(name), settle);
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1742
1965
|
handleInput(data: string): void {
|
|
1743
1966
|
this.#nativeVersion++;
|
|
1744
1967
|
if (this.#assignmentPending) {
|
|
@@ -1774,6 +1997,11 @@ export class ModelHubComponent implements Component {
|
|
|
1774
1997
|
}
|
|
1775
1998
|
return;
|
|
1776
1999
|
}
|
|
2000
|
+
// Ctrl+←/→ in the Roles view steps through saved model presets, wrapping.
|
|
2001
|
+
if (rolesView && (matchesKey(data, "ctrl+left") || matchesKey(data, "ctrl+right"))) {
|
|
2002
|
+
this.#switchPreset(matchesKey(data, "ctrl+left") ? -1 : 1);
|
|
2003
|
+
return;
|
|
2004
|
+
}
|
|
1777
2005
|
// Alt+←/→ cycles whichever tab strip is on screen: role tabs in the
|
|
1778
2006
|
// Roles view, kind tabs in every browser view. Ctrl+←/→ is unusable on
|
|
1779
2007
|
// macOS (Spaces shortcut). macOS terminals (ghostty, Terminal.app,
|
|
@@ -1818,7 +2046,13 @@ export class ModelHubComponent implements Component {
|
|
|
1818
2046
|
|
|
1819
2047
|
if (rolesView) {
|
|
1820
2048
|
const printable = extractPrintableText(data);
|
|
1821
|
-
|
|
2049
|
+
// Typing searches the catalog from the sidebar, and from the rows
|
|
2050
|
+
// while a query is live (the picker shows its search field only
|
|
2051
|
+
// then); otherwise the rows' letter commands own printable keys.
|
|
2052
|
+
const typed = printable !== undefined && printable.trim().length > 0;
|
|
2053
|
+
const editsQuery =
|
|
2054
|
+
this.#browser.query.length > 0 && (printable !== undefined || matchesKey(data, "backspace"));
|
|
2055
|
+
if ((this.#focus === "scope" && typed) || editsQuery) {
|
|
1822
2056
|
this.#setActiveEntry("all");
|
|
1823
2057
|
this.#focus = "list";
|
|
1824
2058
|
this.#browser.handleInput(data);
|
|
@@ -1895,6 +2129,7 @@ export class ModelHubComponent implements Component {
|
|
|
1895
2129
|
return;
|
|
1896
2130
|
}
|
|
1897
2131
|
strip.input.handleInput(data);
|
|
2132
|
+
if (strip.purpose === "compaction") strip.error = undefined;
|
|
1898
2133
|
return;
|
|
1899
2134
|
}
|
|
1900
2135
|
if (moveStripSelection(strip, data)) return;
|
|
@@ -2054,6 +2289,13 @@ export class ModelHubComponent implements Component {
|
|
|
2054
2289
|
case "save":
|
|
2055
2290
|
if (this.#callbacks.onSavePreset) this.#openNameStrip("preset");
|
|
2056
2291
|
return;
|
|
2292
|
+
case "nextPreset":
|
|
2293
|
+
case "prevPreset":
|
|
2294
|
+
this.#switchPreset(action === "nextPreset" ? 1 : -1);
|
|
2295
|
+
return;
|
|
2296
|
+
case "compaction":
|
|
2297
|
+
this.#openCompactionStrip();
|
|
2298
|
+
return;
|
|
2057
2299
|
case "thinking":
|
|
2058
2300
|
if (role) {
|
|
2059
2301
|
const target = this.#roleThinkingTarget(role);
|
|
@@ -2252,8 +2494,18 @@ export class ModelHubComponent implements Component {
|
|
|
2252
2494
|
ROLE_TABS.map(tab => ({ label: tab === "kind" ? "kinds" : tab })),
|
|
2253
2495
|
Math.max(0, active),
|
|
2254
2496
|
);
|
|
2497
|
+
const { names, active: activePreset } = this.#presets();
|
|
2498
|
+
// p/P type into a live query instead of switching presets.
|
|
2499
|
+
const presetKeys =
|
|
2500
|
+
this.#browser.query.length > 0
|
|
2501
|
+
? formatKeyHints(["ctrl+left", "ctrl+right"])
|
|
2502
|
+
: `${formatKeyHints(["ctrl+left", "ctrl+right"])} · ${formatKeyHints(["p", "shift+p"])}`;
|
|
2503
|
+
const preset =
|
|
2504
|
+
names.length > 0
|
|
2505
|
+
? ` ${theme.fg("dim", "Preset:")} ${activePreset ? theme.fg("accent", activePreset) : theme.fg("muted", "custom")} ${theme.fg("dim", presetKeys)}`
|
|
2506
|
+
: "";
|
|
2255
2507
|
return truncateToWidth(
|
|
2256
|
-
` ${theme.fg("dim", "Roles:")} ${track} ${theme.fg("dim", formatKeyHints(["alt+left", "alt+right"]))}`,
|
|
2508
|
+
` ${theme.fg("dim", "Roles:")} ${track} ${theme.fg("dim", formatKeyHints(["alt+left", "alt+right"]))}${preset}`,
|
|
2257
2509
|
width,
|
|
2258
2510
|
);
|
|
2259
2511
|
}
|
|
@@ -2524,6 +2776,9 @@ export class ModelHubComponent implements Component {
|
|
|
2524
2776
|
const strip = this.#strip;
|
|
2525
2777
|
if (strip) {
|
|
2526
2778
|
if (strip.kind === "name") {
|
|
2779
|
+
if (strip.purpose === "compaction") {
|
|
2780
|
+
return `${enter} ${compactionConfirmPending(strip) ? "accept" : "set"} compaction point · ${cancel} cancel`;
|
|
2781
|
+
}
|
|
2527
2782
|
if (strip.purpose === "preset") {
|
|
2528
2783
|
return `${enter} save preset · ${cancel} cancel`;
|
|
2529
2784
|
}
|
|
@@ -2550,16 +2805,26 @@ export class ModelHubComponent implements Component {
|
|
|
2550
2805
|
const entry = this.#activeEntry();
|
|
2551
2806
|
if (entry.kind === "roles") {
|
|
2552
2807
|
if (this.#focus !== "list") {
|
|
2553
|
-
|
|
2808
|
+
const presets =
|
|
2809
|
+
this.#presets().names.length > 0 ? ` · ${formatKeyHints(["ctrl+left", "ctrl+right"])} preset` : "";
|
|
2810
|
+
return `${upDown} providers · ${enterRight} roles · ${altLeftRight} tabs${presets} · ${cancel} close`;
|
|
2811
|
+
}
|
|
2812
|
+
// A live query takes printable keys and backspace, so the letter commands rest.
|
|
2813
|
+
if (this.#browser.query.length > 0) {
|
|
2814
|
+
return `${upDown} rows · ${enter} pick · type to search · ${left} providers · ${cancel} close`;
|
|
2554
2815
|
}
|
|
2555
2816
|
const row = this.#rolesRows[this.#roleIndex];
|
|
2817
|
+
const compaction =
|
|
2818
|
+
this.#callbacks.onCompactionPointChange && this.#roleRowModel()
|
|
2819
|
+
? ` · ${formatKeyHint("k")} compaction limit`
|
|
2820
|
+
: "";
|
|
2556
2821
|
if (row?.kind === "fallback") {
|
|
2557
2822
|
// Advertise `t` only when the entry resolves: wildcards always
|
|
2558
2823
|
// inherit and unknown models have no ladder to offer, so the
|
|
2559
2824
|
// action would be inert there.
|
|
2560
2825
|
const editable = this.#resolveFallbackEntry(row.role, row.chainIndex) !== undefined;
|
|
2561
2826
|
const thinking = editable ? ` · ${formatKeyHint("t")} thinking` : "";
|
|
2562
|
-
return `${upDown} rows · ${enter} replace · ${formatKeyHint("f")} add another · ${formatKeyHint("x")} remove${thinking} · [/] reorder · ${left} providers`;
|
|
2827
|
+
return `${upDown} rows · ${enter} replace · ${formatKeyHint("f")} add another · ${formatKeyHint("x")} remove${thinking}${compaction} · [/] reorder · ${left} providers`;
|
|
2563
2828
|
}
|
|
2564
2829
|
if (row?.kind === "chainKey") {
|
|
2565
2830
|
return `${upDown} rows · ${formatKeyHints(["enter", "f"])} add fallback · ${formatKeyHint("x")} clear chain · ${left} providers`;
|
|
@@ -2572,7 +2837,8 @@ export class ModelHubComponent implements Component {
|
|
|
2572
2837
|
const editable = row?.kind === "role" && this.#roleThinkingTarget(row.role) !== undefined;
|
|
2573
2838
|
const thinking = editable ? ` · ${formatKeyHint("t")} thinking` : "";
|
|
2574
2839
|
const savePreset = this.#callbacks.onSavePreset ? ` · ${formatKeyHint("s")} save preset` : "";
|
|
2575
|
-
|
|
2840
|
+
const switchPreset = this.#presets().names.length > 0 ? ` · ${formatKeyHints(["p", "shift+p"])} preset` : "";
|
|
2841
|
+
return `${upDown} rows · ${enter} pick · ${formatKeyHint("f")} fallback · ${formatKeyHint("x")} clear${thinking}${compaction} · ${formatKeyHint("c")} cycle · [/] reorder · ${formatKeyHint("n")} new${savePreset}${switchPreset}`;
|
|
2576
2842
|
}
|
|
2577
2843
|
if (entry.kind === "provider" && entry.locked) {
|
|
2578
2844
|
return entry.oauth
|
|
@@ -2597,12 +2863,26 @@ export class ModelHubComponent implements Component {
|
|
|
2597
2863
|
|
|
2598
2864
|
#renderStrip(width: number, strip: StripState): string {
|
|
2599
2865
|
if (strip.kind === "name") {
|
|
2600
|
-
const
|
|
2601
|
-
|
|
2866
|
+
const labelText =
|
|
2867
|
+
strip.purpose === "compaction"
|
|
2868
|
+
? `${strip.model.id} limit:`
|
|
2869
|
+
: strip.purpose === "preset"
|
|
2870
|
+
? "Preset name:"
|
|
2871
|
+
: "New role name:";
|
|
2602
2872
|
const label = theme.fg("accent", labelText);
|
|
2603
|
-
|
|
2873
|
+
// A compaction point is at most a few characters (`1500k`, `12.5%`); a
|
|
2874
|
+
// narrow field leaves the row for its notice.
|
|
2875
|
+
const maxInputWidth = strip.purpose === "compaction" ? 10 : 32;
|
|
2876
|
+
const inputWidth = Math.max(8, Math.min(maxInputWidth, width - visibleWidth(labelText) - 24));
|
|
2604
2877
|
const inputLine = strip.input.render(inputWidth)[0] ?? "";
|
|
2605
|
-
|
|
2878
|
+
let hint: string;
|
|
2879
|
+
if (strip.purpose === "compaction") {
|
|
2880
|
+
const notice = compactionNotice(strip, this.#callbacks.previewCompactionPoint);
|
|
2881
|
+
hint = theme.fg(notice.style, notice.text);
|
|
2882
|
+
} else {
|
|
2883
|
+
hint = theme.fg("dim", "(letters, digits, - and _)");
|
|
2884
|
+
}
|
|
2885
|
+
return truncateToWidth(`${label} ${inputLine} ${hint}`, width);
|
|
2606
2886
|
}
|
|
2607
2887
|
|
|
2608
2888
|
const prefix =
|
|
@@ -3024,7 +3304,9 @@ export class ModelHubComponent implements Component {
|
|
|
3024
3304
|
size: "lg",
|
|
3025
3305
|
layout: "rows",
|
|
3026
3306
|
preview: "side",
|
|
3027
|
-
|
|
3307
|
+
// The Roles rows' letter commands own printable keys until a query
|
|
3308
|
+
// is live, so the sheet offers no search field to type into.
|
|
3309
|
+
query: rolesView && this.#browser.query.length === 0 ? null : this.#browser.query,
|
|
3028
3310
|
cursor: this.#browser.cursor,
|
|
3029
3311
|
placeholder: "Search models…",
|
|
3030
3312
|
scopes: this.#pickerScopes(),
|
|
@@ -3130,6 +3412,17 @@ export class ModelHubComponent implements Component {
|
|
|
3130
3412
|
if (entry.kind === "provider" && entry.providerId && this.#refreshingProviders.has(entry.providerId)) {
|
|
3131
3413
|
return `${entry.label} · refreshing model list…`;
|
|
3132
3414
|
}
|
|
3415
|
+
const presets = rolesView ? this.#presets() : undefined;
|
|
3416
|
+
if (presets && presets.names.length > 0) {
|
|
3417
|
+
return [
|
|
3418
|
+
span("Preset ", "muted"),
|
|
3419
|
+
presets.active ? span(presets.active, "strong") : span("custom", "muted"),
|
|
3420
|
+
span(
|
|
3421
|
+
this.#scopedModels.length > 0 ? " · --models scope" : " · Cleared roles fall back to auto-selection",
|
|
3422
|
+
"muted",
|
|
3423
|
+
),
|
|
3424
|
+
];
|
|
3425
|
+
}
|
|
3133
3426
|
if (this.#scopedModels.length > 0) return "--models scope";
|
|
3134
3427
|
if (rolesView) return "Cleared roles fall back to auto-selection";
|
|
3135
3428
|
return undefined;
|
|
@@ -3242,12 +3535,19 @@ export class ModelHubComponent implements Component {
|
|
|
3242
3535
|
if (strip) {
|
|
3243
3536
|
const apply =
|
|
3244
3537
|
strip.kind === "name"
|
|
3245
|
-
?
|
|
3246
|
-
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3538
|
+
? strip.purpose === "compaction"
|
|
3539
|
+
? pickerAction(
|
|
3540
|
+
"compactionPoint",
|
|
3541
|
+
compactionConfirmPending(strip) ? "Accept compaction point" : "Set compaction point",
|
|
3542
|
+
"enter",
|
|
3543
|
+
{ primary: true },
|
|
3544
|
+
)
|
|
3545
|
+
: pickerAction(
|
|
3546
|
+
strip.purpose === "preset" ? "presetName" : "roleName",
|
|
3547
|
+
strip.purpose === "preset" ? "Save preset" : "Create role",
|
|
3548
|
+
"enter",
|
|
3549
|
+
{ primary: true },
|
|
3550
|
+
)
|
|
3251
3551
|
: pickerAction(
|
|
3252
3552
|
"stripApply",
|
|
3253
3553
|
strip.kind === "thinking" ? "Apply" : strip.kind === "scope" ? "Save to scope" : "Assign / clear",
|
|
@@ -3269,9 +3569,20 @@ export class ModelHubComponent implements Component {
|
|
|
3269
3569
|
}
|
|
3270
3570
|
if (rolesView) {
|
|
3271
3571
|
const row = this.#rolesRows[this.#roleIndex];
|
|
3572
|
+
// A live query takes the letter keys: those buttons keep working but lose their keycaps.
|
|
3573
|
+
const searching = this.#browser.query.length > 0;
|
|
3272
3574
|
const roleAction = (action: RolesAction, label: string, key: string, primary = false) =>
|
|
3273
|
-
pickerAction(
|
|
3575
|
+
pickerAction(
|
|
3576
|
+
`roles:${action}`,
|
|
3577
|
+
label,
|
|
3578
|
+
searching && key !== "enter" ? undefined : key,
|
|
3579
|
+
primary ? { primary: true } : undefined,
|
|
3580
|
+
);
|
|
3274
3581
|
const actions: (TspPickerAction | undefined)[] = [];
|
|
3582
|
+
const compaction =
|
|
3583
|
+
this.#callbacks.onCompactionPointChange && this.#roleRowModel()
|
|
3584
|
+
? roleAction("compaction", "Compaction limit", "k")
|
|
3585
|
+
: undefined;
|
|
3275
3586
|
switch (row?.kind) {
|
|
3276
3587
|
case "role": {
|
|
3277
3588
|
const assigned = this.#roles[row.role];
|
|
@@ -3280,9 +3591,11 @@ export class ModelHubComponent implements Component {
|
|
|
3280
3591
|
roleAction("fallback", "Add fallback", "f"),
|
|
3281
3592
|
assigned && !assigned.autoSelected ? roleAction("clear", "Clear", "x") : undefined,
|
|
3282
3593
|
this.#roleThinkingTarget(row.role) ? roleAction("thinking", "Thinking", "t") : undefined,
|
|
3594
|
+
compaction,
|
|
3283
3595
|
roleAction("cycle", this.#cycleOrder().includes(row.role) ? "Leave cycle" : "Add to cycle", "c"),
|
|
3284
3596
|
roleAction("new", "New role", "n"),
|
|
3285
3597
|
this.#callbacks.onSavePreset ? roleAction("save", "Save preset", "s") : undefined,
|
|
3598
|
+
this.#presets().names.length > 0 ? roleAction("nextPreset", "Next preset", "p") : undefined,
|
|
3286
3599
|
);
|
|
3287
3600
|
break;
|
|
3288
3601
|
}
|
|
@@ -3294,6 +3607,7 @@ export class ModelHubComponent implements Component {
|
|
|
3294
3607
|
this.#resolveFallbackEntry(row.role, row.chainIndex)
|
|
3295
3608
|
? roleAction("thinking", "Thinking", "t")
|
|
3296
3609
|
: undefined,
|
|
3610
|
+
compaction,
|
|
3297
3611
|
roleAction("earlier", "Earlier", "["),
|
|
3298
3612
|
roleAction("later", "Later", "]"),
|
|
3299
3613
|
);
|
|
@@ -3330,6 +3644,19 @@ export class ModelHubComponent implements Component {
|
|
|
3330
3644
|
/** The open strip as the picker's chip strip (role assignment, save scope, thinking level, new role name). */
|
|
3331
3645
|
#pickerStrip(strip: StripState): NonNullable<TspPickerProps["strip"]> {
|
|
3332
3646
|
if (strip.kind === "name") {
|
|
3647
|
+
if (strip.purpose === "compaction") {
|
|
3648
|
+
const notice = compactionNotice(strip, this.#callbacks.previewCompactionPoint);
|
|
3649
|
+
return {
|
|
3650
|
+
label: [
|
|
3651
|
+
span(strip.model.id, "mono"),
|
|
3652
|
+
span(" limit ", "muted"),
|
|
3653
|
+
span(strip.input.getValue(), "mono"),
|
|
3654
|
+
span("▏", "accent"),
|
|
3655
|
+
span(` ${notice.text}`, notice.style),
|
|
3656
|
+
],
|
|
3657
|
+
items: [],
|
|
3658
|
+
};
|
|
3659
|
+
}
|
|
3333
3660
|
return {
|
|
3334
3661
|
label: [
|
|
3335
3662
|
span(strip.purpose === "preset" ? "Preset name " : "New role name ", "muted"),
|
|
@@ -3469,7 +3796,14 @@ export class ModelHubComponent implements Component {
|
|
|
3469
3796
|
/** The selected Roles-view row's preview: its model's facts and its fallback chain. */
|
|
3470
3797
|
#rolePreview(row: RolesRow | undefined): readonly NativeChild[] {
|
|
3471
3798
|
const memo = this.#pickerRolePreview;
|
|
3472
|
-
|
|
3799
|
+
const revision = this.#settings.revision;
|
|
3800
|
+
if (
|
|
3801
|
+
memo !== undefined &&
|
|
3802
|
+
memo.row === row &&
|
|
3803
|
+
memo.roles === this.#roles &&
|
|
3804
|
+
memo.rows === this.#rolesRows &&
|
|
3805
|
+
memo.revision === revision
|
|
3806
|
+
) {
|
|
3473
3807
|
return memo.children;
|
|
3474
3808
|
}
|
|
3475
3809
|
const children: NativeChild[] = [];
|
|
@@ -3543,7 +3877,7 @@ export class ModelHubComponent implements Component {
|
|
|
3543
3877
|
);
|
|
3544
3878
|
break;
|
|
3545
3879
|
}
|
|
3546
|
-
this.#pickerRolePreview = { row, roles: this.#roles, rows: this.#rolesRows, children };
|
|
3880
|
+
this.#pickerRolePreview = { row, roles: this.#roles, rows: this.#rolesRows, revision, children };
|
|
3547
3881
|
return children;
|
|
3548
3882
|
}
|
|
3549
3883
|
|
|
@@ -3643,6 +3977,9 @@ export class ModelHubComponent implements Component {
|
|
|
3643
3977
|
case "presetName":
|
|
3644
3978
|
this.#submitPresetName();
|
|
3645
3979
|
return;
|
|
3980
|
+
case "compactionPoint":
|
|
3981
|
+
this.#submitNameStrip();
|
|
3982
|
+
return;
|
|
3646
3983
|
case "cancel":
|
|
3647
3984
|
if (this.#strip) this.#closeStrip();
|
|
3648
3985
|
return;
|
|
@@ -3693,6 +4030,19 @@ export class ModelHubComponent implements Component {
|
|
|
3693
4030
|
const strip = this.#strip;
|
|
3694
4031
|
if (!strip) return undefined;
|
|
3695
4032
|
if (strip.kind === "name") {
|
|
4033
|
+
if (strip.purpose === "compaction") {
|
|
4034
|
+
const notice = compactionNotice(strip, this.#callbacks.previewCompactionPoint);
|
|
4035
|
+
return node(
|
|
4036
|
+
"row",
|
|
4037
|
+
{ gap: "sm", align: "center" },
|
|
4038
|
+
[
|
|
4039
|
+
text([span(`${strip.model.id} limit:`, "accent")]),
|
|
4040
|
+
col([strip.input], { grow: 1 }),
|
|
4041
|
+
text([span(notice.text, notice.style)], { wrap: "word" }),
|
|
4042
|
+
],
|
|
4043
|
+
"compactionPoint",
|
|
4044
|
+
);
|
|
4045
|
+
}
|
|
3696
4046
|
const preset = strip.purpose === "preset";
|
|
3697
4047
|
return node(
|
|
3698
4048
|
"row",
|
|
@@ -3746,9 +4096,17 @@ export class ModelHubComponent implements Component {
|
|
|
3746
4096
|
if (strip) {
|
|
3747
4097
|
switch (strip.kind) {
|
|
3748
4098
|
case "name":
|
|
3749
|
-
return strip.purpose === "
|
|
3750
|
-
? [
|
|
3751
|
-
|
|
4099
|
+
return strip.purpose === "compaction"
|
|
4100
|
+
? [
|
|
4101
|
+
keys(
|
|
4102
|
+
compactionConfirmPending(strip) ? "accept compaction point" : "set compaction point",
|
|
4103
|
+
"enter",
|
|
4104
|
+
),
|
|
4105
|
+
cancel("cancel"),
|
|
4106
|
+
]
|
|
4107
|
+
: strip.purpose === "preset"
|
|
4108
|
+
? [keys("save preset", "enter"), cancel("cancel")]
|
|
4109
|
+
: [keys("create + pick model", "enter"), cancel("cancel")];
|
|
3752
4110
|
case "role":
|
|
3753
4111
|
return [keys("choose", "left", "right"), keys("assign/clear", "enter"), cancel("cancel")];
|
|
3754
4112
|
case "scope":
|
|
@@ -3769,6 +4127,7 @@ export class ModelHubComponent implements Component {
|
|
|
3769
4127
|
: "assign";
|
|
3770
4128
|
return [keys(pick, "enter"), upDown("models"), keys("providers", "left"), search, kind, cancel("cancel")];
|
|
3771
4129
|
}
|
|
4130
|
+
const presetHint = this.#presets().names.length > 0 ? keys("preset", "ctrl+left", "ctrl+right") : undefined;
|
|
3772
4131
|
const entry = this.#activeEntry();
|
|
3773
4132
|
if (entry.kind === "roles") {
|
|
3774
4133
|
if (this.#focus !== "list") {
|
|
@@ -3776,10 +4135,24 @@ export class ModelHubComponent implements Component {
|
|
|
3776
4135
|
upDown("providers"),
|
|
3777
4136
|
keys("roles", "enter", "right"),
|
|
3778
4137
|
keys("tabs", "alt+left", "alt+right"),
|
|
4138
|
+
presetHint,
|
|
4139
|
+
cancel("close"),
|
|
4140
|
+
];
|
|
4141
|
+
}
|
|
4142
|
+
// A live query takes printable keys and backspace, so the letter commands rest.
|
|
4143
|
+
if (this.#browser.query.length > 0) {
|
|
4144
|
+
return [
|
|
4145
|
+
upDown("rows"),
|
|
4146
|
+
keys("pick", "enter"),
|
|
4147
|
+
search,
|
|
4148
|
+
keys("providers", "left"),
|
|
4149
|
+
presetHint,
|
|
3779
4150
|
cancel("close"),
|
|
3780
4151
|
];
|
|
3781
4152
|
}
|
|
3782
4153
|
const row = this.#rolesRows[this.#roleIndex];
|
|
4154
|
+
const compaction =
|
|
4155
|
+
this.#callbacks.onCompactionPointChange && this.#roleRowModel() ? keys("compaction limit", "k") : undefined;
|
|
3783
4156
|
if (row?.kind === "fallback") {
|
|
3784
4157
|
// Advertise `t` only where a strip would open, as the ANSI footer does.
|
|
3785
4158
|
const editable = this.#resolveFallbackEntry(row.role, row.chainIndex) !== undefined;
|
|
@@ -3789,6 +4162,7 @@ export class ModelHubComponent implements Component {
|
|
|
3789
4162
|
keys("add another", "f"),
|
|
3790
4163
|
keys("remove", "x"),
|
|
3791
4164
|
editable ? keys("thinking", "t") : undefined,
|
|
4165
|
+
compaction,
|
|
3792
4166
|
reorder,
|
|
3793
4167
|
keys("providers", "left"),
|
|
3794
4168
|
];
|
|
@@ -3811,10 +4185,12 @@ export class ModelHubComponent implements Component {
|
|
|
3811
4185
|
keys("fallback", "f"),
|
|
3812
4186
|
keys("clear", "x"),
|
|
3813
4187
|
editable ? keys("thinking", "t") : undefined,
|
|
4188
|
+
compaction,
|
|
3814
4189
|
keys("cycle", "c"),
|
|
3815
4190
|
reorder,
|
|
3816
4191
|
keys("new", "n"),
|
|
3817
4192
|
this.#callbacks.onSavePreset ? keys("save preset", "s") : undefined,
|
|
4193
|
+
this.#presets().names.length > 0 ? keys("preset", "ctrl+left", "ctrl+right", "p", "shift+p") : undefined,
|
|
3818
4194
|
];
|
|
3819
4195
|
}
|
|
3820
4196
|
if (entry.kind === "provider" && entry.locked) {
|