102 lines
3.5 KiB
JavaScript
102 lines
3.5 KiB
JavaScript
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}`)); |