Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| """ | |
| PDF Table Extractor - Flask сервер для работы с GigaChat API | |
| Версия для Hugging Face Spaces | |
| """ | |
| import os | |
| import json | |
| import tempfile | |
| import requests | |
| from flask import Flask, request, jsonify, render_template, send_from_directory | |
| from flask_cors import CORS | |
| from werkzeug.utils import secure_filename | |
| import urllib3 | |
| # Отключаем предупреждения SSL для разработки | |
| urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) | |
| app = Flask(__name__) | |
| CORS(app) # Разрешаем CORS для всех доменов | |
| # Конфигурация для Hugging Face | |
| app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production') | |
| app.config['UPLOAD_FOLDER'] = '/tmp/uploads' # Используем /tmp в Spaces | |
| app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # Максимальный размер файла 50MB | |
| # Создаем папку для загрузок | |
| os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) | |
| # Конфигурация GigaChat API | |
| OAUTH_URL = "https://ngw.devices.sberbank.ru:9443/api/v2/oauth" | |
| GIGACHAT_URL = "https://gigachat.devices.sberbank.ru/api/v1/chat/completions" | |
| UPLOAD_URL = "https://gigachat.devices.sberbank.ru/api/v1/files" | |
| def allowed_file(filename): | |
| """Проверяем, что файл имеет допустимое расширение""" | |
| return '.' in filename and filename.rsplit('.', 1)[1].lower() == 'pdf' | |
| # ========== РОУТЫ ДЛЯ СТАТИЧЕСКИХ ФАЙЛОВ ========== | |
| def serve_static_files(filename): | |
| """Обслуживание статических файлов""" | |
| return send_from_directory('static', filename) | |
| # ========== ГЛАВНЫЕ РОУТЫ ========== | |
| def index(): | |
| """Главная страница""" | |
| return render_template('index.html') | |
| def health(): | |
| """Health check для Hugging Face""" | |
| return jsonify({ | |
| "status": "healthy", | |
| "service": "PDF Table Extractor", | |
| "version": "1.0.0", | |
| "environment": os.environ.get('ENVIRONMENT', 'development') | |
| }) | |
| def get_oauth_token(): | |
| """Получение токена доступа через API ключ""" | |
| try: | |
| # Получаем данные из запроса | |
| data = request.get_json() | |
| if not data: | |
| return jsonify({"error": "No JSON data provided"}), 400 | |
| api_key = data.get('api_key') | |
| if not api_key: | |
| return jsonify({"error": "API key is required"}), 400 | |
| print(f"Получен запрос на получение токена с API ключем: {api_key[:20]}...") | |
| # Заголовки для запроса к GigaChat | |
| headers = { | |
| "Content-Type": "application/x-www-form-urlencoded", | |
| "Accept": "application/json", | |
| "RqUID": "87d5de19-0c14-4223-b7c2-ccea3182470a", | |
| "Authorization": f"Basic {api_key}" | |
| } | |
| payload = {"scope": "GIGACHAT_API_PERS"} | |
| # Отправляем запрос к GigaChat API | |
| print(f"Отправляем запрос к {OAUTH_URL}") | |
| response = requests.post( | |
| OAUTH_URL, | |
| headers=headers, | |
| data=payload, | |
| verify=False, # Отключаем проверку SSL для разработки | |
| timeout=30 | |
| ) | |
| # Проверяем ответ | |
| response.raise_for_status() | |
| result = response.json() | |
| print(f"Токен успешно получен: {result.get('access_token')[:20]}...") | |
| return jsonify({ | |
| "access_token": result.get("access_token"), | |
| "expires_at": result.get("expires_at"), | |
| "token_type": result.get("token_type", "Bearer") | |
| }) | |
| except requests.exceptions.RequestException as e: | |
| error_msg = f"Ошибка при запросе к API: {str(e)}" | |
| print(f"ERROR: {error_msg}") | |
| if hasattr(e, 'response') and e.response: | |
| print(f"Response status: {e.response.status_code}") | |
| print(f"Response text: {e.response.text}") | |
| return jsonify({"error": error_msg}), 500 | |
| except Exception as e: | |
| error_msg = f"Неожиданная ошибка: {str(e)}" | |
| print(f"ERROR: {error_msg}") | |
| return jsonify({"error": error_msg}), 500 | |
| def upload_file(): | |
| """Загрузка файла на сервер GigaChat""" | |
| try: | |
| print("=" * 60) | |
| print("DEBUG: Начало загрузки файла в GigaChat") | |
| # Проверяем заголовок авторизации | |
| auth_header = request.headers.get('Authorization') | |
| if not auth_header: | |
| print("ERROR: Отсутствует заголовок Authorization") | |
| return jsonify({"error": "Authorization header is required"}), 401 | |
| print(f"DEBUG: Токен получен: {auth_header[:50]}...") | |
| # Проверяем наличие файла | |
| if 'file' not in request.files: | |
| print("ERROR: В запросе нет файла") | |
| return jsonify({"error": "No file part in request"}), 400 | |
| file = request.files['file'] | |
| if file.filename == '': | |
| print("ERROR: Файл не выбран") | |
| return jsonify({"error": "No file selected"}), 400 | |
| if not allowed_file(file.filename): | |
| print(f"ERROR: Неподдерживаемый формат файла: {file.filename}") | |
| return jsonify({"error": "Only PDF files are allowed"}), 400 | |
| print(f"DEBUG: Загружаемый файл: {file.filename}, размер: {file.content_length} байт") | |
| # Сохраняем файл временно | |
| filename = secure_filename(file.filename) | |
| temp_filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename) | |
| file.save(temp_filepath) | |
| print(f"DEBUG: Файл сохранен временно: {temp_filepath} ({os.path.getsize(temp_filepath)} bytes)") | |
| try: | |
| # Подготавливаем запрос к GigaChat API | |
| headers = { | |
| "Accept": "application/json", | |
| "Authorization": auth_header | |
| } | |
| # Открываем файл для отправки | |
| with open(temp_filepath, 'rb') as f: | |
| files = { | |
| "file": (filename, f, "application/pdf"), | |
| "purpose": (None, "general") | |
| } | |
| print(f"DEBUG: Отправляем файл в GigaChat API...") | |
| print(f"DEBUG: URL: {UPLOAD_URL}") | |
| response = requests.post( | |
| UPLOAD_URL, | |
| headers=headers, | |
| files=files, | |
| verify=False, | |
| timeout=60 | |
| ) | |
| print(f"DEBUG: Статус ответа GigaChat: {response.status_code}") | |
| print(f"DEBUG: Ответ GigaChat: {response.text[:200]}...") | |
| response.raise_for_status() | |
| result = response.json() | |
| print(f"DEBUG: Файл успешно загружен в GigaChat. ID файла: {result.get('id')}") | |
| # Удаляем временный файл | |
| if os.path.exists(temp_filepath): | |
| os.remove(temp_filepath) | |
| return jsonify({ | |
| "id": result.get("id"), | |
| "filename": filename, | |
| "object": result.get("object", "file"), | |
| "bytes": result.get("bytes", 0), | |
| "status": "uploaded" | |
| }) | |
| except requests.exceptions.RequestException as e: | |
| print(f"ERROR: Ошибка при запросе к GigaChat: {str(e)}") | |
| if hasattr(e, 'response') and e.response: | |
| print(f"ERROR: Response status: {e.response.status_code}") | |
| print(f"ERROR: Response text: {e.response.text[:500]}") | |
| raise e | |
| except Exception as e: | |
| print(f"ERROR: Неожиданная ошибка при загрузке файла: {str(e)}") | |
| return jsonify({"error": f"Upload failed: {str(e)}"}), 500 | |
| def chat_completions(): | |
| """Запрос к GigaChat API для извлечения данных из таблицы""" | |
| try: | |
| # Получаем данные из запроса | |
| data = request.get_json() | |
| if not data: | |
| return jsonify({"error": "No JSON data provided"}), 400 | |
| print("=" * 60) | |
| print("DEBUG: Получен запрос к chat/completions") | |
| print(f"DEBUG: Данные запроса: {json.dumps(data, ensure_ascii=False)[:500]}") | |
| # Проверяем наличие attachments | |
| messages = data.get('messages', []) | |
| if messages: | |
| first_message = messages[0] | |
| attachments = first_message.get('attachments', []) | |
| print(f"DEBUG: Прикрепленные файлы (attachments): {attachments}") | |
| # Проверяем заголовок авторизации | |
| auth_header = request.headers.get('Authorization') | |
| if not auth_header: | |
| return jsonify({"error": "Authorization header is required"}), 401 | |
| # Подготавливаем заголовки для GigaChat API | |
| headers = { | |
| "Content-Type": "application/json", | |
| "Accept": "application/json", | |
| "Authorization": auth_header | |
| } | |
| # Отправляем запрос к GigaChat API | |
| print(f"DEBUG: Отправляем запрос к {GIGACHAT_URL}") | |
| response = requests.post( | |
| GIGACHAT_URL, | |
| headers=headers, | |
| json=data, | |
| verify=False, | |
| timeout=120 | |
| ) | |
| response.raise_for_status() | |
| result = response.json() | |
| print( | |
| f"DEBUG: Получен ответ от GigaChat. Количество токенов: {result.get('usage', {}).get('total_tokens', 'unknown')}") | |
| print(f"DEBUG: Ответ: {json.dumps(result, ensure_ascii=False)[:500]}...") | |
| print("=" * 60) | |
| return jsonify(result) | |
| except requests.exceptions.RequestException as e: | |
| error_msg = f"Ошибка при запросе к GigaChat API: {str(e)}" | |
| print(f"ERROR: {error_msg}") | |
| if hasattr(e, 'response') and e.response: | |
| print(f"ERROR: Response status: {e.response.status_code}") | |
| print(f"ERROR: Response text: {e.response.text[:500]}") | |
| return jsonify({"error": error_msg}), 500 | |
| except Exception as e: | |
| error_msg = f"Неожиданная ошибка: {str(e)}" | |
| print(f"ERROR: {error_msg}") | |
| return jsonify({"error": error_msg}), 500 | |
| def test_api(): | |
| """Тестовый endpoint для проверки работы сервера""" | |
| return jsonify({ | |
| "status": "ok", | |
| "message": "Сервер работает корректно", | |
| "environment": os.environ.get('ENVIRONMENT', 'development'), | |
| "endpoints": { | |
| "GET /": "Главная страница", | |
| "GET /health": "Health check", | |
| "GET /test": "Тестовый endpoint", | |
| "POST /api/oauth": "Получение токена доступа", | |
| "POST /api/files": "Загрузка файла в GigaChat", | |
| "POST /api/chat/completions": "Запрос к GigaChat API" | |
| } | |
| }) | |
| # ========== УНИВЕРСАЛЬНЫЙ РОУТ ДЛЯ СТАТИЧЕСКИХ ФАЙЛОВ ========== | |
| def catch_all(filename): | |
| """Универсальный роут для статических файлов""" | |
| if filename.endswith('.css'): | |
| return send_from_directory('static', filename) | |
| elif filename.endswith('.js'): | |
| return send_from_directory('static', filename) | |
| elif filename.endswith('.html'): | |
| return send_from_directory('templates', filename) | |
| elif filename == 'favicon.ico': | |
| return '', 404 # Или верните favicon | |
| else: | |
| return jsonify({"error": "File not found", "path": filename}), 404 | |
| # ========== ЗАПУСК ПРИЛОЖЕНИЯ ========== | |
| if __name__ == '__main__': | |
| # Получаем порт из окружения (Hugging Face использует 7860) | |
| port = int(os.environ.get('PORT', 7860)) | |
| # Создаем необходимые папки | |
| os.makedirs('static', exist_ok=True) | |
| os.makedirs('templates', exist_ok=True) | |
| os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True) | |
| print("=" * 60) | |
| print("📊 PDF Table Extractor Server") | |
| print("🚀 Версия для Hugging Face Spaces") | |
| print("=" * 60) | |
| print(f"Порт: {port}") | |
| print(f"Папка загрузок: {app.config['UPLOAD_FOLDER']}") | |
| print(f"Режим: {os.environ.get('ENVIRONMENT', 'development')}") | |
| print("=" * 60) | |
| # Запускаем сервер | |
| app.run( | |
| debug=False, # В продакшне всегда False! | |
| host='0.0.0.0', | |
| port=port, | |
| threaded=True | |
| ) |