@pellux/goodvibes-tui 1.0.0 → 1.7.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 +130 -127
- package/README.md +32 -14
- package/docs/foundation-artifacts/operator-contract.json +1 -1
- package/package.json +2 -2
- package/src/cli/ensure-goodvibes-gitignore.ts +32 -0
- package/src/cli/entrypoint.ts +2 -0
- package/src/core/alert-gating.ts +67 -0
- package/src/core/approval-alert.ts +72 -0
- package/src/core/budget-breach-notifier.ts +106 -0
- package/src/core/conversation-rendering.ts +19 -0
- package/src/core/conversation.ts +58 -0
- package/src/core/focus-tracker.ts +41 -0
- package/src/core/long-task-notifier.ts +33 -6
- package/src/core/stream-event-wiring.ts +104 -2
- package/src/core/stream-stall-watchdog.ts +41 -17
- package/src/core/system-message-router.ts +37 -51
- package/src/core/turn-cancellation.ts +20 -0
- package/src/core/turn-event-wiring.ts +64 -3
- package/src/export/cost-utils.ts +126 -8
- package/src/input/command-registry.ts +54 -1
- package/src/input/commands/checkpoint-runtime.ts +280 -0
- package/src/input/commands/codebase-runtime.ts +192 -0
- package/src/input/commands/diff-runtime.ts +61 -30
- package/src/input/commands/eval.ts +1 -1
- package/src/input/commands/health-runtime.ts +1 -1
- package/src/input/commands/image-runtime.ts +112 -0
- package/src/input/commands/intelligence-runtime.ts +11 -1
- package/src/input/commands/local-auth-runtime.ts +4 -1
- package/src/input/commands/local-runtime.ts +9 -3
- package/src/input/commands/marketplace-runtime.ts +2 -2
- package/src/input/commands/memory.ts +6 -1
- package/src/input/commands/operator-panel-runtime.ts +40 -24
- package/src/input/commands/planning-runtime.ts +26 -3
- package/src/input/commands/platform-sandbox-runtime.ts +1 -6
- package/src/input/commands/plugin-runtime.ts +2 -2
- package/src/input/commands/policy-dispatch.ts +21 -0
- package/src/input/commands/provider-accounts-runtime.ts +1 -1
- package/src/input/commands/qrcode-runtime.ts +3 -3
- package/src/input/commands/recall-review.ts +35 -0
- package/src/input/commands/remote-runtime.ts +3 -3
- package/src/input/commands/runtime-services.ts +54 -13
- package/src/input/commands/services-runtime.ts +1 -1
- package/src/input/commands/session-content.ts +12 -0
- package/src/input/commands/session-workflow.ts +19 -1
- package/src/input/commands/settings-sync-runtime.ts +1 -1
- package/src/input/commands/share-runtime.ts +9 -2
- package/src/input/commands/shell-core.ts +15 -7
- package/src/input/commands/skills-runtime.ts +2 -8
- package/src/input/commands/subscription-runtime.ts +2 -2
- package/src/input/commands/test-runtime.ts +277 -0
- package/src/input/commands/websearch-runtime.ts +101 -0
- package/src/input/commands/work-plan-runtime.ts +36 -10
- package/src/input/commands/workstream-runtime.ts +338 -0
- package/src/input/commands.ts +12 -0
- package/src/input/config-modal-types.ts +161 -0
- package/src/input/config-modal.ts +377 -0
- package/src/input/feed-context-factory.ts +7 -8
- package/src/input/handler-command-route.ts +27 -17
- package/src/input/handler-content-actions.ts +22 -8
- package/src/input/handler-feed-routes.ts +107 -7
- package/src/input/handler-feed.ts +49 -13
- package/src/input/handler-interactions.ts +3 -3
- package/src/input/handler-modal-routes.ts +65 -0
- package/src/input/handler-modal-stack.ts +3 -5
- package/src/input/handler-modal-token-routes.ts +16 -49
- package/src/input/handler-picker-routes.ts +11 -94
- package/src/input/handler-shortcuts.ts +24 -14
- package/src/input/handler-types.ts +3 -6
- package/src/input/handler-ui-state.ts +10 -22
- package/src/input/handler.ts +10 -28
- package/src/input/keybindings.ts +22 -8
- package/src/input/model-picker-types.ts +24 -6
- package/src/input/model-picker.ts +58 -0
- package/src/input/panel-integration-actions.ts +9 -170
- package/src/input/settings-modal-data.ts +95 -0
- package/src/input/settings-modal-mutations.ts +6 -4
- package/src/main.ts +41 -44
- package/src/panels/agent-inspector-shared.ts +35 -11
- package/src/panels/base-panel.ts +22 -1
- package/src/panels/builtin/agent.ts +21 -107
- package/src/panels/builtin/development.ts +16 -62
- package/src/panels/builtin/knowledge.ts +8 -32
- package/src/panels/builtin/operations.ts +84 -428
- package/src/panels/builtin/session.ts +21 -112
- package/src/panels/builtin/shared.ts +9 -43
- package/src/panels/builtin-modals.ts +218 -0
- package/src/panels/builtin-panels.ts +5 -0
- package/src/panels/cost-tracker-panel.ts +63 -14
- package/src/panels/diff-panel.ts +123 -60
- package/src/panels/eval-registry.ts +60 -0
- package/src/panels/fleet-panel.ts +800 -0
- package/src/panels/fleet-read-model.ts +477 -0
- package/src/panels/fleet-steer.ts +92 -0
- package/src/panels/fleet-stop.ts +125 -0
- package/src/panels/fleet-tabs.ts +230 -0
- package/src/panels/fleet-transcript.ts +356 -0
- package/src/panels/git-panel.ts +9 -9
- package/src/panels/index.ts +7 -31
- package/src/panels/local-auth-panel.ts +10 -0
- package/src/panels/modals/hooks-modal.ts +187 -0
- package/src/panels/modals/keybindings-modal.ts +151 -0
- package/src/panels/modals/knowledge-modal.ts +158 -0
- package/src/panels/modals/local-auth-modal.ts +132 -0
- package/src/panels/modals/marketplace-modal.ts +218 -0
- package/src/panels/modals/memory-modal.ts +180 -0
- package/src/panels/modals/modal-surface-helpers.ts +54 -0
- package/src/panels/modals/modal-theme.ts +36 -0
- package/src/panels/modals/pairing-modal.ts +144 -0
- package/src/panels/modals/planning-modal.ts +282 -0
- package/src/panels/modals/plugins-modal.ts +174 -0
- package/src/panels/modals/policy-modal.ts +256 -0
- package/src/panels/modals/provider-health-modal.ts +136 -0
- package/src/panels/modals/remote-modal.ts +115 -0
- package/src/panels/modals/sandbox-modal.ts +150 -0
- package/src/panels/modals/security-modal.ts +217 -0
- package/src/panels/modals/services-modal.ts +179 -0
- package/src/panels/modals/settings-sync-modal.ts +142 -0
- package/src/panels/modals/skills-modal.ts +189 -0
- package/src/panels/modals/subscription-modal.ts +172 -0
- package/src/panels/modals/work-plan-modal.ts +149 -0
- package/src/panels/panel-confirm-overlay.ts +72 -0
- package/src/panels/panel-manager.ts +108 -5
- package/src/panels/project-planning-answer-actions.ts +4 -2
- package/src/panels/scrollable-list-panel.ts +9 -0
- package/src/panels/skills-panel.ts +7 -51
- package/src/panels/token-budget-panel.ts +5 -3
- package/src/panels/types.ts +26 -0
- package/src/permissions/hunk-selection.ts +179 -0
- package/src/permissions/prompt.ts +60 -4
- package/src/renderer/compaction-history-modal.ts +19 -3
- package/src/renderer/compaction-preview.ts +13 -2
- package/src/renderer/compaction-quality.ts +100 -0
- package/src/renderer/config-modal.ts +81 -0
- package/src/renderer/conversation-overlays.ts +10 -17
- package/src/renderer/fleet-tab-strip.ts +56 -0
- package/src/renderer/footer-tips.ts +10 -2
- package/src/renderer/git-status.ts +40 -0
- package/src/renderer/help-overlay.ts +2 -2
- package/src/renderer/model-workspace.ts +44 -1
- package/src/renderer/panel-workspace-bar.ts +8 -2
- package/src/renderer/shell-surface.ts +8 -1
- package/src/renderer/turn-injection.ts +114 -0
- package/src/renderer/ui-factory.ts +91 -7
- package/src/runtime/bootstrap-command-context.ts +24 -1
- package/src/runtime/bootstrap-command-parts.ts +36 -7
- package/src/runtime/bootstrap-core.ts +19 -4
- package/src/runtime/bootstrap-hook-bridge.ts +5 -0
- package/src/runtime/bootstrap-shell.ts +41 -17
- package/src/runtime/bootstrap.ts +11 -17
- package/src/runtime/code-index-services.ts +112 -0
- package/src/runtime/orchestrator-core-services.ts +49 -0
- package/src/runtime/process-lifecycle.ts +3 -1
- package/src/runtime/services.ts +91 -43
- package/src/runtime/ui-services.ts +8 -0
- package/src/runtime/workstream-services.ts +195 -0
- package/src/shell/blocking-input.ts +22 -1
- package/src/shell/ui-openers.ts +129 -17
- package/src/utils/format-duration.ts +8 -18
- package/src/utils/splash-lines.ts +1 -1
- package/src/version.ts +1 -1
- package/src/panels/agent-inspector-panel.ts +0 -786
- package/src/panels/approval-panel.ts +0 -252
- package/src/panels/automation-control-panel.ts +0 -479
- package/src/panels/cockpit-panel.ts +0 -481
- package/src/panels/cockpit-read-model.ts +0 -233
- package/src/panels/communication-panel.ts +0 -256
- package/src/panels/control-plane-panel.ts +0 -470
- package/src/panels/debug-panel.ts +0 -609
- package/src/panels/docs-panel.ts +0 -375
- package/src/panels/eval-panel.ts +0 -627
- package/src/panels/file-explorer-panel.ts +0 -664
- package/src/panels/file-preview-panel.ts +0 -517
- package/src/panels/hooks-panel.ts +0 -401
- package/src/panels/incident-review-panel.ts +0 -406
- package/src/panels/intelligence-panel.ts +0 -383
- package/src/panels/knowledge-graph-panel.ts +0 -506
- package/src/panels/marketplace-panel.ts +0 -399
- package/src/panels/memory-panel.ts +0 -558
- package/src/panels/ops-control-panel.ts +0 -329
- package/src/panels/ops-strategy-panel.ts +0 -321
- package/src/panels/orchestration-panel.ts +0 -465
- package/src/panels/panel-list-panel.ts +0 -557
- package/src/panels/plan-dashboard-panel.ts +0 -707
- package/src/panels/policy-panel.ts +0 -517
- package/src/panels/project-planning-panel.ts +0 -721
- package/src/panels/provider-health-panel.ts +0 -678
- package/src/panels/provider-health-tracker.ts +0 -306
- package/src/panels/provider-health-views.ts +0 -560
- package/src/panels/qr-panel.ts +0 -280
- package/src/panels/remote-panel.ts +0 -534
- package/src/panels/routes-panel.ts +0 -241
- package/src/panels/sandbox-panel.ts +0 -456
- package/src/panels/security-panel.ts +0 -447
- package/src/panels/services-panel.ts +0 -329
- package/src/panels/session-browser-panel.ts +0 -487
- package/src/panels/settings-sync-panel.ts +0 -398
- package/src/panels/subscription-panel.ts +0 -342
- package/src/panels/symbol-outline-panel.ts +0 -619
- package/src/panels/system-messages-panel.ts +0 -364
- package/src/panels/tasks-panel.ts +0 -608
- package/src/panels/thinking-panel.ts +0 -333
- package/src/panels/tool-inspector-panel.ts +0 -537
- package/src/panels/work-plan-panel.ts +0 -530
- package/src/panels/worktree-panel.ts +0 -360
- package/src/panels/wrfc-panel.ts +0 -790
- package/src/renderer/agent-detail-modal.ts +0 -465
- package/src/renderer/live-tail-modal.ts +0 -156
- package/src/renderer/process-modal.ts +0 -656
- package/src/renderer/qr-renderer.ts +0 -120
|
@@ -5,7 +5,6 @@ import type { CapabilityFilter, CategoryFilter, ModelPickerModal } from './model
|
|
|
5
5
|
import { MODEL_PICKER_CHROME_LINES } from '../renderer/model-picker-overlay.ts';
|
|
6
6
|
import { resolveAndValidatePath } from '@pellux/goodvibes-sdk/platform/utils';
|
|
7
7
|
import { logger } from '@pellux/goodvibes-sdk/platform/utils';
|
|
8
|
-
import type { ProcessEntry } from '../renderer/process-modal.ts';
|
|
9
8
|
import type { BlockActionId } from '../renderer/block-actions.ts';
|
|
10
9
|
|
|
11
10
|
type ModelPickerRouteState = {
|
|
@@ -93,6 +92,13 @@ export function handleModelPickerToken(state: ModelPickerRouteState, token: Inpu
|
|
|
93
92
|
}
|
|
94
93
|
state.modelPicker.close();
|
|
95
94
|
if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
|
|
95
|
+
} else if (mode === 'embeddingProvider') {
|
|
96
|
+
const selectedProvider = state.modelPicker.embeddingProviders[idx];
|
|
97
|
+
if (selectedProvider) {
|
|
98
|
+
state.commandContext?.completeEmbeddingProviderSelection?.(selectedProvider.id);
|
|
99
|
+
}
|
|
100
|
+
state.modelPicker.close();
|
|
101
|
+
if (state.modalStack[state.modalStack.length - 1] === 'modelPicker') state.modalStack.pop();
|
|
96
102
|
} else if (mode === 'contextCap') {
|
|
97
103
|
const capModel = state.modelPicker.contextCapPendingModel;
|
|
98
104
|
if (capModel) {
|
|
@@ -201,99 +207,10 @@ function cycleCapabilityFilter(modelPicker: ModelPickerModal): void {
|
|
|
201
207
|
modelPicker.setCapabilityFilter(cycle[(cur + 1) % cycle.length]!);
|
|
202
208
|
}
|
|
203
209
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
moveDown: () => void;
|
|
209
|
-
getSelected: () => ProcessEntry | undefined;
|
|
210
|
-
close: () => void;
|
|
211
|
-
open: () => void;
|
|
212
|
-
killSelected: () => boolean;
|
|
213
|
-
refresh: () => void;
|
|
214
|
-
};
|
|
215
|
-
liveTailModal: {
|
|
216
|
-
open: (entry: ProcessEntry) => void;
|
|
217
|
-
};
|
|
218
|
-
agentDetailModal: {
|
|
219
|
-
open: (id: string) => void;
|
|
220
|
-
};
|
|
221
|
-
modalOpened: (name: string) => void;
|
|
222
|
-
requestRender: () => void;
|
|
223
|
-
handleEscape: () => void;
|
|
224
|
-
};
|
|
225
|
-
|
|
226
|
-
export function handleProcessModalToken(state: ProcessRouteState, token: InputToken): boolean {
|
|
227
|
-
if (!state.processModal.active) return false;
|
|
228
|
-
|
|
229
|
-
if (token.type === 'key') {
|
|
230
|
-
if (token.logicalName === 'escape') {
|
|
231
|
-
state.handleEscape();
|
|
232
|
-
return true;
|
|
233
|
-
}
|
|
234
|
-
if (token.logicalName === 'up') state.processModal.moveUp();
|
|
235
|
-
else if (token.logicalName === 'down') state.processModal.moveDown();
|
|
236
|
-
else if (token.logicalName === 'enter') {
|
|
237
|
-
const entry = state.processModal.getSelected();
|
|
238
|
-
if (entry) {
|
|
239
|
-
if (entry.type === 'agent') {
|
|
240
|
-
state.modalOpened('agentDetail');
|
|
241
|
-
state.processModal.close();
|
|
242
|
-
state.agentDetailModal.open(entry.id);
|
|
243
|
-
} else {
|
|
244
|
-
state.modalOpened('liveTail');
|
|
245
|
-
state.processModal.close();
|
|
246
|
-
state.liveTailModal.open(entry);
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
} else if (token.type === 'text' && token.value === 'k') {
|
|
251
|
-
const killed = state.processModal.killSelected();
|
|
252
|
-
if (killed) state.processModal.refresh();
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
state.requestRender();
|
|
256
|
-
return true;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
type LiveTailRouteState = {
|
|
260
|
-
liveTailModal: {
|
|
261
|
-
active: boolean;
|
|
262
|
-
scrollUp: () => void;
|
|
263
|
-
scrollDown: () => void;
|
|
264
|
-
killProcess: () => void;
|
|
265
|
-
close: () => void;
|
|
266
|
-
};
|
|
267
|
-
processModal: {
|
|
268
|
-
open: () => void;
|
|
269
|
-
};
|
|
270
|
-
requestRender: () => void;
|
|
271
|
-
handleEscape: () => void;
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
export function handleLiveTailToken(state: LiveTailRouteState, token: InputToken): boolean {
|
|
275
|
-
if (!state.liveTailModal.active) return false;
|
|
276
|
-
|
|
277
|
-
const killAndReturn = (): void => {
|
|
278
|
-
state.liveTailModal.killProcess();
|
|
279
|
-
state.handleEscape();
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
if (token.type === 'key') {
|
|
283
|
-
if (token.logicalName === 'escape') {
|
|
284
|
-
state.handleEscape();
|
|
285
|
-
return true;
|
|
286
|
-
}
|
|
287
|
-
if (token.logicalName === 'up') state.liveTailModal.scrollUp();
|
|
288
|
-
else if (token.logicalName === 'down') state.liveTailModal.scrollDown();
|
|
289
|
-
else if (token.logicalName === 'k') killAndReturn();
|
|
290
|
-
} else if (token.type === 'text' && token.value === 'k') {
|
|
291
|
-
killAndReturn();
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
state.requestRender();
|
|
295
|
-
return true;
|
|
296
|
-
}
|
|
210
|
+
// W6.2 e / W6.1 retirement: the process-modal and live-tail-modal token routes
|
|
211
|
+
// were removed with those modals (F2 now opens Fleet, which subsumes the live
|
|
212
|
+
// process tree). ProcessModal/AgentDetailModal/LiveTailModal were structurally
|
|
213
|
+
// unreachable after the F2 repoint and were deleted.
|
|
297
214
|
|
|
298
215
|
type EscapeOnlyModalRouteState = {
|
|
299
216
|
active: boolean;
|
|
@@ -55,13 +55,16 @@ export function handleGlobalShortcutToken(
|
|
|
55
55
|
): boolean {
|
|
56
56
|
if (token.type !== 'key') return false;
|
|
57
57
|
|
|
58
|
-
// Fast-path:
|
|
59
|
-
|
|
58
|
+
// Fast-path: BARE pageup/pagedown scroll the transcript. The `!token.ctrl`
|
|
59
|
+
// guard is load-bearing: Ctrl+PageUp/PageDown are the panel-tab-prev/next
|
|
60
|
+
// chords, so they must fall through to the keybinding lookup below instead of
|
|
61
|
+
// being swallowed here as a scroll.
|
|
62
|
+
if (token.logicalName === 'pageup' && !token.ctrl) {
|
|
60
63
|
if (state.panelFocused) return false;
|
|
61
64
|
state.scroll(-Math.max(1, viewportHeight - 2));
|
|
62
65
|
return true;
|
|
63
66
|
}
|
|
64
|
-
if (token.logicalName === 'pagedown') {
|
|
67
|
+
if (token.logicalName === 'pagedown' && !token.ctrl) {
|
|
65
68
|
if (state.panelFocused) return false;
|
|
66
69
|
state.scroll(Math.max(1, viewportHeight - 2));
|
|
67
70
|
return true;
|
|
@@ -101,6 +104,13 @@ export function handleGlobalShortcutToken(
|
|
|
101
104
|
case 'panel-close': {
|
|
102
105
|
const pm = state.panelManager;
|
|
103
106
|
const active = pm.getActivePanel();
|
|
107
|
+
// Wave-3: give the active panel a chance to consume Ctrl+X for an
|
|
108
|
+
// in-panel action (FleetPanel session-tab detach) before it closes the
|
|
109
|
+
// panel outright — see Panel.interceptPanelClose's doc comment.
|
|
110
|
+
if (active?.interceptPanelClose?.()) {
|
|
111
|
+
state.requestRender();
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
104
114
|
if (active) {
|
|
105
115
|
pm.close(active.id);
|
|
106
116
|
state.requestRender();
|
|
@@ -160,17 +170,17 @@ export function handleGlobalShortcutToken(
|
|
|
160
170
|
}
|
|
161
171
|
|
|
162
172
|
case 'panel-ops': {
|
|
163
|
-
// Ctrl+O: open the
|
|
164
|
-
//
|
|
165
|
-
//
|
|
166
|
-
//
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
173
|
+
// Ctrl+O: open AND focus the Fleet panel. The former Ops Control panel was
|
|
174
|
+
// retired to an 'ops-control' -> 'fleet' alias (W6.1); rather than route
|
|
175
|
+
// through the now-aliased openOpsPanel callback (which opens without
|
|
176
|
+
// transferring focus), open 'fleet' directly and grab focus — mirroring
|
|
177
|
+
// the footer indicator's [Enter] -> openFleetPanel path so j/k/i/K land in
|
|
178
|
+
// the panel immediately instead of the composer.
|
|
179
|
+
const pm = state.panelManager;
|
|
180
|
+
pm.open('fleet');
|
|
181
|
+
pm.focusPanels();
|
|
182
|
+
state.panelFocused = true;
|
|
183
|
+
state.requestRender();
|
|
174
184
|
return true;
|
|
175
185
|
}
|
|
176
186
|
|
|
@@ -23,14 +23,12 @@ import type { SelectionManager } from './selection.ts';
|
|
|
23
23
|
import type { InfiniteBuffer } from '../core/history.ts';
|
|
24
24
|
import type { AutocompleteEngine } from './autocomplete.ts';
|
|
25
25
|
import type { BookmarkModal } from './bookmark-modal.ts';
|
|
26
|
-
import type { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
|
|
27
|
-
import type { LiveTailModal } from '../renderer/live-tail-modal.ts';
|
|
28
26
|
import type { SettingsModal } from './settings-modal.ts';
|
|
29
27
|
import type { McpWorkspace } from './mcp-workspace.ts';
|
|
30
28
|
import type { SessionPickerModal } from './session-picker-modal.ts';
|
|
29
|
+
import type { ConfigModal } from './config-modal.ts';
|
|
31
30
|
import type { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
32
31
|
import type { ContextInspectorModal } from '../renderer/context-inspector.ts';
|
|
33
|
-
import type { ProcessModal } from '../renderer/process-modal.ts';
|
|
34
32
|
import type { FilePickerModal } from './file-picker.ts';
|
|
35
33
|
import type { BlockActionsMenu } from '../renderer/block-actions.ts';
|
|
36
34
|
import type { SelectionModal } from './selection-modal.ts';
|
|
@@ -88,6 +86,7 @@ export interface InputHandlerLike {
|
|
|
88
86
|
lastCopyTime: number;
|
|
89
87
|
lastBlockCopyTime: number;
|
|
90
88
|
lastCtrlCTime: number;
|
|
89
|
+
lastCtrlCTimeoutId: ReturnType<typeof setTimeout> | null;
|
|
91
90
|
|
|
92
91
|
// ── Modal state ───────────────────────────────────────────────────────────
|
|
93
92
|
commandMode: boolean;
|
|
@@ -104,14 +103,12 @@ export interface InputHandlerLike {
|
|
|
104
103
|
|
|
105
104
|
// ── Modal objects ─────────────────────────────────────────────────────────
|
|
106
105
|
bookmarkModal: BookmarkModal;
|
|
107
|
-
agentDetailModal: AgentDetailModal;
|
|
108
|
-
liveTailModal: LiveTailModal;
|
|
109
106
|
settingsModal: SettingsModal;
|
|
110
107
|
mcpWorkspace: McpWorkspace;
|
|
111
108
|
sessionPickerModal: SessionPickerModal;
|
|
112
109
|
profilePickerModal: ProfilePickerModal;
|
|
110
|
+
configModal: ConfigModal;
|
|
113
111
|
contextInspectorModal: ContextInspectorModal;
|
|
114
|
-
processModal: ProcessModal;
|
|
115
112
|
modelPicker: ModelPickerModal;
|
|
116
113
|
filePicker: FilePickerModal;
|
|
117
114
|
blockActionsMenu: BlockActionsMenu;
|
|
@@ -55,14 +55,12 @@ export type ActiveModalState = {
|
|
|
55
55
|
helpOverlayActive: boolean;
|
|
56
56
|
shortcutsOverlayActive: boolean;
|
|
57
57
|
bookmarkModal: { active: boolean; close: () => void };
|
|
58
|
-
agentDetailModal: { active: boolean; close: () => void };
|
|
59
|
-
liveTailModal: { active: boolean; close: () => void };
|
|
60
58
|
settingsModal: { active: boolean; close: () => void };
|
|
61
59
|
mcpWorkspace?: { active: boolean; close: () => void; reopen: () => void };
|
|
62
60
|
sessionPickerModal: { active: boolean; close: () => void };
|
|
63
61
|
profilePickerModal: { active: boolean; close: () => void };
|
|
62
|
+
configModal: { active: boolean; close: () => void };
|
|
64
63
|
contextInspectorModal: { active: boolean; close: () => void };
|
|
65
|
-
processModal: { active: boolean; close: () => void };
|
|
66
64
|
modelPicker: { active: boolean; close: () => void };
|
|
67
65
|
filePicker: { active: boolean; close: () => void };
|
|
68
66
|
blockActionsMenu: { active: boolean; close: () => void };
|
|
@@ -75,14 +73,12 @@ export function getActiveModalName(state: ActiveModalState): string | null {
|
|
|
75
73
|
if (state.helpOverlayActive) return 'help';
|
|
76
74
|
if (state.shortcutsOverlayActive) return 'shortcuts';
|
|
77
75
|
if (state.bookmarkModal.active) return 'bookmark';
|
|
78
|
-
if (state.agentDetailModal.active) return 'agentDetail';
|
|
79
|
-
if (state.liveTailModal.active) return 'liveTail';
|
|
80
76
|
if (state.settingsModal.active) return 'settings';
|
|
81
77
|
if (state.mcpWorkspace?.active) return 'mcpWorkspace';
|
|
82
78
|
if (state.sessionPickerModal.active) return 'sessionPicker';
|
|
83
79
|
if (state.profilePickerModal.active) return 'profilePicker';
|
|
80
|
+
if (state.configModal.active) return 'config';
|
|
84
81
|
if (state.contextInspectorModal.active) return 'contextInspector';
|
|
85
|
-
if (state.processModal.active) return 'process';
|
|
86
82
|
if (state.modelPicker.active) return 'modelPicker';
|
|
87
83
|
if (state.filePicker.active) return 'filePicker';
|
|
88
84
|
if (state.blockActionsMenu.active) return 'blockActions';
|
|
@@ -96,14 +92,12 @@ export type ModalCloseOps = {
|
|
|
96
92
|
resetHelp: () => void;
|
|
97
93
|
resetShortcuts: () => void;
|
|
98
94
|
closeBookmark: () => void;
|
|
99
|
-
closeAgentDetail: () => void;
|
|
100
|
-
closeLiveTail: () => void;
|
|
101
95
|
closeSettings: () => void;
|
|
102
96
|
closeMcpWorkspace: () => void;
|
|
103
97
|
closeSessionPicker: () => void;
|
|
104
98
|
closeProfilePicker: () => void;
|
|
99
|
+
closeConfigModal: () => void;
|
|
105
100
|
closeContextInspector: () => void;
|
|
106
|
-
closeProcess: () => void;
|
|
107
101
|
closeModelPicker: () => void;
|
|
108
102
|
closeFilePicker: () => void;
|
|
109
103
|
closeBlockActions: () => void;
|
|
@@ -123,12 +117,6 @@ export function closeModalByName(name: string, ops: ModalCloseOps): void {
|
|
|
123
117
|
case 'bookmark':
|
|
124
118
|
ops.closeBookmark();
|
|
125
119
|
break;
|
|
126
|
-
case 'agentDetail':
|
|
127
|
-
ops.closeAgentDetail();
|
|
128
|
-
break;
|
|
129
|
-
case 'liveTail':
|
|
130
|
-
ops.closeLiveTail();
|
|
131
|
-
break;
|
|
132
120
|
case 'settings':
|
|
133
121
|
ops.closeSettings();
|
|
134
122
|
break;
|
|
@@ -141,12 +129,12 @@ export function closeModalByName(name: string, ops: ModalCloseOps): void {
|
|
|
141
129
|
case 'profilePicker':
|
|
142
130
|
ops.closeProfilePicker();
|
|
143
131
|
break;
|
|
132
|
+
case 'config':
|
|
133
|
+
ops.closeConfigModal();
|
|
134
|
+
break;
|
|
144
135
|
case 'contextInspector':
|
|
145
136
|
ops.closeContextInspector();
|
|
146
137
|
break;
|
|
147
|
-
case 'process':
|
|
148
|
-
ops.closeProcess();
|
|
149
|
-
break;
|
|
150
138
|
case 'modelPicker':
|
|
151
139
|
ops.closeModelPicker();
|
|
152
140
|
break;
|
|
@@ -172,10 +160,10 @@ export type ModalOpenOps = {
|
|
|
172
160
|
openHelp: () => void;
|
|
173
161
|
openShortcuts: () => void;
|
|
174
162
|
openBookmark: () => void;
|
|
175
|
-
openProcess: () => void;
|
|
176
163
|
openContextInspector: () => void;
|
|
177
164
|
openMcpWorkspace?: () => void;
|
|
178
165
|
openOnboarding?: () => void;
|
|
166
|
+
openConfigModal?: () => void;
|
|
179
167
|
openCommandMode: () => void;
|
|
180
168
|
};
|
|
181
169
|
|
|
@@ -190,15 +178,15 @@ export function reopenModalByName(name: string, ops: ModalOpenOps): void {
|
|
|
190
178
|
case 'bookmark':
|
|
191
179
|
ops.openBookmark();
|
|
192
180
|
break;
|
|
193
|
-
case 'process':
|
|
194
|
-
ops.openProcess();
|
|
195
|
-
break;
|
|
196
181
|
case 'contextInspector':
|
|
197
182
|
ops.openContextInspector();
|
|
198
183
|
break;
|
|
199
184
|
case 'mcpWorkspace':
|
|
200
185
|
ops.openMcpWorkspace?.();
|
|
201
186
|
break;
|
|
187
|
+
case 'config':
|
|
188
|
+
ops.openConfigModal?.();
|
|
189
|
+
break;
|
|
202
190
|
case 'onboarding':
|
|
203
191
|
ops.openOnboarding?.();
|
|
204
192
|
break;
|
package/src/input/handler.ts
CHANGED
|
@@ -19,15 +19,13 @@ import type { SelectionResult, SelectionAction } from './selection-modal.ts';
|
|
|
19
19
|
import { SearchManager } from './search.ts';
|
|
20
20
|
import { InputHistory, HistorySearch } from './input-history.ts';
|
|
21
21
|
import type { BlockMeta, ConversationManager } from '../core/conversation';
|
|
22
|
-
import { ProcessModal } from '../renderer/process-modal.ts';
|
|
23
|
-
import { LiveTailModal } from '../renderer/live-tail-modal.ts';
|
|
24
22
|
import { BlockActionsMenu } from '../renderer/block-actions.ts';
|
|
25
|
-
import { AgentDetailModal } from '../renderer/agent-detail-modal.ts';
|
|
26
23
|
import { ContextInspectorModal } from '../renderer/context-inspector.ts';
|
|
27
24
|
import { BookmarkModal } from './bookmark-modal.ts';
|
|
28
25
|
import { SettingsModal } from './settings-modal.ts';
|
|
29
26
|
import { McpWorkspace } from './mcp-workspace.ts';
|
|
30
27
|
import { SessionPickerModal } from './session-picker-modal.ts';
|
|
28
|
+
import { ConfigModal } from './config-modal.ts';
|
|
31
29
|
import { ProfilePickerModal } from './profile-picker-modal.ts';
|
|
32
30
|
import { OnboardingWizardController, type OnboardingWizardAction, type OnboardingWizardMode } from './onboarding/onboarding-wizard.ts';
|
|
33
31
|
import {
|
|
@@ -106,9 +104,7 @@ import {
|
|
|
106
104
|
handleBlockActionsToken,
|
|
107
105
|
handleEscapeOnlyModalToken,
|
|
108
106
|
handleFilePickerToken,
|
|
109
|
-
handleLiveTailToken,
|
|
110
107
|
handleModelPickerToken,
|
|
111
|
-
handleProcessModalToken,
|
|
112
108
|
} from './handler-picker-routes.ts';
|
|
113
109
|
import { handleGlobalShortcutToken } from './handler-shortcuts.ts';
|
|
114
110
|
import { feedInputTokens } from './handler-feed.ts';
|
|
@@ -172,6 +168,10 @@ export class InputHandler implements InputHandlerLike {
|
|
|
172
168
|
public pasteRegistry = new Map<string, string>();
|
|
173
169
|
public nextPasteId = 1;
|
|
174
170
|
public lastCtrlCTime = 0;
|
|
171
|
+
/** Pending "hide the exit notice" timer from the last empty-prompt Ctrl+C
|
|
172
|
+
* press, if any — cleared before every subsequent press decides its own
|
|
173
|
+
* outcome (see handleCtrlC in handler-content-actions.ts). */
|
|
174
|
+
public lastCtrlCTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
|
175
175
|
/** Long-lived feed context — reused across every feed() call to avoid per-keystroke allocation. */
|
|
176
176
|
public feedContext!: import('./handler-feed.ts').InputFeedContext;
|
|
177
177
|
public commandRegistry: CommandRegistry | null = null;
|
|
@@ -180,13 +180,11 @@ export class InputHandler implements InputHandlerLike {
|
|
|
180
180
|
public modelPicker: ModelPickerModal;
|
|
181
181
|
public selectionModal = new SelectionModal();
|
|
182
182
|
public searchManager = new SearchManager();
|
|
183
|
-
public processModal: ProcessModal;
|
|
184
|
-
public liveTailModal: LiveTailModal;
|
|
185
|
-
public agentDetailModal: AgentDetailModal;
|
|
186
183
|
public contextInspectorModal = new ContextInspectorModal();
|
|
187
184
|
public bookmarkModal: BookmarkModal;
|
|
188
185
|
public blockActionsMenu = new BlockActionsMenu();
|
|
189
186
|
public settingsModal = new SettingsModal();
|
|
187
|
+
public configModal = new ConfigModal();
|
|
190
188
|
public mcpWorkspace = new McpWorkspace();
|
|
191
189
|
public onboardingWizard = new OnboardingWizardController();
|
|
192
190
|
public onboardingModelPickerCancelSnapshot: OnboardingWizardSnapshot | null = null;
|
|
@@ -268,23 +266,8 @@ export class InputHandler implements InputHandlerLike {
|
|
|
268
266
|
uiServices.providers.benchmarkStore,
|
|
269
267
|
uiServices.providers.providerRegistry,
|
|
270
268
|
);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
processManager: uiServices.shell.processManager,
|
|
274
|
-
wrfcController: uiServices.agents.wrfcController,
|
|
275
|
-
});
|
|
276
|
-
this.liveTailModal = new LiveTailModal({
|
|
277
|
-
agentManager: uiServices.agents.agentManager,
|
|
278
|
-
processManager: uiServices.shell.processManager,
|
|
279
|
-
});
|
|
280
|
-
this.agentDetailModal = new AgentDetailModal({
|
|
281
|
-
agentManager: uiServices.agents.agentManager,
|
|
282
|
-
agentMessageBus: uiServices.agents.agentMessageBus,
|
|
283
|
-
sessionLogPathResolver: (agentId) => uiServices.environment.shellPaths.resolveProjectPath('tui', 'sessions', `${agentId}.jsonl`),
|
|
284
|
-
// SDK 0.23.0: supply wrfcController so the modal can show constraint data
|
|
285
|
-
wrfcController: uiServices.agents.wrfcController,
|
|
286
|
-
cancelAgent: (agentId: string) => uiServices.agents.agentManager.cancel(agentId),
|
|
287
|
-
});
|
|
269
|
+
// W6.1 retirement: ProcessModal/LiveTailModal/AgentDetailModal were removed
|
|
270
|
+
// — F2 now opens the Fleet panel, which subsumes the live process tree.
|
|
288
271
|
this.bookmarkModal = new BookmarkModal(uiServices.shell.bookmarkManager);
|
|
289
272
|
this.sessionPickerModal = new SessionPickerModal(uiServices.sessions.sessionManager);
|
|
290
273
|
this.profilePickerModal = new ProfilePickerModal(uiServices.shell.profileManager);
|
|
@@ -318,6 +301,7 @@ export class InputHandler implements InputHandlerLike {
|
|
|
318
301
|
mcpWorkspace: this.mcpWorkspace,
|
|
319
302
|
sessionPickerModal: this.sessionPickerModal,
|
|
320
303
|
profilePickerModal: this.profilePickerModal,
|
|
304
|
+
configModal: this.configModal,
|
|
321
305
|
historySearch: this.historySearch,
|
|
322
306
|
commandRegistry: this.commandRegistry,
|
|
323
307
|
commandContext: this.commandContext,
|
|
@@ -325,9 +309,6 @@ export class InputHandler implements InputHandlerLike {
|
|
|
325
309
|
filePicker: this.filePicker,
|
|
326
310
|
modelPicker: this.modelPicker,
|
|
327
311
|
onboardingWizard: this.onboardingWizard,
|
|
328
|
-
processModal: this.processModal,
|
|
329
|
-
liveTailModal: this.liveTailModal,
|
|
330
|
-
agentDetailModal: this.agentDetailModal,
|
|
331
312
|
contextInspectorModal: this.contextInspectorModal,
|
|
332
313
|
blockActionsMenu: this.blockActionsMenu,
|
|
333
314
|
searchManager: this.searchManager,
|
|
@@ -337,6 +318,7 @@ export class InputHandler implements InputHandlerLike {
|
|
|
337
318
|
panelManager: this.uiServices.shell.panelManager,
|
|
338
319
|
keybindingsManager: this.uiServices.shell.keybindingsManager,
|
|
339
320
|
killRing: this.killRing,
|
|
321
|
+
focusTracker: this.uiServices.platform.focusTracker,
|
|
340
322
|
getHistory: this.getHistory,
|
|
341
323
|
getViewportHeight: this.getViewportHeight,
|
|
342
324
|
getScrollTop: this.getScrollTop,
|
package/src/input/keybindings.ts
CHANGED
|
@@ -88,7 +88,7 @@ export const ACTION_DESCRIPTIONS: Record<KeyAction, string> = {
|
|
|
88
88
|
'panel-tab-7': 'Jump to workspace panel tab 7',
|
|
89
89
|
'panel-tab-8': 'Jump to workspace panel tab 8',
|
|
90
90
|
'panel-tab-9': 'Jump to workspace panel tab 9',
|
|
91
|
-
'panel-ops': 'Open the
|
|
91
|
+
'panel-ops': 'Open and focus the Fleet panel',
|
|
92
92
|
'panel-focus-toggle': 'Switch keyboard focus between top and bottom pane',
|
|
93
93
|
'history-search': 'Reverse input history search',
|
|
94
94
|
'search': 'Toggle conversation search',
|
|
@@ -119,8 +119,14 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
|
|
|
119
119
|
'panel-picker': [{ key: 'p', ctrl: true }],
|
|
120
120
|
'panel-close': [{ key: 'x', ctrl: true }],
|
|
121
121
|
'panel-close-all': [{ key: 'x', ctrl: true, shift: true }],
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
// Ctrl+] stays the primary next-tab chord; Ctrl+PageDown is added as a second
|
|
123
|
+
// binding. Ctrl+[ was REMOVED as prev-tab: it is byte 0x1B (ESC), so the
|
|
124
|
+
// legacy tokenizer path emits it as 'escape' — the binding never matched on
|
|
125
|
+
// most terminals AND the key fired Escape (a split-brain chord). Ctrl+PageUp
|
|
126
|
+
// replaces it: Ctrl+PageUp/PageDown tokenize consistently (\x1b[5;5~ /
|
|
127
|
+
// \x1b[6;5~ -> pageup/pagedown ctrl:true) in both the legacy and CSI-u paths.
|
|
128
|
+
'panel-tab-next': [{ key: ']', ctrl: true }, { key: 'pagedown', ctrl: true }],
|
|
129
|
+
'panel-tab-prev': [{ key: 'pageup', ctrl: true }],
|
|
124
130
|
// Alt+1..9: jump directly to the Nth workspace panel tab (across both panes).
|
|
125
131
|
// The tokenizer delivers Alt as the token's `meta` modifier; comboMatches /
|
|
126
132
|
// lookup treat `meta` as an alias for `alt`, so these alt-combos route through
|
|
@@ -134,10 +140,10 @@ export const DEFAULT_KEYBINDINGS: Record<KeyAction, KeyCombo[]> = {
|
|
|
134
140
|
'panel-tab-7': [{ key: '7', alt: true }],
|
|
135
141
|
'panel-tab-8': [{ key: '8', alt: true }],
|
|
136
142
|
'panel-tab-9': [{ key: '9', alt: true }],
|
|
137
|
-
// Ctrl+O: open the Ops Control panel
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
//
|
|
143
|
+
// Ctrl+O: open and focus the Fleet panel. The former Ops Control panel was
|
|
144
|
+
// retired to an 'ops-control' -> 'fleet' alias (W6.1); the binding is KEPT
|
|
145
|
+
// (repointed, not removed) so the Ctrl+O muscle memory still lands somewhere
|
|
146
|
+
// useful. Routed globally in handleGlobalShortcutToken.
|
|
141
147
|
'panel-ops': [{ key: 'o', ctrl: true }],
|
|
142
148
|
// Ctrl+G: toggle keyboard focus between the top and bottom panes. Ctrl+G is
|
|
143
149
|
// otherwise unbound in the default table.
|
|
@@ -355,7 +361,15 @@ export class KeybindingsManager {
|
|
|
355
361
|
if (combo.ctrl) parts.push('Ctrl');
|
|
356
362
|
if (combo.alt) parts.push('Alt');
|
|
357
363
|
if (combo.shift) parts.push('Shift');
|
|
358
|
-
|
|
364
|
+
// Friendly display for named keys with a conventional abbreviation, so a
|
|
365
|
+
// chord like Ctrl+PageUp reads consistently with the "PageUp / PageDn"
|
|
366
|
+
// scroll help text. Single chars upper-case; other named keys pass through.
|
|
367
|
+
const KEY_DISPLAY: Record<string, string> = { pageup: 'PageUp', pagedown: 'PageDn' };
|
|
368
|
+
parts.push(
|
|
369
|
+
combo.key.length === 1
|
|
370
|
+
? combo.key.toUpperCase()
|
|
371
|
+
: KEY_DISPLAY[combo.key] ?? combo.key,
|
|
372
|
+
);
|
|
359
373
|
return parts.join('+');
|
|
360
374
|
}
|
|
361
375
|
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { ModelDefinition } from '@pellux/goodvibes-sdk/platform/providers';
|
|
2
2
|
|
|
3
|
-
export type PickerMode = 'model' | 'provider' | 'effort' | 'contextCap';
|
|
3
|
+
export type PickerMode = 'model' | 'provider' | 'effort' | 'contextCap' | 'embeddingProvider';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Which config keys the model picker writes to on commit.
|
|
7
|
-
* 'main'
|
|
8
|
-
* 'helper'
|
|
9
|
-
* 'tool'
|
|
10
|
-
* 'tts'
|
|
7
|
+
* 'main' -> provider.provider + provider.model (default)
|
|
8
|
+
* 'helper' -> helper.globalProvider + helper.globalModel (+ helper.enabled: true)
|
|
9
|
+
* 'tool' -> tools.llmProvider + tools.llmModel (+ tools.llmEnabled: true)
|
|
10
|
+
* 'tts' -> tts.llmProvider + tts.llmModel
|
|
11
|
+
* 'embeddings' -> provider.embeddingProvider, via MemoryEmbeddingProviderRegistry.setDefaultProvider()
|
|
12
|
+
* (not an LLM route — no model concept, see ModelPickerTargetInfo.configuredNote)
|
|
11
13
|
*/
|
|
12
|
-
export type ModelPickerTarget = 'main' | 'helper' | 'tool' | 'tts';
|
|
14
|
+
export type ModelPickerTarget = 'main' | 'helper' | 'tool' | 'tts' | 'embeddings';
|
|
13
15
|
|
|
14
16
|
export type ModelPickerFocusPane = 'targets' | 'items';
|
|
15
17
|
|
|
@@ -21,6 +23,22 @@ export interface ModelPickerTargetInfo {
|
|
|
21
23
|
readonly model: string;
|
|
22
24
|
readonly enabled: boolean;
|
|
23
25
|
readonly inherited: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Honest override for the "Current:" summary line. Used by the 'embeddings'
|
|
28
|
+
* target (which has no model concept — only a provider id + dimensions +
|
|
29
|
+
* configured state) so the renderer never prints a phantom "model:" value.
|
|
30
|
+
* When set, this replaces the computed provider:model route text.
|
|
31
|
+
*/
|
|
32
|
+
readonly configuredNote?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** One entry in the embedding-provider picker list (PickerMode 'embeddingProvider'). */
|
|
36
|
+
export interface EmbeddingProviderPickerEntry {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly label: string;
|
|
39
|
+
readonly dimensions: number;
|
|
40
|
+
readonly configured: boolean;
|
|
41
|
+
readonly detail?: string;
|
|
24
42
|
}
|
|
25
43
|
|
|
26
44
|
/**
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
BenchmarkSort,
|
|
8
8
|
CapabilityFilter,
|
|
9
9
|
CategoryFilter,
|
|
10
|
+
EmbeddingProviderPickerEntry,
|
|
10
11
|
FilteredModelsCache,
|
|
11
12
|
FilteredProvidersCache,
|
|
12
13
|
GroupByMode,
|
|
@@ -40,6 +41,7 @@ export type {
|
|
|
40
41
|
BenchmarkSort,
|
|
41
42
|
CapabilityFilter,
|
|
42
43
|
CategoryFilter,
|
|
44
|
+
EmbeddingProviderPickerEntry,
|
|
43
45
|
GroupByMode,
|
|
44
46
|
ModelFamily,
|
|
45
47
|
ModelPickerFocusPane,
|
|
@@ -86,6 +88,10 @@ export class ModelPickerModal {
|
|
|
86
88
|
public models: ModelDefinition[] = [];
|
|
87
89
|
public providers: string[] = [];
|
|
88
90
|
public effortLevels: string[] = [];
|
|
91
|
+
/** Entries for PickerMode 'embeddingProvider' — the 'embeddings' target's own tiny item list. */
|
|
92
|
+
public embeddingProviders: EmbeddingProviderPickerEntry[] = [];
|
|
93
|
+
/** Mode to restore when navigating the targets rail away from 'embeddings'. */
|
|
94
|
+
private nonEmbeddingMode: PickerMode = 'model';
|
|
89
95
|
/** The model chosen in model-mode, awaiting effort selection. */
|
|
90
96
|
public pendingModel: ModelDefinition | null = null;
|
|
91
97
|
/** The model awaiting context cap input (contextCap mode). */
|
|
@@ -148,9 +154,20 @@ export class ModelPickerModal {
|
|
|
148
154
|
}
|
|
149
155
|
|
|
150
156
|
setTarget(target: ModelPickerTarget): void {
|
|
157
|
+
const previousTarget = this.target;
|
|
151
158
|
this.target = target;
|
|
152
159
|
const idx = this.targetInfos.findIndex((entry) => entry.target === target);
|
|
153
160
|
this.targetIndex = idx >= 0 ? idx : this.targetIndex;
|
|
161
|
+
// The 'embeddings' target routes through its own tiny item-list mode
|
|
162
|
+
// (embedding providers are not ModelDefinition-shaped) rather than
|
|
163
|
+
// whatever LLM mode the picker session was already in. Remember the
|
|
164
|
+
// mode we came from so tabbing back to another target restores it.
|
|
165
|
+
if (target === 'embeddings' && this.mode !== 'embeddingProvider') {
|
|
166
|
+
this.nonEmbeddingMode = this.mode;
|
|
167
|
+
this.mode = 'embeddingProvider';
|
|
168
|
+
} else if (previousTarget === 'embeddings' && target !== 'embeddings' && this.mode === 'embeddingProvider') {
|
|
169
|
+
this.mode = this.nonEmbeddingMode;
|
|
170
|
+
}
|
|
154
171
|
this.alignSelectionToTarget();
|
|
155
172
|
}
|
|
156
173
|
|
|
@@ -171,6 +188,13 @@ export class ModelPickerModal {
|
|
|
171
188
|
this.selectedIndex = modelIdx >= 0 ? modelIdx : 0;
|
|
172
189
|
this.scrollOffset = 0;
|
|
173
190
|
this._scrollToSelection(20);
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (this.mode === 'embeddingProvider') {
|
|
194
|
+
const providerIdx = this.embeddingProviders.findIndex((provider) => provider.id === info.provider);
|
|
195
|
+
this.selectedIndex = providerIdx >= 0 ? providerIdx : 0;
|
|
196
|
+
this.scrollOffset = 0;
|
|
197
|
+
this._scrollToSelection(20);
|
|
174
198
|
}
|
|
175
199
|
}
|
|
176
200
|
|
|
@@ -273,6 +297,27 @@ export class ModelPickerModal {
|
|
|
273
297
|
this.scrollOffset = 0;
|
|
274
298
|
}
|
|
275
299
|
|
|
300
|
+
/**
|
|
301
|
+
* Open the embedding-provider list — entry point for the 'embeddings' target.
|
|
302
|
+
* Deliberately NOT routed through openAllModels (ModelDefinition-shaped) or
|
|
303
|
+
* openProviders (LLM-provider label resolution) — embedding providers are a
|
|
304
|
+
* flat {id, label, dimensions, configured} list with no model concept, and
|
|
305
|
+
* reusing either existing opener would mislabel or drop unrecognized ids.
|
|
306
|
+
*/
|
|
307
|
+
openEmbeddingProviders(providers: EmbeddingProviderPickerEntry[], currentId: string): void {
|
|
308
|
+
this.previousMode = null;
|
|
309
|
+
this.embeddingProviders = providers;
|
|
310
|
+
this.mode = 'embeddingProvider';
|
|
311
|
+
this.active = true;
|
|
312
|
+
this.pendingModel = null;
|
|
313
|
+
this.focusPane = 'items';
|
|
314
|
+
this.searchFocused = false;
|
|
315
|
+
this.query = '';
|
|
316
|
+
const idx = providers.findIndex((provider) => provider.id === currentId);
|
|
317
|
+
this.selectedIndex = idx >= 0 ? idx : 0;
|
|
318
|
+
this.scrollOffset = 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
276
321
|
/** Transition to model list filtered by provider (called from provider mode Enter). */
|
|
277
322
|
showModelsForProvider(models: ModelDefinition[], _provider: string): void {
|
|
278
323
|
this.previousMode = 'provider';
|
|
@@ -311,6 +356,8 @@ export class ModelPickerModal {
|
|
|
311
356
|
this.targetIndex = 0;
|
|
312
357
|
this.models = [];
|
|
313
358
|
this.providers = [];
|
|
359
|
+
this.embeddingProviders = [];
|
|
360
|
+
this.nonEmbeddingMode = 'model';
|
|
314
361
|
this.pendingModel = null;
|
|
315
362
|
this.contextCapPendingModel = null;
|
|
316
363
|
this.contextCapQuery = '';
|
|
@@ -473,6 +520,16 @@ export class ModelPickerModal {
|
|
|
473
520
|
this.providerItemsCache = cache;
|
|
474
521
|
return result;
|
|
475
522
|
}
|
|
523
|
+
if (this.mode === 'embeddingProvider') {
|
|
524
|
+
// Tiny list (typically 2-4 entries) — no caching infrastructure needed.
|
|
525
|
+
// Unconfigured providers are shown honestly (isConfigured: false), never hidden.
|
|
526
|
+
return this.embeddingProviders.map((provider) => ({
|
|
527
|
+
id: provider.id,
|
|
528
|
+
label: provider.label,
|
|
529
|
+
detail: `${provider.dimensions}d${provider.configured ? '' : ' · unconfigured'}`,
|
|
530
|
+
isConfigured: provider.configured,
|
|
531
|
+
}));
|
|
532
|
+
}
|
|
476
533
|
// effort mode
|
|
477
534
|
return buildEffortItems(this.effortLevels);
|
|
478
535
|
}
|
|
@@ -481,6 +538,7 @@ export class ModelPickerModal {
|
|
|
481
538
|
getItemCount(): number {
|
|
482
539
|
if (this.mode === 'model') return this.getFilteredModels().length;
|
|
483
540
|
if (this.mode === 'provider') return this.getFilteredProviders().length;
|
|
541
|
+
if (this.mode === 'embeddingProvider') return this.embeddingProviders.length;
|
|
484
542
|
return this.effortLevels.length;
|
|
485
543
|
}
|
|
486
544
|
|