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>
This commit is contained in:
68
core/config.py
Normal file
68
core/config.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import os
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# __file__ is core/config.py; app root is one level up
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
BASE_DIR = os.path.dirname(_HERE)
|
||||
|
||||
# Ensure .env is loaded from the app root
|
||||
load_dotenv(os.path.join(BASE_DIR, ".env"))
|
||||
|
||||
def get_clean_env(key, default=None):
|
||||
val = os.getenv(key, default)
|
||||
return val.strip() if val else None
|
||||
|
||||
API_KEY = get_clean_env("GEMINI_API_KEY")
|
||||
GCP_PROJECT = get_clean_env("GCP_PROJECT")
|
||||
GCP_LOCATION = get_clean_env("GCP_LOCATION", "us-central1")
|
||||
MODEL_LOGIC_HINT = get_clean_env("MODEL_LOGIC", "AUTO")
|
||||
MODEL_WRITER_HINT = get_clean_env("MODEL_WRITER", "AUTO")
|
||||
MODEL_ARTIST_HINT = get_clean_env("MODEL_ARTIST", "AUTO")
|
||||
MODEL_IMAGE_HINT = get_clean_env("MODEL_IMAGE", "AUTO")
|
||||
DEFAULT_BLUEPRINT = "book_def.json"
|
||||
|
||||
# --- SECURITY & ADMIN ---
|
||||
FLASK_SECRET = get_clean_env("FLASK_SECRET_KEY", "dev-secret-key-change-this")
|
||||
ADMIN_USER = get_clean_env("ADMIN_USERNAME")
|
||||
ADMIN_PASSWORD = get_clean_env("ADMIN_PASSWORD")
|
||||
|
||||
if FLASK_SECRET == "dev-secret-key-change-this":
|
||||
print("⚠️ WARNING: Using default FLASK_SECRET_KEY. This is insecure for production.")
|
||||
|
||||
if not API_KEY: raise ValueError("❌ CRITICAL ERROR: GEMINI_API_KEY not found.")
|
||||
|
||||
# --- DATA DIRECTORIES ---
|
||||
DATA_DIR = os.path.join(BASE_DIR, "data")
|
||||
PROJECTS_DIR = os.path.join(DATA_DIR, "projects")
|
||||
PERSONAS_DIR = os.path.join(DATA_DIR, "personas")
|
||||
PERSONAS_FILE = os.path.join(PERSONAS_DIR, "personas.json")
|
||||
FONTS_DIR = os.path.join(DATA_DIR, "fonts")
|
||||
|
||||
# --- ENSURE DIRECTORIES EXIST ---
|
||||
for d in [DATA_DIR, PROJECTS_DIR, PERSONAS_DIR, FONTS_DIR]:
|
||||
if not os.path.exists(d): os.makedirs(d, exist_ok=True)
|
||||
|
||||
# --- AUTHENTICATION ---
|
||||
GOOGLE_CREDS = os.getenv("GOOGLE_APPLICATION_CREDENTIALS")
|
||||
if GOOGLE_CREDS:
|
||||
if not os.path.isabs(GOOGLE_CREDS):
|
||||
GOOGLE_CREDS = os.path.join(BASE_DIR, GOOGLE_CREDS)
|
||||
|
||||
if os.path.exists(GOOGLE_CREDS):
|
||||
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = GOOGLE_CREDS
|
||||
else:
|
||||
print(f"⚠️ Warning: GOOGLE_APPLICATION_CREDENTIALS file not found at: {GOOGLE_CREDS}")
|
||||
|
||||
# --- DEFINITIONS ---
|
||||
LENGTH_DEFINITIONS = {
|
||||
"01": {"label": "Chapter Book", "words": "5,000 - 10,000", "chapters": 10, "depth": 1},
|
||||
"1": {"label": "Flash Fiction", "words": "500 - 1,500", "chapters": 1, "depth": 1},
|
||||
"2": {"label": "Short Story", "words": "5,000 - 10,000", "chapters": 5, "depth": 1},
|
||||
"2b": {"label": "Young Adult", "words": "50,000 - 70,000", "chapters": 25, "depth": 3},
|
||||
"3": {"label": "Novella", "words": "20,000 - 40,000", "chapters": 15, "depth": 2},
|
||||
"4": {"label": "Novel", "words": "60,000 - 80,000", "chapters": 30, "depth": 3},
|
||||
"5": {"label": "Epic", "words": "100,000+", "chapters": 50, "depth": 4}
|
||||
}
|
||||
|
||||
# --- SYSTEM ---
|
||||
VERSION = "1.4.0"
|
||||
Reference in New Issue
Block a user