Final changes and update

This commit is contained in:
2026-02-04 20:19:07 -05:00
parent 6e7ff0ae1d
commit 9f8f094564
21 changed files with 1816 additions and 645 deletions

View File

@@ -21,6 +21,14 @@ def set_log_file(filepath):
def set_log_callback(callback):
_log_context.callback = callback
def set_progress_callback(callback):
_log_context.progress_callback = callback
def update_progress(percent):
if getattr(_log_context, 'progress_callback', None):
try: _log_context.progress_callback(percent)
except: pass
def clean_json(text):
text = text.replace("```json", "").replace("```", "").strip()
# Robust extraction: find first { or [ and last } or ]
@@ -32,6 +40,32 @@ def clean_json(text):
else:
return text[start_arr:text.rfind(']')+1]
def sanitize_filename(name):
"""Sanitizes a string to be safe for filenames."""
if not name: return "Untitled"
safe = "".join([c for c in name if c.isalnum() or c=='_']).replace(" ", "_")
return safe if safe else "Untitled"
def chapter_sort_key(ch):
"""Sort key for chapters handling integers, strings, Prologue, and Epilogue."""
num = ch.get('num', 0)
if isinstance(num, int): return num
if isinstance(num, str) and num.isdigit(): return int(num)
s = str(num).lower().strip()
if 'prologue' in s: return -1
if 'epilogue' in s: return 9999
return 999
def get_sorted_book_folders(run_dir):
"""Returns a list of book folder names in a run directory, sorted numerically."""
if not os.path.exists(run_dir): return []
subdirs = [d for d in os.listdir(run_dir) if os.path.isdir(os.path.join(run_dir, d)) and d.startswith("Book_")]
def sort_key(d):
parts = d.split('_')
if len(parts) > 1 and parts[1].isdigit(): return int(parts[1])
return 0
return sorted(subdirs, key=sort_key)
# --- SHARED UTILS ---
def log(phase, msg):
timestamp = datetime.datetime.now().strftime('%H:%M:%S')
@@ -158,45 +192,4 @@ def log_usage(folder, model_label, usage_metadata=None, image_count=0):
"est_cost_usd": round(cost, 4)
}
with open(log_path, 'w') as f: json.dump(data, f, indent=2)
def normalize_settings(bp):
"""
CRITICAL: Enforces defaults.
1. If series_metadata is missing, force it to SINGLE mode.
2. If length_settings is missing, force explicit numbers.
"""
# Force Series Default (1 Book)
if 'series_metadata' not in bp:
bp['series_metadata'] = {
"is_series": False,
"mode": "single",
"series_title": "Standalone",
"total_books_to_generate": 1
}
# Check for empty series count just in case
if bp['series_metadata'].get('total_books_to_generate') is None:
bp['series_metadata']['total_books_to_generate'] = 1
# Force Length Defaults
settings = bp.get('length_settings', {})
label = settings.get('label', 'Novella') # Default to Novella if nothing provided
# Get defaults based on label (or Novella if unknown)
presets = get_length_presets()
defaults = presets.get(label, presets['Novella'])
if 'chapters' not in settings: settings['chapters'] = defaults['chapters']
if 'words' not in settings: settings['words'] = defaults['words']
# Smart Depth Calculation (if not manually set)
if 'depth' not in settings:
c = int(settings['chapters'])
if c <= 5: settings['depth'] = 1
elif c <= 20: settings['depth'] = 2
elif c <= 40: settings['depth'] = 3
else: settings['depth'] = 4
bp['length_settings'] = settings
return bp
with open(log_path, 'w') as f: json.dump(data, f, indent=2)