[ZILANT] коррекция работы фронта с долгим ответом ИИ
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-08-02 16:52:48 +03:00
parent 9b8b5514b8
commit 739e7171ea
3 changed files with 67 additions and 20 deletions
+16 -2
View File
@@ -1011,12 +1011,23 @@ def api_generate_shortname(post_id):
try: try:
post = db.get_post(post_id) post = db.get_post(post_id)
if not post: if not post:
return jsonify({'error': 'Post not found'}), 404 return jsonify({'success': False, 'error': 'Post not found'}), 404
log_event(f"Генерация названия для поста ID: {post_id}") log_event(f"Генерация названия для поста ID: {post_id}")
shortname = generate_ai_shortname(post['text']) shortname = generate_ai_shortname(post['text'])
if not shortname:
return jsonify({
'success': False,
'error': 'Не удалось сгенерировать название (пустой ответ AI)',
}), 500
# После долгого AI-запроса MySQL-соединение могло протухнуть
try:
db.conn.ping(reconnect=True)
except Exception:
db.close()
db = get_db()
# Обновляем shortname в базе данных
with db.conn.cursor() as cursor: with db.conn.cursor() as cursor:
cursor.execute( cursor.execute(
"UPDATE posts SET shortname = %s WHERE id = %s", "UPDATE posts SET shortname = %s WHERE id = %s",
@@ -1031,7 +1042,10 @@ def api_generate_shortname(post_id):
log_event(error_msg) log_event(error_msg)
return jsonify({'success': False, 'error': error_msg}), 500 return jsonify({'success': False, 'error': error_msg}), 500
finally: finally:
try:
db.close() db.close()
except Exception:
pass
@bp.route('/api/publish_post/<int:post_id>', methods=['POST']) @bp.route('/api/publish_post/<int:post_id>', methods=['POST'])
@login_required @login_required
+18 -8
View File
@@ -91,7 +91,7 @@ def _generate_shortname_by_api(text):
} }
try: try:
response = requests.post(url, headers=headers, json=data, timeout=60) response = requests.post(url, headers=headers, json=data, timeout=180)
# Обработка ошибки 429 (Too Many Requests) # Обработка ошибки 429 (Too Many Requests)
if response.status_code == 429: if response.status_code == 429:
@@ -109,12 +109,14 @@ def _generate_shortname_by_api(text):
log_message(f"Ошибка 429: Превышен лимит запросов. Ответ API: {json.dumps(error_info, ensure_ascii=False)}") log_message(f"Ошибка 429: Превышен лимит запросов. Ответ API: {json.dumps(error_info, ensure_ascii=False)}")
print("Ошибка 429: Превышен лимит запросов. Подробности в логе.") print("Ошибка 429: Превышен лимит запросов. Подробности в логе.")
sys.exit(1) # Не sys.exit — эта функция вызывается и из веб-API Flask
raise RuntimeError("RouterAI 429: превышен лимит запросов")
response.raise_for_status() response.raise_for_status()
result = response.json() result = response.json()
# Извлекаем только текст ответа # Извлекаем только текст ответа (у thinking-моделей ответ в content,
# рассуждения — в reasoning_content / reasoning)
message = result['choices'][0]['message'] message = result['choices'][0]['message']
shortname = (message.get('content') or '').strip() shortname = (message.get('content') or '').strip()
if not shortname: if not shortname:
@@ -123,6 +125,12 @@ def _generate_shortname_by_api(text):
# Удаляем возможные кавычки и лишние символы # Удаляем возможные кавычки и лишние символы
shortname = re.sub(r'^["\']|["\']$', '', shortname) shortname = re.sub(r'^["\']|["\']$', '', shortname)
# Если модель вернула многострочный ответ — берём первую непустую строку
for line in shortname.splitlines():
line = line.strip()
if line:
shortname = line
break
# Обрезаем до 80 символов # Обрезаем до 80 символов
return shortname[:80] return shortname[:80]
@@ -143,12 +151,14 @@ def _generate_shortname_by_api(text):
log_message(f"Ошибка 429: Превышен лимит запросов. Ответ API: {json.dumps(error_info, ensure_ascii=False)}") log_message(f"Ошибка 429: Превышен лимит запросов. Ответ API: {json.dumps(error_info, ensure_ascii=False)}")
print("Ошибка 429: Превышен лимит запросов. Подробности в логе.") print("Ошибка 429: Превышен лимит запросов. Подробности в логе.")
sys.exit(1) raise RuntimeError("RouterAI 429: превышен лимит запросов")
else: else:
error_msg = f"HTTP ошибка при генерации названия: {e}" error_msg = f"HTTP ошибка при генерации названия: {e}"
log_message(error_msg) log_message(error_msg)
print(error_msg) print(error_msg)
return None return None
except RuntimeError:
raise
except Exception as e: except Exception as e:
error_msg = f"Ошибка при генерации названия: {e}" error_msg = f"Ошибка при генерации названия: {e}"
log_message(error_msg) log_message(error_msg)
@@ -238,10 +248,10 @@ def ai_shortname_all():
log_message("Обработка всех записей завершена") log_message("Обработка всех записей завершена")
print("Обработка завершена") print("Обработка завершена")
except SystemExit: except RuntimeError as e:
# Перехватываем системный выход для корректного закрытия соединения # 429 и прочие явные ошибки RouterAI
log_message("Завершение работы из-за ошибки 429") log_message(f"Завершение работы: {e}")
print("Завершение работы из-за ошибки 429") print(f"Завершение работы: {e}")
except Exception as e: except Exception as e:
error_msg = f"Произошла ошибка при обработке записей: {e}" error_msg = f"Произошла ошибка при обработке записей: {e}"
log_message(error_msg) log_message(error_msg)
+32 -9
View File
@@ -1579,23 +1579,46 @@
'X-Requested-With': 'XMLHttpRequest' 'X-Requested-With': 'XMLHttpRequest'
} }
}) })
.then(response => response.json()) .then(async response => {
.then(data => { let data = null;
try {
data = await response.json();
} catch (e) {
// Прокси мог оборвать долгий ответ (502/504 HTML) — название часто уже в БД
throw new Error(
`Сервер вернул не-JSON (HTTP ${response.status}). ` +
`Если запрос был долгим, название могло уже сохраниться — обновите список.`
);
}
if (!response.ok) {
throw new Error((data && data.error) || `HTTP ${response.status}`);
}
return data;
})
.then(async data => {
hideProcessingMessage(); hideProcessingMessage();
if (data.success) { if (data && data.success) {
alert('Название успешно сгенерировано: ' + data.shortname); alert('Название успешно сгенерировано: ' + data.shortname);
loadPosts(); // Перезагружаем список постов await loadPosts();
// Восстанавливаем выбранный пост
highlightSelectedPost(postId); highlightSelectedPost(postId);
showPostDetails(postId); await showPostDetails(postId);
} else { } else {
alert('Ошибка генерации названия: ' + (data.error || 'неизвестная ошибка')); alert('Ошибка генерации названия: ' + ((data && data.error) || 'неизвестная ошибка'));
await loadPosts();
} }
}) })
.catch(error => { .catch(async error => {
hideProcessingMessage(); hideProcessingMessage();
console.error('Ошибка генерации названия:', error); console.error('Ошибка генерации названия:', error);
alert('Произошла ошибка при генерации названия'); // После таймаута прокси название часто уже записано — подтянем список
try {
await loadPosts();
highlightSelectedPost(postId);
await showPostDetails(postId);
} catch (e) {
console.error('Не удалось обновить список после ошибки:', e);
}
alert('Ошибка при генерации названия: ' + (error.message || error));
}); });
} }
// Функция для публикации конкретного поста // Функция для публикации конкретного поста