fix: Server-Auto-Start im CLI — kein manueller Vorstart nötig

- 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
This commit is contained in:
xray 2026-07-09 10:57:19 +02:00
parent da86c2d83b
commit b9dadb479a

View file

@ -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/<modell>.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);
}
}