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 += "