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.
@@ -0,0 +1,72 @@
1
+ const https = require('https');
2
+ const http = require('http');
3
+ const { GITHUB_RELEASES_URL, GITHUB_API_HEADERS } = require('../config/constants');
4
+
5
+ /**
6
+ * Fetch all releases from llama.cpp GitHub repository
7
+ * @param {number} limit - Maximum number of releases to fetch
8
+ * @returns {Promise<Array>} Array of release data
9
+ */
10
+ async function fetchAllReleases(limit = 20) {
11
+ return new Promise((resolve, reject) => {
12
+ const options = {
13
+ hostname: 'api.github.com',
14
+ path: `/repos/ggml-org/llama.cpp/releases?per_page=${limit}`,
15
+ method: 'GET',
16
+ headers: GITHUB_API_HEADERS
17
+ };
18
+
19
+ const protocol = https;
20
+ const req = protocol.request(options, (res) => {
21
+ let data = '';
22
+
23
+ if (res.statusCode !== 200) {
24
+ reject(new Error(`Request failed with status code ${res.statusCode}`));
25
+ res.resume();
26
+ return;
27
+ }
28
+
29
+ res.on('data', (chunk) => {
30
+ data += chunk;
31
+ });
32
+
33
+ res.on('end', () => {
34
+ try {
35
+ resolve(JSON.parse(data));
36
+ } catch (e) {
37
+ reject(new Error(`Failed to parse response: ${e.message}`));
38
+ }
39
+ });
40
+ });
41
+
42
+ req.on('error', (e) => {
43
+ reject(new Error(`Request error: ${e.message}`));
44
+ });
45
+
46
+ req.setTimeout(30000, () => {
47
+ req.destroy();
48
+ reject(new Error('Request timed out'));
49
+ });
50
+
51
+ req.end();
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Fetch the latest release from llama.cpp GitHub repository
57
+ * @returns {Promise<Object>} The latest release data
58
+ */
59
+ async function fetchLatestRelease() {
60
+ const releases = await fetchAllReleases(1);
61
+
62
+ if (releases.length === 0) {
63
+ throw new Error('No releases found');
64
+ }
65
+
66
+ return releases[0];
67
+ }
68
+
69
+ module.exports = {
70
+ fetchAllReleases,
71
+ fetchLatestRelease
72
+ };
@@ -0,0 +1,174 @@
1
+ const fs = require('fs');
2
+ const { execSync } = require('child_process');
3
+ const { LEMONADE_SERVER_DEFAULT_PATH } = require('../config/constants');
4
+ const { findLlamaServer } = require('../utils/system');
5
+ const { getLlamaServerPath } = require('./asset-manager');
6
+
7
+ /**
8
+ * Build server command arguments
9
+ * @param {Object} config - Server configuration
10
+ * @returns {Array} Array of command arguments
11
+ */
12
+ function buildServerArgs(config) {
13
+ const { host, port, logLevel, modelDir, llamacppArgs } = config;
14
+ const args = [
15
+ 'serve',
16
+ '--log-level', logLevel || 'info',
17
+ '--host', host,
18
+ '--port', port.toString()
19
+ ];
20
+
21
+ if (modelDir && modelDir !== 'None') {
22
+ args.push('--extra-models-dir', modelDir);
23
+ }
24
+
25
+ if (llamacppArgs) {
26
+ args.push('--llamacpp-args', `"${llamacppArgs}"`);
27
+ }
28
+
29
+ return args;
30
+ }
31
+
32
+ /**
33
+ * Format command for cross-platform display
34
+ * @param {string} serverPath - Path to server binary
35
+ * @param {Array} args - Command arguments
36
+ * @param {Object} envVars - Environment variables
37
+ * @returns {string} Formatted command string
38
+ */
39
+ function formatCommand(serverPath, args, envVars = {}) {
40
+ const isWindows = process.platform === 'win32';
41
+
42
+ if (isWindows) {
43
+ let cmd = '';
44
+ for (const [key, value] of Object.entries(envVars)) {
45
+ if (value) {
46
+ cmd += `set ${key}="${value}" && `;
47
+ }
48
+ }
49
+
50
+ const quotedPath = serverPath.includes(' ') ? `"${serverPath}"` : serverPath;
51
+ const quotedArgs = args.map(arg => arg.includes(' ') ? `"${arg}"` : arg);
52
+
53
+ return cmd + quotedPath + ' ' + quotedArgs.join(' ');
54
+ } else {
55
+ let cmd = '';
56
+ for (const [key, value] of Object.entries(envVars)) {
57
+ if (value) {
58
+ cmd += `${key}="${value}" `;
59
+ }
60
+ }
61
+
62
+ const quotedArgs = args.map(arg => arg.includes(' ') ? `"${arg}"` : arg);
63
+ return cmd + serverPath + ' ' + quotedArgs.join(' ');
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Launch lemonade-server with the specified configuration
69
+ * @param {Object} config - Server configuration
70
+ */
71
+ async function launchLemonadeServer(config) {
72
+ const {
73
+ host,
74
+ port,
75
+ logLevel,
76
+ modelDir,
77
+ llamacppArgs,
78
+ runMode,
79
+ customLlamacppPath,
80
+ customBackendType,
81
+ customServerPath,
82
+ backend
83
+ } = config;
84
+
85
+ console.log('\n=== Launching Lemonade Server ===\n');
86
+ console.log(`Host: ${host}`);
87
+ console.log(`Port: ${port}`);
88
+ console.log(`Log Level: ${logLevel}`);
89
+ console.log(`Backend: ${backend || 'auto'}`);
90
+ console.log(`Model Directory: ${modelDir || 'default'}`);
91
+ console.log(`Run Mode: ${runMode || 'headless'}`);
92
+ if (llamacppArgs) {
93
+ console.log(`llama.cpp Args: ${llamacppArgs}`);
94
+ }
95
+ if (customLlamacppPath) {
96
+ console.log(`Custom llama.cpp: ${customLlamacppPath}`);
97
+ }
98
+ console.log('');
99
+
100
+ const serverPath = LEMONADE_SERVER_DEFAULT_PATH;
101
+ const args = buildServerArgs(config);
102
+
103
+ let backendTypeToUse = customBackendType;
104
+ if (!backendTypeToUse && backend && backend !== 'auto') {
105
+ backendTypeToUse = backend;
106
+ }
107
+
108
+ let serverBinary = customServerPath;
109
+ if (!serverBinary && customLlamacppPath) {
110
+ serverBinary = findLlamaServer(customLlamacppPath);
111
+ }
112
+
113
+ const envVars = {};
114
+ if (backendTypeToUse && backendTypeToUse !== 'auto' && serverBinary) {
115
+ const backendEnvVar = `LEMONADE_LLAMACPP_${backendTypeToUse.toUpperCase()}_BIN`;
116
+ // Point to the actual binary, not the directory
117
+ const binaryPath = findLlamaServer(customLlamacppPath || serverBinary);
118
+ envVars[backendEnvVar] = binaryPath;
119
+ }
120
+
121
+ if (!fs.existsSync(serverPath)) {
122
+ console.error(`\n❌ Error: lemonade-server not found at ${serverPath}`);
123
+ console.log('\n📋 Expected Location:');
124
+ console.log(` ${serverPath}`);
125
+ console.log('\n📥 How to Install Lemonade Server:');
126
+ console.log(' Visit: https://lemonade-server.ai');
127
+
128
+ const command = formatCommand(serverPath, args, envVars);
129
+ console.log('\n🔧 Once installed, this is the command that will be run:');
130
+ console.log(` ${command}`);
131
+
132
+ if (Object.keys(envVars).length > 0) {
133
+ console.log('\n💡 Environment Variable Set:');
134
+ for (const [key, value] of Object.entries(envVars)) {
135
+ console.log(` ${key}=${value}`);
136
+ }
137
+ }
138
+
139
+ console.log('\n💡 After installing lemonade-server, run this tool again to start the server.');
140
+ console.log('');
141
+ return;
142
+ }
143
+
144
+ for (const [key, value] of Object.entries(envVars)) {
145
+ process.env[key] = value;
146
+ console.log(`Set ${key}=${value}`);
147
+ }
148
+
149
+ if (Object.keys(envVars).length === 0) {
150
+ console.log('Using default backend configuration');
151
+ }
152
+
153
+ console.log(`\nCommand: ${serverPath} ${args.join(' ')}`);
154
+ console.log('\nStarting server...\n');
155
+
156
+ try {
157
+ const command = formatCommand(serverPath, args, {});
158
+ execSync(command, {
159
+ stdio: 'inherit',
160
+ env: process.env
161
+ });
162
+ } catch (error) {
163
+ console.error(`Server exited with error code: ${error.status}`);
164
+ if (error.status !== null) {
165
+ process.exit(error.status);
166
+ }
167
+ }
168
+ }
169
+
170
+ module.exports = {
171
+ buildServerArgs,
172
+ formatCommand,
173
+ launchLemonadeServer
174
+ };
@@ -0,0 +1,150 @@
1
+ const os = require('os');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+
5
+ /**
6
+ * Detect user's system for suggested asset
7
+ * @returns {Object} System information
8
+ */
9
+ function detectSystem() {
10
+ const platform = process.platform;
11
+ const arch = process.arch;
12
+
13
+ let osType = 'unknown';
14
+ let archType = arch;
15
+
16
+ if (platform === 'win32') {
17
+ osType = 'windows';
18
+ if (arch === 'x64') archType = 'x64';
19
+ else if (arch === 'arm64') archType = 'arm64';
20
+ else if (arch === 'ia32') archType = 'x86';
21
+ } else if (platform === 'darwin') {
22
+ osType = 'macos';
23
+ if (arch === 'arm64') archType = 'arm64';
24
+ else if (arch === 'x64') archType = 'x64';
25
+ } else if (platform === 'linux') {
26
+ osType = 'linux';
27
+ if (arch === 'x64') archType = 'x64';
28
+ else if (arch === 'arm64') archType = 'arm64';
29
+ else if (arch === 'arm') archType = 'armv7l';
30
+ }
31
+
32
+ return { platform, arch: archType, osType };
33
+ }
34
+
35
+ /**
36
+ * Format bytes to human readable string
37
+ * @param {number} bytes - Bytes to format
38
+ * @returns {string} Formatted byte string
39
+ */
40
+ function formatBytes(bytes) {
41
+ if (bytes === 0) return '0 B';
42
+ const k = 1024;
43
+ const sizes = ['B', 'KB', 'MB', 'GB'];
44
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
45
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
46
+ }
47
+
48
+ /**
49
+ * Infer backend type from asset name
50
+ * @param {string} assetName - Asset filename
51
+ * @returns {string} Backend type
52
+ */
53
+ function inferBackendType(assetName) {
54
+ const name = assetName.toLowerCase();
55
+
56
+ if (name.includes('rocm') || name.includes('hip')) return 'rocm';
57
+ if (name.includes('vulkan')) return 'vulkan';
58
+ if (name.includes('cuda')) return 'cuda';
59
+ if (name.includes('sycl')) return 'sycl';
60
+ if (name.includes('opencl')) return 'opencl';
61
+ if (name.includes('cpu')) return 'cpu';
62
+
63
+ return 'cpu';
64
+ }
65
+
66
+ /**
67
+ * Categorize asset based on its name
68
+ * @param {string} assetName - Asset filename
69
+ * @returns {string} Category
70
+ */
71
+ function categorizeAsset(assetName) {
72
+ const name = assetName.toLowerCase();
73
+
74
+ if (name.includes('cuda')) return 'CUDA';
75
+ if (name.includes('rocm') || name.includes('hip')) return 'ROCm';
76
+ if (name.includes('vulkan')) return 'Vulkan';
77
+ if (name.includes('sycl')) return 'SYCL';
78
+ if (name.includes('opencl')) return 'OpenCL';
79
+ if (name.includes('cpu')) return 'CPU';
80
+ if (name.includes('macos') || name.includes('xcframework')) return 'macOS';
81
+ if (name.includes('ubuntu') || name.includes('linux')) return 'Linux';
82
+ if (name.includes('win') || name.includes('windows')) return 'Windows';
83
+
84
+ return 'Other';
85
+ }
86
+
87
+ /**
88
+ * Determine asset type (zip or tar.gz)
89
+ * @param {string} assetName - Asset filename
90
+ * @returns {string} 'zip', 'tar', or 'unknown'
91
+ */
92
+ function getAssetType(assetName) {
93
+ if (assetName.endsWith('.zip')) return 'zip';
94
+ if (assetName.endsWith('.tar.gz')) return 'tar';
95
+ return 'unknown';
96
+ }
97
+
98
+ /**
99
+ * Filter assets for llama-server binaries only
100
+ * @param {Array} assets - Array of asset objects
101
+ * @returns {Array} Filtered assets
102
+ */
103
+ function filterServerAssets(assets) {
104
+ return assets.filter(asset => {
105
+ const name = asset.name.toLowerCase();
106
+ return name.includes('bin') && (name.endsWith('.zip') || name.endsWith('.tar.gz'));
107
+ });
108
+ }
109
+
110
+ /**
111
+ * Find the llama-server binary in extracted directory
112
+ * @param {string} extractDir - Directory to search
113
+ * @returns {string|null} Path to llama-server or null
114
+ */
115
+ function findLlamaServer(extractDir) {
116
+ const candidates = process.platform === 'win32'
117
+ ? ['llama-server.exe']
118
+ : ['llama-server'];
119
+
120
+ function searchDir(dir) {
121
+ if (!fs.existsSync(dir)) return null;
122
+
123
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
124
+
125
+ for (const entry of entries) {
126
+ const fullPath = path.join(dir, entry.name);
127
+
128
+ if (entry.isDirectory()) {
129
+ const result = searchDir(fullPath);
130
+ if (result) return result;
131
+ } else if (entry.isFile() && candidates.includes(entry.name)) {
132
+ return fullPath;
133
+ }
134
+ }
135
+
136
+ return null;
137
+ }
138
+
139
+ return searchDir(extractDir);
140
+ }
141
+
142
+ module.exports = {
143
+ detectSystem,
144
+ formatBytes,
145
+ inferBackendType,
146
+ categorizeAsset,
147
+ getAssetType,
148
+ filterServerAssets,
149
+ findLlamaServer
150
+ };