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>
52 lines
2.2 KiB
Python
52 lines
2.2 KiB
Python
import os
|
|
import json
|
|
from core import utils
|
|
from ai import models as ai_models
|
|
|
|
|
|
def generate_blurb(bp, folder):
|
|
utils.log("MARKETING", "Generating blurb...")
|
|
meta = bp.get('book_metadata', {})
|
|
|
|
beats = bp.get('plot_beats', [])
|
|
beats_text = "\n".join(f" - {b}" for b in beats[:6]) if beats else " - (no beats provided)"
|
|
|
|
chars = bp.get('characters', [])
|
|
protagonist = next((c for c in chars if 'protagonist' in c.get('role', '').lower()), None)
|
|
protagonist_desc = f"{protagonist['name']} — {protagonist.get('description', '')}" if protagonist else "the protagonist"
|
|
|
|
prompt = f"""
|
|
ROLE: Marketing Copywriter
|
|
TASK: Write a compelling back-cover blurb for a {meta.get('genre', 'fiction')} novel.
|
|
|
|
BOOK DETAILS:
|
|
- TITLE: {meta.get('title')}
|
|
- GENRE: {meta.get('genre')}
|
|
- AUDIENCE: {meta.get('target_audience', 'General')}
|
|
- PROTAGONIST: {protagonist_desc}
|
|
- LOGLINE: {bp.get('manual_instruction', '(none)')}
|
|
- KEY PLOT BEATS:
|
|
{beats_text}
|
|
|
|
BLURB STRUCTURE:
|
|
1. HOOK (1-2 sentences): Open with the protagonist's world and the inciting disruption. Make it urgent.
|
|
2. STAKES (2-3 sentences): Raise the central conflict. What does the protagonist stand to lose?
|
|
3. TENSION (1-2 sentences): Hint at the impossible choice or escalating danger without revealing the resolution.
|
|
4. HOOK CLOSE (1 sentence): End with a tantalising question or statement that demands the reader open the book.
|
|
|
|
RULES:
|
|
- 150-200 words total.
|
|
- DO NOT reveal the ending or resolution.
|
|
- Match the genre's marketing tone ({meta.get('genre', 'fiction')}: e.g. thriller = urgent/terse, romance = emotionally charged, fantasy = epic/wondrous, horror = dread-laden).
|
|
- Use present tense for the blurb voice.
|
|
- No "Blurb:", no title prefix, no labels — marketing copy only.
|
|
"""
|
|
try:
|
|
response = ai_models.model_writer.generate_content(prompt)
|
|
utils.log_usage(folder, ai_models.model_writer.name, response.usage_metadata)
|
|
blurb = response.text
|
|
with open(os.path.join(folder, "blurb.txt"), "w") as f: f.write(blurb)
|
|
with open(os.path.join(folder, "back_cover.txt"), "w") as f: f.write(blurb)
|
|
except:
|
|
utils.log("MARKETING", "Failed to generate blurb.")
|