From b9dadb479a82c947d060fa1eef053326d3a14e73 Mon Sep 17 00:00:00 2001 From: xray Date: Thu, 9 Jul 2026 10:57:19 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20Server-Auto-Start=20im=20CLI=20=E2=80=94?= =?UTF-8?q?=20kein=20manueller=20Vorstart=20n=C3=B6tig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CLI startet llama-server automatisch bei fehlender Verbindung - Modellauswahl per Ziffer (Default=3=kleinstes) - Live-Logs während Server-Start - Timeout 120s für große Modelle vom USB-Stick - Kein interaktives start-server.sh mehr nötig --- cli/crowdcode.js | 183 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 145 insertions(+), 38 deletions(-) diff --git a/cli/crowdcode.js b/cli/crowdcode.js index bbb28ce..9550a17 100644 --- a/cli/crowdcode.js +++ b/cli/crowdcode.js @@ -295,7 +295,7 @@ async function applyActions(actions) { case 'bash': { console.log(` ${C.cyan}⚡${C.reset} Bash-Befehl erkannt:`); console.log(` ${C.grey}${a.command}${C.reset}`); - const confirm = await ask(` ${C.yellow}Ausführen?${C.reset} (j/N) `); + const confirm = await askSingleLine(` ${C.yellow}Ausführen?${C.reset} (j/N) `); if (confirm.toLowerCase() === 'j' || confirm.toLowerCase() === 'ja') { try { const out = execSync(a.command, { cwd: WORKSPACE, timeout: 30000, encoding: 'utf8', maxBuffer: 1024 * 512 }); @@ -314,19 +314,119 @@ async function applyActions(actions) { } // ─── Modelle aus dem MiniLlama/models-Ordner anzeigen ───────────────────────── -function showAvailableModels(modelsDir) { +function getLocalModels() { + const modelsDir = path.join(__dirname, '..', 'models'); if (!fs.existsSync(modelsDir)) return []; - const files = fs.readdirSync(modelsDir).filter(f => f.endsWith('.gguf')); - if (files.length === 0) return []; + return fs.readdirSync(modelsDir) + .filter(f => f.endsWith('.gguf')) + .sort() + .map((name, i) => { + const fullPath = path.join(modelsDir, name); + try { + const size = (fs.statSync(fullPath).size / 1024 / 1024 / 1024).toFixed(1); + return { index: i + 1, name, path: fullPath, size }; + } catch { return { index: i + 1, name, path: fullPath, size: '?' }; } + }); +} + +function showLocalModels(models) { + if (models.length === 0) return; console.log(`\n${C.bold}📦 Verfügbare Modelle:${C.reset}`); - files.forEach((f, i) => { - try { - const stat = fs.statSync(path.join(modelsDir, f)); - const size = (stat.size / 1024 / 1024 / 1024).toFixed(1); - console.log(` ${C.cyan}${i + 1})${C.reset} ${f} ${C.grey}(${size} GB)${C.reset}`); - } catch { console.log(` ${C.cyan}${i + 1})${C.reset} ${f}`); } + models.forEach(m => { + console.log(` ${C.cyan}${m.index})${C.reset} ${m.name} ${C.grey}(${m.size} GB)${C.reset}`); + }); +} + +// ─── Server starten ─────────────────────────────────────────────────────────── +function findLlamaServer() { + // Pfade für llama-server Binary suchen + const candidates = [ + path.join(__dirname, '..', 'bin', 'linux', 'llama-server'), + path.join(__dirname, '..', 'bin', 'win', 'llama-server.exe'), + ]; + for (const p of candidates) { + if (fs.existsSync(p)) return p; + } + return null; +} + +async function startServerWithModel(localModels) { + showLocalModels(localModels); + if (localModels.length === 0) { + console.log(`\n${C.red}❌ Keine .gguf-Modelle im MiniLlama/models/-Ordner gefunden.${C.reset}`); + process.exit(1); + } + + // User wählt Modell per Ziffer + let modelPath = null; + while (!modelPath) { + const input = await askSingleLine(`\n${C.yellow}🔌 Server starten mit Modell Nr.${C.reset} [1-${localModels.length}] ${C.dim}(Enter=3=kleinstes)${C.reset}: `); + const choice = input ? parseInt(input) : 3; + if (!isNaN(choice) && choice >= 1 && choice <= localModels.length) { + modelPath = localModels[choice - 1]; + } else { + console.log(` ${C.red}Bitte eine Zahl zwischen 1 und ${localModels.length}${C.reset}`); + } + } + + const serverBin = findLlamaServer(); + if (!serverBin) { + console.log(`\n${C.red}❌ Kein llama-server Binary gefunden.${C.reset}`); + console.log(` ${C.dim}Erwartet in bin/linux/llama-server oder bin/win/llama-server.exe${C.reset}`); + process.exit(1); + } + + console.log(`\n${C.dim}🚀 Starte Server mit ${modelPath.name}...${C.reset}\n`); + + const { spawn } = require('child_process'); + const MINILLAMA_DIR = path.join(__dirname, '..'); + + return new Promise((resolve, reject) => { + const server = spawn(serverBin, [ + '-m', modelPath.path, + '--host', '127.0.0.1', + '--port', '8080', + '--ctx-size', '4096', + '--n-gpu-layers', '0', + ], { + cwd: MINILLAMA_DIR, + stdio: ['ignore', 'pipe', 'pipe'], + }); + + let started = false; + + server.stdout.on('data', (data) => { + const text = data.toString(); + process.stdout.write(text); // Live-Logs zeigen + if (!started && (text.includes('model loaded') || text.includes('listening on') || text.includes('start'))) { + started = true; + // Kurz warten bis Server wirklich bereit + setTimeout(() => resolve(server), 500); + } + }); + + server.stderr.on('data', (data) => { + const text = data.toString(); + process.stdout.write(text); // Live-Logs zeigen + if (!started && (text.includes('model loaded') || text.includes('listening on') || text.includes('start')) && !text.includes('error')) { + started = true; + setTimeout(() => resolve(server), 500); + } + }); + + server.on('error', (err) => { + reject(new Error(`Server-Start fehlgeschlagen: ${err.message}`)); + }); + + server.on('exit', (code) => { + if (!started) reject(new Error(`Server beendet mit Code ${code}`)); + }); + + // Timeout: Wenn Server nicht innerhalb von 120s startet (große Modelle vom Stick) + setTimeout(() => { + if (!started) reject(new Error('Timeout: Server nicht innerhalb von 120s gestartet. Möglicherweise zu großes Modell oder USB-Stick zu langsam.')); + }, 120000); }); - return files; } // ─── Hauptprogramm ──────────────────────────────────────────────────────────── @@ -337,43 +437,50 @@ async function main() { console.log(`${C.bold}${C.cyan}╚══════════════════════════════════════════╝${C.reset}`); console.log(`${C.grey} API: ${API} | Workspace: ${WORKSPACE}${C.reset}\n`); - // Verfügbare Modelle prüfen - const modelsDir = path.join(__dirname, '..', 'models'); - const localModels = showAvailableModels(modelsDir); + // Verfügbare Modelle anzeigen + const localModels = getLocalModels(); - // API-Verbindung testen - console.log(`\n${C.dim}🔌 Teste Verbindung zu ${API}...${C.reset}`); + // Lokale Modelle IMMER anzeigen (auch wenn Server schon läuft) + if (localModels.length > 0) { + console.log(`${C.bold}📦 Verfügbare lokale Modelle:${C.reset}`); + localModels.forEach(m => { + console.log(` ${C.cyan}${m.index})${C.reset} ${m.name} ${C.grey}(${m.size} GB)${C.reset}`); + }); + } + + // API-Verbindung testen → falls Server schon läuft, direkt loslegen + let serverProcess = null; let connected = false; + console.log(`\n${C.dim}🔌 Teste Verbindung zu ${API}...${C.reset}`); try { const models = await fetchModels(); if (models.length > 0) { const modelName = MODEL || models[0]?.id || models[0]?.name || models[0]?.model || '(unbekannt)'; - console.log(` ${C.green}✅${C.reset} Verbunden. Modell: ${C.bold}${modelName}${C.reset}`); + console.log(` ${C.green}✅${C.reset} Server läuft bereits. Modell: ${C.bold}${modelName}${C.reset}`); connected = true; } else { - console.log(` ${C.yellow}⚠️${C.reset} Server verbunden, aber keine Modelle geladen.`); - console.log(` ${C.dim} Stelle sicher, dass ein Modell geladen ist.${C.reset}`); - connected = true; // Server läuft trotzdem + console.log(` ${C.yellow}⚠️${C.reset} Server antwortet, aber kein Modell geladen.`); + console.log(` ${C.dim} → Starte Server neu mit einem lokalen Modell.${C.reset}`); } - } catch (e) { - console.log(` ${C.red}❌${C.reset} Keine Verbindung zu ${API}`); - console.log(` ${C.dim} Starte zuerst den Server: ./start-server.sh${C.reset}`); - console.log(` ${C.dim} Oder: ./bin/linux/llama-server -m models/.gguf --host 127.0.0.1 --port 8080${C.reset}`); - if (localModels.length > 0) { - const ans = await ask(` ${C.yellow}Server lokal starten?${C.reset} (j/N) `); - if (ans.toLowerCase() === 'j' || ans.toLowerCase() === 'ja') { - const serverPath = path.join(__dirname, '..', 'start-server.sh'); - if (fs.existsSync(serverPath)) { - const child = require('child_process').spawn(serverPath, [], { stdio: 'inherit', cwd: path.join(__dirname, '..') }); - console.log(` ${C.dim}Server gestartet, drücke Enter wenn bereit...${C.reset}`); - await ask(''); - // Erneut testen - try { await fetchModels(); connected = true; } catch { } - } - } + } catch { + console.log(` ${C.red}❌${C.reset} Kein Server an ${API}`); + console.log(` ${C.dim} → Starte Server automatisch mit lokalem Modell.${C.reset}`); + } + + // Server starten, falls nicht verbunden + if (!connected) { + if (localModels.length === 0) { + console.log(`\n${C.red}❌ Keine .gguf-Modelle gefunden.${C.reset}`); + console.log(` ${C.dim}Lege Modelle in models/ ab oder starte llama-server manuell.${C.reset}`); + process.exit(1); } - if (!connected) { - console.log(`\n${C.red}Bitte zuerst llama-server starten.${C.reset}`); + + try { + serverProcess = await startServerWithModel(localModels); + connected = true; + console.log(`\n${C.green}✅ Server bereit.${C.reset}`); + } catch (err) { + console.log(`\n${C.red}❌ Server-Start fehlgeschlagen: ${err.message}${C.reset}`); process.exit(1); } }