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'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Поднимаемся на уровень выше, чтобы попасть в корень проекта const PROJECT_ROOT = path.join(__dirname, '..'); dotenv.config({ path: path.join(PROJECT_ROOT, '.env') }); const app = express(); const PORT = process.env.PORT || 3000; app.use(cors()); app.use(express.json()); 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 инициализирован."); console.log("⚠️ Модель НЕ загружена."); } catch (err) { console.error("❌ Критическая ошибка инициализации движка:", err.message); process.exit(1); } } initServer(); // --- API Истории Чатов --- // 1. Загрузка истории чата app.get('/api/chat/load', async (req, res) => { const { id } = req.query; if (!id) return res.status(400).json({ error: 'ID чата не указан' }); const filePath = path.join(PROJECT_ROOT, 'userdata', `${id}.json`); try { // Читаем файл const data = await fs.promises.readFile(filePath, 'utf8'); res.json(JSON.parse(data)); } catch (err) { // Если файла нет (новый чат) - возвращаем пустой массив res.json([]); } }); // 2. Сохранение истории чата app.post('/api/chat/save', async (req, res) => { const { id, history } = req.body; if (!id || !history) return res.status(400).json({ error: 'Нет ID или истории' }); const userDataDir = path.join(PROJECT_ROOT, 'userdata'); // Если папки нет, создаем if (!fs.existsSync(userDataDir)) { await fs.promises.mkdir(userDataDir); } const filePath = path.join(userDataDir, `${id}.json`); try { await fs.promises.writeFile(filePath, JSON.stringify(history, null, 2)); res.json({ success: true }); } catch (err) { console.error("Ошибка сохранения чата:", err); res.status(500).json({ error: 'Не удалось сохранить файл' }); } }); // --- API Управления Моделью --- 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 }); } }); 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 }); } }); app.post('/api/server/stop', (req, res) => { console.log("🛑 [СЕРВЕР] Остановка..."); res.json({ success: true, message: "Остановка..." }); setTimeout(() => process.exit(0), 500); }); 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, history } = 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 }); // Формируем промпт из истории + новое сообщение let fullPrompt = ""; if (Array.isArray(history)) { history.forEach(msg => { fullPrompt += `${msg.role === 'user' ? 'User' : 'Bot'}: ${msg.content}\n`; }); } fullPrompt += `User: ${message}\nBot:`; await session.prompt(fullPrompt, { 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(); } }); // --- API Переименования Чата --- app.post('/api/chat/rename', async (req, res) => { const { oldId, newName } = req.body; if (!oldId || !newName) { return res.status(400).json({ error: 'Не указан старый ID или новое имя' }); } // Защита от некорректных имен файлов // Убираем символы, запрещенные в Windows: \ / : * ? " < > | const cleanName = newName.replace(/[\\/:*?"<>|]/g, ''); if (cleanName === '') { return res.status(400).json({ error: 'Имя не может быть пустым' }); } const oldPath = path.join(PROJECT_ROOT, 'userdata', `${oldId}.json`); const newPath = path.join(PROJECT_ROOT, 'userdata', `${cleanName}.json`); try { // Переименовываем файл await fs.promises.rename(oldPath, newPath); res.json({ success: true, newId: cleanName }); } catch (err) { console.error("Ошибка переименования:", err); res.status(500).json({ error: err.message }); } }); app.listen(PORT, () => { console.log(`✅ [СЕРВЕР] Веб-сервер запущен на порту: ${PORT}`); });