diff --git a/db_edit.py b/db_edit.py index 280e101..0146071 100644 --- a/db_edit.py +++ b/db_edit.py @@ -1011,12 +1011,23 @@ def api_generate_shortname(post_id): try: post = db.get_post(post_id) 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}") 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: cursor.execute( "UPDATE posts SET shortname = %s WHERE id = %s", @@ -1031,7 +1042,10 @@ def api_generate_shortname(post_id): log_event(error_msg) return jsonify({'success': False, 'error': error_msg}), 500 finally: - db.close() + try: + db.close() + except Exception: + pass @bp.route('/api/publish_post/', methods=['POST']) @login_required diff --git a/db_update_shortname.py b/db_update_shortname.py index da74b30..0d273c1 100644 --- a/db_update_shortname.py +++ b/db_update_shortname.py @@ -91,7 +91,7 @@ def _generate_shortname_by_api(text): } 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) 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)}") print("Ошибка 429: Превышен лимит запросов. Подробности в логе.") - sys.exit(1) + # Не sys.exit — эта функция вызывается и из веб-API Flask + raise RuntimeError("RouterAI 429: превышен лимит запросов") response.raise_for_status() result = response.json() - # Извлекаем только текст ответа + # Извлекаем только текст ответа (у thinking-моделей ответ в content, + # рассуждения — в reasoning_content / reasoning) message = result['choices'][0]['message'] shortname = (message.get('content') or '').strip() if not shortname: @@ -123,6 +125,12 @@ def _generate_shortname_by_api(text): # Удаляем возможные кавычки и лишние символы shortname = re.sub(r'^["\']|["\']$', '', shortname) + # Если модель вернула многострочный ответ — берём первую непустую строку + for line in shortname.splitlines(): + line = line.strip() + if line: + shortname = line + break # Обрезаем до 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)}") print("Ошибка 429: Превышен лимит запросов. Подробности в логе.") - sys.exit(1) + raise RuntimeError("RouterAI 429: превышен лимит запросов") else: error_msg = f"HTTP ошибка при генерации названия: {e}" log_message(error_msg) print(error_msg) return None + except RuntimeError: + raise except Exception as e: error_msg = f"Ошибка при генерации названия: {e}" log_message(error_msg) @@ -238,10 +248,10 @@ def ai_shortname_all(): log_message("Обработка всех записей завершена") print("Обработка завершена") - except SystemExit: - # Перехватываем системный выход для корректного закрытия соединения - log_message("Завершение работы из-за ошибки 429") - print("Завершение работы из-за ошибки 429") + except RuntimeError as e: + # 429 и прочие явные ошибки RouterAI + log_message(f"Завершение работы: {e}") + print(f"Завершение работы: {e}") except Exception as e: error_msg = f"Произошла ошибка при обработке записей: {e}" log_message(error_msg) diff --git a/templates/index.html b/templates/index.html index 96cadf2..b9ebca8 100644 --- a/templates/index.html +++ b/templates/index.html @@ -1579,23 +1579,46 @@ 'X-Requested-With': 'XMLHttpRequest' } }) - .then(response => response.json()) - .then(data => { + .then(async response => { + 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(); - if (data.success) { + if (data && data.success) { alert('Название успешно сгенерировано: ' + data.shortname); - loadPosts(); // Перезагружаем список постов - // Восстанавливаем выбранный пост + await loadPosts(); highlightSelectedPost(postId); - showPostDetails(postId); + await showPostDetails(postId); } else { - alert('Ошибка генерации названия: ' + (data.error || 'неизвестная ошибка')); + alert('Ошибка генерации названия: ' + ((data && data.error) || 'неизвестная ошибка')); + await loadPosts(); } }) - .catch(error => { + .catch(async error => { hideProcessingMessage(); console.error('Ошибка генерации названия:', error); - alert('Произошла ошибка при генерации названия'); + // После таймаута прокси название часто уже записано — подтянем список + try { + await loadPosts(); + highlightSelectedPost(postId); + await showPostDetails(postId); + } catch (e) { + console.error('Не удалось обновить список после ошибки:', e); + } + alert('Ошибка при генерации названия: ' + (error.message || error)); }); } // Функция для публикации конкретного поста