@astralyn/pi 0.85.1 → 0.85.2
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 +28 -0
- package/dist/cli.js +94 -91
- package/dist/core/agent-session.js +2 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/background/history.d.ts.map +1 -1
- package/dist/core/background/history.js +6 -0
- package/dist/core/background/history.js.map +1 -1
- package/dist/core/background/presentation.d.ts.map +1 -1
- package/dist/core/background/presentation.js +41 -4
- package/dist/core/background/presentation.js.map +1 -1
- package/dist/core/background/service.d.ts +2 -2
- package/dist/core/background/service.d.ts.map +1 -1
- package/dist/core/background/service.js +38 -15
- package/dist/core/background/service.js.map +1 -1
- package/dist/core/background/types.d.ts +9 -0
- package/dist/core/background/types.d.ts.map +1 -1
- package/dist/core/background/types.js +4 -0
- package/dist/core/background/types.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +5 -12
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +11 -19
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/settings.d.ts +27 -0
- package/dist/core/compaction/settings.d.ts.map +1 -0
- package/dist/core/compaction/settings.js +40 -0
- package/dist/core/compaction/settings.js.map +1 -0
- package/dist/core/keybindings.d.ts +15 -0
- package/dist/core/keybindings.d.ts.map +1 -1
- package/dist/core/keybindings.js +12 -0
- package/dist/core/keybindings.js.map +1 -1
- package/dist/core/model-config.d.ts +32 -10
- package/dist/core/model-config.d.ts.map +1 -1
- package/dist/core/model-config.js +38 -1
- package/dist/core/model-config.js.map +1 -1
- package/dist/core/model-runtime.d.ts +2 -0
- package/dist/core/model-runtime.d.ts.map +1 -1
- package/dist/core/model-runtime.js +4 -0
- package/dist/core/model-runtime.js.map +1 -1
- package/dist/core/settings-manager.d.ts +4 -7
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +7 -6
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.js +1 -1
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/shell-execution.d.ts.map +1 -1
- package/dist/core/tools/shell-execution.js +3 -1
- package/dist/core/tools/shell-execution.js.map +1 -1
- package/dist/extensions/background/manager.d.ts +8 -5
- package/dist/extensions/background/manager.d.ts.map +1 -1
- package/dist/extensions/background/manager.js +115 -39
- package/dist/extensions/background/manager.js.map +1 -1
- package/dist/extensions/background/task-view.d.ts +5 -0
- package/dist/extensions/background/task-view.d.ts.map +1 -1
- package/dist/extensions/background/task-view.js +4 -0
- package/dist/extensions/background/task-view.js.map +1 -1
- package/dist/extensions/btw/controller.d.ts +1 -1
- package/dist/extensions/btw/controller.d.ts.map +1 -1
- package/dist/extensions/btw/controller.js +4 -4
- package/dist/extensions/btw/controller.js.map +1 -1
- package/dist/extensions/btw/index.js +3 -3
- package/dist/extensions/btw/index.js.map +1 -1
- package/dist/extensions/index.js +2 -2
- package/dist/extensions/index.js.map +1 -1
- package/dist/extensions/provider/catalog.d.ts +57 -0
- package/dist/extensions/provider/catalog.d.ts.map +1 -0
- package/dist/extensions/provider/catalog.js +181 -0
- package/dist/extensions/provider/catalog.js.map +1 -0
- package/dist/extensions/provider/compat-fields.d.ts +25 -0
- package/dist/extensions/provider/compat-fields.d.ts.map +1 -0
- package/dist/extensions/provider/compat-fields.js +148 -0
- package/dist/extensions/provider/compat-fields.js.map +1 -0
- package/dist/extensions/provider/configuration.d.ts +8 -0
- package/dist/extensions/provider/configuration.d.ts.map +1 -0
- package/dist/extensions/provider/configuration.js +21 -0
- package/dist/extensions/provider/configuration.js.map +1 -0
- package/dist/extensions/provider/connection.d.ts +14 -0
- package/dist/extensions/provider/connection.d.ts.map +1 -0
- package/dist/extensions/provider/connection.js +86 -0
- package/dist/extensions/provider/connection.js.map +1 -0
- package/dist/extensions/provider/constants.d.ts +38 -0
- package/dist/extensions/provider/constants.d.ts.map +1 -0
- package/dist/extensions/provider/constants.js +75 -0
- package/dist/extensions/provider/constants.js.map +1 -0
- package/dist/extensions/provider/index.d.ts +4 -0
- package/dist/extensions/provider/index.d.ts.map +1 -0
- package/dist/extensions/provider/index.js +65 -0
- package/dist/extensions/provider/index.js.map +1 -0
- package/dist/extensions/provider/probe.d.ts +34 -0
- package/dist/extensions/provider/probe.d.ts.map +1 -0
- package/dist/extensions/provider/probe.js +178 -0
- package/dist/extensions/provider/probe.js.map +1 -0
- package/dist/extensions/provider/refresh.d.ts +30 -0
- package/dist/extensions/provider/refresh.d.ts.map +1 -0
- package/dist/extensions/provider/refresh.js +66 -0
- package/dist/extensions/provider/refresh.js.map +1 -0
- package/dist/extensions/provider/store.d.ts +131 -0
- package/dist/extensions/provider/store.d.ts.map +1 -0
- package/dist/extensions/provider/store.js +503 -0
- package/dist/extensions/provider/store.js.map +1 -0
- package/dist/extensions/provider/ui/app.d.ts +25 -0
- package/dist/extensions/provider/ui/app.d.ts.map +1 -0
- package/dist/extensions/provider/ui/app.js +153 -0
- package/dist/extensions/provider/ui/app.js.map +1 -0
- package/dist/extensions/provider/ui/builtin-data.d.ts +53 -0
- package/dist/extensions/provider/ui/builtin-data.d.ts.map +1 -0
- package/dist/extensions/provider/ui/builtin-data.js +315 -0
- package/dist/extensions/provider/ui/builtin-data.js.map +1 -0
- package/dist/extensions/provider/ui/compat.d.ts +57 -0
- package/dist/extensions/provider/ui/compat.d.ts.map +1 -0
- package/dist/extensions/provider/ui/compat.js +427 -0
- package/dist/extensions/provider/ui/compat.js.map +1 -0
- package/dist/extensions/provider/ui/dialogs.d.ts +30 -0
- package/dist/extensions/provider/ui/dialogs.d.ts.map +1 -0
- package/dist/extensions/provider/ui/dialogs.js +89 -0
- package/dist/extensions/provider/ui/dialogs.js.map +1 -0
- package/dist/extensions/provider/ui/dictionary.d.ts +29 -0
- package/dist/extensions/provider/ui/dictionary.d.ts.map +1 -0
- package/dist/extensions/provider/ui/dictionary.js +385 -0
- package/dist/extensions/provider/ui/dictionary.js.map +1 -0
- package/dist/extensions/provider/ui/editor.d.ts +91 -0
- package/dist/extensions/provider/ui/editor.d.ts.map +1 -0
- package/dist/extensions/provider/ui/editor.js +667 -0
- package/dist/extensions/provider/ui/editor.js.map +1 -0
- package/dist/extensions/provider/ui/fetch-models.d.ts +39 -0
- package/dist/extensions/provider/ui/fetch-models.d.ts.map +1 -0
- package/dist/extensions/provider/ui/fetch-models.js +320 -0
- package/dist/extensions/provider/ui/fetch-models.js.map +1 -0
- package/dist/extensions/provider/ui/model-fields.d.ts +43 -0
- package/dist/extensions/provider/ui/model-fields.d.ts.map +1 -0
- package/dist/extensions/provider/ui/model-fields.js +441 -0
- package/dist/extensions/provider/ui/model-fields.js.map +1 -0
- package/dist/extensions/provider/ui/model-options.d.ts +93 -0
- package/dist/extensions/provider/ui/model-options.d.ts.map +1 -0
- package/dist/extensions/provider/ui/model-options.js +529 -0
- package/dist/extensions/provider/ui/model-options.js.map +1 -0
- package/dist/extensions/provider/ui/pane.d.ts +81 -0
- package/dist/extensions/provider/ui/pane.d.ts.map +1 -0
- package/dist/extensions/provider/ui/pane.js +3 -0
- package/dist/extensions/provider/ui/pane.js.map +1 -0
- package/dist/extensions/provider/ui/provider-fields.d.ts +60 -0
- package/dist/extensions/provider/ui/provider-fields.d.ts.map +1 -0
- package/dist/extensions/provider/ui/provider-fields.js +326 -0
- package/dist/extensions/provider/ui/provider-fields.js.map +1 -0
- package/dist/extensions/provider/ui/provider-list.d.ts +34 -0
- package/dist/extensions/provider/ui/provider-list.d.ts.map +1 -0
- package/dist/extensions/provider/ui/provider-list.js +162 -0
- package/dist/extensions/provider/ui/provider-list.js.map +1 -0
- package/dist/extensions/provider/ui/session.d.ts +20 -0
- package/dist/extensions/provider/ui/session.d.ts.map +1 -0
- package/dist/extensions/provider/ui/session.js +52 -0
- package/dist/extensions/provider/ui/session.js.map +1 -0
- package/dist/extensions/provider/ui/thinking-map.d.ts +20 -0
- package/dist/extensions/provider/ui/thinking-map.d.ts.map +1 -0
- package/dist/extensions/provider/ui/thinking-map.js +166 -0
- package/dist/extensions/provider/ui/thinking-map.js.map +1 -0
- package/dist/extensions/provider/ui/value-row.d.ts +88 -0
- package/dist/extensions/provider/ui/value-row.d.ts.map +1 -0
- package/dist/extensions/provider/ui/value-row.js +158 -0
- package/dist/extensions/provider/ui/value-row.js.map +1 -0
- package/dist/extensions/statusline/index.js +2 -2
- package/dist/extensions/statusline/index.js.map +1 -1
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +4 -2
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/subagent/resolve.d.ts +5 -0
- package/dist/extensions/subagent/resolve.d.ts.map +1 -1
- package/dist/extensions/subagent/resolve.js +15 -7
- package/dist/extensions/subagent/resolve.js.map +1 -1
- package/dist/extensions/subagent/schema.d.ts +2 -0
- package/dist/extensions/subagent/schema.d.ts.map +1 -1
- package/dist/extensions/subagent/schema.js +1 -0
- package/dist/extensions/subagent/schema.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +10 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +20 -2
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +6 -1
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/rpc-entry.js +94 -91
- package/docs/bundled/README.md +1 -1
- package/docs/bundled/extensions/background.md +5 -5
- package/docs/bundled/extensions/provider.md +117 -0
- package/docs/bundled/extensions/statusline.md +1 -1
- package/docs/bundled/extensions/subagent.md +7 -3
- package/docs/compaction.md +16 -6
- package/docs/docs.json +2 -2
- package/docs/keybindings.md +3 -0
- package/docs/settings.md +11 -7
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/extensions/router/constants.d.ts +0 -23
- package/dist/extensions/router/constants.d.ts.map +0 -1
- package/dist/extensions/router/constants.js +0 -30
- package/dist/extensions/router/constants.js.map +0 -1
- package/dist/extensions/router/dialog-host.d.ts +0 -60
- package/dist/extensions/router/dialog-host.d.ts.map +0 -1
- package/dist/extensions/router/dialog-host.js +0 -196
- package/dist/extensions/router/dialog-host.js.map +0 -1
- package/dist/extensions/router/dialog.d.ts +0 -45
- package/dist/extensions/router/dialog.d.ts.map +0 -1
- package/dist/extensions/router/dialog.js +0 -378
- package/dist/extensions/router/dialog.js.map +0 -1
- package/dist/extensions/router/identity.d.ts +0 -15
- package/dist/extensions/router/identity.d.ts.map +0 -1
- package/dist/extensions/router/identity.js +0 -173
- package/dist/extensions/router/identity.js.map +0 -1
- package/dist/extensions/router/index.d.ts +0 -15
- package/dist/extensions/router/index.d.ts.map +0 -1
- package/dist/extensions/router/index.js +0 -70
- package/dist/extensions/router/index.js.map +0 -1
- package/dist/extensions/router/presets.d.ts +0 -51
- package/dist/extensions/router/presets.d.ts.map +0 -1
- package/dist/extensions/router/presets.js +0 -108
- package/dist/extensions/router/presets.js.map +0 -1
- package/dist/extensions/router/probe.d.ts +0 -26
- package/dist/extensions/router/probe.d.ts.map +0 -1
- package/dist/extensions/router/probe.js +0 -216
- package/dist/extensions/router/probe.js.map +0 -1
- package/dist/extensions/router/register.d.ts +0 -12
- package/dist/extensions/router/register.d.ts.map +0 -1
- package/dist/extensions/router/register.js +0 -90
- package/dist/extensions/router/register.js.map +0 -1
- package/dist/extensions/router/state.d.ts +0 -20
- package/dist/extensions/router/state.d.ts.map +0 -1
- package/dist/extensions/router/state.js +0 -169
- package/dist/extensions/router/state.js.map +0 -1
- package/dist/extensions/router/store.d.ts +0 -12
- package/dist/extensions/router/store.d.ts.map +0 -1
- package/dist/extensions/router/store.js +0 -266
- package/dist/extensions/router/store.js.map +0 -1
- package/dist/extensions/router/stream.d.ts +0 -12
- package/dist/extensions/router/stream.d.ts.map +0 -1
- package/dist/extensions/router/stream.js +0 -233
- package/dist/extensions/router/stream.js.map +0 -1
- package/dist/extensions/router/transport.d.ts +0 -9
- package/dist/extensions/router/transport.d.ts.map +0 -1
- package/dist/extensions/router/transport.js +0 -81
- package/dist/extensions/router/transport.js.map +0 -1
- package/dist/extensions/router/types.d.ts +0 -39
- package/dist/extensions/router/types.d.ts.map +0 -1
- package/dist/extensions/router/types.js +0 -2
- package/dist/extensions/router/types.js.map +0 -1
- package/dist/extensions/router/ui.d.ts +0 -26
- package/dist/extensions/router/ui.d.ts.map +0 -1
- package/dist/extensions/router/ui.js +0 -897
- package/dist/extensions/router/ui.js.map +0 -1
- package/docs/bundled/extensions/router.md +0 -197
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two-pane provider editor.
|
|
3
|
+
*
|
|
4
|
+
* Left column: API Auth / Fetch Models, the provider's
|
|
5
|
+
* models (name → id → "New Model" fallback, one draft at a time), then
|
|
6
|
+
* + Add Model and Delete Provider. Right column hosts the selected item's
|
|
7
|
+
* field pane and sub-pane stack; both columns keep their own selection and
|
|
8
|
+
* scroll position. The frame height is fixed: each column scrolls inside a
|
|
9
|
+
* fixed window with a (n/N) position indicator instead of resizing, and the
|
|
10
|
+
* selected row stays accent in both panes while the unfocused pane's other
|
|
11
|
+
* rows dim back. Typing on a value row overwrites, Enter tweaks or enters,
|
|
12
|
+
* ←/→ switch panes, Esc cancels/pops/backs out.
|
|
13
|
+
*/
|
|
14
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
15
|
+
import { DynamicBorder } from "../../../modes/interactive/components/dynamic-border.js";
|
|
16
|
+
import { keyHint, rawKeyHint } from "../../../modes/interactive/components/keybinding-hints.js";
|
|
17
|
+
import { isBuiltinProviderId } from "../catalog.js";
|
|
18
|
+
import { effectiveModelSettings, hasProviderSettings } from "../configuration.js";
|
|
19
|
+
import { fetchProviderModels, importProviderModels } from "../connection.js";
|
|
20
|
+
import { DELETE } from "../store.js";
|
|
21
|
+
import { ConfirmPane, InfoPane } from "./dialogs.js";
|
|
22
|
+
import { FetchModelsPane } from "./fetch-models.js";
|
|
23
|
+
import { ModelFieldsPane } from "./model-fields.js";
|
|
24
|
+
import { ApiAuthPane } from "./provider-fields.js";
|
|
25
|
+
import { CURSOR, truncateMiddle, windowLines } from "./value-row.js";
|
|
26
|
+
const LEFT_WIDTH_MIN = 16;
|
|
27
|
+
const LEFT_WIDTH_MAX = 40;
|
|
28
|
+
/** Fixed editor body height: both columns scroll inside it rather than resizing the frame. */
|
|
29
|
+
const BODY_ROWS = 14;
|
|
30
|
+
const MIN_WIDTH = 56;
|
|
31
|
+
function padTo(line, width) {
|
|
32
|
+
const missing = width - visibleWidth(line);
|
|
33
|
+
return missing > 0 ? line + " ".repeat(missing) : line;
|
|
34
|
+
}
|
|
35
|
+
/** Stable identity across left-list rebuilds (a rename keeps the selection on the model). */
|
|
36
|
+
function leftItemKey(item) {
|
|
37
|
+
if (!item)
|
|
38
|
+
return "";
|
|
39
|
+
switch (item.kind) {
|
|
40
|
+
case "model":
|
|
41
|
+
return `model:${item.modelId}`;
|
|
42
|
+
default:
|
|
43
|
+
return item.kind;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
export class ProviderEditorScreen {
|
|
47
|
+
constructor(tui, theme, keybindings, done, options) {
|
|
48
|
+
this.leftItems = [];
|
|
49
|
+
this.leftIndex = 0;
|
|
50
|
+
this.focusPane = "left";
|
|
51
|
+
this.stack = [];
|
|
52
|
+
this.disposed = false;
|
|
53
|
+
this.renaming = false;
|
|
54
|
+
this._focused = false;
|
|
55
|
+
this.tui = tui;
|
|
56
|
+
this.theme = theme;
|
|
57
|
+
this.keybindings = keybindings;
|
|
58
|
+
this.done = done;
|
|
59
|
+
this.options = options;
|
|
60
|
+
this.host = this.createHost();
|
|
61
|
+
this.rebuildLeftItems();
|
|
62
|
+
this.resetRightStack();
|
|
63
|
+
}
|
|
64
|
+
// ------------------------------------------------------------------
|
|
65
|
+
// Component / Focusable
|
|
66
|
+
// ------------------------------------------------------------------
|
|
67
|
+
get focused() {
|
|
68
|
+
return this._focused;
|
|
69
|
+
}
|
|
70
|
+
set focused(value) {
|
|
71
|
+
this._focused = value;
|
|
72
|
+
this.topPane()?.setFocused(value && this.focusPane === "right");
|
|
73
|
+
}
|
|
74
|
+
invalidate() {
|
|
75
|
+
this.cache = undefined;
|
|
76
|
+
}
|
|
77
|
+
dispose() {
|
|
78
|
+
if (this.disposed)
|
|
79
|
+
return;
|
|
80
|
+
this.disposed = true;
|
|
81
|
+
for (const pane of this.stack)
|
|
82
|
+
pane.dispose?.();
|
|
83
|
+
this.stack = [];
|
|
84
|
+
// An unsubmitted draft never reaches disk; leaving the editor drops it.
|
|
85
|
+
if (!hasProviderSettings(this.options.store.getProvider(this.options.providerId))) {
|
|
86
|
+
this.options.store.discardProviderDraft(this.options.providerId);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
render(width) {
|
|
90
|
+
if (this.cache && this.cache.width === width)
|
|
91
|
+
return this.cache.lines;
|
|
92
|
+
const lines = this.renderFrame(width);
|
|
93
|
+
this.cache = { width, lines };
|
|
94
|
+
return lines;
|
|
95
|
+
}
|
|
96
|
+
handleInput(data) {
|
|
97
|
+
if (this.focusPane === "right") {
|
|
98
|
+
const pane = this.topPane();
|
|
99
|
+
if (pane && !pane.isEditing?.() && this.keybindings.matches(data, "app.provider.switchPaneLeft")) {
|
|
100
|
+
pane.setFocused(false);
|
|
101
|
+
this.focusPane = "left";
|
|
102
|
+
this.refresh();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
pane?.handleInput(data);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const kb = this.keybindings;
|
|
109
|
+
if (kb.matches(data, "tui.select.up")) {
|
|
110
|
+
this.moveLeft(-1);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
if (kb.matches(data, "tui.select.down")) {
|
|
114
|
+
this.moveLeft(1);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (kb.matches(data, "app.provider.switchPaneRight")) {
|
|
118
|
+
this.activateLeftItem("inspect");
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (kb.matches(data, "tui.select.confirm")) {
|
|
122
|
+
this.activateLeftItem("activate");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (kb.matches(data, "tui.select.cancel"))
|
|
126
|
+
this.done("back");
|
|
127
|
+
}
|
|
128
|
+
/** Rebuild from the store view after a save merged external changes. */
|
|
129
|
+
syncFromStore() {
|
|
130
|
+
if (this.disposed || this.renaming)
|
|
131
|
+
return;
|
|
132
|
+
const selected = this.leftItems[this.leftIndex];
|
|
133
|
+
const selectedKey = leftItemKey(selected);
|
|
134
|
+
this.rebuildLeftItems();
|
|
135
|
+
const nextIndex = this.leftItems.findIndex((item) => leftItemKey(item) === selectedKey);
|
|
136
|
+
this.leftIndex = nextIndex >= 0 ? nextIndex : Math.min(this.leftIndex, this.selectableIndex(0));
|
|
137
|
+
// If the selected model vanished externally, rebuild the right column.
|
|
138
|
+
if (nextIndex < 0 && selected?.kind === "model") {
|
|
139
|
+
this.resetRightStack();
|
|
140
|
+
}
|
|
141
|
+
this.refresh();
|
|
142
|
+
}
|
|
143
|
+
// ------------------------------------------------------------------
|
|
144
|
+
// Left column
|
|
145
|
+
// ------------------------------------------------------------------
|
|
146
|
+
rebuildLeftItems() {
|
|
147
|
+
const items = [{ kind: "apiAuth" }, { kind: "fetch" }, { kind: "separator" }];
|
|
148
|
+
for (const model of this.options.store.getModels(this.options.providerId)) {
|
|
149
|
+
items.push({ kind: "model", modelId: model.id });
|
|
150
|
+
}
|
|
151
|
+
if (this.draft)
|
|
152
|
+
items.push({ kind: "draft" });
|
|
153
|
+
items.push({ kind: "separator" }, { kind: "addModel" }, { kind: "deleteProvider" });
|
|
154
|
+
this.leftItems = items;
|
|
155
|
+
}
|
|
156
|
+
selectableIndex(start) {
|
|
157
|
+
const index = this.leftItems.findIndex((item, i) => i >= start && item.kind !== "separator");
|
|
158
|
+
return index >= 0 ? index : 0;
|
|
159
|
+
}
|
|
160
|
+
moveLeft(delta) {
|
|
161
|
+
const selectable = this.leftItems
|
|
162
|
+
.map((item, index) => ({ item, index }))
|
|
163
|
+
.filter((entry) => entry.item.kind !== "separator");
|
|
164
|
+
if (selectable.length === 0)
|
|
165
|
+
return;
|
|
166
|
+
const position = selectable.findIndex((entry) => entry.index === this.leftIndex);
|
|
167
|
+
const next = selectable[(position + delta + selectable.length) % selectable.length];
|
|
168
|
+
if (next.index === this.leftIndex)
|
|
169
|
+
return;
|
|
170
|
+
this.leftIndex = next.index;
|
|
171
|
+
// Moving the left selection replaces the right column (cancels fetch/edit state).
|
|
172
|
+
this.resetRightStack();
|
|
173
|
+
this.refresh();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Enter activates the row; → only focuses its pane. Action rows therefore
|
|
177
|
+
* never fire from a focus move, matching their InfoPane's "Press Enter".
|
|
178
|
+
*/
|
|
179
|
+
activateLeftItem(via) {
|
|
180
|
+
const item = this.leftItems[this.leftIndex];
|
|
181
|
+
if (!item)
|
|
182
|
+
return;
|
|
183
|
+
switch (item.kind) {
|
|
184
|
+
case "apiAuth":
|
|
185
|
+
case "model":
|
|
186
|
+
case "draft":
|
|
187
|
+
this.focusPane = "right";
|
|
188
|
+
this.topPane()?.setFocused(this._focused);
|
|
189
|
+
this.refresh();
|
|
190
|
+
return;
|
|
191
|
+
case "fetch":
|
|
192
|
+
this.focusPane = "right";
|
|
193
|
+
if (via === "activate") {
|
|
194
|
+
const pane = this.topPane();
|
|
195
|
+
if (pane instanceof FetchModelsPane)
|
|
196
|
+
pane.start();
|
|
197
|
+
}
|
|
198
|
+
this.topPane()?.setFocused(this._focused);
|
|
199
|
+
this.refresh();
|
|
200
|
+
return;
|
|
201
|
+
case "addModel": {
|
|
202
|
+
if (via === "inspect") {
|
|
203
|
+
this.focusPane = "right";
|
|
204
|
+
this.topPane()?.setFocused(this._focused);
|
|
205
|
+
this.refresh();
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
if (!this.draft) {
|
|
209
|
+
this.draft = { fields: {} };
|
|
210
|
+
this.rebuildLeftItems();
|
|
211
|
+
}
|
|
212
|
+
const draftIndex = this.leftItems.findIndex((entry) => entry.kind === "draft");
|
|
213
|
+
if (draftIndex >= 0)
|
|
214
|
+
this.leftIndex = draftIndex;
|
|
215
|
+
this.resetRightStack();
|
|
216
|
+
this.focusPane = "right";
|
|
217
|
+
const pane = this.topPane();
|
|
218
|
+
pane?.setFocused(this._focused);
|
|
219
|
+
if (pane instanceof ModelFieldsPane && !this.draft.fields.id)
|
|
220
|
+
pane.startEditingId();
|
|
221
|
+
this.refresh();
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
case "deleteProvider": {
|
|
225
|
+
if (via === "inspect") {
|
|
226
|
+
this.focusPane = "right";
|
|
227
|
+
this.topPane()?.setFocused(this._focused);
|
|
228
|
+
this.refresh();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (this.host.isCurrentProvider()) {
|
|
232
|
+
this.host.notify("The current model's provider cannot be deleted; switch models with /model first.", "error");
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
const builtin = isBuiltinProviderId(this.options.providerId);
|
|
236
|
+
this.host.confirm(`Delete provider "${this.options.providerId}" from models.json?` +
|
|
237
|
+
(builtin ? " The built-in catalog remains and its models may reappear." : "") +
|
|
238
|
+
" Stored credentials in auth.json are kept.", "Delete Provider", () => {
|
|
239
|
+
this.options.store.removeProvider(this.options.providerId);
|
|
240
|
+
this.options.refresher.touch(this.options.providerId);
|
|
241
|
+
this.done("deleted");
|
|
242
|
+
});
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
default:
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
renderLeft(width) {
|
|
250
|
+
const theme = this.theme;
|
|
251
|
+
const focused = this.focusPane === "left";
|
|
252
|
+
const rows = [];
|
|
253
|
+
const renderRow = (index) => {
|
|
254
|
+
const item = this.leftItems[index];
|
|
255
|
+
if (item.kind === "separator") {
|
|
256
|
+
return theme.fg("borderMuted", ` ${"─".repeat(Math.max(1, width - 4))}`);
|
|
257
|
+
}
|
|
258
|
+
return this.renderLeftItem(item, index === this.leftIndex, focused, width);
|
|
259
|
+
};
|
|
260
|
+
if (this.leftItems.length <= BODY_ROWS) {
|
|
261
|
+
for (let index = 0; index < this.leftItems.length; index++)
|
|
262
|
+
rows.push(renderRow(index));
|
|
263
|
+
}
|
|
264
|
+
else {
|
|
265
|
+
// One row carries the (n/N) position indicator, like the /model selector.
|
|
266
|
+
const slots = BODY_ROWS - 1;
|
|
267
|
+
const start = Math.max(0, Math.min(this.leftIndex - Math.floor(slots / 2), this.leftItems.length - slots));
|
|
268
|
+
for (let index = start; index < start + slots; index++)
|
|
269
|
+
rows.push(renderRow(index));
|
|
270
|
+
const selectable = this.leftItems.filter((item) => item.kind !== "separator");
|
|
271
|
+
const position = selectable.indexOf(this.leftItems[this.leftIndex]);
|
|
272
|
+
rows.push(theme.fg("dim", ` (${position + 1}/${selectable.length})`));
|
|
273
|
+
}
|
|
274
|
+
while (rows.length < BODY_ROWS)
|
|
275
|
+
rows.push("");
|
|
276
|
+
return rows;
|
|
277
|
+
}
|
|
278
|
+
renderLeftItem(item, active, focused, width) {
|
|
279
|
+
const theme = this.theme;
|
|
280
|
+
const store = this.options.store;
|
|
281
|
+
// Single-accent focus rule: the marker and accent exist only in the
|
|
282
|
+
// focused column; an unfocused column keeps its content plain.
|
|
283
|
+
const lit = active && focused;
|
|
284
|
+
const marker = lit ? theme.fg("accent", `${CURSOR} `) : " ";
|
|
285
|
+
const style = (text, dim = false) => theme.fg(lit ? "accent" : dim ? "dim" : "text", text);
|
|
286
|
+
let text;
|
|
287
|
+
let note;
|
|
288
|
+
switch (item.kind) {
|
|
289
|
+
case "apiAuth":
|
|
290
|
+
text = "API Auth";
|
|
291
|
+
break;
|
|
292
|
+
case "fetch":
|
|
293
|
+
text = "Fetch Models";
|
|
294
|
+
note = this.fetchStatus;
|
|
295
|
+
break;
|
|
296
|
+
case "model": {
|
|
297
|
+
const model = store.getModel(this.options.providerId, item.modelId);
|
|
298
|
+
text = model ? modelDisplayName(model) : item.modelId;
|
|
299
|
+
break;
|
|
300
|
+
}
|
|
301
|
+
case "draft": {
|
|
302
|
+
text = this.draft?.fields.name ?? this.draft?.fields.id ?? "New Model";
|
|
303
|
+
note = "· draft";
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case "addModel":
|
|
307
|
+
text = "+ Add Model";
|
|
308
|
+
break;
|
|
309
|
+
case "deleteProvider":
|
|
310
|
+
text = "Delete Provider";
|
|
311
|
+
break;
|
|
312
|
+
default:
|
|
313
|
+
text = "";
|
|
314
|
+
}
|
|
315
|
+
const dim = item.kind === "draft" && !this.draft?.fields.name && !this.draft?.fields.id;
|
|
316
|
+
// Long model names keep head and tail (the distinctive parts) instead of a hard cut.
|
|
317
|
+
if (item.kind === "model" || item.kind === "draft") {
|
|
318
|
+
const budget = Math.max(8, width - 2 - (note ? visibleWidth(note) + 1 : 0));
|
|
319
|
+
text = truncateMiddle(text, budget);
|
|
320
|
+
}
|
|
321
|
+
const noteText = note ? theme.fg("dim", ` ${note}`) : "";
|
|
322
|
+
return truncateToWidth(marker + style(text, dim) + noteText, width);
|
|
323
|
+
}
|
|
324
|
+
// ------------------------------------------------------------------
|
|
325
|
+
// Right column (pane stack)
|
|
326
|
+
// ------------------------------------------------------------------
|
|
327
|
+
topPane() {
|
|
328
|
+
return this.stack[this.stack.length - 1];
|
|
329
|
+
}
|
|
330
|
+
pushPane(pane) {
|
|
331
|
+
this.topPane()?.setFocused(false);
|
|
332
|
+
this.focusPane = "right";
|
|
333
|
+
this.stack.push(pane);
|
|
334
|
+
pane.setFocused(this._focused && this.focusPane === "right");
|
|
335
|
+
this.refresh();
|
|
336
|
+
}
|
|
337
|
+
popPane() {
|
|
338
|
+
if (this.stack.length <= 1) {
|
|
339
|
+
// Esc on the base pane moves focus back to the left column.
|
|
340
|
+
this.topPane()?.setFocused(false);
|
|
341
|
+
this.focusPane = "left";
|
|
342
|
+
this.refresh();
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
const pane = this.stack.pop();
|
|
346
|
+
pane?.setFocused(false);
|
|
347
|
+
pane?.dispose?.();
|
|
348
|
+
this.topPane()?.setFocused(this._focused && this.focusPane === "right");
|
|
349
|
+
this.refresh();
|
|
350
|
+
}
|
|
351
|
+
resetRightStack() {
|
|
352
|
+
for (const pane of this.stack)
|
|
353
|
+
pane.dispose?.();
|
|
354
|
+
const item = this.leftItems[this.leftIndex];
|
|
355
|
+
this.stack = [this.createBasePane(item)];
|
|
356
|
+
this.topPane()?.setFocused(this._focused && this.focusPane === "right");
|
|
357
|
+
}
|
|
358
|
+
createBasePane(item) {
|
|
359
|
+
switch (item?.kind) {
|
|
360
|
+
case "apiAuth":
|
|
361
|
+
return new ApiAuthPane(this.host, this.options.registry);
|
|
362
|
+
case "fetch":
|
|
363
|
+
return new FetchModelsPane(this.host);
|
|
364
|
+
case "model":
|
|
365
|
+
return new ModelFieldsPane(this.host, this.persistedHandle(item.modelId));
|
|
366
|
+
case "draft":
|
|
367
|
+
return new ModelFieldsPane(this.host, this.draftHandle());
|
|
368
|
+
case "addModel":
|
|
369
|
+
return new InfoPane(this.host, [
|
|
370
|
+
"Press Enter to create a model.",
|
|
371
|
+
"A draft is kept in memory until its id is set; Esc on the draft discards it.",
|
|
372
|
+
], () => this.activateLeftItem("activate"));
|
|
373
|
+
case "deleteProvider":
|
|
374
|
+
return new InfoPane(this.host, ["Press Enter to delete this provider from models.json."], () => this.activateLeftItem("activate"));
|
|
375
|
+
default:
|
|
376
|
+
return new InfoPane(this.host, []);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
persistedHandle(modelId) {
|
|
380
|
+
const store = this.options.store;
|
|
381
|
+
const providerId = this.options.providerId;
|
|
382
|
+
const ref = { id: modelId };
|
|
383
|
+
let renamingSnapshot;
|
|
384
|
+
return {
|
|
385
|
+
isDraft: false,
|
|
386
|
+
read: () => renamingSnapshot ?? store.getModel(providerId, ref.id) ?? { id: ref.id },
|
|
387
|
+
setField: (path, value) => {
|
|
388
|
+
store.setModelField(providerId, ref.id, path, value);
|
|
389
|
+
},
|
|
390
|
+
rename: async (newId) => {
|
|
391
|
+
const oldId = ref.id;
|
|
392
|
+
renamingSnapshot = store.getModel(providerId, oldId);
|
|
393
|
+
this.renaming = true;
|
|
394
|
+
try {
|
|
395
|
+
const error = await store.renameModel(providerId, oldId, newId);
|
|
396
|
+
if (error)
|
|
397
|
+
return error;
|
|
398
|
+
ref.id = newId;
|
|
399
|
+
const selected = this.leftItems[this.leftIndex];
|
|
400
|
+
if (selected?.kind === "model" && selected.modelId === oldId)
|
|
401
|
+
selected.modelId = newId;
|
|
402
|
+
this.options.refresher.touch(providerId);
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
finally {
|
|
406
|
+
renamingSnapshot = undefined;
|
|
407
|
+
this.renaming = false;
|
|
408
|
+
this.syncFromStore();
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
draftHandle() {
|
|
414
|
+
const draft = this.draft;
|
|
415
|
+
if (!draft)
|
|
416
|
+
throw new Error("No model draft");
|
|
417
|
+
return {
|
|
418
|
+
isDraft: true,
|
|
419
|
+
rename: async () => "Commit the model draft before renaming it.",
|
|
420
|
+
read: () => draft.fields,
|
|
421
|
+
setField: (path, value) => {
|
|
422
|
+
if (path.length === 1) {
|
|
423
|
+
const key = path[0];
|
|
424
|
+
if (value === DELETE)
|
|
425
|
+
delete draft.fields[key];
|
|
426
|
+
else
|
|
427
|
+
draft.fields[key] = value;
|
|
428
|
+
}
|
|
429
|
+
else {
|
|
430
|
+
// Nested draft fields (thinkingLevelMap.medium, compat.x, cost.input…)
|
|
431
|
+
const [head, ...rest] = path;
|
|
432
|
+
const container = { ...(draft.fields[head] ?? {}) };
|
|
433
|
+
let current = container;
|
|
434
|
+
for (const segment of rest.slice(0, -1)) {
|
|
435
|
+
const next = current[segment];
|
|
436
|
+
if (typeof next === "object" && next !== null && !Array.isArray(next)) {
|
|
437
|
+
current = next;
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
const created = {};
|
|
441
|
+
current[segment] = created;
|
|
442
|
+
current = created;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
const leaf = rest[rest.length - 1];
|
|
446
|
+
if (value === DELETE)
|
|
447
|
+
delete current[leaf];
|
|
448
|
+
else
|
|
449
|
+
current[leaf] = value;
|
|
450
|
+
draft.fields[head] = container;
|
|
451
|
+
}
|
|
452
|
+
this.refresh();
|
|
453
|
+
},
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
// ------------------------------------------------------------------
|
|
457
|
+
// EditorHost implementation
|
|
458
|
+
// ------------------------------------------------------------------
|
|
459
|
+
createHost() {
|
|
460
|
+
const store = this.options.store;
|
|
461
|
+
const providerId = this.options.providerId;
|
|
462
|
+
return {
|
|
463
|
+
tui: this.tui,
|
|
464
|
+
theme: this.theme,
|
|
465
|
+
keybindings: this.keybindings,
|
|
466
|
+
store,
|
|
467
|
+
refresher: this.options.refresher,
|
|
468
|
+
providerId,
|
|
469
|
+
pushPane: (pane) => this.pushPane(pane),
|
|
470
|
+
popPane: () => this.popPane(),
|
|
471
|
+
mutate: (apply) => {
|
|
472
|
+
const before = store.pendingCount;
|
|
473
|
+
apply();
|
|
474
|
+
if (store.pendingCount !== before &&
|
|
475
|
+
(!store.isDraftProvider(providerId) || hasProviderSettings(store.getProvider(providerId)))) {
|
|
476
|
+
this.options.refresher.touch(providerId);
|
|
477
|
+
}
|
|
478
|
+
this.refresh();
|
|
479
|
+
},
|
|
480
|
+
refresh: () => this.refresh(),
|
|
481
|
+
notify: (message, type) => this.options.notify(message, type),
|
|
482
|
+
effectiveApi: (model) => {
|
|
483
|
+
return effectiveModelSettings(providerId, store.getProvider(providerId), model).api;
|
|
484
|
+
},
|
|
485
|
+
effectiveBaseUrl: (model) => {
|
|
486
|
+
return effectiveModelSettings(providerId, store.getProvider(providerId), model).baseUrl;
|
|
487
|
+
},
|
|
488
|
+
commitModelDraft: () => this.commitModelDraft(),
|
|
489
|
+
discardModelDraft: () => this.discardModelDraft(),
|
|
490
|
+
isCurrentModel: (modelId) => this.options.currentModel?.provider === providerId && this.options.currentModel.id === modelId,
|
|
491
|
+
isCurrentProvider: () => this.options.currentModel?.provider === providerId,
|
|
492
|
+
setFetchStatus: (status) => {
|
|
493
|
+
this.fetchStatus = status;
|
|
494
|
+
this.refresh();
|
|
495
|
+
},
|
|
496
|
+
runFetch: (signal) => fetchProviderModels(this.options, signal),
|
|
497
|
+
importModels: async (models, signal) => {
|
|
498
|
+
const error = await importProviderModels(this.options, models, signal);
|
|
499
|
+
if (!this.disposed)
|
|
500
|
+
this.syncFromStore();
|
|
501
|
+
return error;
|
|
502
|
+
},
|
|
503
|
+
confirm: (message, actionLabel, action) => this.pushPane(new ConfirmPane(this.host, message, actionLabel, action)),
|
|
504
|
+
onModelRemoved: () => {
|
|
505
|
+
this.rebuildLeftItems();
|
|
506
|
+
// Land on the next row where the model was; walk back over separators.
|
|
507
|
+
this.leftIndex = Math.min(this.leftIndex, this.leftItems.length - 1);
|
|
508
|
+
while (this.leftIndex > 0 && this.leftItems[this.leftIndex]?.kind === "separator")
|
|
509
|
+
this.leftIndex--;
|
|
510
|
+
if (this.leftItems[this.leftIndex]?.kind === "separator")
|
|
511
|
+
this.leftIndex = this.selectableIndex(0);
|
|
512
|
+
this.focusPane = "left";
|
|
513
|
+
this.resetRightStack();
|
|
514
|
+
this.refresh();
|
|
515
|
+
},
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
/** Drop the in-memory draft: the selection returns to + Add Model with focus on the left. */
|
|
519
|
+
discardModelDraft() {
|
|
520
|
+
if (!this.draft)
|
|
521
|
+
return;
|
|
522
|
+
this.draft = undefined;
|
|
523
|
+
this.rebuildLeftItems();
|
|
524
|
+
const addIndex = this.leftItems.findIndex((item) => item.kind === "addModel");
|
|
525
|
+
this.leftIndex = addIndex >= 0 ? addIndex : this.selectableIndex(0);
|
|
526
|
+
this.focusPane = "left";
|
|
527
|
+
this.resetRightStack();
|
|
528
|
+
this.refresh();
|
|
529
|
+
}
|
|
530
|
+
commitModelDraft() {
|
|
531
|
+
const draft = this.draft;
|
|
532
|
+
if (!draft)
|
|
533
|
+
return "No model draft.";
|
|
534
|
+
const id = (draft.fields.id ?? "").trim();
|
|
535
|
+
if (!id)
|
|
536
|
+
return "Model id is required.";
|
|
537
|
+
if (this.options.store.getModel(this.options.providerId, id))
|
|
538
|
+
return `Model "${id}" already exists.`;
|
|
539
|
+
if (!this.host.effectiveApi(draft.fields)) {
|
|
540
|
+
return "Cannot resolve an api — set one under Model-Specific API or the provider's API Auth.";
|
|
541
|
+
}
|
|
542
|
+
if (!this.host.effectiveBaseUrl(draft.fields)) {
|
|
543
|
+
return "Cannot resolve a baseUrl — set one under Model-Specific API or the provider's API Auth.";
|
|
544
|
+
}
|
|
545
|
+
const fields = { ...draft.fields };
|
|
546
|
+
const model = { ...fields, id };
|
|
547
|
+
this.draft = undefined;
|
|
548
|
+
this.host.mutate(() => this.options.store.addModel(this.options.providerId, model));
|
|
549
|
+
this.rebuildLeftItems();
|
|
550
|
+
const index = this.leftItems.findIndex((item) => item.kind === "model" && item.modelId === id);
|
|
551
|
+
if (index >= 0)
|
|
552
|
+
this.leftIndex = index;
|
|
553
|
+
this.resetRightStack();
|
|
554
|
+
this.refresh();
|
|
555
|
+
return undefined;
|
|
556
|
+
}
|
|
557
|
+
// ------------------------------------------------------------------
|
|
558
|
+
// Frame rendering
|
|
559
|
+
// ------------------------------------------------------------------
|
|
560
|
+
refresh() {
|
|
561
|
+
this.invalidate();
|
|
562
|
+
this.tui.requestRender();
|
|
563
|
+
}
|
|
564
|
+
renderFrame(width) {
|
|
565
|
+
const theme = this.theme;
|
|
566
|
+
const border = new DynamicBorder((text) => theme.fg("border", text)).render(width)[0];
|
|
567
|
+
if (width < MIN_WIDTH) {
|
|
568
|
+
return [
|
|
569
|
+
border,
|
|
570
|
+
truncateToWidth(theme.fg("warning", ` /provider needs at least ${MIN_WIDTH} columns`), width),
|
|
571
|
+
border,
|
|
572
|
+
];
|
|
573
|
+
}
|
|
574
|
+
const leftWidth = this.computeLeftWidth(width);
|
|
575
|
+
const rightWidth = width - leftWidth - 3;
|
|
576
|
+
const separator = theme.fg("border", " │ ");
|
|
577
|
+
const title = this.renderTitle(width);
|
|
578
|
+
// The crumb marks drill-down depth only; base rows are already named by the left selection.
|
|
579
|
+
const crumb = truncateToWidth(this.theme.fg("muted", this.topPane()?.crumb ?? ""), rightWidth);
|
|
580
|
+
const leftLines = this.renderLeft(leftWidth);
|
|
581
|
+
const pane = this.topPane();
|
|
582
|
+
const paneLines = pane ? windowLines(theme, pane.render(rightWidth), BODY_ROWS - 1, pane.scrollWindow?.()) : [];
|
|
583
|
+
const rightLines = [crumb, ...paneLines];
|
|
584
|
+
const body = [];
|
|
585
|
+
for (let row = 0; row < BODY_ROWS; row++) {
|
|
586
|
+
const left = padTo(truncateToWidth(leftLines[row] ?? "", leftWidth), leftWidth);
|
|
587
|
+
const right = truncateToWidth(rightLines[row] ?? "", rightWidth);
|
|
588
|
+
body.push(left + separator + right);
|
|
589
|
+
}
|
|
590
|
+
const footerLines = this.renderFooter(width);
|
|
591
|
+
return [border, title, border, ...body, border, ...footerLines, border];
|
|
592
|
+
}
|
|
593
|
+
/** Content-driven left width: fits the longest visible label, clamped to 16–40 and 45% of the terminal. */
|
|
594
|
+
computeLeftWidth(width) {
|
|
595
|
+
let longest = 12;
|
|
596
|
+
for (const item of this.leftItems) {
|
|
597
|
+
if (item.kind === "separator")
|
|
598
|
+
continue;
|
|
599
|
+
let label = "";
|
|
600
|
+
switch (item.kind) {
|
|
601
|
+
case "apiAuth":
|
|
602
|
+
label = "API Auth";
|
|
603
|
+
break;
|
|
604
|
+
case "fetch":
|
|
605
|
+
label = `Fetch Models${this.fetchStatus ? ` ${this.fetchStatus}` : ""}`;
|
|
606
|
+
break;
|
|
607
|
+
case "model": {
|
|
608
|
+
const model = this.options.store.getModel(this.options.providerId, item.modelId);
|
|
609
|
+
label = model ? modelDisplayName(model) : item.modelId;
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
case "draft":
|
|
613
|
+
label = `${this.draft?.fields.name ?? this.draft?.fields.id ?? "New Model"} · draft`;
|
|
614
|
+
break;
|
|
615
|
+
case "addModel":
|
|
616
|
+
label = "+ Add Model";
|
|
617
|
+
break;
|
|
618
|
+
case "deleteProvider":
|
|
619
|
+
label = "Delete Provider";
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
longest = Math.max(longest, visibleWidth(label));
|
|
623
|
+
}
|
|
624
|
+
const fit = Math.max(LEFT_WIDTH_MIN, Math.min(LEFT_WIDTH_MAX, longest + 2));
|
|
625
|
+
return Math.min(fit, Math.max(LEFT_WIDTH_MIN, Math.floor(width * 0.45)));
|
|
626
|
+
}
|
|
627
|
+
renderTitle(width) {
|
|
628
|
+
const theme = this.theme;
|
|
629
|
+
let title = ` /provider · ${this.options.providerId}`;
|
|
630
|
+
if (isBuiltinProviderId(this.options.providerId))
|
|
631
|
+
title += " · overlays built-in catalog";
|
|
632
|
+
return truncateToWidth(theme.fg("accent", theme.bold(title)), width);
|
|
633
|
+
}
|
|
634
|
+
renderFooter(width) {
|
|
635
|
+
if (this.focusPane === "right") {
|
|
636
|
+
// The editor owns the way-back hint so every pane's footer is consistent.
|
|
637
|
+
const hints = [this.topPane()?.hints() ?? "", keyHint("app.provider.switchPaneLeft", "focus left")]
|
|
638
|
+
.filter(Boolean)
|
|
639
|
+
.join(" ");
|
|
640
|
+
return [truncateToWidth(hints, width)];
|
|
641
|
+
}
|
|
642
|
+
const hints = [
|
|
643
|
+
rawKeyHint("↑↓", "move"),
|
|
644
|
+
keyHint("tui.select.confirm", this.leftActivateLabel()),
|
|
645
|
+
keyHint("app.provider.switchPaneRight", "focus right"),
|
|
646
|
+
keyHint("tui.select.cancel", "back"),
|
|
647
|
+
].join(" ");
|
|
648
|
+
return [truncateToWidth(hints, width)];
|
|
649
|
+
}
|
|
650
|
+
/** What Enter does to the selected left-column row, so the footer never lies about action rows. */
|
|
651
|
+
leftActivateLabel() {
|
|
652
|
+
switch (this.leftItems[this.leftIndex]?.kind) {
|
|
653
|
+
case "fetch":
|
|
654
|
+
return "fetch";
|
|
655
|
+
case "addModel":
|
|
656
|
+
return "create";
|
|
657
|
+
case "deleteProvider":
|
|
658
|
+
return "delete";
|
|
659
|
+
default:
|
|
660
|
+
return "open";
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
function modelDisplayName(model) {
|
|
665
|
+
return model.name ?? model.id;
|
|
666
|
+
}
|
|
667
|
+
//# sourceMappingURL=editor.js.map
|