lemonade-interactive-loader 1.0.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 +288 -0
- package/index.js +16 -0
- package/package.json +40 -0
- package/src/README.md +88 -0
- package/src/cli/menu.js +359 -0
- package/src/cli/prompts.js +247 -0
- package/src/cli/setup-wizard.js +243 -0
- package/src/config/constants.js +83 -0
- package/src/config/index.js +49 -0
- package/src/index.js +58 -0
- package/src/services/asset-manager.js +159 -0
- package/src/services/download.js +102 -0
- package/src/services/github.js +72 -0
- package/src/services/server.js +174 -0
- package/src/utils/system.js +150 -0
package/src/cli/menu.js
ADDED
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const { loadConfig } = require('../config');
|
|
3
|
+
const { getAllInstalledAssets, getLlamaServerPath, downloadAndExtractLlamaCpp, deleteInstalledAsset } = require('../services/asset-manager');
|
|
4
|
+
const { selectLlamaCppRelease, selectAsset, askLaunchServer } = require('./prompts');
|
|
5
|
+
const { runSetupWizard } = require('./setup-wizard');
|
|
6
|
+
const { launchLemonadeServer } = require('../services/server');
|
|
7
|
+
const { inferBackendType, formatBytes } = require('../utils/system');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Display and handle main menu selection
|
|
11
|
+
* @returns {Promise<string>} Selected command
|
|
12
|
+
*/
|
|
13
|
+
async function showMainMenu() {
|
|
14
|
+
console.log('');
|
|
15
|
+
console.log('╔════════════════════════════════════════════════════════╗');
|
|
16
|
+
console.log('║ 🍋 Lemonade Interactive Launcher ║');
|
|
17
|
+
console.log('╚════════════════════════════════════════════════════════╝');
|
|
18
|
+
console.log('');
|
|
19
|
+
|
|
20
|
+
// Check if configuration exists
|
|
21
|
+
const config = loadConfig();
|
|
22
|
+
const hasConfig = Object.keys(config).length > 0;
|
|
23
|
+
|
|
24
|
+
// Show message if no configuration exists
|
|
25
|
+
if (!hasConfig) {
|
|
26
|
+
console.log('⚠️ No configuration found. Please run Setup first.\n');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Build choices based on whether config exists
|
|
30
|
+
let choices;
|
|
31
|
+
if (hasConfig) {
|
|
32
|
+
choices = [
|
|
33
|
+
{ name: '▶️ Start Server with Current Config', value: 'serve' },
|
|
34
|
+
{ name: '✏️ Edit Configuration', value: 'edit' },
|
|
35
|
+
{ name: '👁️ View Configuration', value: 'view' },
|
|
36
|
+
{ name: '🔄 Reset Configuration', value: 'reset' },
|
|
37
|
+
new inquirer.Separator(' ──────────────────────────────────────'),
|
|
38
|
+
{ name: '🚀 Setup - Configure Lemonade Server', value: 'setup' },
|
|
39
|
+
{ name: '📦 Download Custom llama.cpp Builds', value: 'manage' }
|
|
40
|
+
];
|
|
41
|
+
} else {
|
|
42
|
+
choices = [
|
|
43
|
+
{ name: '🚀 Setup - Configure Lemonade Server', value: 'setup' },
|
|
44
|
+
{ name: '📦 Download Custom llama.cpp Builds', value: 'manage' }
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const { command } = await inquirer.prompt([
|
|
49
|
+
{
|
|
50
|
+
type: 'list',
|
|
51
|
+
name: 'command',
|
|
52
|
+
message: 'What would you like to do?',
|
|
53
|
+
choices: choices
|
|
54
|
+
}
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
return command;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Display and handle manage submenu selection
|
|
62
|
+
* @returns {Promise<string>} Selected action
|
|
63
|
+
*/
|
|
64
|
+
async function showManageMenu() {
|
|
65
|
+
const { manageAction } = await inquirer.prompt([
|
|
66
|
+
{
|
|
67
|
+
type: 'list',
|
|
68
|
+
name: 'manageAction',
|
|
69
|
+
message: 'What would you like to do?',
|
|
70
|
+
choices: [
|
|
71
|
+
{ name: '👁️ View installed builds', value: 'view' },
|
|
72
|
+
{ name: '🗑️ Delete installed build', value: 'delete' },
|
|
73
|
+
{ name: '⬇️ Download new build', value: 'download' },
|
|
74
|
+
{ name: '← Back to main menu', value: 'back' }
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
return manageAction;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* View all installed builds
|
|
84
|
+
*/
|
|
85
|
+
async function viewInstalledBuilds() {
|
|
86
|
+
const installedAssets = getAllInstalledAssets();
|
|
87
|
+
|
|
88
|
+
if (installedAssets.length === 0) {
|
|
89
|
+
console.log('\nNo custom llama.cpp builds installed.');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
console.log('\n=== Installed Custom Builds ===\n');
|
|
94
|
+
installedAssets.forEach((asset, index) => {
|
|
95
|
+
const serverPath = getLlamaServerPath(asset.installPath);
|
|
96
|
+
console.log(`${index + 1}. ${asset.assetName}`);
|
|
97
|
+
console.log(` Path: ${asset.installPath}`);
|
|
98
|
+
console.log(` Backend: ${asset.backendType.toUpperCase()}`);
|
|
99
|
+
console.log(` Installed: ${new Date(asset.installTime).toLocaleString()}`);
|
|
100
|
+
console.log(` Server Binary: ${serverPath || 'Not found'}`);
|
|
101
|
+
console.log('');
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Delete installed builds interactively
|
|
107
|
+
*/
|
|
108
|
+
async function deleteInstalledBuild() {
|
|
109
|
+
let continueDeleting = true;
|
|
110
|
+
|
|
111
|
+
while (continueDeleting) {
|
|
112
|
+
const installedAssets = getAllInstalledAssets();
|
|
113
|
+
|
|
114
|
+
if (installedAssets.length === 0) {
|
|
115
|
+
console.log('\nNo custom llama.cpp builds installed.');
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const choices = installedAssets.map((asset, index) => ({
|
|
120
|
+
name: `${asset.assetName} | Backend: ${asset.backendType.toUpperCase()} | ${new Date(asset.installTime).toLocaleDateString()}`,
|
|
121
|
+
value: index
|
|
122
|
+
}));
|
|
123
|
+
|
|
124
|
+
choices.unshift({
|
|
125
|
+
name: '← Cancel',
|
|
126
|
+
value: -1
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const { deleteIndex } = await inquirer.prompt([
|
|
130
|
+
{
|
|
131
|
+
type: 'list',
|
|
132
|
+
name: 'deleteIndex',
|
|
133
|
+
message: 'Select a build to delete:',
|
|
134
|
+
choices: choices
|
|
135
|
+
}
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
if (deleteIndex < 0) {
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const assetToDelete = installedAssets[deleteIndex];
|
|
143
|
+
const { confirmDelete } = await inquirer.prompt([
|
|
144
|
+
{
|
|
145
|
+
type: 'confirm',
|
|
146
|
+
name: 'confirmDelete',
|
|
147
|
+
message: `Are you sure you want to delete "${assetToDelete.assetName}"? This cannot be undone.`,
|
|
148
|
+
default: false
|
|
149
|
+
}
|
|
150
|
+
]);
|
|
151
|
+
|
|
152
|
+
if (confirmDelete) {
|
|
153
|
+
const success = deleteInstalledAsset(assetToDelete.installPath);
|
|
154
|
+
if (success) {
|
|
155
|
+
console.log(`✓ Deleted: ${assetToDelete.assetName}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const { deleteAnother } = await inquirer.prompt([
|
|
160
|
+
{
|
|
161
|
+
type: 'confirm',
|
|
162
|
+
name: 'deleteAnother',
|
|
163
|
+
message: 'Do you want to delete another build?',
|
|
164
|
+
default: false
|
|
165
|
+
}
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
if (!deleteAnother) {
|
|
169
|
+
continueDeleting = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Download a new build interactively
|
|
176
|
+
*/
|
|
177
|
+
async function downloadNewBuild() {
|
|
178
|
+
console.log('\nFetching recent llama.cpp builds...');
|
|
179
|
+
const release = await selectLlamaCppRelease();
|
|
180
|
+
const asset = await selectAsset(release);
|
|
181
|
+
const version = release.tag_name;
|
|
182
|
+
const installPath = await downloadAndExtractLlamaCpp(asset, version);
|
|
183
|
+
|
|
184
|
+
console.log(`\n✓ Build ready at: ${installPath}`);
|
|
185
|
+
console.log(` Backend Type: ${inferBackendType(asset.name).toUpperCase()}`);
|
|
186
|
+
|
|
187
|
+
const { downloadAnother } = await inquirer.prompt([
|
|
188
|
+
{
|
|
189
|
+
type: 'confirm',
|
|
190
|
+
name: 'downloadAnother',
|
|
191
|
+
message: 'Do you want to download another build?',
|
|
192
|
+
default: false
|
|
193
|
+
}
|
|
194
|
+
]);
|
|
195
|
+
|
|
196
|
+
if (downloadAnother) {
|
|
197
|
+
await downloadNewBuild();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* View current configuration
|
|
203
|
+
*/
|
|
204
|
+
async function viewConfiguration() {
|
|
205
|
+
const config = loadConfig();
|
|
206
|
+
|
|
207
|
+
if (Object.keys(config).length === 0) {
|
|
208
|
+
console.log('No configuration found. Run "setup" to configure.');
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log('\n=== Current Configuration ===\n');
|
|
213
|
+
console.log(`Host: ${config.host}`);
|
|
214
|
+
console.log(`Port: ${config.port}`);
|
|
215
|
+
console.log(`Log Level: ${config.logLevel}`);
|
|
216
|
+
console.log(`Backend: ${config.backend}`);
|
|
217
|
+
console.log(`Model Directory: ${config.modelDir}`);
|
|
218
|
+
console.log(`Run Mode: ${config.runMode}`);
|
|
219
|
+
console.log(`llama.cpp Args: ${config.llamacppArgs || 'None'}`);
|
|
220
|
+
|
|
221
|
+
if (config.customLlamacppPath) {
|
|
222
|
+
console.log(`Custom llama.cpp Build: ${config.customLlamacppPath}`);
|
|
223
|
+
console.log(` Backend Type: ${config.customBackendType?.toUpperCase() || 'Unknown'}`);
|
|
224
|
+
console.log(` Server Binary: ${config.customServerPath || 'Not found'}`);
|
|
225
|
+
} else {
|
|
226
|
+
console.log(`Custom llama.cpp Build: Using bundled build`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const installedAssets = getAllInstalledAssets();
|
|
230
|
+
if (installedAssets.length > 0) {
|
|
231
|
+
console.log('\n=== All Installed Custom Builds ===\n');
|
|
232
|
+
installedAssets.forEach((asset, index) => {
|
|
233
|
+
const serverPath = getLlamaServerPath(asset.installPath);
|
|
234
|
+
console.log(`${index + 1}. ${asset.assetName}`);
|
|
235
|
+
console.log(` Path: ${asset.installPath}`);
|
|
236
|
+
console.log(` Backend: ${asset.backendType.toUpperCase()}`);
|
|
237
|
+
console.log(` Installed: ${new Date(asset.installTime).toLocaleString()}`);
|
|
238
|
+
console.log(` Server Binary: ${serverPath || 'Not found'}`);
|
|
239
|
+
console.log('');
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Reset configuration
|
|
246
|
+
*/
|
|
247
|
+
async function resetConfiguration() {
|
|
248
|
+
const { confirmReset } = await inquirer.prompt([
|
|
249
|
+
{
|
|
250
|
+
type: 'confirm',
|
|
251
|
+
name: 'confirmReset',
|
|
252
|
+
message: 'Are you sure you want to reset all configuration? This cannot be undone.',
|
|
253
|
+
default: false
|
|
254
|
+
}
|
|
255
|
+
]);
|
|
256
|
+
|
|
257
|
+
if (confirmReset) {
|
|
258
|
+
const { resetConfig } = require('../config');
|
|
259
|
+
resetConfig();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Handle main menu command
|
|
265
|
+
* @param {string} command - Selected command
|
|
266
|
+
*/
|
|
267
|
+
async function handleCommand(command) {
|
|
268
|
+
switch (command) {
|
|
269
|
+
case 'setup':
|
|
270
|
+
await runSetupWizard(false);
|
|
271
|
+
if (await askLaunchServer()) {
|
|
272
|
+
const config = loadConfig();
|
|
273
|
+
if (Object.keys(config).length > 0) {
|
|
274
|
+
await launchLemonadeServer(config);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
break;
|
|
278
|
+
|
|
279
|
+
case 'edit':
|
|
280
|
+
await runSetupWizard(true);
|
|
281
|
+
if (await askLaunchServer()) {
|
|
282
|
+
const config = loadConfig();
|
|
283
|
+
if (Object.keys(config).length > 0) {
|
|
284
|
+
await launchLemonadeServer(config);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
break;
|
|
288
|
+
|
|
289
|
+
case 'view':
|
|
290
|
+
await viewConfiguration();
|
|
291
|
+
break;
|
|
292
|
+
|
|
293
|
+
case 'reset':
|
|
294
|
+
await resetConfiguration();
|
|
295
|
+
break;
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
case 'manage':
|
|
300
|
+
let manageAction;
|
|
301
|
+
do {
|
|
302
|
+
manageAction = await showManageMenu();
|
|
303
|
+
|
|
304
|
+
switch (manageAction) {
|
|
305
|
+
case 'view':
|
|
306
|
+
await viewInstalledBuilds();
|
|
307
|
+
break;
|
|
308
|
+
case 'delete':
|
|
309
|
+
await deleteInstalledBuild();
|
|
310
|
+
break;
|
|
311
|
+
case 'download':
|
|
312
|
+
await downloadNewBuild();
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
} while (manageAction !== 'back');
|
|
316
|
+
break;
|
|
317
|
+
|
|
318
|
+
case 'serve':
|
|
319
|
+
const config = loadConfig();
|
|
320
|
+
if (Object.keys(config).length === 0) {
|
|
321
|
+
console.log('No configuration found. Please run "setup" first.');
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
await launchLemonadeServer(config);
|
|
325
|
+
break;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Run the main CLI loop
|
|
331
|
+
*/
|
|
332
|
+
async function runCLI() {
|
|
333
|
+
let continueRunning = true;
|
|
334
|
+
|
|
335
|
+
while (continueRunning) {
|
|
336
|
+
const command = await showMainMenu();
|
|
337
|
+
await handleCommand(command);
|
|
338
|
+
|
|
339
|
+
const { continueRunning: shouldContinue } = await inquirer.prompt([
|
|
340
|
+
{
|
|
341
|
+
type: 'confirm',
|
|
342
|
+
name: 'continueRunning',
|
|
343
|
+
message: 'Would you like to return to the main menu?',
|
|
344
|
+
default: true
|
|
345
|
+
}
|
|
346
|
+
]);
|
|
347
|
+
|
|
348
|
+
continueRunning = shouldContinue;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
console.log('\n👋 Goodbye!\n');
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
module.exports = {
|
|
355
|
+
showMainMenu,
|
|
356
|
+
showManageMenu,
|
|
357
|
+
handleCommand,
|
|
358
|
+
runCLI
|
|
359
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const { DEFAULTS, BACKEND_TYPES, LOG_LEVELS, RUN_MODES, HOSTS } = require('../config/constants');
|
|
3
|
+
const { loadConfig } = require('../config');
|
|
4
|
+
const { detectSystem, formatBytes, filterServerAssets, inferBackendType } = require('../utils/system');
|
|
5
|
+
const { fetchAllReleases } = require('../services/github');
|
|
6
|
+
const { getAllInstalledAssets, getLlamaServerPath, downloadAndExtractLlamaCpp, selectInstalledAsset, isAssetInstalled } = require('../services/asset-manager');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Select a llama.cpp release from list
|
|
10
|
+
* @returns {Promise<Object>} Selected release
|
|
11
|
+
*/
|
|
12
|
+
async function selectLlamaCppRelease() {
|
|
13
|
+
console.log('\nFetching available releases...');
|
|
14
|
+
|
|
15
|
+
let releases;
|
|
16
|
+
try {
|
|
17
|
+
releases = await fetchAllReleases(20);
|
|
18
|
+
console.log(`Found ${releases.length} releases.\n`);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.error(`Error fetching releases: ${error.message}`);
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const releaseChoices = releases.map(release => {
|
|
25
|
+
const serverAssets = filterServerAssets(release.assets);
|
|
26
|
+
const installedCount = serverAssets.filter(asset =>
|
|
27
|
+
isAssetInstalled(release.tag_name, asset.name)
|
|
28
|
+
).length;
|
|
29
|
+
|
|
30
|
+
const totalAssets = serverAssets.length;
|
|
31
|
+
let status = '';
|
|
32
|
+
if (installedCount === totalAssets && totalAssets > 0) {
|
|
33
|
+
status = ' ✓ All assets installed';
|
|
34
|
+
} else if (installedCount > 0) {
|
|
35
|
+
status = ` (${installedCount}/${totalAssets} installed)`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
name: `${release.tag_name} - ${new Date(release.published_at).toLocaleDateString()}${status}`,
|
|
40
|
+
value: release
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const { selectedRelease } = await inquirer.prompt([
|
|
45
|
+
{
|
|
46
|
+
type: 'list',
|
|
47
|
+
name: 'selectedRelease',
|
|
48
|
+
message: 'Select a release:',
|
|
49
|
+
choices: releaseChoices
|
|
50
|
+
}
|
|
51
|
+
]);
|
|
52
|
+
|
|
53
|
+
return selectedRelease;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Select an asset from a release
|
|
58
|
+
* @param {Object} release - Release object
|
|
59
|
+
* @returns {Promise<Object>} Selected asset
|
|
60
|
+
*/
|
|
61
|
+
async function selectAsset(release) {
|
|
62
|
+
const systemInfo = detectSystem();
|
|
63
|
+
const serverAssets = filterServerAssets(release.assets);
|
|
64
|
+
|
|
65
|
+
const byPlatform = {};
|
|
66
|
+
serverAssets.forEach(asset => {
|
|
67
|
+
let platform = 'Other';
|
|
68
|
+
const name = asset.name.toLowerCase();
|
|
69
|
+
|
|
70
|
+
if (name.includes('win') || name.includes('windows')) platform = 'Windows';
|
|
71
|
+
else if (name.includes('ubuntu') || name.includes('linux')) platform = 'Linux';
|
|
72
|
+
else if (name.includes('macos') || name.includes('mac')) platform = 'macOS';
|
|
73
|
+
else if (name.includes('rocm')) platform = 'ROCm (Linux)';
|
|
74
|
+
else if (name.includes('cuda')) platform = 'CUDA (Linux)';
|
|
75
|
+
|
|
76
|
+
if (!byPlatform[platform]) {
|
|
77
|
+
byPlatform[platform] = [];
|
|
78
|
+
}
|
|
79
|
+
byPlatform[platform].push(asset);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const assetChoices = [];
|
|
83
|
+
for (const [platform, assets] of Object.entries(byPlatform)) {
|
|
84
|
+
assetChoices.push({
|
|
85
|
+
name: `── ${platform} ──`,
|
|
86
|
+
disabled: true
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
assets.forEach(asset => {
|
|
90
|
+
const name = asset.name.toLowerCase();
|
|
91
|
+
let isCurrentPlatform = false;
|
|
92
|
+
|
|
93
|
+
if (systemInfo.osType === 'windows' && platform === 'Windows') {
|
|
94
|
+
if (systemInfo.arch === 'x64' && name.includes('x64')) isCurrentPlatform = true;
|
|
95
|
+
else if (systemInfo.arch === 'arm64' && name.includes('arm64')) isCurrentPlatform = true;
|
|
96
|
+
else if (!name.includes('x64') && !name.includes('arm64')) isCurrentPlatform = true;
|
|
97
|
+
} else if (systemInfo.osType === 'linux' && platform === 'Linux') {
|
|
98
|
+
if (systemInfo.arch === 'x64' && name.includes('x64')) isCurrentPlatform = true;
|
|
99
|
+
else if (systemInfo.arch === 'arm64' && name.includes('aarch64')) isCurrentPlatform = true;
|
|
100
|
+
else if (!name.includes('x64') && !name.includes('aarch64')) isCurrentPlatform = true;
|
|
101
|
+
} else if (systemInfo.osType === 'macos' && platform === 'macOS') {
|
|
102
|
+
if (systemInfo.arch === 'arm64' && (name.includes('arm64') || name.includes('aarch64'))) isCurrentPlatform = true;
|
|
103
|
+
else if (systemInfo.arch === 'x64' && name.includes('x64')) isCurrentPlatform = true;
|
|
104
|
+
else if (!name.includes('arm64') && !name.includes('x64')) isCurrentPlatform = true;
|
|
105
|
+
} else if (systemInfo.osType === 'linux' && platform.includes('ROCm')) {
|
|
106
|
+
if (systemInfo.arch === 'x64') isCurrentPlatform = true;
|
|
107
|
+
} else if (systemInfo.osType === 'linux' && platform.includes('CUDA')) {
|
|
108
|
+
if (systemInfo.arch === 'x64') isCurrentPlatform = true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const version = release.tag_name;
|
|
112
|
+
const isInstalled = isAssetInstalled(version, asset.name);
|
|
113
|
+
|
|
114
|
+
let marker = '';
|
|
115
|
+
if (isInstalled) {
|
|
116
|
+
marker = ' ✓ Already installed';
|
|
117
|
+
} else if (isCurrentPlatform) {
|
|
118
|
+
marker = ' ← Best match';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
assetChoices.push({
|
|
122
|
+
name: `${asset.name} (${formatBytes(asset.size)})${marker}`,
|
|
123
|
+
value: asset,
|
|
124
|
+
disabled: false
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const { selectedAsset } = await inquirer.prompt([
|
|
130
|
+
{
|
|
131
|
+
type: 'list',
|
|
132
|
+
name: 'selectedAsset',
|
|
133
|
+
message: 'Select the asset to download:',
|
|
134
|
+
choices: assetChoices
|
|
135
|
+
}
|
|
136
|
+
]);
|
|
137
|
+
|
|
138
|
+
return selectedAsset;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Select from installed assets
|
|
143
|
+
* @returns {Promise<Object|null>} Selected asset or null
|
|
144
|
+
*/
|
|
145
|
+
async function selectInstalledAssetPrompt() {
|
|
146
|
+
const installedAssets = getAllInstalledAssets();
|
|
147
|
+
|
|
148
|
+
if (installedAssets.length === 0) {
|
|
149
|
+
console.log('\nNo custom llama.cpp builds installed.');
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const choices = installedAssets.map((asset, index) => {
|
|
154
|
+
const installDate = new Date(asset.installTime).toLocaleString();
|
|
155
|
+
const backendType = asset.backendType.toUpperCase();
|
|
156
|
+
const serverPath = getLlamaServerPath(asset.installPath);
|
|
157
|
+
const hasServer = serverPath ? '✓' : '✗';
|
|
158
|
+
|
|
159
|
+
return {
|
|
160
|
+
name: `[${index + 1}] ${asset.assetName} | Backend: ${backendType} | Installed: ${installDate} | Server: ${hasServer}`,
|
|
161
|
+
value: asset
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
choices.unshift({
|
|
166
|
+
name: '── Skip - Use bundled build ──',
|
|
167
|
+
value: null,
|
|
168
|
+
disabled: false
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const { selectedInstalled } = await inquirer.prompt([
|
|
172
|
+
{
|
|
173
|
+
type: 'list',
|
|
174
|
+
name: 'selectedInstalled',
|
|
175
|
+
message: 'Select an installed custom build (or skip):',
|
|
176
|
+
choices: choices
|
|
177
|
+
}
|
|
178
|
+
]);
|
|
179
|
+
|
|
180
|
+
if (selectedInstalled) {
|
|
181
|
+
const serverPath = getLlamaServerPath(selectedInstalled.installPath);
|
|
182
|
+
if (!serverPath) {
|
|
183
|
+
console.log('\n⚠️ Warning: llama-server binary not found in this installation.');
|
|
184
|
+
console.log(' If you selected "auto" backend, it may not use this binary.');
|
|
185
|
+
} else {
|
|
186
|
+
console.log(`\n✓ Selected: ${selectedInstalled.assetName}`);
|
|
187
|
+
console.log(` Backend Type: ${selectedInstalled.backendType.toUpperCase()}`);
|
|
188
|
+
console.log(` Server Binary: ${serverPath}`);
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
...selectedInstalled,
|
|
192
|
+
serverPath
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Ask if user wants to launch the server
|
|
202
|
+
* @returns {Promise<boolean>}
|
|
203
|
+
*/
|
|
204
|
+
async function askLaunchServer() {
|
|
205
|
+
const { launchNow } = await inquirer.prompt([
|
|
206
|
+
{
|
|
207
|
+
type: 'confirm',
|
|
208
|
+
name: 'launchNow',
|
|
209
|
+
message: 'Would you like to launch the server now?',
|
|
210
|
+
default: true
|
|
211
|
+
}
|
|
212
|
+
]);
|
|
213
|
+
|
|
214
|
+
return launchNow;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Display configuration summary
|
|
219
|
+
* @param {Object} config - Configuration object
|
|
220
|
+
*/
|
|
221
|
+
function displayConfigSummary(config) {
|
|
222
|
+
console.log('\n=== Configuration Summary ===\n');
|
|
223
|
+
console.log(`Host: ${config.host}`);
|
|
224
|
+
console.log(`Port: ${config.port}`);
|
|
225
|
+
console.log(`Log Level: ${config.logLevel}`);
|
|
226
|
+
console.log(`Backend: ${config.backend}`);
|
|
227
|
+
console.log(`Model Directory: ${config.modelDir}`);
|
|
228
|
+
console.log(`Run Mode: ${config.runMode}`);
|
|
229
|
+
console.log(`llama.cpp Args: ${config.llamacppArgs || 'None'}`);
|
|
230
|
+
|
|
231
|
+
if (config.customLlamacppPath) {
|
|
232
|
+
console.log(`Custom llama.cpp Build: ${config.customLlamacppPath}`);
|
|
233
|
+
console.log(` Backend Type: ${config.customBackendType?.toUpperCase() || 'Unknown'}`);
|
|
234
|
+
console.log(` Server Binary: ${config.customServerPath || 'Not found'}`);
|
|
235
|
+
} else {
|
|
236
|
+
console.log(`Custom llama.cpp Build: Using bundled build`);
|
|
237
|
+
}
|
|
238
|
+
console.log('');
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
module.exports = {
|
|
242
|
+
selectLlamaCppRelease,
|
|
243
|
+
selectAsset,
|
|
244
|
+
selectInstalledAssetPrompt,
|
|
245
|
+
askLaunchServer,
|
|
246
|
+
displayConfigSummary
|
|
247
|
+
};
|