pi-llama-cpp 0.12.0 → 0.14.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/README.md +20 -9
- package/package.json +3 -3
- package/src/api/client.ts +25 -0
- package/src/constants.ts +8 -5
- package/src/enums/status.ts +0 -1
- package/src/interfaces/endpoints/models.ts +1 -1
- package/src/interfaces/settings.ts +3 -2
- package/src/interfaces/sortBy.ts +4 -0
- package/src/managers/command/models.ts +247 -0
- package/src/managers/command.ts +29 -459
- package/src/managers/events.ts +2 -2
- package/src/managers/server.ts +29 -5
- package/src/managers/settings.ts +12 -88
- package/src/models/baseModel.ts +27 -10
- package/src/models/legacyModel.ts +2 -2
- package/src/models/routerModel.ts +2 -2
- package/src/models/singleModel.ts +1 -1
- package/src/server.ts +35 -48
- package/src/sse/client.ts +113 -59
- package/src/sse/fetch.ts +43 -0
- package/src/sse/manager.ts +9 -27
- package/src/sse/types.ts +0 -4
- package/src/ui/dialog/base.ts +118 -0
- package/src/ui/dialog/confirm.ts +45 -0
- package/src/ui/dialog/factory.ts +111 -0
- package/src/ui/dialog/input.ts +63 -0
- package/src/ui/dialog/options.ts +23 -0
- package/src/ui/editors/editorOptions.ts +43 -0
- package/src/ui/editors/itemBuilder.ts +47 -0
- package/src/ui/editors/listEditor.ts +291 -0
- package/src/ui/editors/override/entry.ts +24 -0
- package/src/ui/editors/override/entryEditor.ts +166 -0
- package/src/ui/editors/override/fields.ts +294 -0
- package/src/ui/editors/override/handlers.ts +118 -0
- package/src/ui/editors/override/itemBuilder.ts +42 -0
- package/src/ui/editors/override/overrideList.ts +127 -0
- package/src/ui/editors/server/builder.ts +60 -0
- package/src/ui/editors/server/fields.ts +84 -0
- package/src/ui/editors/server/handlers.ts +32 -0
- package/src/ui/editors/server/itemBuilder.ts +68 -0
- package/src/ui/editors/server/serverEditor.ts +161 -0
- package/src/ui/editors/server/utils.ts +45 -0
- package/src/ui/editors/server/wizard.ts +110 -0
- package/src/ui/editors/settingField.ts +37 -0
- package/src/ui/editors/settingsListFactory.ts +33 -0
- package/src/ui/settings/index.ts +237 -0
- package/src/ui/strings.ts +8 -4
- package/src/utils/health.ts +48 -0
- package/src/utils/serverIds.ts +21 -0
- package/src/utils/settingsStore.ts +1 -1
- package/src/utils/urlResolver.ts +129 -0
- package/src/utils/urls.ts +33 -13
- package/tests/commandManager.test.ts +38 -7
- package/tests/dialog.test.ts +95 -2
- package/tests/health.test.ts +116 -0
- package/tests/legacyModel.test.ts +34 -28
- package/tests/overrides.test.ts +123 -68
- package/tests/routerModel.test.ts +67 -68
- package/tests/server.test.ts +47 -16
- package/tests/serverManager.test.ts +4 -4
- package/tests/settings.test.ts +10 -8
- package/tests/singleModel.test.ts +12 -12
- package/tests/sseManager.test.ts +6 -24
- package/src/ui/dialog.ts +0 -287
- package/src/ui/overrideEntryEditor.ts +0 -119
- package/src/ui/overrideSettingsList.ts +0 -682
- package/src/ui/serverListEditor.ts +0 -32
- package/src/ui/serverSettingsList.ts +0 -466
|
@@ -1,466 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getSettingsListTheme,
|
|
3
|
-
type Theme,
|
|
4
|
-
} from "@earendil-works/pi-coding-agent";
|
|
5
|
-
import type {
|
|
6
|
-
Component,
|
|
7
|
-
Focusable,
|
|
8
|
-
KeybindingsManager,
|
|
9
|
-
TUI,
|
|
10
|
-
} from "@earendil-works/pi-tui";
|
|
11
|
-
import { SettingsList, type SettingItem } from "@earendil-works/pi-tui";
|
|
12
|
-
import type { LlamaServer } from "../interfaces/settings";
|
|
13
|
-
import { ConfirmDialog, InputDialog } from "./dialog";
|
|
14
|
-
import { formatServerSuffix, normalizeServerUrl } from "./serverListEditor";
|
|
15
|
-
import { fieldMessage, FIELDS, HINTS, TITLES } from "./strings";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Options for the SettingsList-based server editor.
|
|
19
|
-
*/
|
|
20
|
-
export interface ServerSettingsListOptions {
|
|
21
|
-
/** TUI instance, used to request re-renders after async persists */
|
|
22
|
-
tui: TUI;
|
|
23
|
-
/** Theme for dialogs (from the ctx.ui.custom factory) */
|
|
24
|
-
theme: Theme;
|
|
25
|
-
/** App keybindings manager (injected by ctx.ui.custom) */
|
|
26
|
-
keybindings: KeybindingsManager;
|
|
27
|
-
/**
|
|
28
|
-
* Snapshot of the merged `llamaSettings.servers` to edit. Replaced
|
|
29
|
-
* in place (`options.servers = next`) after every successful persist
|
|
30
|
-
* so the rebuilt rows reflect the adopted list.
|
|
31
|
-
*/
|
|
32
|
-
servers: LlamaServer[];
|
|
33
|
-
/** Persists a new server list; a rejection keeps the current list */
|
|
34
|
-
persist: (next: LlamaServer[]) => Promise<void>;
|
|
35
|
-
/** Closes the editor (called on Esc in the server list) */
|
|
36
|
-
done: () => void;
|
|
37
|
-
/** Notifies about persistence errors */
|
|
38
|
-
onError: (message: string) => void;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Builds the SettingItem for one server's editable fields (URL, id, name).
|
|
43
|
-
* Each field is infinite → opens an `InputDialog` submenu; commits are
|
|
44
|
-
* reported by the containing `SettingsList`'s `onChange` (field id → value).
|
|
45
|
-
*/
|
|
46
|
-
const buildServerFieldItems = (
|
|
47
|
-
server: LlamaServer,
|
|
48
|
-
theme: Theme,
|
|
49
|
-
tui: TUI,
|
|
50
|
-
): SettingItem[] => [
|
|
51
|
-
{
|
|
52
|
-
id: "url",
|
|
53
|
-
label: FIELDS.serverUrl.label,
|
|
54
|
-
description: FIELDS.serverUrl.description,
|
|
55
|
-
currentValue: server.url,
|
|
56
|
-
submenu: InputDialog.inputSubmenu(
|
|
57
|
-
theme,
|
|
58
|
-
tui,
|
|
59
|
-
TITLES.edit(FIELDS.serverUrl.label),
|
|
60
|
-
fieldMessage(FIELDS.serverUrl),
|
|
61
|
-
FIELDS.serverUrl.placeholder,
|
|
62
|
-
server.url,
|
|
63
|
-
normalizeServerUrl,
|
|
64
|
-
),
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
id: "id",
|
|
68
|
-
label: FIELDS.providerId.label,
|
|
69
|
-
description: FIELDS.providerId.description,
|
|
70
|
-
currentValue: server.id ?? "",
|
|
71
|
-
submenu: InputDialog.inputSubmenu(
|
|
72
|
-
theme,
|
|
73
|
-
tui,
|
|
74
|
-
TITLES.edit(FIELDS.providerId.label),
|
|
75
|
-
fieldMessage(FIELDS.providerId),
|
|
76
|
-
FIELDS.providerId.placeholder,
|
|
77
|
-
server.id ?? "",
|
|
78
|
-
),
|
|
79
|
-
},
|
|
80
|
-
{
|
|
81
|
-
id: "name",
|
|
82
|
-
label: FIELDS.displayName.label,
|
|
83
|
-
description: FIELDS.displayName.description,
|
|
84
|
-
currentValue: server.name ?? "",
|
|
85
|
-
submenu: InputDialog.inputSubmenu(
|
|
86
|
-
theme,
|
|
87
|
-
tui,
|
|
88
|
-
TITLES.edit(FIELDS.displayName.label),
|
|
89
|
-
fieldMessage(FIELDS.displayName),
|
|
90
|
-
FIELDS.displayName.placeholder,
|
|
91
|
-
server.name ?? "",
|
|
92
|
-
),
|
|
93
|
-
},
|
|
94
|
-
];
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Builds the SettingItem for one server row in the top-level list.
|
|
98
|
-
* Enter drills into the server's field-edit submenu.
|
|
99
|
-
*/
|
|
100
|
-
const buildServerRow = (
|
|
101
|
-
server: LlamaServer,
|
|
102
|
-
index: number,
|
|
103
|
-
theme: Theme,
|
|
104
|
-
tui: TUI,
|
|
105
|
-
onChange: (field: string, value: string) => void,
|
|
106
|
-
): SettingItem => ({
|
|
107
|
-
id: `server-${index}`,
|
|
108
|
-
label: server.url,
|
|
109
|
-
description: HINTS.serverRow,
|
|
110
|
-
currentValue: formatServerSuffix(server),
|
|
111
|
-
submenu: (_cv, done) => {
|
|
112
|
-
// Rebuild the field items on open so they prefill with current values
|
|
113
|
-
const items = buildServerFieldItems(server, theme, tui);
|
|
114
|
-
return new SettingsList(
|
|
115
|
-
items,
|
|
116
|
-
Math.min(items.length + 2, 15),
|
|
117
|
-
getSettingsListTheme(),
|
|
118
|
-
// Field commits arrive here: done(value) from the input submenus
|
|
119
|
-
(field, value) => onChange(field, value),
|
|
120
|
-
done,
|
|
121
|
-
);
|
|
122
|
-
},
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Wrapper around a `SettingsList` of servers that adds `a` (add) and `d`
|
|
127
|
-
* (delete) support at the list level.
|
|
128
|
-
*
|
|
129
|
-
* - **a** opens the add wizard — a sequence of framed `InputDialog`s
|
|
130
|
-
* (URL → optional ID → optional name, mirroring `/login`'s sequential
|
|
131
|
-
* prompts). Esc at any step aborts without persisting; the server is
|
|
132
|
-
* saved only after the final step.
|
|
133
|
-
* - **d** opens a `ConfirmDialog` (Delete/Cancel select list).
|
|
134
|
-
* - **Enter** on a server row drills into its field-edit submenu.
|
|
135
|
-
* - **Esc** closes the editor.
|
|
136
|
-
*/
|
|
137
|
-
export class ServerSettingsList implements Component, Focusable {
|
|
138
|
-
private settingsList: SettingsList;
|
|
139
|
-
private mode: "list" | "wizard" | "confirm" = "list";
|
|
140
|
-
private wizardStep = 0;
|
|
141
|
-
private wizardUrl = "";
|
|
142
|
-
private wizardId = "";
|
|
143
|
-
private wizardDialog: InputDialog | undefined;
|
|
144
|
-
private confirmDialog: ConfirmDialog | undefined;
|
|
145
|
-
private isFocused = false;
|
|
146
|
-
|
|
147
|
-
constructor(private readonly options: ServerSettingsListOptions) {
|
|
148
|
-
this.settingsList = this.buildSettingsList();
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/**
|
|
152
|
-
* Builds the top-level `SettingsList` from the current
|
|
153
|
-
* `options.servers`. Called on construction and rebuilt after every
|
|
154
|
-
* successful persist so added/deleted/edited servers are reflected
|
|
155
|
-
* immediately (the initial snapshot is not mutated by `persist`).
|
|
156
|
-
*/
|
|
157
|
-
private buildSettingsList(): SettingsList {
|
|
158
|
-
const { theme, tui } = this.options;
|
|
159
|
-
const items: SettingItem[] = this.options.servers.map((server, i) =>
|
|
160
|
-
buildServerRow(server, i, theme, tui, (field, value) =>
|
|
161
|
-
this.handleFieldChange(field, value),
|
|
162
|
-
),
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
return new SettingsList(
|
|
166
|
-
items,
|
|
167
|
-
Math.min(items.length + 2, 15),
|
|
168
|
-
getSettingsListTheme(),
|
|
169
|
-
() => {
|
|
170
|
-
// Top-level onChange is a no-op (servers don't change at this level)
|
|
171
|
-
},
|
|
172
|
-
() => {
|
|
173
|
-
this.options.done();
|
|
174
|
-
},
|
|
175
|
-
);
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// -- Focusable -----------------------------------------------------------
|
|
179
|
-
|
|
180
|
-
get focused(): boolean {
|
|
181
|
-
return this.isFocused;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
set focused(value: boolean) {
|
|
185
|
-
this.isFocused = value;
|
|
186
|
-
if (this.wizardDialog) this.wizardDialog.focused = value;
|
|
187
|
-
if (this.confirmDialog) this.confirmDialog.focused = value;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// -- Component -----------------------------------------------------------
|
|
191
|
-
|
|
192
|
-
invalidate(): void {
|
|
193
|
-
this.settingsList.invalidate();
|
|
194
|
-
this.wizardDialog?.invalidate();
|
|
195
|
-
this.confirmDialog?.invalidate();
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
handleInput(data: string): void {
|
|
199
|
-
if (this.mode === "wizard") {
|
|
200
|
-
this.wizardDialog?.handleInput(data);
|
|
201
|
-
return;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (this.mode === "confirm") {
|
|
205
|
-
this.confirmDialog?.handleInput(data);
|
|
206
|
-
return;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const kb = this.options.keybindings;
|
|
210
|
-
if (kb.matches(data, "tui.select.cancel")) {
|
|
211
|
-
this.options.done();
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
// Track selection for add/delete
|
|
215
|
-
if (kb.matches(data, "tui.select.up")) {
|
|
216
|
-
this._selectedIndex =
|
|
217
|
-
this._selectedIndex === 0
|
|
218
|
-
? this.options.servers.length - 1
|
|
219
|
-
: this._selectedIndex - 1;
|
|
220
|
-
}
|
|
221
|
-
if (kb.matches(data, "tui.select.down")) {
|
|
222
|
-
this._selectedIndex =
|
|
223
|
-
this._selectedIndex === this.options.servers.length - 1
|
|
224
|
-
? 0
|
|
225
|
-
: this._selectedIndex + 1;
|
|
226
|
-
}
|
|
227
|
-
if (data === "a") {
|
|
228
|
-
this.beginWizard();
|
|
229
|
-
return;
|
|
230
|
-
}
|
|
231
|
-
if (data === "d") {
|
|
232
|
-
this.beginConfirm();
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
// Delegate to SettingsList (it handles up/down internally for rendering)
|
|
236
|
-
this.settingsList.handleInput(data);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
render(width: number): string[] {
|
|
240
|
-
if (this.mode === "wizard") {
|
|
241
|
-
return this.wizardDialog?.render(width) ?? [];
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (this.mode === "confirm") {
|
|
245
|
-
return this.confirmDialog?.render(width) ?? [];
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// List mode: the shortcuts live in the rows' description; only when the
|
|
249
|
-
// list is empty (no rows → no description) show them as the hint line
|
|
250
|
-
const settingsLines = this.settingsList.render(width);
|
|
251
|
-
if (this.options.servers.length === 0) {
|
|
252
|
-
settingsLines[settingsLines.length - 1] = getSettingsListTheme().hint(
|
|
253
|
-
HINTS.emptyServers,
|
|
254
|
-
);
|
|
255
|
-
}
|
|
256
|
-
return settingsLines;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// -- helpers ---------------------------------------------------------------
|
|
260
|
-
|
|
261
|
-
private selectedIndex(): number {
|
|
262
|
-
// Manual tracker for add/delete targeting, kept in sync with the
|
|
263
|
-
// SettingsList's cursor by rebuildList (SettingsList doesn't expose
|
|
264
|
-
// a getter)
|
|
265
|
-
return this._selectedIndex;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
private _selectedIndex = 0;
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Rebuilds the server list and places the cursor on `targetIndex`
|
|
272
|
-
* (clamped to the last row). A fresh SettingsList starts its cursor
|
|
273
|
-
* at index 0, so `selectItem` is used to restore the position.
|
|
274
|
-
*/
|
|
275
|
-
private rebuildList(targetIndex: number): void {
|
|
276
|
-
this.settingsList = this.buildSettingsList();
|
|
277
|
-
const count = this.options.servers.length;
|
|
278
|
-
if (count === 0) {
|
|
279
|
-
this._selectedIndex = 0;
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
const target = Math.min(targetIndex, count - 1);
|
|
283
|
-
this._selectedIndex = target;
|
|
284
|
-
this.settingsList.selectItem(`server-${target}`);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// -- add wizard ---------------------------------------------------------------
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Opens the add wizard at step 1/3 (URL). Esc at any step cancels the
|
|
291
|
-
* whole wizard; the server is persisted only after the final step.
|
|
292
|
-
*/
|
|
293
|
-
private beginWizard(): void {
|
|
294
|
-
this.mode = "wizard";
|
|
295
|
-
this.wizardStep = 0;
|
|
296
|
-
this.wizardUrl = "";
|
|
297
|
-
this.wizardId = "";
|
|
298
|
-
this.showWizardStep();
|
|
299
|
-
this.options.tui.requestRender();
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
private showWizardStep(): void {
|
|
303
|
-
const { theme, tui } = this.options;
|
|
304
|
-
const step = this.wizardStep;
|
|
305
|
-
|
|
306
|
-
const dialog =
|
|
307
|
-
step === 0
|
|
308
|
-
? new InputDialog({
|
|
309
|
-
theme,
|
|
310
|
-
tui,
|
|
311
|
-
title: TITLES.addServerStep(1),
|
|
312
|
-
message: fieldMessage(FIELDS.serverUrl),
|
|
313
|
-
placeholder: FIELDS.serverUrl.placeholder,
|
|
314
|
-
validate: normalizeServerUrl,
|
|
315
|
-
onSubmit: (value) => this.wizardSubmit(value),
|
|
316
|
-
onCancel: () => this.cancelWizard(),
|
|
317
|
-
})
|
|
318
|
-
: step === 1
|
|
319
|
-
? new InputDialog({
|
|
320
|
-
theme,
|
|
321
|
-
tui,
|
|
322
|
-
title: TITLES.addServerStep(2),
|
|
323
|
-
message: fieldMessage(FIELDS.providerId, "(optional)"),
|
|
324
|
-
placeholder: FIELDS.providerId.placeholder,
|
|
325
|
-
onSubmit: (value) => this.wizardSubmit(value),
|
|
326
|
-
onCancel: () => this.cancelWizard(),
|
|
327
|
-
})
|
|
328
|
-
: new InputDialog({
|
|
329
|
-
theme,
|
|
330
|
-
tui,
|
|
331
|
-
title: TITLES.addServerStep(3),
|
|
332
|
-
message: fieldMessage(FIELDS.displayName, "(optional)"),
|
|
333
|
-
placeholder: FIELDS.displayName.placeholder,
|
|
334
|
-
onSubmit: (value) => this.wizardSubmit(value),
|
|
335
|
-
onCancel: () => this.cancelWizard(),
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
this.wizardDialog = dialog;
|
|
339
|
-
dialog.focused = this.isFocused;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
private wizardSubmit(value: string): void {
|
|
343
|
-
if (this.wizardStep === 0) {
|
|
344
|
-
this.wizardUrl = value;
|
|
345
|
-
this.wizardStep = 1;
|
|
346
|
-
this.showWizardStep();
|
|
347
|
-
} else if (this.wizardStep === 1) {
|
|
348
|
-
this.wizardId = value;
|
|
349
|
-
this.wizardStep = 2;
|
|
350
|
-
this.showWizardStep();
|
|
351
|
-
} else {
|
|
352
|
-
// Final step (display name): commit the collected fields
|
|
353
|
-
this.wizardDialog = undefined;
|
|
354
|
-
void this.saveNewServer(this.wizardUrl, this.wizardId, value);
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
this.options.tui.requestRender();
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
private cancelWizard(): void {
|
|
361
|
-
this.wizardDialog = undefined;
|
|
362
|
-
this.mode = "list";
|
|
363
|
-
this.options.tui.requestRender();
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
private async saveNewServer(
|
|
367
|
-
url: string,
|
|
368
|
-
id: string,
|
|
369
|
-
name: string,
|
|
370
|
-
): Promise<void> {
|
|
371
|
-
const next = [
|
|
372
|
-
...this.options.servers,
|
|
373
|
-
{
|
|
374
|
-
url,
|
|
375
|
-
...(id.length > 0 ? { id } : {}),
|
|
376
|
-
...(name.length > 0 ? { name } : {}),
|
|
377
|
-
},
|
|
378
|
-
];
|
|
379
|
-
try {
|
|
380
|
-
await this.options.persist(next);
|
|
381
|
-
// Adopt the persisted list and rebuild so the new row appears;
|
|
382
|
-
// the new server is appended last — put the cursor on it
|
|
383
|
-
this.options.servers = next;
|
|
384
|
-
this.rebuildList(next.length - 1);
|
|
385
|
-
this.mode = "list";
|
|
386
|
-
} catch (err) {
|
|
387
|
-
this.options.onError(String(err));
|
|
388
|
-
this.mode = "list";
|
|
389
|
-
}
|
|
390
|
-
this.options.tui.requestRender();
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// -- delete ------------------------------------------------------------------
|
|
394
|
-
|
|
395
|
-
private beginConfirm(): void {
|
|
396
|
-
const server = this.options.servers[this.selectedIndex()];
|
|
397
|
-
if (!server) return;
|
|
398
|
-
|
|
399
|
-
this.mode = "confirm";
|
|
400
|
-
this.confirmDialog = new ConfirmDialog({
|
|
401
|
-
theme: this.options.theme,
|
|
402
|
-
tui: this.options.tui,
|
|
403
|
-
title: TITLES.deleteServer,
|
|
404
|
-
message: `Delete "${server.url}"?`,
|
|
405
|
-
onConfirm: () => {
|
|
406
|
-
this.confirmDialog = undefined;
|
|
407
|
-
void this.deleteSelected();
|
|
408
|
-
},
|
|
409
|
-
onCancel: () => {
|
|
410
|
-
this.confirmDialog = undefined;
|
|
411
|
-
this.mode = "list";
|
|
412
|
-
this.options.tui.requestRender();
|
|
413
|
-
},
|
|
414
|
-
});
|
|
415
|
-
this.confirmDialog.focused = this.isFocused;
|
|
416
|
-
this.options.tui.requestRender();
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
private async deleteSelected(): Promise<void> {
|
|
420
|
-
const idx = this.selectedIndex();
|
|
421
|
-
const servers = this.options.servers;
|
|
422
|
-
const next = servers.filter((_, i) => i !== idx);
|
|
423
|
-
try {
|
|
424
|
-
await this.options.persist(next);
|
|
425
|
-
// Adopt the persisted list and rebuild so the row disappears;
|
|
426
|
-
// the server that followed the deleted one now sits at the same
|
|
427
|
-
// index (rebuildList clamps when the last row was deleted)
|
|
428
|
-
this.options.servers = next;
|
|
429
|
-
this.rebuildList(idx);
|
|
430
|
-
this.mode = "list";
|
|
431
|
-
} catch (err) {
|
|
432
|
-
this.options.onError(String(err));
|
|
433
|
-
this.mode = "list";
|
|
434
|
-
}
|
|
435
|
-
this.options.tui.requestRender();
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
private async handleFieldChange(field: string, value: string): Promise<void> {
|
|
439
|
-
const idx = this._selectedIndex;
|
|
440
|
-
const server = this.options.servers[idx];
|
|
441
|
-
if (!server) return;
|
|
442
|
-
|
|
443
|
-
const next = this.options.servers.map((s, i) => {
|
|
444
|
-
if (i !== idx) return s;
|
|
445
|
-
if (field === "url") return { ...s, url: value };
|
|
446
|
-
if (field === "id") {
|
|
447
|
-
const { id, ...rest } = s;
|
|
448
|
-
return value.length === 0 ? rest : { ...rest, id: value };
|
|
449
|
-
}
|
|
450
|
-
const { name, ...rest } = s;
|
|
451
|
-
return value.length === 0 ? rest : { ...rest, name: value };
|
|
452
|
-
});
|
|
453
|
-
|
|
454
|
-
try {
|
|
455
|
-
await this.options.persist(next);
|
|
456
|
-
// URL/id/name changes alter row labels → rebuild the list;
|
|
457
|
-
// keep the cursor on the edited server
|
|
458
|
-
this.options.servers = next;
|
|
459
|
-
this.rebuildList(idx);
|
|
460
|
-
this.options.tui.requestRender();
|
|
461
|
-
} catch (err) {
|
|
462
|
-
this.options.onError(String(err));
|
|
463
|
-
this.options.tui.requestRender();
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
}
|