feat: Express + Whisper + 3 LLM + UI с микрофоном
This commit is contained in:
@@ -1,19 +1,70 @@
|
||||
const http = require('http');
|
||||
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';
|
||||
|
||||
http.createServer((req, res) => {
|
||||
if (req.url === '/healthz') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end('{"status":"ok"}');
|
||||
} else if (req.url === '/api/profiles') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify([
|
||||
{"id": "ddm", "name": "DDM-120", "desc": "Быстрая модель"},
|
||||
{"id": "qwen", "name": "Qwen", "desc": "Мощная модель"},
|
||||
{"id": "deepseek", "name": "DeepSeek", "desc": "Глубокая аналитика"},
|
||||
]));
|
||||
} else {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
||||
res.end('Say OK');
|
||||
const upload = multer({ storage: multer.memoryStorage() });
|
||||
|
||||
// ====== Models ======
|
||||
const MODELS = {
|
||||
ddm: { id: 'ddm-120', name: 'DDM-120', desc: 'Быстрая модель', temp: 0.7, tokens: 1024, system: 'Ты — полезный ассистент. Отвечай кратко и по делу на том же языке, что и запрос.' },
|
||||
qwen: { id: 'qwen-plus', name: 'Qwen', desc: 'Мощная модель', temp: 0.7, tokens: 2048, system: 'Ты — эксперт-аналитик. Отвечай развернуто, на языке запроса.' },
|
||||
deepseek: { id: 'deepseek-chat', name: 'DeepSeek', desc: 'Глубокая аналитика', temp: 0.7, tokens: 4096, system: 'Ты — глубокий аналитик и программист. Думай step-by-step, на языке запроса.' },
|
||||
};
|
||||
|
||||
// ====== 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');
|
||||
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 });
|
||||
}
|
||||
}).listen(PORT, () => console.log(':' + PORT));
|
||||
});
|
||||
|
||||
app.post('/api/chat', async (req, res) => {
|
||||
try {
|
||||
const { model, message } = req.body;
|
||||
const m = MODELS[model];
|
||||
if (!m) return res.status(400).json({ error: 'Unknown model' });
|
||||
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: [
|
||||
{ role: 'system', content: m.system },
|
||||
{ role: 'user', content: message || 'Привет' },
|
||||
],
|
||||
}),
|
||||
});
|
||||
const data = await r.json();
|
||||
res.json({ response: data.choices?.[0]?.message?.content || 'Нет ответа' });
|
||||
} catch (e) {
|
||||
res.status(500).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/', (_req, res) => res.sendFile(path.join(__dirname, 'public', 'index.html')));
|
||||
|
||||
app.listen(PORT, () => console.log(`Say:${PORT}`));
|
||||
Reference in New Issue
Block a user