File size: 4,322 Bytes
6c7818b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
815c829
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env node
import readline from "readline";
import { spawn, execSync } from "child_process";
import { initializeDatabase, db } from "./modules/database.js";
import { bootUp, getPrompt, log, C, setReadline } from "./modules/ui.js";
import { commandHandlers } from "./modules/commands.js";
import { route } from "./modules/router.js";
import { GGUF } from "./config.js";

const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
setReadline(rl); // Give the UI module access to the readline instance

let multiline = false;
let multiBuf = [];

function refreshPrompt() {
	const newPrompt = getPrompt();
	rl.setPrompt(newPrompt);
	rl.prompt(true);
}

function handleMultiline(line) {
	if (line.trim() === "```") {
		if (!multiline) {
			multiline = true;
			multiBuf = [];
			log.gray("(multiline ON — type ``` to send)");
			rl.setPrompt(''); // Use a blank prompt for multiline input
			rl.prompt();
		} else {
			multiline = false;
			return multiBuf.join("\n");
		}
		return null;
	}
	if (multiline) {
		multiBuf.push(line);
		return null;
	}
	return line.trim();
}

async function processInput(input, source = 'terminal') {
	if (!input) return false;

	try {
		const lowerInput = input.trim().toLowerCase();

		if (lowerInput === 'exit' || lowerInput === 'quit' || lowerInput === 'bye') {
			await commandHandlers.quit();
			// Return a special value to signal that we are quitting and should not refresh the prompt.
			return { isQuitting: true };
		} else if (input.trim().startsWith('/')) {
			const [command] = input.substring(1).split(/\s+/);
			const handler = commandHandlers[command];
			if (handler) {
				await handler(input);
			} else {
				log.warn(`Unknown command, brah. Sending to router anyway...`);
				await route(input);
			}
		} else {
			await route(input);
		}
		// Return a default object if not quitting.
		return { isQuitting: false };
	} catch (err) {
		log.error(`Magnus error: An unhandled exception occurred.`);
		console.error(err);
		return { isQuitting: false };
	}
}

let serverProcess = null; // Keep the server process in a higher scope

function startServer() { // No longer returns the process
	log.info('[SERVER] Firing up the llama-server engine...');
	serverProcess = spawn('llama-server', ['-m', GGUF, '--jinja'], {
		detached: true,
		stdio: 'ignore'
	});
	serverProcess.unref(); // Let the main process exit independently
	log.gray(`[SERVER] Llama-server is shredding on PID: ${serverProcess.pid}`);
}

function shutdown(exitCode = 0) {
	log.warn("\n[CLEANUP] Taking down the server... Catch ya later, legend!");
	if (serverProcess && serverProcess.pid) {
		try {
			if (process.platform === "win32") {
				// Bitchin' Fix: Use execSync to block until the command finishes. No more zombies!
				execSync(`taskkill /pid ${serverProcess.pid} /f /t`);
			} else {
				// Kill the entire process group. The `-` is the magic here.
				process.kill(-serverProcess.pid, 'SIGTERM');
			}
			log.success("[CLEANUP] Server process terminated.");
		} catch (e) {
			log.error(`[CLEANUP] Bogus! Couldn't kill server process ${serverProcess.pid}: ${e.message}`);
		}
	}
	process.exit(exitCode);
}

async function main() {
	startServer();
	log.gray('[SYSTEM] Giving the server 5 seconds to warm up...');
	await new Promise(resolve => setTimeout(resolve, 5000)); // Simple 5-second delay
	log.success('[SERVER] Aight, server should be ready. Let\'s rock!');

	initializeDatabase();
	await bootUp();

	rl.setPrompt(getPrompt());
	rl.prompt();
	rl.on("line", async (line) => {
		const processedLine = handleMultiline(line);

		if (processedLine === null) return; // Multiline is active, just wait.

		rl.pause(); // Pause the prompt while processing and streaming
		const { isQuitting } = await processInput(processedLine, 'terminal');
		if (isQuitting) {
			shutdown(0); // Gracefully shutdown if command handler signals a quit
			return;
		}
		if (!multiline) refreshPrompt();
		rl.resume();
	});

	// Catch Ctrl+C and other termination signals for a graceful shutdown
	process.on('SIGINT', () => shutdown(0));
	process.on('SIGTERM', () => shutdown(0));
}

main().catch(err => { console.error(err); shutdown(1); });