import os from flask import Flask, render_template, request, jsonify from PIL import Image import google.generativeai as genai import base64 import io import logging import json import re import random from playwright.sync_api import sync_playwright, TimeoutError as PlaywrightTimeoutError from flask_socketio import SocketIO, emit app = Flask(__name__) app.config['SECRET_KEY'] = str(random.randint(11111,99999999999999999999999999)) socketio = SocketIO(app, cors_allowed_origins="*") # For development; restrict in production. # --- API Key Configuration --- GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY") if not GOOGLE_API_KEY: raise ValueError("GOOGLE_API_KEY environment variable not set.") genai.configure(api_key=GOOGLE_API_KEY) # --- Free-tier Gemini Models --- AVAILABLE_MODELS = ["gemini-1.5-flash"] DEFAULT_MODEL = "gemini-1.5-flash" # --- Optimization Parameters --- DEFAULT_MAX_HEIGHT = 1000 DEFAULT_IMAGE_FORMAT = "PNG" DEFAULT_TIMEOUT = 10000 # --- Ensure Playwright uses the same cache path at runtime --- os.environ["PLAYWRIGHT_BROWSERS_PATH"] = "/app/.cache/playwright" # --- Utility Functions --- def screenshot_from_url(url: str, max_height: int = DEFAULT_MAX_HEIGHT, image_format: str = DEFAULT_IMAGE_FORMAT, timeout: int = DEFAULT_TIMEOUT) -> Image.Image: app.logger.info(f"Taking screenshot of {url} with timeout {timeout}ms") try: with sync_playwright() as p: browser = p.chromium.launch(headless=True) context = browser.new_context() page = context.new_page() try: page.goto(url, timeout=timeout) page.wait_for_load_state("networkidle", timeout=timeout) except PlaywrightTimeoutError: app.logger.warning(f"Timeout waiting for networkidle on {url}. Capturing partial screenshot.") screenshot_bytes = page.screenshot(full_page=True, type=image_format.lower(), timeout=timeout) browser.close() socketio.emit('progress', {'percent': 25, 'message': 'Screenshot taken'}) image = Image.open(io.BytesIO(screenshot_bytes)) if image.height > max_height: ratio = max_height / image.height new_width = int(image.width * ratio) image = image.resize((new_width, max_height), Image.LANCZOS) app.logger.info("Screenshot captured successfully") return image except Exception as e: app.logger.error(f"Error taking screenshot: {e}") socketio.emit('progress', {'percent': 100, 'message': f'Error: {str(e)}'}) raise Exception(f"Failed to capture screenshot: {str(e)}") def parse_model_response(response_text: str) -> dict: app.logger.info("Parsing model response") try: # Use regex to find content within START and STOP tokens pattern = r"==START_JSON==(.*?)==STOP_JSON==" match = re.search(pattern, response_text, re.DOTALL) if match: json_content = match.group(1).strip() files = json.loads(json_content) if not isinstance(files, dict) or "files" not in files or not isinstance(files["files"], dict): raise ValueError("Invalid JSON structure") socketio.emit('progress', {'percent': 90, 'message': 'JSON parsed'}) else: app.logger.warning("No JSON found within START/STOP tokens, attempting custom parsing") pattern = r"### (.+?)\n```(?:\w+)?\n(.*?)\n```" matches = re.findall(pattern, response_text, re.DOTALL) if not matches: raise ValueError(f"Could not parse response into files. Response start: {response_text[:200]}") files = { "files": { filename.strip(): {"content": content.strip()} for filename, content in matches } } socketio.emit('progress', {'percent': 90, 'message': 'Parsed with fallback method'}) # Keep the original index.html content intact (full HTML with and \n" combined_html += "\n" # For preview and standalone file, use the original full index.html content html_content = index_html_content if index_html_content else "

No HTML file generated

" if not index_html_content: html_content = next( (f["content"] for fname, f in files["files"].items() if fname.endswith(".html")), "

No HTML file generated

" ) app.logger.info("HTML and files extracted successfully") socketio.emit('progress', {'percent': 100, 'message': 'Processing complete'}) return files, html_content, combined_html except Exception as e: app.logger.error(f"Error extracting files: {e}") socketio.emit('progress', {'percent': 100, 'message': f'Error: {str(e)}'}) return {"files": {"error.txt": {"content": f"Error: {str(e)}"}}}, f"Error: {str(e)}", f"Error: {str(e)}" # --- Flask Routes --- @app.route('/') def index(): return render_template('index.html', models=AVAILABLE_MODELS, default_model=DEFAULT_MODEL) @app.route('/process_url', methods=['POST']) def process_url(): data = request.form url = data.get('url') max_height = int(data.get('max_height', DEFAULT_MAX_HEIGHT)) image_format = data.get('image_format', DEFAULT_IMAGE_FORMAT) timeout = int(data.get('timeout', DEFAULT_TIMEOUT)) model_name = data.get('model_name', DEFAULT_MODEL) try: files, html_content, combined_html = image_to_html(screenshot_from_url(url, max_height, image_format, timeout), model_name) return jsonify({"files": files["files"], "preview": html_content, "combined_html": combined_html}) except Exception as e: return jsonify({"error": str(e)}), 500 @app.route('/process_image', methods=['POST']) def process_image(): if 'image' not in request.files: return jsonify({"error": "No image uploaded"}), 400 image_file = request.files['image'] model_name = request.form.get('model_name', DEFAULT_MODEL) try: files, html_content, combined_html = image_to_html(Image.open(image_file), model_name) return jsonify({"files": files["files"], "preview": html_content, "combined_html": combined_html}) except Exception as e: return jsonify({"error": str(e)}), 500 @socketio.on('connect') def test_connect(): app.logger.info("Client connected") if __name__ == '__main__': socketio.run(app, host='0.0.0.0', port=7860, debug=False, allow_unsafe_werkzeug=True)