diff --git a/backend/src/controllers/assistantController.js b/backend/src/controllers/assistantController.js index a8a4fd3..b56f8a8 100644 --- a/backend/src/controllers/assistantController.js +++ b/backend/src/controllers/assistantController.js @@ -421,11 +421,11 @@ function bumpUsage(field) { /* Низкоуровневый вызов OpenAI-совместимого chat/completions. */ /* Возвращает { text, error } — error: 'off'|'rate_limit'|'http'|'timeout'|'network'|'empty'|null. */ -async function callLLM(messages, maxTokens, override) { +async function callLLM(messages, maxTokens, override, timeoutMs) { const cfg = override || llmConfig(); if (typeof fetch !== 'function' || !cfg.on) return { text: null, error: 'off' }; const ctrl = new AbortController(); - const timer = setTimeout(() => ctrl.abort(), 15000); + const timer = setTimeout(() => ctrl.abort(), timeoutMs || 15000); try { const r = await fetch(cfg.url, { method: 'POST', @@ -451,12 +451,12 @@ function _recordFailover(failed, served, reason) { } function _clearFailover() { try { db.prepare("DELETE FROM app_settings WHERE key = 'assistant_failover'").run(); } catch (e) {} } -async function callLLMFailover(messages, maxTokens) { +async function callLLMFailover(messages, maxTokens, timeoutMs) { const cfgs = providersOrdered(); if (!cfgs.length) return { text: null, error: 'off' }; let last = { text: null, error: 'off' }, firstErr = null; for (let i = 0; i < cfgs.length; i++) { - last = await callLLM(messages, maxTokens, cfgs[i]); + last = await callLLM(messages, maxTokens, cfgs[i], timeoutMs); if (i === 0) firstErr = last.error; if (last.text) { if (i === 0) _clearFailover(); // активный работает — снимаем флаг @@ -760,7 +760,7 @@ async function flashcardsFromText(req, res) { 'Верни СТРОГО JSON-массив из ' + count + ' объектов вида {"front":"...","back":"..."} без markdown и пояснений. ' + 'front — короткий вопрос, back — краткий ответ (1–2 предложения). По-русски. Формулы в LaTeX между $...$. Никакого текста вне JSON.'; let rr; - try { rr = await callLLMFailover([{ role: 'system', content: sys }, { role: 'user', content: text }], 1600); } + try { rr = await callLLMFailover([{ role: 'system', content: sys }, { role: 'user', content: text }], 1600, 40000); } catch (e) { return res.status(502).json({ error: 'Не удалось обратиться к ИИ' }); } const raw = rr && rr.text; let cards = []; @@ -800,7 +800,7 @@ async function questionsFromText(req, res) { 'РОВНО 4 варианта; correct — индекс правильного (0..3); ровно один правильный. ' + 'По-русски, формулы в LaTeX между $...$. Никакого текста вне JSON, без markdown.'; let rr; - try { rr = await callLLMFailover([{ role: 'system', content: sys }, { role: 'user', content: text }], 2200); } + try { rr = await callLLMFailover([{ role: 'system', content: sys }, { role: 'user', content: text }], 2200, 45000); } catch (e) { return res.status(502).json({ error: 'Не удалось обратиться к ИИ' }); } const raw = rr && rr.text; let questions = [];