Files
airllm-fork-nodejs/server/server.js

176 lines
6.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import express from 'express';
import cors from 'cors';
import dotenv from 'dotenv';
import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
import { getLlama, LlamaChatSession } from 'node-llama-cpp';
// Определяем пути
// __dirname теперь указывает на папку server
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Поднимаемся на уровень выше, чтобы попасть в корень проекта
const PROJECT_ROOT = path.join(__dirname, '..');
// Настраиваем dotenv явно, чтобы он нашел файл .env в корне
dotenv.config({ path: path.join(PROJECT_ROOT, '.env') });
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
// Статику (HTML, CSS, JS) теперь берем из папки public в корне
app.use(express.static(path.join(PROJECT_ROOT, 'public')));
let llama = null;
let model = null;
let context = null;
// --- Инициализация Сервера ---
console.log("🌐 Запуск веб-сервера...");
async function initServer() {
try {
llama = await getLlama();
console.log("✅ Движок Llama инициализирован (папка server/).");
console.log("⚠️ Модель НЕ загружена. Ожидание команды из интерфейса...");
} catch (err) {
console.error("❌ Критическая ошибка инициализации движка:", err.message);
process.exit(1);
}
}
initServer();
// --- API Управления Моделью ---
// 1. Загрузка модели
app.post('/api/model/load', async (req, res) => {
const { path: inputPath } = req.body;
if (!inputPath) {
return res.status(400).json({ success: false, message: 'Путь к модели не указан' });
}
if (model) {
return res.status(400).json({ success: false, message: 'Модель уже загружена.' });
}
// Логика путей:
// Если путь относительный (начинается с ./ или имени файла), склеиваем с КОРНЕМ ПРОЕКТА.
// Если абсолютный — оставляем как есть.
let absolutePath;
if (path.isAbsolute(inputPath)) {
absolutePath = inputPath;
} else {
absolutePath = path.join(PROJECT_ROOT, inputPath);
}
try {
console.log(`🔄 [ЗАГРУЗКА] Попытка загрузки модели из: ${absolutePath}`);
model = await llama.loadModel({ modelPath: absolutePath });
context = await model.createContext({
contextSize: 2048
});
console.log("✅ [УСПЕХ] Модель успешно загружена.");
res.json({ success: true, message: `Модель загружена` });
} catch (err) {
console.error(`❌ [ОШИБКА] Не удалось загрузить модель: ${err.message}`);
model = null;
context = null;
res.status(500).json({ success: false, message: err.message });
}
});
// 2. Выгрузка модели
app.post('/api/model/unload', async (req, res) => {
if (!model) {
return res.status(400).json({ success: false, message: 'Нет активной модели.' });
}
try {
console.log("🔄 [ВЫГРУЗКА] Освобождение ресурсов модели...");
if (context) context.dispose();
if (model) model.dispose();
model = null;
context = null;
console.log("✅ [УСПЕХ] Модель выгружена.");
res.json({ success: true, message: "Модель выгружена." });
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
});
// 3. Остановка сервера
app.post('/api/server/stop', (req, res) => {
console.log("🛑 [СЕРВЕР] Остановка...");
res.json({ success: true, message: "Остановка..." });
setTimeout(() => process.exit(0), 500);
});
// --- API Автодополнения ---
// Считывает папку model из КОРНЯ ПРОЕКТА
app.get('/api/models/list', async (req, res) => {
const modelDir = path.join(PROJECT_ROOT, 'model');
try {
await fs.promises.access(modelDir);
const files = await fs.promises.readdir(modelDir);
const ggufModels = files.filter(file => file.endsWith('.gguf')).sort();
res.json(ggufModels);
} catch (err) {
console.log("⚠️ Папка 'model' не найдена в корне проекта.");
res.json([]);
}
});
// --- API Чата ---
app.post('/api/chat', async (req, res) => {
if (!model || !context) {
return res.status(503).send("Модель не загружена.");
}
const { message } = req.body;
if (!message) return res.status(400).json({ error: 'Message is required' });
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.setHeader('Transfer-Encoding', 'chunked');
let sequence;
try {
sequence = context.getSequence();
const session = new LlamaChatSession({ contextSequence: sequence });
await session.prompt(message, {
onToken: (token) => {
try {
const chunk = model.detokenize(token);
res.write(chunk);
} catch (decodeErr) {
console.error("Ошибка декодирования:", decodeErr);
}
},
temperature: 0.7,
maxTokens: 2000,
});
res.end();
} catch (err) {
console.error("Ошибка генерации:", err.message);
res.write("\n\n[Ошибка генерации]");
res.end();
} finally {
if (sequence) sequence.dispose();
}
});
app.listen(PORT, () => {
console.log(`✅ [СЕРВЕР] Веб-сервер запущен на порту: www.localhost:${PORT}`);
});