@linxiraos/pi-tui 1.1.27 → 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 +47 -463
- package/README.md +7 -5
- package/dist/types/app-keybindings.d.ts +1 -1
- 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 +2 -2
- 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 +5 -0
- package/dist/types/chat/chat-transcript-builder.d.ts +2 -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 +15 -2
- package/dist/types/chrome/shared.d.ts +9 -0
- 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 +15 -4
- package/dist/types/components/form.d.ts +1 -0
- package/dist/types/components/image.d.ts +12 -44
- 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/markdown.d.ts +36 -3
- 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 +1 -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/backend.d.ts +3 -2
- package/dist/types/native/blobs.d.ts +7 -3
- package/dist/types/native/encode.d.ts +3 -4
- package/dist/types/native/overlay.d.ts +5 -0
- 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 +2 -0
- package/dist/types/overlays/agent-transcript-viewer.d.ts +11 -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/history-search.d.ts +10 -0
- package/dist/types/overlays/hook-selector.d.ts +9 -4
- 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 +23 -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/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/usage-dashboard.d.ts +8 -4
- package/dist/types/overlays/usage-display.d.ts +3 -1
- package/dist/types/prompt/composer.d.ts +1 -1
- package/dist/types/prompt/custom-editor.d.ts +11 -8
- package/dist/types/prompt/image-source.d.ts +2 -1
- package/dist/types/prompt/interactive-context-helpers.d.ts +4 -1
- package/dist/types/prompt/model-mention-autocomplete.d.ts +1 -1
- package/dist/types/prompt/welcome.d.ts +6 -1
- 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/status-line/component.d.ts +3 -0
- 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 +27 -35
- package/dist/types/terminal-multiplexer.d.ts +127 -14
- package/dist/types/terminal.d.ts +22 -6
- 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/tmux.d.ts +11 -4
- 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 +0 -4
- package/dist/types/tools/write.d.ts +9 -2
- package/dist/types/tools/yield.d.ts +22 -0
- package/dist/types/tui.d.ts +31 -0
- package/dist/types/utils.d.ts +6 -0
- package/dist/types/vim.d.ts +19 -5
- package/package.json +13 -9
- package/src/app-keybindings.d.ts +1 -1
- package/src/app-keybindings.ts +1 -1
- 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 +26 -5
- package/src/apps/git/git-tui.ts +2 -2
- package/src/apps/git/help.ts +29 -20
- 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/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 +5 -0
- package/src/chat/assistant-message.ts +219 -50
- package/src/chat/chat-transcript-builder.d.ts +2 -0
- package/src/chat/chat-transcript-builder.ts +5 -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 +80 -6
- package/src/chat/user-message.d.ts +15 -2
- package/src/chat/user-message.ts +55 -3
- package/src/chrome/shared.ts +53 -0
- package/src/chrome/transcript-container.d.ts +6 -0
- package/src/chrome/transcript-container.ts +93 -13
- 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 +15 -4
- package/src/components/editor.ts +294 -46
- package/src/components/form.d.ts +1 -0
- package/src/components/form.ts +8 -0
- package/src/components/image.d.ts +12 -44
- package/src/components/image.ts +47 -151
- 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/markdown.d.ts +36 -3
- package/src/components/markdown.ts +433 -318
- 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 +1 -1
- package/src/fuzzy.d.ts +29 -1
- package/src/fuzzy.ts +115 -30
- package/src/key-hint-format.d.ts +1 -1
- package/src/key-hint-format.ts +1 -1
- 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 +4 -4
- 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/backend.d.ts +3 -2
- package/src/native/backend.ts +174 -18
- package/src/native/blobs.d.ts +7 -3
- package/src/native/blobs.ts +47 -12
- package/src/native/encode.d.ts +3 -4
- package/src/native/encode.ts +58 -31
- package/src/native/overlay.d.ts +5 -0
- package/src/native/overlay.ts +13 -0
- package/src/native/reconcile.ts +4 -0
- 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 +44 -29
- package/src/overlays/agent-transcript-viewer.ts +91 -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 +7 -10
- package/src/overlays/btw-history-panel.ts +12 -1
- package/src/overlays/copy-selector.ts +30 -8
- 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/jobs-panel.ts +28 -46
- package/src/overlays/model-browser.ts +341 -110
- package/src/overlays/model-hub.ts +300 -49
- package/src/overlays/move-overlay.ts +28 -14
- package/src/overlays/oauth-selector.ts +54 -70
- package/src/overlays/plan-review-overlay.ts +194 -70
- package/src/overlays/plugin-settings.ts +3 -3
- package/src/overlays/report-panel.ts +2 -2
- package/src/overlays/rewind-selector.ts +86 -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 +4 -3
- 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/tree-selector.ts +41 -20
- package/src/overlays/usage-dashboard.ts +100 -50
- package/src/overlays/usage-display.ts +10 -4
- package/src/prompt/attachment-chips.ts +15 -11
- package/src/prompt/composer-attachments.ts +6 -4
- package/src/prompt/composer.ts +4 -15
- package/src/prompt/custom-editor.ts +54 -26
- package/src/prompt/image-source.ts +2 -1
- package/src/prompt/interactive-context-helpers.ts +30 -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/tips.txt +6 -5
- package/src/prompt/welcome.ts +55 -12
- 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 +11 -0
- 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/status-line/component.ts +292 -78
- 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 +27 -35
- package/src/terminal-capabilities.ts +75 -189
- package/src/terminal-multiplexer.d.ts +127 -14
- package/src/terminal-multiplexer.ts +112 -33
- package/src/terminal.d.ts +22 -6
- package/src/terminal.ts +139 -37
- 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 +6 -1
- 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/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.ts +92 -42
- 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 +149 -50
- package/src/tools/task.d.ts +0 -4
- package/src/tools/task.ts +112 -59
- 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 +31 -0
- package/src/tui.ts +309 -160
- 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.ts +8 -0
|
@@ -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 {
|
|
@@ -170,9 +170,31 @@ export interface ModelHubCallbacks {
|
|
|
170
170
|
onSwitchPreset?: (name: string) => void | Promise<void>;
|
|
171
171
|
/** Persist a new quick-switch cycle order (the ctrl+p role cycle). */
|
|
172
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;
|
|
173
192
|
onCancel: () => void;
|
|
174
193
|
}
|
|
175
194
|
|
|
195
|
+
/** Why {@link ModelHubCallbacks.onCompactionPointChange} kept the field open. */
|
|
196
|
+
export type CompactionPointChangeResult = { kind: "error"; message: string } | { kind: "confirm"; message: string };
|
|
197
|
+
|
|
176
198
|
export interface ModelHubOptions {
|
|
177
199
|
/** Preselect this provider's sidebar entry (e.g. when reopening after /login). */
|
|
178
200
|
initialProviderId?: string;
|
|
@@ -224,6 +246,20 @@ type StripState =
|
|
|
224
246
|
kind: "name";
|
|
225
247
|
purpose: "role" | "preset";
|
|
226
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 };
|
|
227
263
|
};
|
|
228
264
|
|
|
229
265
|
/** A Roles-view command; keys and the picker's action bar both run {@link ModelHubComponent}'s `#runRolesAction`. */
|
|
@@ -236,6 +272,7 @@ type RolesAction =
|
|
|
236
272
|
| "later"
|
|
237
273
|
| "new"
|
|
238
274
|
| "thinking"
|
|
275
|
+
| "compaction"
|
|
239
276
|
| "save"
|
|
240
277
|
| "nextPreset"
|
|
241
278
|
| "prevPreset";
|
|
@@ -249,6 +286,7 @@ const ROLES_ACTION_KEYS: Record<string, RolesAction> = {
|
|
|
249
286
|
"]": "later",
|
|
250
287
|
n: "new",
|
|
251
288
|
t: "thinking",
|
|
289
|
+
k: "compaction",
|
|
252
290
|
s: "save",
|
|
253
291
|
// Letter twins of ctrl+←/→, which macOS reserves for switching Spaces.
|
|
254
292
|
p: "nextPreset",
|
|
@@ -286,6 +324,41 @@ function providerInitials(providerId: string): string {
|
|
|
286
324
|
|
|
287
325
|
const PROVIDER_REFRESH_DEBOUNCE_MS = 120;
|
|
288
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
|
+
|
|
289
362
|
const MODEL_KIND_TABS: ReadonlyArray<"all" | ModelKind> = ["all", ...MODEL_KINDS];
|
|
290
363
|
const ROLE_TABS = ["all", "chat", "kind"] as const;
|
|
291
364
|
type RoleTab = (typeof ROLE_TABS)[number];
|
|
@@ -334,7 +407,15 @@ export class ModelHubComponent implements Component {
|
|
|
334
407
|
#roles: RoleAssignments = {};
|
|
335
408
|
#availableItems: ModelBrowserItem[] = [];
|
|
336
409
|
#recentItems: ModelBrowserItem[] = [];
|
|
410
|
+
/** Selectors of {@link #recentItems}, rebuilt with it, for search hit counts. */
|
|
411
|
+
#recentSelectors: ReadonlySet<string> = new Set();
|
|
337
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;
|
|
338
419
|
#modelKindTab: "all" | ModelKind = "all";
|
|
339
420
|
#roleTab: RoleTab = "all";
|
|
340
421
|
#configError: string | undefined;
|
|
@@ -428,6 +509,7 @@ export class ModelHubComponent implements Component {
|
|
|
428
509
|
row: RolesRow | undefined;
|
|
429
510
|
roles: RoleAssignments;
|
|
430
511
|
rows: readonly RolesRow[];
|
|
512
|
+
revision: number;
|
|
431
513
|
children: readonly NativeChild[];
|
|
432
514
|
}
|
|
433
515
|
| undefined;
|
|
@@ -518,22 +600,6 @@ export class ModelHubComponent implements Component {
|
|
|
518
600
|
return this.#settings.knownRoleIds.filter(role => !this.#settings.getRoleInfo(role).hidden);
|
|
519
601
|
}
|
|
520
602
|
|
|
521
|
-
/**
|
|
522
|
-
* Models a `--models`/`enabledModels` scope exposes. The scope only resolves
|
|
523
|
-
* chat models (it feeds Ctrl+P cycling), so available non-chat runners
|
|
524
|
-
* (judge, search, image, …) join from the registry — runtime role
|
|
525
|
-
* resolution ignores the scope for them as well.
|
|
526
|
-
*/
|
|
527
|
-
#scopedPool(): Model[] {
|
|
528
|
-
const pool = this.#scopedModels.map(scoped => scoped.model);
|
|
529
|
-
for (const model of this.#registry.getAvailable("all")) {
|
|
530
|
-
if (modelKind(model) === "chat") continue;
|
|
531
|
-
if (this.#scopedModels.some(scoped => modelsAreEqual(scoped.model, model))) continue;
|
|
532
|
-
pool.push(model);
|
|
533
|
-
}
|
|
534
|
-
return pool;
|
|
535
|
-
}
|
|
536
|
-
|
|
537
603
|
/** Resolve every known role: configured values first, auto-selection for the rest. */
|
|
538
604
|
#reloadRoles(autoCandidates: ReadonlyArray<Model>): void {
|
|
539
605
|
const allModels = this.#scopedModels.length > 0 ? autoCandidates : this.#registry.getAll("all");
|
|
@@ -552,7 +618,10 @@ export class ModelHubComponent implements Component {
|
|
|
552
618
|
if (this.#scopedModels.length > 0) {
|
|
553
619
|
this.#configError = undefined;
|
|
554
620
|
try {
|
|
555
|
-
allModels =
|
|
621
|
+
allModels = scopedRolePool(
|
|
622
|
+
this.#scopedModels.map(scoped => scoped.model),
|
|
623
|
+
this.#registry,
|
|
624
|
+
);
|
|
556
625
|
} catch (error) {
|
|
557
626
|
this.#configError = error instanceof Error ? error.message : String(error);
|
|
558
627
|
allModels = this.#scopedModels.map(scoped => scoped.model);
|
|
@@ -587,6 +656,7 @@ export class ModelHubComponent implements Component {
|
|
|
587
656
|
if (item) this.#recentItems.push(item);
|
|
588
657
|
if (this.#recentItems.length >= RECENT_LIMIT) break;
|
|
589
658
|
}
|
|
659
|
+
this.#recentSelectors = new Set(this.#recentItems.map(item => item.selector));
|
|
590
660
|
|
|
591
661
|
this.#buildSidebar(allModels, availableModels);
|
|
592
662
|
this.#restoreSidebarAnchor(anchor);
|
|
@@ -823,11 +893,13 @@ export class ModelHubComponent implements Component {
|
|
|
823
893
|
? items.filter(item => this.#settings.getRoleInfo(assigning.role).accepts(item.model))
|
|
824
894
|
: items;
|
|
825
895
|
this.#candidateItems = [...scoped];
|
|
896
|
+
this.#candidateCatalog = scoped === this.#availableItems ? this.#availableItems : undefined;
|
|
826
897
|
this.#applyModelKind();
|
|
827
898
|
}
|
|
828
899
|
|
|
829
900
|
#applyModelKind(): void {
|
|
830
901
|
const kind = this.#modelKindTab;
|
|
902
|
+
this.#browserCatalog = kind === "all" ? this.#candidateCatalog : undefined;
|
|
831
903
|
this.#browser.setItems(
|
|
832
904
|
kind === "all"
|
|
833
905
|
? [...this.#candidateItems]
|
|
@@ -936,16 +1008,14 @@ export class ModelHubComponent implements Component {
|
|
|
936
1008
|
this.#composeEntries();
|
|
937
1009
|
return;
|
|
938
1010
|
}
|
|
939
|
-
const matches =
|
|
1011
|
+
const matches = this.#catalogMatches(query);
|
|
940
1012
|
const counts = new Map<string, number>();
|
|
1013
|
+
let recentCount = 0;
|
|
941
1014
|
for (const item of matches) {
|
|
942
1015
|
counts.set(item.provider, (counts.get(item.provider) ?? 0) + 1);
|
|
1016
|
+
if (this.#recentSelectors.has(item.selector)) recentCount++;
|
|
943
1017
|
}
|
|
944
|
-
|
|
945
|
-
this.#recentSearchCount = matches.reduce(
|
|
946
|
-
(total, item) => total + (recentSelectors.has(item.selector) ? 1 : 0),
|
|
947
|
-
0,
|
|
948
|
-
);
|
|
1018
|
+
this.#recentSearchCount = recentCount;
|
|
949
1019
|
this.#searchTotal = matches.length;
|
|
950
1020
|
this.#searchCounts = counts;
|
|
951
1021
|
this.#composeEntries();
|
|
@@ -959,6 +1029,24 @@ export class ModelHubComponent implements Component {
|
|
|
959
1029
|
}
|
|
960
1030
|
}
|
|
961
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
|
+
|
|
962
1050
|
/**
|
|
963
1051
|
* Entries the scope hop skips: separators always; while searching, also
|
|
964
1052
|
* the Roles view (not a model scope), an empty Recent, locked providers,
|
|
@@ -1149,7 +1237,13 @@ export class ModelHubComponent implements Component {
|
|
|
1149
1237
|
#roleForScope(role: string, scope: ModelRoleSelectionScope): ResolvedModelRoleValue {
|
|
1150
1238
|
const roleValue =
|
|
1151
1239
|
scope === "project" ? this.#settings.getProjectModelRole(role) : this.#settings.getGlobalModelRole(role);
|
|
1152
|
-
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");
|
|
1153
1247
|
const roleLookup: ModelRoleLookup = {
|
|
1154
1248
|
getModelRole: scopedRole =>
|
|
1155
1249
|
scope === "project"
|
|
@@ -1731,10 +1825,46 @@ export class ModelHubComponent implements Component {
|
|
|
1731
1825
|
this.#strip = { kind: "name", purpose, input: new Input() };
|
|
1732
1826
|
}
|
|
1733
1827
|
|
|
1734
|
-
/**
|
|
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. */
|
|
1735
1847
|
#submitNameStrip(): void {
|
|
1736
1848
|
const strip = this.#strip;
|
|
1737
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
|
+
}
|
|
1738
1868
|
if (strip.purpose === "preset") {
|
|
1739
1869
|
this.#submitPresetName();
|
|
1740
1870
|
return;
|
|
@@ -1916,7 +2046,13 @@ export class ModelHubComponent implements Component {
|
|
|
1916
2046
|
|
|
1917
2047
|
if (rolesView) {
|
|
1918
2048
|
const printable = extractPrintableText(data);
|
|
1919
|
-
|
|
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) {
|
|
1920
2056
|
this.#setActiveEntry("all");
|
|
1921
2057
|
this.#focus = "list";
|
|
1922
2058
|
this.#browser.handleInput(data);
|
|
@@ -1993,6 +2129,7 @@ export class ModelHubComponent implements Component {
|
|
|
1993
2129
|
return;
|
|
1994
2130
|
}
|
|
1995
2131
|
strip.input.handleInput(data);
|
|
2132
|
+
if (strip.purpose === "compaction") strip.error = undefined;
|
|
1996
2133
|
return;
|
|
1997
2134
|
}
|
|
1998
2135
|
if (moveStripSelection(strip, data)) return;
|
|
@@ -2156,6 +2293,9 @@ export class ModelHubComponent implements Component {
|
|
|
2156
2293
|
case "prevPreset":
|
|
2157
2294
|
this.#switchPreset(action === "nextPreset" ? 1 : -1);
|
|
2158
2295
|
return;
|
|
2296
|
+
case "compaction":
|
|
2297
|
+
this.#openCompactionStrip();
|
|
2298
|
+
return;
|
|
2159
2299
|
case "thinking":
|
|
2160
2300
|
if (role) {
|
|
2161
2301
|
const target = this.#roleThinkingTarget(role);
|
|
@@ -2355,9 +2495,14 @@ export class ModelHubComponent implements Component {
|
|
|
2355
2495
|
Math.max(0, active),
|
|
2356
2496
|
);
|
|
2357
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"])}`;
|
|
2358
2503
|
const preset =
|
|
2359
2504
|
names.length > 0
|
|
2360
|
-
? ` ${theme.fg("dim", "Preset:")} ${activePreset ? theme.fg("accent", activePreset) : theme.fg("muted", "custom")} ${theme.fg("dim",
|
|
2505
|
+
? ` ${theme.fg("dim", "Preset:")} ${activePreset ? theme.fg("accent", activePreset) : theme.fg("muted", "custom")} ${theme.fg("dim", presetKeys)}`
|
|
2361
2506
|
: "";
|
|
2362
2507
|
return truncateToWidth(
|
|
2363
2508
|
` ${theme.fg("dim", "Roles:")} ${track} ${theme.fg("dim", formatKeyHints(["alt+left", "alt+right"]))}${preset}`,
|
|
@@ -2631,6 +2776,9 @@ export class ModelHubComponent implements Component {
|
|
|
2631
2776
|
const strip = this.#strip;
|
|
2632
2777
|
if (strip) {
|
|
2633
2778
|
if (strip.kind === "name") {
|
|
2779
|
+
if (strip.purpose === "compaction") {
|
|
2780
|
+
return `${enter} ${compactionConfirmPending(strip) ? "accept" : "set"} compaction point · ${cancel} cancel`;
|
|
2781
|
+
}
|
|
2634
2782
|
if (strip.purpose === "preset") {
|
|
2635
2783
|
return `${enter} save preset · ${cancel} cancel`;
|
|
2636
2784
|
}
|
|
@@ -2661,14 +2809,22 @@ export class ModelHubComponent implements Component {
|
|
|
2661
2809
|
this.#presets().names.length > 0 ? ` · ${formatKeyHints(["ctrl+left", "ctrl+right"])} preset` : "";
|
|
2662
2810
|
return `${upDown} providers · ${enterRight} roles · ${altLeftRight} tabs${presets} · ${cancel} close`;
|
|
2663
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`;
|
|
2815
|
+
}
|
|
2664
2816
|
const row = this.#rolesRows[this.#roleIndex];
|
|
2817
|
+
const compaction =
|
|
2818
|
+
this.#callbacks.onCompactionPointChange && this.#roleRowModel()
|
|
2819
|
+
? ` · ${formatKeyHint("k")} compaction limit`
|
|
2820
|
+
: "";
|
|
2665
2821
|
if (row?.kind === "fallback") {
|
|
2666
2822
|
// Advertise `t` only when the entry resolves: wildcards always
|
|
2667
2823
|
// inherit and unknown models have no ladder to offer, so the
|
|
2668
2824
|
// action would be inert there.
|
|
2669
2825
|
const editable = this.#resolveFallbackEntry(row.role, row.chainIndex) !== undefined;
|
|
2670
2826
|
const thinking = editable ? ` · ${formatKeyHint("t")} thinking` : "";
|
|
2671
|
-
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`;
|
|
2672
2828
|
}
|
|
2673
2829
|
if (row?.kind === "chainKey") {
|
|
2674
2830
|
return `${upDown} rows · ${formatKeyHints(["enter", "f"])} add fallback · ${formatKeyHint("x")} clear chain · ${left} providers`;
|
|
@@ -2682,7 +2838,7 @@ export class ModelHubComponent implements Component {
|
|
|
2682
2838
|
const thinking = editable ? ` · ${formatKeyHint("t")} thinking` : "";
|
|
2683
2839
|
const savePreset = this.#callbacks.onSavePreset ? ` · ${formatKeyHint("s")} save preset` : "";
|
|
2684
2840
|
const switchPreset = this.#presets().names.length > 0 ? ` · ${formatKeyHints(["p", "shift+p"])} preset` : "";
|
|
2685
|
-
return `${upDown} rows · ${enter} pick · ${formatKeyHint("f")} fallback · ${formatKeyHint("x")} clear${thinking} · ${formatKeyHint("c")} cycle · [/] reorder · ${formatKeyHint("n")} new${savePreset}${switchPreset}`;
|
|
2841
|
+
return `${upDown} rows · ${enter} pick · ${formatKeyHint("f")} fallback · ${formatKeyHint("x")} clear${thinking}${compaction} · ${formatKeyHint("c")} cycle · [/] reorder · ${formatKeyHint("n")} new${savePreset}${switchPreset}`;
|
|
2686
2842
|
}
|
|
2687
2843
|
if (entry.kind === "provider" && entry.locked) {
|
|
2688
2844
|
return entry.oauth
|
|
@@ -2707,12 +2863,26 @@ export class ModelHubComponent implements Component {
|
|
|
2707
2863
|
|
|
2708
2864
|
#renderStrip(width: number, strip: StripState): string {
|
|
2709
2865
|
if (strip.kind === "name") {
|
|
2710
|
-
const
|
|
2711
|
-
|
|
2866
|
+
const labelText =
|
|
2867
|
+
strip.purpose === "compaction"
|
|
2868
|
+
? `${strip.model.id} limit:`
|
|
2869
|
+
: strip.purpose === "preset"
|
|
2870
|
+
? "Preset name:"
|
|
2871
|
+
: "New role name:";
|
|
2712
2872
|
const label = theme.fg("accent", labelText);
|
|
2713
|
-
|
|
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));
|
|
2714
2877
|
const inputLine = strip.input.render(inputWidth)[0] ?? "";
|
|
2715
|
-
|
|
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);
|
|
2716
2886
|
}
|
|
2717
2887
|
|
|
2718
2888
|
const prefix =
|
|
@@ -3134,7 +3304,9 @@ export class ModelHubComponent implements Component {
|
|
|
3134
3304
|
size: "lg",
|
|
3135
3305
|
layout: "rows",
|
|
3136
3306
|
preview: "side",
|
|
3137
|
-
|
|
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,
|
|
3138
3310
|
cursor: this.#browser.cursor,
|
|
3139
3311
|
placeholder: "Search models…",
|
|
3140
3312
|
scopes: this.#pickerScopes(),
|
|
@@ -3363,12 +3535,19 @@ export class ModelHubComponent implements Component {
|
|
|
3363
3535
|
if (strip) {
|
|
3364
3536
|
const apply =
|
|
3365
3537
|
strip.kind === "name"
|
|
3366
|
-
?
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
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
|
+
)
|
|
3372
3551
|
: pickerAction(
|
|
3373
3552
|
"stripApply",
|
|
3374
3553
|
strip.kind === "thinking" ? "Apply" : strip.kind === "scope" ? "Save to scope" : "Assign / clear",
|
|
@@ -3390,9 +3569,20 @@ export class ModelHubComponent implements Component {
|
|
|
3390
3569
|
}
|
|
3391
3570
|
if (rolesView) {
|
|
3392
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;
|
|
3393
3574
|
const roleAction = (action: RolesAction, label: string, key: string, primary = false) =>
|
|
3394
|
-
pickerAction(
|
|
3575
|
+
pickerAction(
|
|
3576
|
+
`roles:${action}`,
|
|
3577
|
+
label,
|
|
3578
|
+
searching && key !== "enter" ? undefined : key,
|
|
3579
|
+
primary ? { primary: true } : undefined,
|
|
3580
|
+
);
|
|
3395
3581
|
const actions: (TspPickerAction | undefined)[] = [];
|
|
3582
|
+
const compaction =
|
|
3583
|
+
this.#callbacks.onCompactionPointChange && this.#roleRowModel()
|
|
3584
|
+
? roleAction("compaction", "Compaction limit", "k")
|
|
3585
|
+
: undefined;
|
|
3396
3586
|
switch (row?.kind) {
|
|
3397
3587
|
case "role": {
|
|
3398
3588
|
const assigned = this.#roles[row.role];
|
|
@@ -3401,6 +3591,7 @@ export class ModelHubComponent implements Component {
|
|
|
3401
3591
|
roleAction("fallback", "Add fallback", "f"),
|
|
3402
3592
|
assigned && !assigned.autoSelected ? roleAction("clear", "Clear", "x") : undefined,
|
|
3403
3593
|
this.#roleThinkingTarget(row.role) ? roleAction("thinking", "Thinking", "t") : undefined,
|
|
3594
|
+
compaction,
|
|
3404
3595
|
roleAction("cycle", this.#cycleOrder().includes(row.role) ? "Leave cycle" : "Add to cycle", "c"),
|
|
3405
3596
|
roleAction("new", "New role", "n"),
|
|
3406
3597
|
this.#callbacks.onSavePreset ? roleAction("save", "Save preset", "s") : undefined,
|
|
@@ -3416,6 +3607,7 @@ export class ModelHubComponent implements Component {
|
|
|
3416
3607
|
this.#resolveFallbackEntry(row.role, row.chainIndex)
|
|
3417
3608
|
? roleAction("thinking", "Thinking", "t")
|
|
3418
3609
|
: undefined,
|
|
3610
|
+
compaction,
|
|
3419
3611
|
roleAction("earlier", "Earlier", "["),
|
|
3420
3612
|
roleAction("later", "Later", "]"),
|
|
3421
3613
|
);
|
|
@@ -3452,6 +3644,19 @@ export class ModelHubComponent implements Component {
|
|
|
3452
3644
|
/** The open strip as the picker's chip strip (role assignment, save scope, thinking level, new role name). */
|
|
3453
3645
|
#pickerStrip(strip: StripState): NonNullable<TspPickerProps["strip"]> {
|
|
3454
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
|
+
}
|
|
3455
3660
|
return {
|
|
3456
3661
|
label: [
|
|
3457
3662
|
span(strip.purpose === "preset" ? "Preset name " : "New role name ", "muted"),
|
|
@@ -3591,7 +3796,14 @@ export class ModelHubComponent implements Component {
|
|
|
3591
3796
|
/** The selected Roles-view row's preview: its model's facts and its fallback chain. */
|
|
3592
3797
|
#rolePreview(row: RolesRow | undefined): readonly NativeChild[] {
|
|
3593
3798
|
const memo = this.#pickerRolePreview;
|
|
3594
|
-
|
|
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
|
+
) {
|
|
3595
3807
|
return memo.children;
|
|
3596
3808
|
}
|
|
3597
3809
|
const children: NativeChild[] = [];
|
|
@@ -3665,7 +3877,7 @@ export class ModelHubComponent implements Component {
|
|
|
3665
3877
|
);
|
|
3666
3878
|
break;
|
|
3667
3879
|
}
|
|
3668
|
-
this.#pickerRolePreview = { row, roles: this.#roles, rows: this.#rolesRows, children };
|
|
3880
|
+
this.#pickerRolePreview = { row, roles: this.#roles, rows: this.#rolesRows, revision, children };
|
|
3669
3881
|
return children;
|
|
3670
3882
|
}
|
|
3671
3883
|
|
|
@@ -3765,6 +3977,9 @@ export class ModelHubComponent implements Component {
|
|
|
3765
3977
|
case "presetName":
|
|
3766
3978
|
this.#submitPresetName();
|
|
3767
3979
|
return;
|
|
3980
|
+
case "compactionPoint":
|
|
3981
|
+
this.#submitNameStrip();
|
|
3982
|
+
return;
|
|
3768
3983
|
case "cancel":
|
|
3769
3984
|
if (this.#strip) this.#closeStrip();
|
|
3770
3985
|
return;
|
|
@@ -3815,6 +4030,19 @@ export class ModelHubComponent implements Component {
|
|
|
3815
4030
|
const strip = this.#strip;
|
|
3816
4031
|
if (!strip) return undefined;
|
|
3817
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
|
+
}
|
|
3818
4046
|
const preset = strip.purpose === "preset";
|
|
3819
4047
|
return node(
|
|
3820
4048
|
"row",
|
|
@@ -3868,9 +4096,17 @@ export class ModelHubComponent implements Component {
|
|
|
3868
4096
|
if (strip) {
|
|
3869
4097
|
switch (strip.kind) {
|
|
3870
4098
|
case "name":
|
|
3871
|
-
return strip.purpose === "
|
|
3872
|
-
? [
|
|
3873
|
-
|
|
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")];
|
|
3874
4110
|
case "role":
|
|
3875
4111
|
return [keys("choose", "left", "right"), keys("assign/clear", "enter"), cancel("cancel")];
|
|
3876
4112
|
case "scope":
|
|
@@ -3903,7 +4139,20 @@ export class ModelHubComponent implements Component {
|
|
|
3903
4139
|
cancel("close"),
|
|
3904
4140
|
];
|
|
3905
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,
|
|
4150
|
+
cancel("close"),
|
|
4151
|
+
];
|
|
4152
|
+
}
|
|
3906
4153
|
const row = this.#rolesRows[this.#roleIndex];
|
|
4154
|
+
const compaction =
|
|
4155
|
+
this.#callbacks.onCompactionPointChange && this.#roleRowModel() ? keys("compaction limit", "k") : undefined;
|
|
3907
4156
|
if (row?.kind === "fallback") {
|
|
3908
4157
|
// Advertise `t` only where a strip would open, as the ANSI footer does.
|
|
3909
4158
|
const editable = this.#resolveFallbackEntry(row.role, row.chainIndex) !== undefined;
|
|
@@ -3913,6 +4162,7 @@ export class ModelHubComponent implements Component {
|
|
|
3913
4162
|
keys("add another", "f"),
|
|
3914
4163
|
keys("remove", "x"),
|
|
3915
4164
|
editable ? keys("thinking", "t") : undefined,
|
|
4165
|
+
compaction,
|
|
3916
4166
|
reorder,
|
|
3917
4167
|
keys("providers", "left"),
|
|
3918
4168
|
];
|
|
@@ -3935,6 +4185,7 @@ export class ModelHubComponent implements Component {
|
|
|
3935
4185
|
keys("fallback", "f"),
|
|
3936
4186
|
keys("clear", "x"),
|
|
3937
4187
|
editable ? keys("thinking", "t") : undefined,
|
|
4188
|
+
compaction,
|
|
3938
4189
|
keys("cycle", "c"),
|
|
3939
4190
|
reorder,
|
|
3940
4191
|
keys("new", "n"),
|