Commit Graph

21 Commits

Author SHA1 Message Date
bcba67a35f Add orphaned job prevention in generate_book_task
- Guard checks at task start verify: run exists in DB, project folder exists,
  bible.json exists and is parseable, and bible has at least one book
- Any failed check marks the run as 'failed' and returns early, preventing
  jobs from writing to the wrong book or orphaned project directories

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 10:05:59 -05:00
98a330c416 Add run tagging (DB column + migration + route + UI)
- Added tags VARCHAR(300) column to Run model
- Added startup ALTER TABLE migration in app.py
- New POST /run/<id>/set_tags route saves comma-separated tags
- Tag badges + collapsible edit form in run_details.html header area

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 10:05:30 -05:00
af2050160e Add run deletion with filesystem cleanup
- New POST /run/<id>/delete route removes run from DB and deletes run directory
- Only allows deletion of non-active runs (blocks running/queued)
- Delete Run button shown in run_details.html header for non-active runs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 10:04:44 -05:00
203d74f61d Add bible download route and UI button for run details
- New GET /run/<id>/download_bible route serves project bible.json as attachment
- Download Bible button added to run_details.html header toolbar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-22 10:04:11 -05:00
ba56bc1ec1 Auto-commit: v2.15 — Startup state cleanup + concurrent jobs UI
- Remove ai_blueprint.md from git tracking (already gitignored)
- web/app.py: Unify startup reset — all non-terminal states (running,
  queued, interrupted) are reset to 'failed' with per-job logging
- web/routes/project.py: Add active_runs list to view_project() context
- templates/project.html: Add Active Jobs card showing all running/queued
  jobs with status badge, start time, progress bar, and View Details link;
  Generate button and Stop buttons now driven by active_runs list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 19:12:33 -05:00
81340a18ea Auto-commit: v2.14 — Stuck job robustness (heartbeat, retry, stale watcher, granular logging)
- web/db.py: Add last_heartbeat column to Run model
- core/utils.py: Add set_heartbeat_callback() and send_heartbeat()
- web/tasks.py: Add _robust_update_run_status() with 5-retry exponential backoff;
  add db_heartbeat_callback(); remove all bare except:pass on DB status updates;
  set start_time + last_heartbeat when marking run as 'running'
- web/app.py: Add last_heartbeat column migration; add _stale_job_watcher()
  background thread (checks every 5 min, 15-min heartbeat threshold, 2-hr start_time threshold)
- cli/engine.py: Add phase-level logging banners and try/except wrappers in
  process_book(); add utils.send_heartbeat() after each chapter save;
  add start/finish logging in run_generation()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 19:00:29 -05:00
97efd51fd5 Auto-commit: v2.13 — Add Live Status diagnostic panel to run_details UI
- Backend (web/routes/run.py): Extended /run/<id>/status JSON response with
  server_timestamp, db_log_count, and latest_log_timestamp so clients can
  detect whether the DB is being written to independently of the log text.

- Frontend (templates/run_details.html):
  • Added Live Status Panel above the System Log card, showing:
    - Polling state badge (Initializing / Requesting / Waiting Ns / Error / Idle)
    - Last Successful Update timestamp (HH:MM:SS, updated every successful poll)
    - DB diagnostics (log count + latest log timestamp from server response)
    - Last Error message displayed inline when a poll fails
    - Force Refresh button to immediately trigger a new poll
  • Refactored JS polling loop: countdown timer with clearCountdown/
    startWaitCountdown helpers, forceRefresh() clears pending timers before
    re-polling, explicit pollTimer/countdownInterval tracking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 18:48:06 -05:00
4e39e18dfe Auto-commit: v2.12 — Fix frontend stuck on Initializing/Waiting for logs
- web/tasks.py: db_log_callback now writes non-OperationalError exceptions to data/app.log for visibility
- web/tasks.py: generate_book_task restructured with try...finally to guarantee final status update — run can never be left in 'running' state if worker crashes
- templates/project.html: added .catch() to fetchLog() with console.error + polling resume on failure; added manual Refresh button to status bar
- templates/run_details.html: improved .catch() in updateLog() with descriptive message + 5s retry; added manual Refresh button to status bar

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 18:40:28 -05:00
87f24d2bd8 Auto-commit: v2.11 — Fix live UI log feed (db_log_callback + run_status)
- web/tasks.py: db_log_callback bare `except: break` replaced with
  explicit `except Exception as _e: print(...)` so insertion failures
  are visible in Docker logs. Also fixed datetime.utcnow() → .isoformat()
  for clean string storage in SQLite.
  Same fix applied to db_progress_callback.

- web/routes/run.py (run_status): added db.session.expire_all() to
  force fresh reads; raw sqlite3 bypass query when ORM returns no rows;
  file fallback wrapped in try/except with stdout error reporting;
  secondary check for web_console.log inside the run directory;
  utf-8 encoding on all file opens.

- ai_blueprint.md: bumped to v2.11, documented root causes and fixes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 15:28:27 -05:00
493435e43c Auto-commit: v2.10 — Docker/compose hardening for Portainer on Pi
docker-compose.yml:
- Add PYTHONIOENCODING=utf-8 env var (guarantees UTF-8 stdout in all
  Python environments, including Docker slim images on ARM).
- Add logging driver section: json-file, max-size 10m, max-file 5.
  Without this the json-file log on a Raspberry Pi SD card grows
  unbounded and eventually kills the container or fills the disk.

web/requirements_web.txt:
- Pin huey==2.6.0 so a future pip upgrade cannot silently change the
  Consumer() API and re-introduce the loglevel= TypeError that caused
  all tasks to stay queued forever.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 12:07:27 -05:00
0d4b9b761b Auto-commit: v2.10 — Docker diagnostic logging for consumer & task execution
- web/app.py: Startup banner to docker logs (Python version, platform,
  Huey version, DB paths). All print() calls now flush=True so Docker
  captures them immediately. Emoji-free for robust stdout encoding.
  Startup now detects orphaned queued runs (queue empty but DB queued)
  and resets them to 'failed' so the UI does not stay stuck on reload.
  Huey logging configured at INFO level so task pick-up/completion
  appears in `docker logs`. Consumer skip reason logged explicitly.
- web/tasks.py: generate_book_task now emits [TASK run=N] lines to
  stdout (docker logs) at pick-up, log-file creation, DB status update,
  and on error (with full traceback) so failures are always visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 12:05:07 -05:00
a324355cdf Auto-commit: v2.10 — Fix Huey consumer never starting (loglevel= TypeError)
Root cause: Consumer(huey, workers=1, worker_type='thread', loglevel=20)
raised TypeError on every app start because Huey 2.6.0 does not accept
a `loglevel` keyword argument. The exception was silently caught and only
printed to stdout, so the consumer never ran and all tasks stayed 'queued'
forever — causing the 'Preparing environment / Waiting for logs' hang.

Fixes:
- web/app.py: Remove invalid `loglevel=20` from Consumer(); configure
  Huey logging via logging.basicConfig(WARNING) instead. Add persistent
  error logging to data/consumer_error.log for future diagnosis.
- core/config.py: Replace emoji print() calls with ASCII-safe equivalents
  to prevent UnicodeEncodeError on Windows cp1252 terminals at import time.
- core/config.py: Update VERSION to 2.9 (was stale at 1.5.0).
- ai_blueprint.md: Bump to v2.10, document root cause and fixes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 12:02:18 -05:00
1f01fedf00 Auto-commit: v2.9 — Fix background task hangs (OAuth headless guard, SQLite timeouts, log touch)
- ai/setup.py: Added threading import; OAuth block now detects background/headless
  threads and skips run_local_server to prevent indefinite blocking. Logs a clear
  warning and falls back to ADC for Vertex AI. Token file only written when creds
  are not None.
- web/tasks.py: All sqlite3.connect() calls now use timeout=30, check_same_thread=False.
  OperationalError on the initial status update is caught and logged via utils.log.
  generate_book_task now touches initial_log immediately so the UI polling endpoint
  always finds an existing file even if the worker crashes on the next line.
- ai_blueprint.md: Bumped to v2.9; Section 12.D sub-items 1-3 marked ; item 13
  added to summary.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 10:50:00 -05:00
a24d2809f3 Auto-commit: Fix 'create new book not showing anything' — 3 root causes
1. templates/project_setup.html: s.tropes|join and s.formatting_rules|join
   raised Jinja2 UndefinedError when AI failed and fallback dict lacked those
   keys → 500 blank page. Fixed with (s.tropes or [])|join(', ').

2. web/routes/project.py (project_setup_wizard): Removed silent redirect-to-
   dashboard when model_logic is None. Now renders the setup form with a
   complete default suggestions dict (all fields present, lists as []) plus a
   clear warning flash so the user can fill it in manually.

3. web/routes/project.py (create_project_final): planner.enrich() was called
   with the full bible dict — enrich() reads manual_instruction from the top
   level (got 'A generic story' fallback) and wrote results into book_metadata
   instead of the bible's books[0]. Fixed to build a proper per-book blueprint,
   call enrich, and merge characters/plot_beats back into the correct locations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 10:25:34 -05:00
1f799227d9 Auto-commit: Fix spinning logs — API timeouts + reliable Huey consumer start
Root causes of indefinite spinning during book create/generate:

1. ai/models.py — ResilientModel.generate_content() had no timeout: a
   stalled Gemini API call would block the thread forever. Now injects
   request_options={"timeout": 180} into every call. Also removed the
   dangerous init_models(force=True) call inside the retry handler, which
   was making a second network call during an existing API failure.

2. ai/setup.py — genai.list_models() calls in get_optimal_model(),
   select_best_models(), and init_models() had no timeout. Added
   request_options={"timeout": 30} to all three calls so model init
   fails fast rather than hanging indefinitely.

3. web/app.py — Huey task consumer only started inside
   `if __name__ == "__main__":`, meaning tasks queued via flask run,
   gunicorn, or other WSGI runners were never executed (status stuck at
   "queued" forever). Moved consumer start to module level with a
   WERKZEUG_RUN_MAIN guard to prevent double-start under the reloader.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 02:16:39 -05:00
83a6a4315b Blueprint v2.4-2.6: Style Rules UI, Lore RAG, Thread Tracking, Redo Book
v2.4 — Item 7: Refresh Style Guidelines
- web/routes/admin.py: Added /admin/refresh-style-guidelines route (AJAX-aware)
- templates/system_status.html: Added 'Refresh Style Rules' button with spinner

v2.5 — Item 8: Lore & Location RAG-Lite
- story/bible_tracker.py: Added update_lore_index() — extracts location/item
  descriptions from chapters into tracking_lore.json
- story/writer.py: Reads chapter locations/key_items, builds LORE_CONTEXT block
  injected into the prompt (graceful degradation if no tags)
- cli/engine.py: Loads tracking_lore.json on resume, calls update_lore_index
  after each chapter, saves tracking_lore.json

v2.5 — Item 9: Structured Story State (Thread Tracking)
- story/state.py (new): load_story_state, update_story_state (extracts
  active_threads, immediate_handoff, resolved_threads via model_logic),
  format_for_prompt (structured context replacing the prev_sum blob)
- cli/engine.py: Loads story_state.json on resume, uses format_for_prompt as
  summary_ctx for write_chapter, updates state after each chapter accepted

v2.6 — Item 10: Redo Book
- templates/consistency_report.html: Added 'Redo Book' form with instruction
  input and confirmation dialog
- web/routes/run.py: Added revise_book route — creates new Run, queues
  generate_book_task with user instruction as feedback

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 01:35:43 -05:00
41f5719974 Add AJAX support to optimize_models endpoint and add CLAUDE.md
- Added jsonify import to admin.py
- optimize_models now returns JSON for AJAX requests (X-Requested-With header)
- Returns structured {status, message} response for success and error cases
- Added CLAUDE.md project configuration file

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-21 01:00:32 -05:00
fd4ce634d4 Fix startup crash by removing unused MiniHuey import
Removed `from huey.contrib.mini import MiniHuey` which caused
`ModuleNotFoundError: No module named 'gevent'` on startup. MiniHuey
was never used; the app correctly uses SqliteHuey via `web.tasks`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 23:51:38 -05:00
28a1308fbc Fix port mismatch: align Flask server to port 5000
web/app.py was hardcoded to port 7070, causing Docker port forwarding
(5000:5000) and the Dockerfile HEALTHCHECK to fail. Changed to port 5000
to match docker-compose.yml and Dockerfile configuration.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 23:40:24 -05:00
db70ad81f7 Blueprint v1.0.4: Implemented AI Context Optimization & Token Management
- core/utils.py: Added estimate_tokens(), truncate_to_tokens(), get_ai_cache(), set_ai_cache(), make_cache_key() utilities
- story/writer.py: Applied truncate_to_tokens() to prev_content (2000 tokens) and prev_sum (600 tokens) context injections
- story/editor.py: Applied truncate_to_tokens() to summary (1000t), last_chapter_text (800t), eval text (7500t), propagation contexts (2500t/3000t)
- web/routes/persona.py: Added MD5-keyed in-memory cache for persona analyze endpoint; truncated sample_text to 750 tokens
- ai/models.py: Added pre-dispatch payload size estimation with 30k-token warning threshold

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 23:30:39 -05:00
f7099cc3e4 v2.0.0: Modularize project into single-responsibility packages
Replaced monolithic modules/ package with a clean architecture:

- core/       config.py, utils.py
- ai/         models.py (ResilientModel), setup.py (init_models)
- story/      planner.py, writer.py, editor.py, style_persona.py, bible_tracker.py
- marketing/  cover.py, blurb.py, fonts.py, assets.py
- export/     exporter.py
- web/        app.py (Flask factory), db.py, helpers.py, tasks.py, routes/{auth,project,run,persona,admin}.py
- cli/        engine.py (run_generation), wizard.py (BookWizard)

Flask routes split into 5 Blueprints; all templates updated with blueprint-
prefixed url_for() calls. Dockerfile and docker-compose updated to use
web.app entry point and new package paths.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 22:20:53 -05:00