@appkit/llamacpp-cli 2.0.0 → 2.1.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 +271 -277
- package/dist/cli.js +133 -23
- package/dist/cli.js.map +1 -1
- package/dist/commands/admin/config.d.ts +1 -1
- package/dist/commands/admin/config.js +5 -5
- package/dist/commands/admin/config.js.map +1 -1
- package/dist/commands/admin/log-config.d.ts +11 -0
- package/dist/commands/admin/log-config.d.ts.map +1 -0
- package/dist/commands/admin/log-config.js +159 -0
- package/dist/commands/admin/log-config.js.map +1 -0
- package/dist/commands/admin/logs.d.ts +2 -3
- package/dist/commands/admin/logs.d.ts.map +1 -1
- package/dist/commands/admin/logs.js +6 -48
- package/dist/commands/admin/logs.js.map +1 -1
- package/dist/commands/admin/status.d.ts.map +1 -1
- package/dist/commands/admin/status.js +1 -0
- package/dist/commands/admin/status.js.map +1 -1
- package/dist/commands/config.d.ts +1 -0
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +63 -196
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/create.d.ts +3 -2
- package/dist/commands/create.d.ts.map +1 -1
- package/dist/commands/create.js +24 -97
- package/dist/commands/create.js.map +1 -1
- package/dist/commands/delete.d.ts.map +1 -1
- package/dist/commands/delete.js +7 -24
- package/dist/commands/delete.js.map +1 -1
- package/dist/commands/internal/server-wrapper.d.ts +15 -0
- package/dist/commands/internal/server-wrapper.d.ts.map +1 -0
- package/dist/commands/internal/server-wrapper.js +126 -0
- package/dist/commands/internal/server-wrapper.js.map +1 -0
- package/dist/commands/logs-all.d.ts +0 -2
- package/dist/commands/logs-all.d.ts.map +1 -1
- package/dist/commands/logs-all.js +1 -61
- package/dist/commands/logs-all.js.map +1 -1
- package/dist/commands/logs.d.ts +2 -5
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +104 -120
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/migrate-labels.d.ts +12 -0
- package/dist/commands/migrate-labels.d.ts.map +1 -0
- package/dist/commands/migrate-labels.js +160 -0
- package/dist/commands/migrate-labels.js.map +1 -0
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +2 -1
- package/dist/commands/ps.js.map +1 -1
- package/dist/commands/rm.d.ts.map +1 -1
- package/dist/commands/rm.js +22 -48
- package/dist/commands/rm.js.map +1 -1
- package/dist/commands/router/config.d.ts +1 -1
- package/dist/commands/router/config.js +6 -6
- package/dist/commands/router/config.js.map +1 -1
- package/dist/commands/router/logs.d.ts +2 -4
- package/dist/commands/router/logs.d.ts.map +1 -1
- package/dist/commands/router/logs.js +34 -189
- package/dist/commands/router/logs.js.map +1 -1
- package/dist/commands/router/status.d.ts.map +1 -1
- package/dist/commands/router/status.js +1 -0
- package/dist/commands/router/status.js.map +1 -1
- package/dist/commands/server-show.d.ts.map +1 -1
- package/dist/commands/server-show.js +3 -0
- package/dist/commands/server-show.js.map +1 -1
- package/dist/commands/start.d.ts.map +1 -1
- package/dist/commands/start.js +21 -72
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/stop.d.ts.map +1 -1
- package/dist/commands/stop.js +10 -26
- package/dist/commands/stop.js.map +1 -1
- package/dist/launchers/llamacpp-admin +8 -0
- package/dist/launchers/llamacpp-router +8 -0
- package/dist/launchers/llamacpp-server +8 -0
- package/dist/lib/admin-manager.d.ts +4 -0
- package/dist/lib/admin-manager.d.ts.map +1 -1
- package/dist/lib/admin-manager.js +42 -18
- package/dist/lib/admin-manager.js.map +1 -1
- package/dist/lib/admin-server.d.ts +48 -1
- package/dist/lib/admin-server.d.ts.map +1 -1
- package/dist/lib/admin-server.js +632 -238
- package/dist/lib/admin-server.js.map +1 -1
- package/dist/lib/config-generator.d.ts +1 -0
- package/dist/lib/config-generator.d.ts.map +1 -1
- package/dist/lib/config-generator.js +12 -5
- package/dist/lib/config-generator.js.map +1 -1
- package/dist/lib/keyboard-manager.d.ts +162 -0
- package/dist/lib/keyboard-manager.d.ts.map +1 -0
- package/dist/lib/keyboard-manager.js +247 -0
- package/dist/lib/keyboard-manager.js.map +1 -0
- package/dist/lib/label-migration.d.ts +65 -0
- package/dist/lib/label-migration.d.ts.map +1 -0
- package/dist/lib/label-migration.js +458 -0
- package/dist/lib/label-migration.js.map +1 -0
- package/dist/lib/launchctl-manager.d.ts +9 -0
- package/dist/lib/launchctl-manager.d.ts.map +1 -1
- package/dist/lib/launchctl-manager.js +65 -19
- package/dist/lib/launchctl-manager.js.map +1 -1
- package/dist/lib/log-management-service.d.ts +51 -0
- package/dist/lib/log-management-service.d.ts.map +1 -0
- package/dist/lib/log-management-service.js +124 -0
- package/dist/lib/log-management-service.js.map +1 -0
- package/dist/lib/log-workers.d.ts +70 -0
- package/dist/lib/log-workers.d.ts.map +1 -0
- package/dist/lib/log-workers.js +217 -0
- package/dist/lib/log-workers.js.map +1 -0
- package/dist/lib/model-downloader.d.ts +9 -1
- package/dist/lib/model-downloader.d.ts.map +1 -1
- package/dist/lib/model-downloader.js +98 -1
- package/dist/lib/model-downloader.js.map +1 -1
- package/dist/lib/model-management-service.d.ts +60 -0
- package/dist/lib/model-management-service.d.ts.map +1 -0
- package/dist/lib/model-management-service.js +246 -0
- package/dist/lib/model-management-service.js.map +1 -0
- package/dist/lib/model-management-service.test.d.ts +2 -0
- package/dist/lib/model-management-service.test.d.ts.map +1 -0
- package/dist/lib/model-management-service.test.js.map +1 -0
- package/dist/lib/model-scanner.d.ts +15 -3
- package/dist/lib/model-scanner.d.ts.map +1 -1
- package/dist/lib/model-scanner.js +174 -17
- package/dist/lib/model-scanner.js.map +1 -1
- package/dist/lib/openapi-spec.d.ts +1335 -0
- package/dist/lib/openapi-spec.d.ts.map +1 -0
- package/dist/lib/openapi-spec.js +1017 -0
- package/dist/lib/openapi-spec.js.map +1 -0
- package/dist/lib/router-logger.d.ts +1 -1
- package/dist/lib/router-logger.d.ts.map +1 -1
- package/dist/lib/router-logger.js +13 -11
- package/dist/lib/router-logger.js.map +1 -1
- package/dist/lib/router-manager.d.ts +4 -0
- package/dist/lib/router-manager.d.ts.map +1 -1
- package/dist/lib/router-manager.js +30 -18
- package/dist/lib/router-manager.js.map +1 -1
- package/dist/lib/router-server.d.ts.map +1 -1
- package/dist/lib/router-server.js +22 -12
- package/dist/lib/router-server.js.map +1 -1
- package/dist/lib/server-config-service.d.ts +51 -0
- package/dist/lib/server-config-service.d.ts.map +1 -0
- package/dist/lib/server-config-service.js +310 -0
- package/dist/lib/server-config-service.js.map +1 -0
- package/dist/lib/server-config-service.test.d.ts +2 -0
- package/dist/lib/server-config-service.test.d.ts.map +1 -0
- package/dist/lib/server-config-service.test.js.map +1 -0
- package/dist/lib/server-lifecycle-service.d.ts +172 -0
- package/dist/lib/server-lifecycle-service.d.ts.map +1 -0
- package/dist/lib/server-lifecycle-service.js +619 -0
- package/dist/lib/server-lifecycle-service.js.map +1 -0
- package/dist/lib/state-manager.d.ts +18 -1
- package/dist/lib/state-manager.d.ts.map +1 -1
- package/dist/lib/state-manager.js +51 -2
- package/dist/lib/state-manager.js.map +1 -1
- package/dist/lib/status-checker.d.ts +11 -4
- package/dist/lib/status-checker.d.ts.map +1 -1
- package/dist/lib/status-checker.js +34 -1
- package/dist/lib/status-checker.js.map +1 -1
- package/dist/lib/validation-service.d.ts +43 -0
- package/dist/lib/validation-service.d.ts.map +1 -0
- package/dist/lib/validation-service.js +112 -0
- package/dist/lib/validation-service.js.map +1 -0
- package/dist/lib/validation-service.test.d.ts +2 -0
- package/dist/lib/validation-service.test.d.ts.map +1 -0
- package/dist/lib/validation-service.test.js.map +1 -0
- package/dist/scripts/http-log-filter.sh +8 -0
- package/dist/tui/ConfigApp.d.ts.map +1 -1
- package/dist/tui/ConfigApp.js +222 -184
- package/dist/tui/ConfigApp.js.map +1 -1
- package/dist/tui/HistoricalMonitorApp.d.ts.map +1 -1
- package/dist/tui/HistoricalMonitorApp.js +12 -0
- package/dist/tui/HistoricalMonitorApp.js.map +1 -1
- package/dist/tui/ModelsApp.d.ts.map +1 -1
- package/dist/tui/ModelsApp.js +93 -17
- package/dist/tui/ModelsApp.js.map +1 -1
- package/dist/tui/MonitorApp.d.ts.map +1 -1
- package/dist/tui/MonitorApp.js +1 -3
- package/dist/tui/MonitorApp.js.map +1 -1
- package/dist/tui/MultiServerMonitorApp.d.ts +3 -3
- package/dist/tui/MultiServerMonitorApp.d.ts.map +1 -1
- package/dist/tui/MultiServerMonitorApp.js +724 -508
- package/dist/tui/MultiServerMonitorApp.js.map +1 -1
- package/dist/tui/RootNavigator.d.ts.map +1 -1
- package/dist/tui/RootNavigator.js +17 -1
- package/dist/tui/RootNavigator.js.map +1 -1
- package/dist/tui/RouterApp.d.ts +6 -0
- package/dist/tui/RouterApp.d.ts.map +1 -0
- package/dist/tui/RouterApp.js +928 -0
- package/dist/tui/RouterApp.js.map +1 -0
- package/dist/tui/SearchApp.d.ts.map +1 -1
- package/dist/tui/SearchApp.js +27 -6
- package/dist/tui/SearchApp.js.map +1 -1
- package/dist/tui/shared/modal-controller.d.ts +65 -0
- package/dist/tui/shared/modal-controller.d.ts.map +1 -0
- package/dist/tui/shared/modal-controller.js +625 -0
- package/dist/tui/shared/modal-controller.js.map +1 -0
- package/dist/tui/shared/overlay-utils.d.ts +7 -0
- package/dist/tui/shared/overlay-utils.d.ts.map +1 -0
- package/dist/tui/shared/overlay-utils.js +54 -0
- package/dist/tui/shared/overlay-utils.js.map +1 -0
- package/dist/types/admin-config.d.ts +15 -2
- package/dist/types/admin-config.d.ts.map +1 -1
- package/dist/types/model-info.d.ts +5 -0
- package/dist/types/model-info.d.ts.map +1 -1
- package/dist/types/router-config.d.ts +2 -2
- package/dist/types/router-config.d.ts.map +1 -1
- package/dist/types/server-config.d.ts +8 -0
- package/dist/types/server-config.d.ts.map +1 -1
- package/dist/types/server-config.js +25 -0
- package/dist/types/server-config.js.map +1 -1
- package/dist/utils/http-log-filter.d.ts +10 -0
- package/dist/utils/http-log-filter.d.ts.map +1 -0
- package/dist/utils/http-log-filter.js +84 -0
- package/dist/utils/http-log-filter.js.map +1 -0
- package/dist/utils/log-parser.d.ts.map +1 -1
- package/dist/utils/log-parser.js +7 -4
- package/dist/utils/log-parser.js.map +1 -1
- package/dist/utils/log-utils.d.ts +59 -4
- package/dist/utils/log-utils.d.ts.map +1 -1
- package/dist/utils/log-utils.js +150 -11
- package/dist/utils/log-utils.js.map +1 -1
- package/dist/utils/shard-utils.d.ts +72 -0
- package/dist/utils/shard-utils.d.ts.map +1 -0
- package/dist/utils/shard-utils.js +168 -0
- package/dist/utils/shard-utils.js.map +1 -0
- package/package.json +18 -4
- package/src/launchers/llamacpp-admin +8 -0
- package/src/launchers/llamacpp-router +8 -0
- package/src/launchers/llamacpp-server +8 -0
- package/web/dist/assets/index-Byhoy86V.css +1 -0
- package/web/dist/assets/index-HSrgvray.js +50 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-Bin89Lwr.css +0 -1
- package/web/dist/assets/index-CVmonw3T.js +0 -17
package/dist/tui/ConfigApp.js
CHANGED
|
@@ -47,6 +47,8 @@ const port_manager_js_1 = require("../lib/port-manager.js");
|
|
|
47
47
|
const model_scanner_js_1 = require("../lib/model-scanner.js");
|
|
48
48
|
const file_utils_js_1 = require("../utils/file-utils.js");
|
|
49
49
|
const log_utils_js_1 = require("../utils/log-utils.js");
|
|
50
|
+
const modal_controller_js_1 = require("./shared/modal-controller.js");
|
|
51
|
+
const keyboard_manager_js_1 = require("../lib/keyboard-manager.js");
|
|
50
52
|
/**
|
|
51
53
|
* Config screen TUI for editing server configuration
|
|
52
54
|
*/
|
|
@@ -61,6 +63,28 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
61
63
|
value: server.modelName,
|
|
62
64
|
originalValue: server.modelName,
|
|
63
65
|
},
|
|
66
|
+
{
|
|
67
|
+
key: 'alias',
|
|
68
|
+
label: 'Alias',
|
|
69
|
+
type: 'text',
|
|
70
|
+
value: server.alias || '',
|
|
71
|
+
originalValue: server.alias || '',
|
|
72
|
+
validation: async (value) => {
|
|
73
|
+
// Empty string is valid (means remove alias)
|
|
74
|
+
if (value.trim() === '')
|
|
75
|
+
return null;
|
|
76
|
+
// Validate format
|
|
77
|
+
const formatError = (0, server_config_js_1.validateAlias)(value.trim());
|
|
78
|
+
if (formatError)
|
|
79
|
+
return formatError;
|
|
80
|
+
// Check uniqueness (exclude current server)
|
|
81
|
+
const conflictingServerId = await state_manager_js_1.stateManager.isAliasAvailable(value.trim(), server.id);
|
|
82
|
+
if (conflictingServerId) {
|
|
83
|
+
return `Alias already used by server: ${conflictingServerId}`;
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
},
|
|
87
|
+
},
|
|
64
88
|
{
|
|
65
89
|
key: 'host',
|
|
66
90
|
label: 'Host',
|
|
@@ -118,8 +142,8 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
118
142
|
value: server.gpuLayers,
|
|
119
143
|
originalValue: server.gpuLayers,
|
|
120
144
|
validation: (value) => {
|
|
121
|
-
if (value <
|
|
122
|
-
return 'Must be
|
|
145
|
+
if (value < -1)
|
|
146
|
+
return 'Must be -1 (all) or a non-negative integer';
|
|
123
147
|
if (value > 999)
|
|
124
148
|
return 'Must be at most 999';
|
|
125
149
|
return null;
|
|
@@ -144,6 +168,9 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
144
168
|
selectedIndex: 0,
|
|
145
169
|
hasChanges: false,
|
|
146
170
|
};
|
|
171
|
+
// Keyboard manager and modal controller for centralized keyboard handling
|
|
172
|
+
const keyboardManager = new keyboard_manager_js_1.KeyboardManager(screen);
|
|
173
|
+
const modalController = new modal_controller_js_1.ModalController(screen, keyboardManager);
|
|
147
174
|
// Create content box
|
|
148
175
|
const contentBox = blessed_1.default.box({
|
|
149
176
|
top: 0,
|
|
@@ -265,6 +292,7 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
265
292
|
screen.render();
|
|
266
293
|
}
|
|
267
294
|
// Create a centered modal box
|
|
295
|
+
// Helper to create modal element (for custom modals with special logic)
|
|
268
296
|
function createModal(title, height = 'shrink') {
|
|
269
297
|
return blessed_1.default.box({
|
|
270
298
|
parent: screen,
|
|
@@ -281,11 +309,34 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
281
309
|
label: ` ${title} `,
|
|
282
310
|
});
|
|
283
311
|
}
|
|
284
|
-
//
|
|
285
|
-
|
|
312
|
+
// Helper to create semi-transparent overlay
|
|
313
|
+
function createOverlay() {
|
|
314
|
+
return blessed_1.default.box({
|
|
315
|
+
parent: screen,
|
|
316
|
+
top: 0,
|
|
317
|
+
left: 0,
|
|
318
|
+
width: '100%',
|
|
319
|
+
height: '100%',
|
|
320
|
+
style: {
|
|
321
|
+
bg: 'black',
|
|
322
|
+
transparent: true,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Number input modal with context size support
|
|
328
|
+
* Note: This function unregisters handlers before opening modal.
|
|
329
|
+
* Handlers are re-registered when modal closes via callback.
|
|
330
|
+
*/
|
|
331
|
+
async function editNumber(field, onClose) {
|
|
286
332
|
unregisterHandlers();
|
|
287
333
|
return new Promise((resolve) => {
|
|
334
|
+
const handleClose = onClose || (() => {
|
|
335
|
+
registerHandlers();
|
|
336
|
+
render();
|
|
337
|
+
});
|
|
288
338
|
const isCtxSize = field.key === 'ctxSize';
|
|
339
|
+
const overlay = createOverlay();
|
|
289
340
|
const modal = createModal(`Edit ${field.label}`, isCtxSize ? 11 : 10);
|
|
290
341
|
const currentDisplay = isCtxSize
|
|
291
342
|
? formatContextSize(field.value)
|
|
@@ -332,9 +383,11 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
332
383
|
? formatContextSize(field.value)
|
|
333
384
|
: String(field.value);
|
|
334
385
|
inputBox.setValue(initialValue);
|
|
386
|
+
screen.append(overlay);
|
|
387
|
+
screen.append(modal);
|
|
335
388
|
screen.render();
|
|
336
389
|
inputBox.focus();
|
|
337
|
-
inputBox.on('submit', (value) => {
|
|
390
|
+
inputBox.on('submit', async (value) => {
|
|
338
391
|
let numValue;
|
|
339
392
|
if (isCtxSize) {
|
|
340
393
|
numValue = parseContextSize(value);
|
|
@@ -346,9 +399,17 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
346
399
|
}
|
|
347
400
|
if (numValue !== null) {
|
|
348
401
|
if (field.validation) {
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
402
|
+
try {
|
|
403
|
+
const error = await field.validation(numValue);
|
|
404
|
+
if (error) {
|
|
405
|
+
infoText.setContent(`{red-fg}Error: ${error}{/red-fg}`);
|
|
406
|
+
screen.render();
|
|
407
|
+
inputBox.focus();
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
catch (err) {
|
|
412
|
+
infoText.setContent(`{red-fg}Validation error: ${err.message}{/red-fg}`);
|
|
352
413
|
screen.render();
|
|
353
414
|
inputBox.focus();
|
|
354
415
|
return;
|
|
@@ -358,34 +419,37 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
358
419
|
updateHasChanges();
|
|
359
420
|
}
|
|
360
421
|
screen.remove(modal);
|
|
361
|
-
|
|
362
|
-
|
|
422
|
+
screen.remove(overlay);
|
|
423
|
+
handleClose();
|
|
363
424
|
resolve();
|
|
364
425
|
});
|
|
365
426
|
inputBox.on('cancel', () => {
|
|
366
427
|
screen.remove(modal);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
resolve();
|
|
370
|
-
});
|
|
371
|
-
inputBox.key(['escape'], () => {
|
|
372
|
-
screen.remove(modal);
|
|
373
|
-
registerHandlers();
|
|
374
|
-
render();
|
|
428
|
+
screen.remove(overlay);
|
|
429
|
+
handleClose();
|
|
375
430
|
resolve();
|
|
376
431
|
});
|
|
377
432
|
});
|
|
378
433
|
}
|
|
379
|
-
|
|
380
|
-
|
|
434
|
+
/**
|
|
435
|
+
* Toggle/select modal
|
|
436
|
+
* Note: This function unregisters handlers before opening modal.
|
|
437
|
+
* Handlers are re-registered when modal closes via callback.
|
|
438
|
+
*/
|
|
439
|
+
async function editSelect(field, onClose) {
|
|
381
440
|
unregisterHandlers();
|
|
382
441
|
return new Promise((resolve) => {
|
|
442
|
+
const handleClose = onClose || (() => {
|
|
443
|
+
registerHandlers();
|
|
444
|
+
render();
|
|
445
|
+
});
|
|
383
446
|
const options = field.options || [];
|
|
384
447
|
let selectedOption = field.type === 'toggle'
|
|
385
448
|
? (field.value ? 1 : 0)
|
|
386
449
|
: options.indexOf(String(field.value));
|
|
387
450
|
if (selectedOption < 0)
|
|
388
451
|
selectedOption = 0;
|
|
452
|
+
const overlay = createOverlay();
|
|
389
453
|
const modal = createModal(field.label, options.length + 6);
|
|
390
454
|
function renderOptions() {
|
|
391
455
|
let content = '\n';
|
|
@@ -407,6 +471,8 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
407
471
|
modal.setContent(content);
|
|
408
472
|
screen.render();
|
|
409
473
|
}
|
|
474
|
+
screen.append(overlay);
|
|
475
|
+
screen.append(modal);
|
|
410
476
|
renderOptions();
|
|
411
477
|
modal.focus();
|
|
412
478
|
modal.key(['up', 'k'], () => {
|
|
@@ -426,28 +492,43 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
426
492
|
}
|
|
427
493
|
updateHasChanges();
|
|
428
494
|
screen.remove(modal);
|
|
429
|
-
|
|
430
|
-
|
|
495
|
+
screen.remove(overlay);
|
|
496
|
+
handleClose();
|
|
431
497
|
resolve();
|
|
432
498
|
});
|
|
433
499
|
modal.key(['escape'], () => {
|
|
434
500
|
screen.remove(modal);
|
|
435
|
-
|
|
436
|
-
|
|
501
|
+
screen.remove(overlay);
|
|
502
|
+
handleClose();
|
|
437
503
|
resolve();
|
|
438
504
|
});
|
|
439
505
|
});
|
|
440
506
|
}
|
|
441
|
-
|
|
442
|
-
|
|
507
|
+
/**
|
|
508
|
+
* Text input modal
|
|
509
|
+
* Note: This function unregisters handlers before opening modal.
|
|
510
|
+
* Handlers are re-registered when modal closes via callback.
|
|
511
|
+
*/
|
|
512
|
+
async function editText(field, onClose) {
|
|
443
513
|
unregisterHandlers();
|
|
444
514
|
return new Promise((resolve) => {
|
|
515
|
+
const handleClose = onClose || (() => {
|
|
516
|
+
registerHandlers();
|
|
517
|
+
render();
|
|
518
|
+
});
|
|
519
|
+
const overlay = createOverlay();
|
|
445
520
|
const modal = createModal(`Edit ${field.label}`, 10);
|
|
521
|
+
// Customize info text based on field type
|
|
522
|
+
const infoContent = field.key === 'customFlags'
|
|
523
|
+
? 'Enter comma-separated flags:'
|
|
524
|
+
: field.key === 'alias'
|
|
525
|
+
? 'Enter alias (or leave empty to remove):'
|
|
526
|
+
: `Enter ${field.label.toLowerCase()}:`;
|
|
446
527
|
const infoText = blessed_1.default.text({
|
|
447
528
|
parent: modal,
|
|
448
529
|
top: 1,
|
|
449
530
|
left: 2,
|
|
450
|
-
content:
|
|
531
|
+
content: infoContent,
|
|
451
532
|
tags: true,
|
|
452
533
|
});
|
|
453
534
|
const inputBox = blessed_1.default.textbox({
|
|
@@ -471,45 +552,72 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
471
552
|
tags: true,
|
|
472
553
|
});
|
|
473
554
|
inputBox.setValue(field.value || '');
|
|
555
|
+
screen.append(overlay);
|
|
556
|
+
screen.append(modal);
|
|
474
557
|
screen.render();
|
|
475
558
|
inputBox.focus();
|
|
476
|
-
inputBox.on('submit', (value) => {
|
|
477
|
-
|
|
559
|
+
inputBox.on('submit', async (value) => {
|
|
560
|
+
const trimmedValue = value.trim();
|
|
561
|
+
// Run validation if present (handle both sync and async)
|
|
562
|
+
if (field.validation) {
|
|
563
|
+
try {
|
|
564
|
+
const error = await field.validation(trimmedValue);
|
|
565
|
+
if (error) {
|
|
566
|
+
infoText.setContent(`{red-fg}Error: ${error}{/red-fg}`);
|
|
567
|
+
screen.render();
|
|
568
|
+
inputBox.focus();
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
catch (err) {
|
|
573
|
+
infoText.setContent(`{red-fg}Validation error: ${err.message}{/red-fg}`);
|
|
574
|
+
screen.render();
|
|
575
|
+
inputBox.focus();
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
field.value = trimmedValue;
|
|
478
580
|
updateHasChanges();
|
|
479
581
|
screen.remove(modal);
|
|
480
|
-
|
|
481
|
-
|
|
582
|
+
screen.remove(overlay);
|
|
583
|
+
handleClose();
|
|
482
584
|
resolve();
|
|
483
585
|
});
|
|
484
586
|
inputBox.on('cancel', () => {
|
|
485
587
|
screen.remove(modal);
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
resolve();
|
|
489
|
-
});
|
|
490
|
-
inputBox.key(['escape'], () => {
|
|
491
|
-
screen.remove(modal);
|
|
492
|
-
registerHandlers();
|
|
493
|
-
render();
|
|
588
|
+
screen.remove(overlay);
|
|
589
|
+
handleClose();
|
|
494
590
|
resolve();
|
|
495
591
|
});
|
|
496
592
|
});
|
|
497
593
|
}
|
|
498
594
|
// Model picker modal
|
|
499
|
-
|
|
595
|
+
/**
|
|
596
|
+
* Model selection modal
|
|
597
|
+
* Note: This function unregisters handlers before opening modal.
|
|
598
|
+
* Handlers are re-registered when modal closes via callback.
|
|
599
|
+
*/
|
|
600
|
+
async function editModel(field, onClose) {
|
|
500
601
|
unregisterHandlers();
|
|
501
602
|
return new Promise(async (resolve) => {
|
|
603
|
+
const handleClose = onClose || (() => {
|
|
604
|
+
registerHandlers();
|
|
605
|
+
render();
|
|
606
|
+
});
|
|
502
607
|
const models = await model_scanner_js_1.modelScanner.scanModels();
|
|
503
608
|
if (models.length === 0) {
|
|
504
609
|
// Show error modal
|
|
610
|
+
const errorOverlay = createOverlay();
|
|
505
611
|
const errorModal = createModal('Error', 7);
|
|
506
612
|
errorModal.setContent('\n {red-fg}No models found in ~/models{/red-fg}\n\n {gray-fg}[ESC] Close{/gray-fg}');
|
|
613
|
+
screen.append(errorOverlay);
|
|
614
|
+
screen.append(errorModal);
|
|
507
615
|
screen.render();
|
|
508
616
|
errorModal.focus();
|
|
509
617
|
errorModal.key(['escape', 'enter'], () => {
|
|
510
618
|
screen.remove(errorModal);
|
|
511
|
-
|
|
512
|
-
|
|
619
|
+
screen.remove(errorOverlay);
|
|
620
|
+
handleClose();
|
|
513
621
|
resolve();
|
|
514
622
|
});
|
|
515
623
|
return;
|
|
@@ -519,6 +627,7 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
519
627
|
selectedIndex = 0;
|
|
520
628
|
let scrollOffset = 0;
|
|
521
629
|
const maxVisible = 8;
|
|
630
|
+
const overlay = createOverlay();
|
|
522
631
|
const modal = createModal('Select Model', maxVisible + 6);
|
|
523
632
|
function renderModels() {
|
|
524
633
|
// Adjust scroll offset
|
|
@@ -559,6 +668,8 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
559
668
|
modal.setContent(content);
|
|
560
669
|
screen.render();
|
|
561
670
|
}
|
|
671
|
+
screen.append(overlay);
|
|
672
|
+
screen.append(modal);
|
|
562
673
|
renderModels();
|
|
563
674
|
modal.focus();
|
|
564
675
|
modal.key(['up', 'k'], () => {
|
|
@@ -573,153 +684,69 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
573
684
|
field.value = models[selectedIndex].filename;
|
|
574
685
|
updateHasChanges();
|
|
575
686
|
screen.remove(modal);
|
|
576
|
-
|
|
577
|
-
|
|
687
|
+
screen.remove(overlay);
|
|
688
|
+
handleClose();
|
|
578
689
|
resolve();
|
|
579
690
|
});
|
|
580
691
|
modal.key(['escape'], () => {
|
|
581
692
|
screen.remove(modal);
|
|
582
|
-
|
|
583
|
-
|
|
693
|
+
screen.remove(overlay);
|
|
694
|
+
handleClose();
|
|
584
695
|
resolve();
|
|
585
696
|
});
|
|
586
697
|
});
|
|
587
698
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
];
|
|
599
|
-
function renderDialog() {
|
|
600
|
-
let content = '\n';
|
|
601
|
-
for (let i = 0; i < options.length; i++) {
|
|
602
|
-
const isSelected = i === selectedOption;
|
|
603
|
-
if (isSelected) {
|
|
604
|
-
content += ` {cyan-fg}► ${options[i].label}{/cyan-fg}\n`;
|
|
605
|
-
}
|
|
606
|
-
else {
|
|
607
|
-
content += ` ${options[i].label}\n`;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
modal.setContent(content);
|
|
611
|
-
screen.render();
|
|
612
|
-
}
|
|
613
|
-
renderDialog();
|
|
614
|
-
modal.focus();
|
|
615
|
-
modal.key(['up', 'k'], () => {
|
|
616
|
-
selectedOption = Math.max(0, selectedOption - 1);
|
|
617
|
-
renderDialog();
|
|
618
|
-
});
|
|
619
|
-
modal.key(['down', 'j'], () => {
|
|
620
|
-
selectedOption = Math.min(options.length - 1, selectedOption + 1);
|
|
621
|
-
renderDialog();
|
|
622
|
-
});
|
|
623
|
-
modal.key(['enter'], () => {
|
|
624
|
-
screen.remove(modal);
|
|
625
|
-
registerHandlers();
|
|
626
|
-
resolve(options[selectedOption].key);
|
|
627
|
-
});
|
|
628
|
-
modal.key(['s', 'S'], () => {
|
|
629
|
-
screen.remove(modal);
|
|
630
|
-
registerHandlers();
|
|
631
|
-
resolve('save');
|
|
632
|
-
});
|
|
633
|
-
modal.key(['d', 'D'], () => {
|
|
634
|
-
screen.remove(modal);
|
|
635
|
-
registerHandlers();
|
|
636
|
-
resolve('discard');
|
|
637
|
-
});
|
|
638
|
-
modal.key(['c', 'C', 'escape'], () => {
|
|
639
|
-
screen.remove(modal);
|
|
640
|
-
registerHandlers();
|
|
641
|
-
resolve('continue');
|
|
642
|
-
});
|
|
643
|
-
});
|
|
699
|
+
/**
|
|
700
|
+
* Show unsaved changes dialog
|
|
701
|
+
* @param onClose - Optional callback when modal closes. Defaults to registering handlers + render.
|
|
702
|
+
* Pass empty function for complex flows where outer function manages handlers.
|
|
703
|
+
*/
|
|
704
|
+
async function showUnsavedDialog(onClose) {
|
|
705
|
+
return modalController.showUnsavedDialog(onClose || (() => {
|
|
706
|
+
registerHandlers();
|
|
707
|
+
render();
|
|
708
|
+
}));
|
|
644
709
|
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
function renderDialog() {
|
|
656
|
-
let content = '\n Restart to apply changes?\n\n';
|
|
657
|
-
for (let i = 0; i < options.length; i++) {
|
|
658
|
-
const isSelected = i === selectedOption;
|
|
659
|
-
if (isSelected) {
|
|
660
|
-
content += ` {cyan-fg}► ${options[i].label}{/cyan-fg}\n`;
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
content += ` ${options[i].label}\n`;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
modal.setContent(content);
|
|
667
|
-
screen.render();
|
|
668
|
-
}
|
|
669
|
-
renderDialog();
|
|
670
|
-
modal.focus();
|
|
671
|
-
modal.key(['up', 'k'], () => {
|
|
672
|
-
selectedOption = Math.max(0, selectedOption - 1);
|
|
673
|
-
renderDialog();
|
|
674
|
-
});
|
|
675
|
-
modal.key(['down', 'j'], () => {
|
|
676
|
-
selectedOption = Math.min(options.length - 1, selectedOption + 1);
|
|
677
|
-
renderDialog();
|
|
678
|
-
});
|
|
679
|
-
modal.key(['enter'], () => {
|
|
680
|
-
screen.remove(modal);
|
|
681
|
-
registerHandlers();
|
|
682
|
-
resolve(options[selectedOption].key);
|
|
683
|
-
});
|
|
684
|
-
modal.key(['y', 'Y'], () => {
|
|
685
|
-
screen.remove(modal);
|
|
686
|
-
registerHandlers();
|
|
687
|
-
resolve(true);
|
|
688
|
-
});
|
|
689
|
-
modal.key(['n', 'N', 'escape'], () => {
|
|
690
|
-
screen.remove(modal);
|
|
691
|
-
registerHandlers();
|
|
692
|
-
resolve(false);
|
|
693
|
-
});
|
|
694
|
-
});
|
|
710
|
+
/**
|
|
711
|
+
* Show restart confirmation dialog
|
|
712
|
+
* @param onClose - Optional callback when modal closes. Defaults to registering handlers + render.
|
|
713
|
+
* Pass empty function for complex flows where outer function manages handlers.
|
|
714
|
+
*/
|
|
715
|
+
async function showRestartDialog(onClose) {
|
|
716
|
+
return modalController.showRestartDialog(server.modelName, onClose || (() => {
|
|
717
|
+
registerHandlers();
|
|
718
|
+
render();
|
|
719
|
+
}));
|
|
695
720
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
721
|
+
/**
|
|
722
|
+
* Show error modal
|
|
723
|
+
* @param message - Error message to display
|
|
724
|
+
* @param onClose - Optional callback when modal closes. Defaults to registering handlers + render.
|
|
725
|
+
* Pass empty function for complex flows where outer function manages handlers.
|
|
726
|
+
*/
|
|
727
|
+
async function showError(message, onClose) {
|
|
728
|
+
await modalController.showError(message, onClose || (() => {
|
|
729
|
+
registerHandlers();
|
|
730
|
+
render();
|
|
731
|
+
}));
|
|
702
732
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
render();
|
|
715
|
-
resolve();
|
|
716
|
-
});
|
|
717
|
-
});
|
|
733
|
+
/**
|
|
734
|
+
* Show success modal
|
|
735
|
+
* @param message - Success message to display
|
|
736
|
+
* @param onClose - Optional callback when modal closes. Defaults to registering handlers + render.
|
|
737
|
+
* Pass empty function for complex flows where outer function manages handlers.
|
|
738
|
+
*/
|
|
739
|
+
async function showSuccess(message, onClose) {
|
|
740
|
+
await modalController.showSuccess(message, onClose || (() => {
|
|
741
|
+
registerHandlers();
|
|
742
|
+
render();
|
|
743
|
+
}));
|
|
718
744
|
}
|
|
719
745
|
// Save changes
|
|
720
746
|
async function saveChanges() {
|
|
721
747
|
// Build updates object
|
|
722
748
|
const modelField = state.fields.find(f => f.key === 'model');
|
|
749
|
+
const aliasField = state.fields.find(f => f.key === 'alias');
|
|
723
750
|
const hostField = state.fields.find(f => f.key === 'host');
|
|
724
751
|
const portField = state.fields.find(f => f.key === 'port');
|
|
725
752
|
const threadsField = state.fields.find(f => f.key === 'threads');
|
|
@@ -763,10 +790,13 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
763
790
|
const wasRunning = currentStatus.status === 'running';
|
|
764
791
|
let shouldRestart = false;
|
|
765
792
|
if (wasRunning) {
|
|
766
|
-
|
|
793
|
+
unregisterHandlers();
|
|
794
|
+
// Pass empty onClose - saveChanges manages handler registration
|
|
795
|
+
shouldRestart = await showRestartDialog(() => { });
|
|
796
|
+
registerHandlers();
|
|
767
797
|
}
|
|
768
798
|
// Show progress
|
|
769
|
-
const progressModal = showProgress('Saving configuration...');
|
|
799
|
+
const progressModal = modalController.showProgress('Saving configuration...');
|
|
770
800
|
try {
|
|
771
801
|
// Parse custom flags
|
|
772
802
|
const customFlags = customFlagsField.value
|
|
@@ -797,6 +827,7 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
797
827
|
id: newServerId,
|
|
798
828
|
modelPath: newModelPath,
|
|
799
829
|
modelName: newModelName,
|
|
830
|
+
...(aliasField.value ? { alias: aliasField.value } : { alias: undefined }),
|
|
800
831
|
host: hostField.value,
|
|
801
832
|
port: portField.value,
|
|
802
833
|
threads: threadsField.value,
|
|
@@ -804,8 +835,8 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
804
835
|
gpuLayers: gpuLayersField.value,
|
|
805
836
|
verbose: verboseField.value,
|
|
806
837
|
customFlags: customFlags && customFlags.length > 0 ? customFlags : undefined,
|
|
807
|
-
label: `
|
|
808
|
-
plistPath: path.join(plistDir, `
|
|
838
|
+
label: `studio.appkit.llamacpp-cli.${newServerId}`,
|
|
839
|
+
plistPath: path.join(plistDir, `studio.appkit.llamacpp-cli.${newServerId}.plist`),
|
|
809
840
|
stdoutPath: path.join(logsDir, `${newServerId}.stdout`),
|
|
810
841
|
stderrPath: path.join(logsDir, `${newServerId}.stderr`),
|
|
811
842
|
status: 'stopped',
|
|
@@ -823,10 +854,10 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
823
854
|
await launchctl_manager_js_1.launchctlManager.startService(newConfig.label);
|
|
824
855
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
825
856
|
const finalStatus = await status_checker_js_1.statusChecker.updateServerStatus(newConfig);
|
|
826
|
-
|
|
857
|
+
modalController.closeProgress(progressModal);
|
|
827
858
|
return finalStatus;
|
|
828
859
|
}
|
|
829
|
-
|
|
860
|
+
modalController.closeProgress(progressModal);
|
|
830
861
|
return newConfig;
|
|
831
862
|
}
|
|
832
863
|
else {
|
|
@@ -845,6 +876,7 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
845
876
|
gpuLayers: gpuLayersField.value,
|
|
846
877
|
verbose: verboseField.value,
|
|
847
878
|
customFlags: customFlags && customFlags.length > 0 ? customFlags : undefined,
|
|
879
|
+
...(aliasField.value ? { alias: aliasField.value } : { alias: undefined }),
|
|
848
880
|
};
|
|
849
881
|
if (newModelPath && newModelName) {
|
|
850
882
|
updatedConfig.modelPath = newModelPath;
|
|
@@ -871,20 +903,20 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
871
903
|
await launchctl_manager_js_1.launchctlManager.startService(fullConfig.label);
|
|
872
904
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
873
905
|
const finalStatus = await status_checker_js_1.statusChecker.updateServerStatus(fullConfig);
|
|
874
|
-
|
|
906
|
+
modalController.closeProgress(progressModal);
|
|
875
907
|
return finalStatus;
|
|
876
908
|
}
|
|
877
|
-
|
|
909
|
+
modalController.closeProgress(progressModal);
|
|
878
910
|
return fullConfig;
|
|
879
911
|
}
|
|
880
912
|
}
|
|
881
913
|
}
|
|
882
914
|
catch (err) {
|
|
883
|
-
|
|
915
|
+
modalController.closeProgress(progressModal);
|
|
884
916
|
await showError(err instanceof Error ? err.message : 'Unknown error');
|
|
885
917
|
return null;
|
|
886
918
|
}
|
|
887
|
-
|
|
919
|
+
modalController.closeProgress(progressModal);
|
|
888
920
|
return null;
|
|
889
921
|
}
|
|
890
922
|
// Handle edit action for selected field
|
|
@@ -908,8 +940,14 @@ async function createConfigUI(screen, server, onBack) {
|
|
|
908
940
|
}
|
|
909
941
|
// Handle escape/cancel
|
|
910
942
|
async function handleEscape() {
|
|
943
|
+
// Don't handle if modal is open
|
|
944
|
+
if (modalController.isModalOpen())
|
|
945
|
+
return;
|
|
911
946
|
if (state.hasChanges) {
|
|
912
|
-
|
|
947
|
+
unregisterHandlers();
|
|
948
|
+
// Pass empty onClose - handleEscape manages handler registration
|
|
949
|
+
const result = await showUnsavedDialog(() => { });
|
|
950
|
+
registerHandlers();
|
|
913
951
|
if (result === 'save') {
|
|
914
952
|
const updated = await saveChanges();
|
|
915
953
|
cleanup();
|