From d487fad8faadaca1060144b4860d4ebc3d461c19 Mon Sep 17 00:00:00 2001 From: ducoterra Date: Sat, 1 Aug 2026 22:24:29 -0400 Subject: [PATCH] increase workers and fix async issues --- AGENTS.md | 33 ++++++--- app.py | 151 ++++++++++++++++++++++++++++++++++++---- gunicorn.conf.py | 2 + pyproject.toml | 1 + uv.lock | 176 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 340 insertions(+), 23 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a1673e3..a450725 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,34 +1,47 @@ # Web On The Fly -Flask app that streams AI-generated fake websites via SSE. +Flask app that queues requests, then streams AI-generated fake websites via SSE through a Valkey-backed worker. This is a Podman project. Use `podman` instead of `docker` for all container commands. ## Commands -- `uv run app.py` — start dev server on port 5000 -- `uv run pyright app.py` — typecheck (strict mode in pyproject.toml) +- `uv run app.py` — start dev server (includes worker thread) on port 5000 +- `uv run pyright app.py` — typecheck (strict mode) - `uv run ruff check app.py` — lint - `uv run ruff check app.py --fix` — auto-fix lint issues +- `podman compose up -d` — start Valkey (required dependency) Run lint/typecheck after any code changes. ## Architecture - Single file: `app.py` -- `/` → returns a loading page with spinner + live character counter -- `/stream` → SSE endpoint; streams LLM output base64-encoded per chunk -- Client JS buffers chunks in-memory (not localStorage), then replaces the document on `[DONE]` +- Valkey (Redis-compatible) is required for request queuing, chunk buffering, and worker coordination +- Request flow: + - `/` → loading page with SSE client + - `/stream` → enqueues request in Valkey, then polls for worker-generated chunks + - Background worker (thread in dev, gunicorn_post_fork in prod) pops from queue, calls LLM, buffers base64-encoded chunks + - Client decodes chunks in-memory, replaces document on `[RENDER]` +- After LLM generation: validates HTML with lxml + LLM JS review, auto-fixes up to 2 times +- THEMES list (50+ items): tuples of `(site_type, description)` — worker picks randomly ## Env vars (loaded from `.env` via python-dotenv) - `OPENAI_API_KEY` — required -- `OPENAI_API_URL` — OpenAI-compatible endpoint (default: aipi.reeseapps.com) -- `MODEL` — model name (default: turbo) +- `OPENAI_API_URL` — OpenAI-compatible endpoint (code default: `https://api.openai.com/v1/chat/completions`; `.env.example` uses `https://aipi.reeseapps.com/v1/chat/completions`) +- `MODEL` — model name (code default: `gpt-4o-mini`; `.env.example` uses `turbo`) +- `VALKEY_URL` — Valkey/Redis URL (default: `redis://localhost:6379/0`) + +## Production + +- Runs via gunicorn: 16 workers, 300s timeout +- `gunicorn_post_fork` hook ensures exactly one worker thread across processes (Valkey-based lock) +- Containerfile uses `uv sync --frozen --no-dev`, runs with `uv run --no-sync` ## Key details -- LLM is called with `stream=True`; chunks are base64-encoded to avoid SSE protocol breaks +- LLM streaming uses `stream=True`; chunks are base64-encoded to avoid SSE protocol breaks - HTTP timeout is 300s (long generations) -- Themes defined in `THEMES` list: tuples of `(site_type, description)` (fake_store, fake_wiki, fake_social, etc.) - `pyright` is configured in strict mode; use `# noqa: BLE001` for intentional broad exception catches +- `lxml.html` is imported dynamically via `importlib.import_module` diff --git a/app.py b/app.py index 2026da4..834896b 100644 --- a/app.py +++ b/app.py @@ -16,6 +16,11 @@ import valkey from dotenv import load_dotenv from flask import Flask, Request, Response, request, stream_with_context +if os.getenv("GUNICORN_WORKER_ID"): + from gevent import monkey + + monkey.patch_all() + lxml_html: Any = importlib.import_module("lxml.html") logging.basicConfig( @@ -88,6 +93,24 @@ body { color: #6366f1; font-variant-numeric: tabular-nums; } +.retry-btn { + margin-top: 20px; + padding: 10px 24px; + background: rgba(99, 102, 241, 0.2); + color: #e0e0e0; + border: 1px solid rgba(99, 102, 241, 0.5); + border-radius: 8px; + font-size: 0.95rem; + cursor: pointer; + font-family: system-ui, -apple-system, sans-serif; + display: none; + transition: all 0.15s ease; +} +.retry-btn:hover { + background: rgba(99, 102, 241, 0.4); + border-color: #6366f1; + color: #fff; +} .particles { position: fixed; inset: 0; @@ -116,9 +139,11 @@ body {
Crafting your page...
The AI is designing something unique for you
Characters: 0
+