const express = require('express'); const multer = require('multer'); const path = require('path'); const app = express(); const PORT = process.env.PORT || 3000; const API_KEY = process.env.API_KEY || ''; const API_BASE = 'https://api.aillm.ru'; const upload = multer({ storage: multer.memoryStorage() }); // ====== Models ====== const MODELS = { 'gpt-oss-120b': { id: 'gpt-oss-120b', name: 'GPT-OSS-120B', desc: '120B reasoning model', temp: 0.7, tokens: 4096, system: 'Ты — полезный ассистент. Отвечай на том же языке, что и запрос.' }, }; // ====== Static ====== app.use(express.static(path.join(__dirname, 'public'))); app.use(express.json()); // ====== Routes ====== app.get('/healthz', (_req, res) => res.json({ status: 'ok' })); app.get('/api/profiles', (_req, res) => { res.json(Object.entries(MODELS).map(([id, m]) => ({ id, name: m.name, desc: m.desc }))); }); app.post('/api/whisper', upload.single('audio'), async (req, res) => { try { if (!req.file) return res.status(400).json({ error: 'No audio file' }); const form = new FormData(); form.append('file', new Blob([req.file.buffer], { type: req.file.mimetype }), 'audio.webm'); form.append('model', 'whisper-large-v3-turbo'); const r = await fetch(`${API_BASE}/v1/audio/transcriptions`, { method: 'POST', headers: { Authorization: `Bearer ${API_KEY}` }, body: form, }); const data = await r.json(); res.json({ text: data.text || '' }); } catch (e) { res.status(500).json({ error: e.message }); } }); // ====== Session memory ====== const sessions = {}; // sessionId → messages[] app.get('/api/history', (req, res) => { const sid = req.query.sid || 'default'; res.json({ messages: sessions[sid] || [] }); }); app.post('/api/chat', async (req, res) => { try { const { model, message, sid, history } = req.body; const m = MODELS[model]; if (!m) return res.status(400).json({ error: 'Неизвестная модель' }); // Собираем историю: system + предыдущие сообщения + новое const id = sid || 'default'; const msgs = history || sessions[id] || []; const fullMessages = [ { role: 'system', content: m.system }, ...msgs, { role: 'user', content: message || 'Привет' }, ]; // Обрезаем чтобы не выйти за лимит токенов while (JSON.stringify(fullMessages).length > 32000 && fullMessages.length > 2) fullMessages.splice(1, 2); const r = await fetch(`${API_BASE}/v1/chat/completions`, { method: 'POST', headers: { Authorization: `Bearer ${API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: m.id, temperature: m.temp, max_tokens: m.tokens, messages: fullMessages, }), }); const data = await r.json(); const response = data.choices?.[0]?.message?.content || 'Нет ответа'; // Сохраняем историю sessions[id] = [ ...msgs, { role: 'user', content: message }, { role: 'assistant', content: response }, ]; res.json({ response, sid: id }); } catch (e) { res.status(500).json({ error: e.message }); } }); app.post('/api/reset', (req, res) => { const sid = req.body.sid || 'default'; delete sessions[sid]; res.json({ ok: true }); }); app.get('/', (_req, res) => res.sendFile(path.join(__dirname, 'public', 'index.html'))); app.listen(PORT, () => console.log(`Say:${PORT}`));