groove-dev 0.27.152 → 0.27.153
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/node_modules/@groove-dev/cli/package.json +1 -1
- package/node_modules/@groove-dev/daemon/package.json +1 -1
- package/node_modules/@groove-dev/daemon/src/llama-server.js +96 -3
- package/node_modules/@groove-dev/daemon/src/model-manager.js +52 -10
- package/node_modules/@groove-dev/daemon/src/routes/providers.js +11 -0
- package/node_modules/@groove-dev/gui/dist/assets/{index-CReKPWhY.js → index-BU_YTEZo.js} +220 -220
- package/node_modules/@groove-dev/gui/dist/assets/index-ChfYTsyc.css +1 -0
- package/node_modules/@groove-dev/gui/dist/index.html +2 -2
- package/node_modules/@groove-dev/gui/package.json +1 -1
- package/node_modules/@groove-dev/gui/src/components/agents/spawn-wizard.jsx +9 -2
- package/node_modules/@groove-dev/gui/src/components/lab/runtime-config.jsx +23 -8
- package/node_modules/@groove-dev/gui/src/components/settings/quick-connect.jsx +8 -1
- package/node_modules/@groove-dev/gui/src/stores/slices/providers-slice.js +13 -0
- package/node_modules/@groove-dev/gui/src/views/models.jsx +15 -2
- package/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/daemon/package.json +1 -1
- package/packages/daemon/src/llama-server.js +96 -3
- package/packages/daemon/src/model-manager.js +52 -10
- package/packages/daemon/src/routes/providers.js +11 -0
- package/packages/gui/dist/assets/{index-CReKPWhY.js → index-BU_YTEZo.js} +220 -220
- package/packages/gui/dist/assets/index-ChfYTsyc.css +1 -0
- package/packages/gui/dist/index.html +2 -2
- package/packages/gui/package.json +1 -1
- package/packages/gui/src/components/agents/spawn-wizard.jsx +9 -2
- package/packages/gui/src/components/lab/runtime-config.jsx +23 -8
- package/packages/gui/src/components/settings/quick-connect.jsx +8 -1
- package/packages/gui/src/stores/slices/providers-slice.js +13 -0
- package/packages/gui/src/views/models.jsx +15 -2
- package/node_modules/@groove-dev/gui/dist/assets/index-CEkPsSAm.css +0 -1
- package/packages/gui/dist/assets/index-CEkPsSAm.css +0 -1
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
// Each model gets its own server on a unique port.
|
|
6
6
|
// Auto-starts when an agent needs a GGUF model, auto-stops when idle.
|
|
7
7
|
|
|
8
|
-
import { spawn, execSync } from 'child_process';
|
|
8
|
+
import { spawn, execSync, execFileSync } from 'child_process';
|
|
9
|
+
import { existsSync, mkdirSync, chmodSync } from 'fs';
|
|
10
|
+
import { resolve } from 'path';
|
|
11
|
+
import { homedir } from 'os';
|
|
9
12
|
|
|
10
13
|
const BASE_PORT = 8081;
|
|
11
14
|
const MAX_SERVERS = 5;
|
|
@@ -25,10 +28,98 @@ export class LlamaServerManager {
|
|
|
25
28
|
execSync('which llama-server', { stdio: 'ignore' });
|
|
26
29
|
return true;
|
|
27
30
|
} catch {
|
|
28
|
-
|
|
31
|
+
// Check common manual install locations
|
|
32
|
+
const paths = [
|
|
33
|
+
resolve(homedir(), '.local', 'bin', 'llama-server'),
|
|
34
|
+
resolve(homedir(), '.groove', 'bin', 'llama-server'),
|
|
35
|
+
'/usr/local/bin/llama-server',
|
|
36
|
+
];
|
|
37
|
+
return paths.some(p => existsSync(p));
|
|
29
38
|
}
|
|
30
39
|
}
|
|
31
40
|
|
|
41
|
+
static getLlamaServerPath() {
|
|
42
|
+
try {
|
|
43
|
+
return execSync('which llama-server', { stdio: 'pipe', encoding: 'utf8' }).trim();
|
|
44
|
+
} catch {
|
|
45
|
+
const paths = [
|
|
46
|
+
resolve(homedir(), '.local', 'bin', 'llama-server'),
|
|
47
|
+
resolve(homedir(), '.groove', 'bin', 'llama-server'),
|
|
48
|
+
'/usr/local/bin/llama-server',
|
|
49
|
+
];
|
|
50
|
+
return paths.find(p => existsSync(p)) || 'llama-server';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static async install() {
|
|
55
|
+
const platform = process.platform;
|
|
56
|
+
|
|
57
|
+
if (platform === 'darwin') {
|
|
58
|
+
try {
|
|
59
|
+
execSync('which brew', { stdio: 'ignore' });
|
|
60
|
+
} catch {
|
|
61
|
+
throw new Error('Homebrew not found. Install it from https://brew.sh then retry.');
|
|
62
|
+
}
|
|
63
|
+
execSync('brew install llama.cpp', { stdio: 'pipe', timeout: 600000 });
|
|
64
|
+
return { method: 'brew', path: execSync('which llama-server', { encoding: 'utf8', stdio: 'pipe' }).trim() };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (platform === 'linux') {
|
|
68
|
+
const installDir = resolve(homedir(), '.local', 'bin');
|
|
69
|
+
mkdirSync(installDir, { recursive: true });
|
|
70
|
+
|
|
71
|
+
const arch = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
72
|
+
const hasCuda = (() => { try { execSync('which nvidia-smi', { stdio: 'ignore' }); return true; } catch { return false; } })();
|
|
73
|
+
|
|
74
|
+
const resp = await fetch('https://api.github.com/repos/ggml-org/llama.cpp/releases/latest', {
|
|
75
|
+
headers: { 'User-Agent': 'groove-dev' },
|
|
76
|
+
});
|
|
77
|
+
if (!resp.ok) throw new Error(`GitHub API error: ${resp.status}`);
|
|
78
|
+
const release = await resp.json();
|
|
79
|
+
|
|
80
|
+
const suffix = hasCuda ? `ubuntu-${arch}-cuda` : `ubuntu-${arch}`;
|
|
81
|
+
let asset = release.assets.find(a => a.name.includes(suffix) && a.name.endsWith('.zip'));
|
|
82
|
+
if (!asset && hasCuda) {
|
|
83
|
+
asset = release.assets.find(a => a.name.includes(`ubuntu-${arch}`) && a.name.endsWith('.zip'));
|
|
84
|
+
}
|
|
85
|
+
if (!asset) {
|
|
86
|
+
asset = release.assets.find(a => a.name.includes('ubuntu') && a.name.includes(arch) && a.name.endsWith('.zip'));
|
|
87
|
+
}
|
|
88
|
+
if (!asset) throw new Error(`No pre-built binary found for linux-${arch}. Build from source: https://github.com/ggml-org/llama.cpp#build`);
|
|
89
|
+
|
|
90
|
+
const tmpZip = `/tmp/groove-llama-${Date.now()}.zip`;
|
|
91
|
+
const tmpDir = `/tmp/groove-llama-extract-${Date.now()}`;
|
|
92
|
+
|
|
93
|
+
execSync(`curl -fSL "${asset.browser_download_url}" -o "${tmpZip}"`, { stdio: 'pipe', timeout: 600000 });
|
|
94
|
+
execSync(`unzip -o "${tmpZip}" -d "${tmpDir}"`, { stdio: 'pipe', timeout: 60000 });
|
|
95
|
+
|
|
96
|
+
const findResult = execSync(`find "${tmpDir}" -name llama-server -type f`, { encoding: 'utf8', stdio: 'pipe' }).trim();
|
|
97
|
+
const binPath = findResult.split('\n')[0];
|
|
98
|
+
if (!binPath) throw new Error('llama-server binary not found in release archive');
|
|
99
|
+
|
|
100
|
+
const destPath = resolve(installDir, 'llama-server');
|
|
101
|
+
execSync(`cp "${binPath}" "${destPath}"`, { stdio: 'pipe' });
|
|
102
|
+
chmodSync(destPath, 0o755);
|
|
103
|
+
|
|
104
|
+
// Copy shared libraries if present
|
|
105
|
+
try {
|
|
106
|
+
const libDir = resolve(binPath, '..', '..', 'lib');
|
|
107
|
+
if (existsSync(libDir)) {
|
|
108
|
+
const userLibDir = resolve(homedir(), '.local', 'lib');
|
|
109
|
+
mkdirSync(userLibDir, { recursive: true });
|
|
110
|
+
execSync(`cp -r "${libDir}/"* "${userLibDir}/"`, { stdio: 'pipe' });
|
|
111
|
+
}
|
|
112
|
+
} catch { /* libs are optional */ }
|
|
113
|
+
|
|
114
|
+
// Cleanup
|
|
115
|
+
try { execSync(`rm -rf "${tmpZip}" "${tmpDir}"`, { stdio: 'ignore' }); } catch { /* best-effort */ }
|
|
116
|
+
|
|
117
|
+
return { method: 'github-release', path: destPath, cuda: hasCuda, release: release.tag_name };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
throw new Error(`Automatic install not supported on ${platform}. Install llama-server manually: https://github.com/ggml-org/llama.cpp#build`);
|
|
121
|
+
}
|
|
122
|
+
|
|
32
123
|
// --- Server Lifecycle ---
|
|
33
124
|
|
|
34
125
|
/**
|
|
@@ -74,9 +165,11 @@ export class LlamaServerManager {
|
|
|
74
165
|
args.push('--flash-attn', 'auto');
|
|
75
166
|
}
|
|
76
167
|
|
|
77
|
-
const
|
|
168
|
+
const serverBin = LlamaServerManager.getLlamaServerPath();
|
|
169
|
+
const proc = spawn(serverBin, args, {
|
|
78
170
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
79
171
|
detached: false,
|
|
172
|
+
env: { ...process.env, LD_LIBRARY_PATH: [resolve(homedir(), '.local', 'lib'), process.env.LD_LIBRARY_PATH].filter(Boolean).join(':') },
|
|
80
173
|
});
|
|
81
174
|
|
|
82
175
|
if (!proc.pid) {
|
|
@@ -69,7 +69,6 @@ export class ModelManager {
|
|
|
69
69
|
async search(query, { limit = 20, sort = 'downloads' } = {}) {
|
|
70
70
|
const params = new URLSearchParams({
|
|
71
71
|
search: query,
|
|
72
|
-
filter: 'gguf',
|
|
73
72
|
sort,
|
|
74
73
|
direction: '-1',
|
|
75
74
|
limit: String(limit),
|
|
@@ -83,15 +82,20 @@ export class ModelManager {
|
|
|
83
82
|
if (!res.ok) throw new Error(`HuggingFace API error: ${res.status}`);
|
|
84
83
|
const models = await res.json();
|
|
85
84
|
|
|
86
|
-
return models.map((m) =>
|
|
87
|
-
id
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
85
|
+
return models.map((m) => {
|
|
86
|
+
const id = m.modelId || m.id;
|
|
87
|
+
const tags = m.tags || [];
|
|
88
|
+
return {
|
|
89
|
+
id,
|
|
90
|
+
name: id.split('/').pop() || id,
|
|
91
|
+
author: id.split('/')[0] || '',
|
|
92
|
+
downloads: m.downloads || 0,
|
|
93
|
+
likes: m.likes || 0,
|
|
94
|
+
tags,
|
|
95
|
+
lastModified: m.lastModified,
|
|
96
|
+
recommendedRuntimes: inferRuntimes(id, tags),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
95
99
|
}
|
|
96
100
|
|
|
97
101
|
async getModelFiles(repoId) {
|
|
@@ -409,3 +413,41 @@ function classifyTier(params, quant) {
|
|
|
409
413
|
if (billions >= 10) return 'medium';
|
|
410
414
|
return 'light';
|
|
411
415
|
}
|
|
416
|
+
|
|
417
|
+
function inferRuntimes(repoId, tags) {
|
|
418
|
+
const lower = repoId.toLowerCase();
|
|
419
|
+
const tagSet = new Set(tags.map((t) => t.toLowerCase()));
|
|
420
|
+
const runtimes = new Set();
|
|
421
|
+
|
|
422
|
+
// GGUF → llama.cpp and (implicitly) Ollama
|
|
423
|
+
if (tagSet.has('gguf') || lower.includes('-gguf') || lower.includes('_gguf')) {
|
|
424
|
+
runtimes.add('llama.cpp');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// MLX-optimized models
|
|
428
|
+
if (tagSet.has('mlx') || lower.includes('-mlx') || lower.includes('_mlx')) {
|
|
429
|
+
runtimes.add('MLX');
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// GPTQ / AWQ quantized → vLLM handles these well
|
|
433
|
+
if (tagSet.has('gptq') || tagSet.has('awq') || lower.includes('-gptq') || lower.includes('-awq')) {
|
|
434
|
+
runtimes.add('vLLM');
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// SafeTensors / standard transformer weights → vLLM, TGI, MLX
|
|
438
|
+
if (tagSet.has('safetensors') || tagSet.has('transformers')) {
|
|
439
|
+
runtimes.add('vLLM');
|
|
440
|
+
runtimes.add('TGI');
|
|
441
|
+
if (!runtimes.has('MLX')) runtimes.add('MLX');
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// If nothing matched, infer from general model traits
|
|
445
|
+
if (runtimes.size === 0) {
|
|
446
|
+
if (tagSet.has('pytorch') || tagSet.has('tf') || tagSet.has('jax')) {
|
|
447
|
+
runtimes.add('vLLM');
|
|
448
|
+
runtimes.add('TGI');
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
return [...runtimes];
|
|
453
|
+
}
|
|
@@ -713,6 +713,17 @@ export function registerProviderRoutes(app, daemon) {
|
|
|
713
713
|
res.json(daemon.llamaServer.getStatus());
|
|
714
714
|
});
|
|
715
715
|
|
|
716
|
+
app.post('/api/llama/install', async (req, res) => {
|
|
717
|
+
try {
|
|
718
|
+
const { LlamaServerManager } = await import('../llama-server.js');
|
|
719
|
+
const result = await LlamaServerManager.install();
|
|
720
|
+
daemon.modelLab.refreshInstalledTools();
|
|
721
|
+
res.json({ success: true, ...result });
|
|
722
|
+
} catch (err) {
|
|
723
|
+
res.status(500).json({ error: err.message });
|
|
724
|
+
}
|
|
725
|
+
});
|
|
726
|
+
|
|
716
727
|
app.get('/api/mlx/status', (req, res) => {
|
|
717
728
|
res.json(daemon.mlxServer.getStatus());
|
|
718
729
|
});
|