Files
bookapp/story/style_persona.py

195 lines
7.5 KiB
Python

import json
import os
import time
from core import config, utils
from ai import models as ai_models
def get_style_guidelines():
defaults = {
"ai_isms": [
'testament to', 'tapestry', 'shiver down spine', 'unspoken agreement',
'palpable tension', 'a sense of', 'suddenly', 'in that moment',
'symphony of', 'dance of', 'azure', 'cerulean',
'delved', 'mined', 'neon-lit', 'bustling', 'weaved', 'intricately',
'a reminder that', 'couldn\'t help but', 'it occurred to',
'the air was thick with', 'etched in', 'a wave of', 'wash of emotion',
'intertwined', 'navigate', 'realm', 'in the grand scheme',
'at the end of the day', 'painting a picture', 'a dance between',
'the weight of', 'visceral reminder', 'stark reminder',
'a symphony', 'a mosaic', 'rich tapestry', 'whirlwind of',
'his/her heart raced', 'time seemed to slow', 'the world fell away',
'needless to say', 'it goes without saying', 'importantly',
'it is worth noting', 'commendable', 'meticulous', 'pivotal',
'in conclusion', 'overall', 'in summary', 'to summarize'
],
"filter_words": [
'felt', 'saw', 'heard', 'realized', 'decided', 'noticed', 'knew', 'thought',
'wondered', 'seemed', 'appeared', 'looked like', 'watched', 'observed', 'sensed'
]
}
path = os.path.join(config.DATA_DIR, "style_guidelines.json")
if os.path.exists(path):
try:
user_data = utils.load_json(path)
if user_data:
if 'ai_isms' in user_data: defaults['ai_isms'] = user_data['ai_isms']
if 'filter_words' in user_data: defaults['filter_words'] = user_data['filter_words']
except: pass
else:
try:
with open(path, 'w') as f: json.dump(defaults, f, indent=2)
except: pass
return defaults
def refresh_style_guidelines(model, folder=None):
utils.log("SYSTEM", "Refreshing Style Guidelines via AI...")
current = get_style_guidelines()
prompt = f"""
ROLE: Literary Editor
TASK: Update 'Banned Words' lists for AI writing.
INPUT_DATA:
- CURRENT_AI_ISMS: {json.dumps(current.get('ai_isms', []))}
- CURRENT_FILTER_WORDS: {json.dumps(current.get('filter_words', []))}
INSTRUCTIONS:
1. Review lists. Remove false positives.
2. Add new common AI tropes (e.g. 'neon-lit', 'bustling', 'a sense of', 'mined', 'delved').
3. Ensure robustness.
OUTPUT_FORMAT (JSON): {{ "ai_isms": [strings], "filter_words": [strings] }}
"""
try:
response = model.generate_content(prompt)
model_name = getattr(model, 'name', ai_models.logic_model_name)
if folder: utils.log_usage(folder, model_name, response.usage_metadata)
new_data = json.loads(utils.clean_json(response.text))
if 'ai_isms' in new_data and 'filter_words' in new_data:
path = os.path.join(config.DATA_DIR, "style_guidelines.json")
with open(path, 'w') as f: json.dump(new_data, f, indent=2)
utils.log("SYSTEM", "Style Guidelines updated.")
return new_data
except Exception as e:
utils.log("SYSTEM", f"Failed to refresh guidelines: {e}")
return current
def create_initial_persona(bp, folder):
utils.log("SYSTEM", "Generating initial Author Persona based on genre/tone...")
meta = bp.get('book_metadata', {})
style = meta.get('style', {})
prompt = f"""
ROLE: Creative Director
TASK: Create a fictional 'Author Persona'.
METADATA:
- TITLE: {meta.get('title')}
- GENRE: {meta.get('genre')}
- TONE: {style.get('tone')}
- AUDIENCE: {meta.get('target_audience')}
OUTPUT_FORMAT (JSON): {{ "name": "Pen Name", "bio": "Description of writing style (voice, sentence structure, vocabulary)...", "age": "...", "gender": "..." }}
"""
try:
response = ai_models.model_logic.generate_content(prompt)
utils.log_usage(folder, ai_models.model_logic.name, response.usage_metadata)
return json.loads(utils.clean_json(response.text))
except Exception as e:
utils.log("SYSTEM", f"Persona generation failed: {e}")
return {"name": "AI Author", "bio": "Standard, balanced writing style."}
def refine_persona(bp, text, folder):
utils.log("SYSTEM", "Refining Author Persona based on recent chapters...")
ad = bp.get('book_metadata', {}).get('author_details', {})
current_bio = ad.get('bio', 'Standard style.')
prompt = f"""
ROLE: Literary Stylist
TASK: Refine Author Bio based on text sample.
INPUT_DATA:
- TEXT_SAMPLE: {text[:3000]}
- CURRENT_BIO: {current_bio}
GOAL: Ensure future chapters sound exactly like the sample. Highlight quirks, patterns, vocabulary.
OUTPUT_FORMAT (JSON): {{ "bio": "Updated bio..." }}
"""
try:
response = ai_models.model_logic.generate_content(prompt)
utils.log_usage(folder, ai_models.model_logic.name, response.usage_metadata)
new_bio = json.loads(utils.clean_json(response.text)).get('bio')
if new_bio:
ad['bio'] = new_bio
utils.log("SYSTEM", " -> Persona bio updated.")
return ad
except: pass
return ad
def update_persona_sample(bp, folder):
utils.log("SYSTEM", "Extracting author persona from manuscript...")
ms_path = os.path.join(folder, "manuscript.json")
if not os.path.exists(ms_path): return
ms = utils.load_json(ms_path)
if not ms: return
full_text = "\n".join([c.get('content', '') for c in ms])
if len(full_text) < 500: return
if not os.path.exists(config.PERSONAS_DIR): os.makedirs(config.PERSONAS_DIR)
meta = bp.get('book_metadata', {})
safe_title = utils.sanitize_filename(meta.get('title', 'book'))[:20]
timestamp = int(time.time())
filename = f"sample_{safe_title}_{timestamp}.txt"
filepath = os.path.join(config.PERSONAS_DIR, filename)
sample_text = full_text[:3000]
with open(filepath, 'w', encoding='utf-8') as f: f.write(sample_text)
author_name = meta.get('author', 'Unknown Author')
# Use a local file mirror for the engine context (runs outside Flask app context)
_personas_file = os.path.join(config.PERSONAS_DIR, "personas.json")
personas = {}
if os.path.exists(_personas_file):
try:
with open(_personas_file, 'r') as f: personas = json.load(f)
except: pass
if author_name not in personas:
utils.log("SYSTEM", f"Generating new persona profile for '{author_name}'...")
prompt = f"""
ROLE: Literary Analyst
TASK: Analyze writing style (Tone, Voice, Vocabulary).
TEXT: {sample_text[:1000]}
OUTPUT: 1-sentence author bio.
"""
try:
response = ai_models.model_logic.generate_content(prompt)
utils.log_usage(folder, ai_models.model_logic.name, response.usage_metadata)
bio = response.text.strip()
except: bio = "Style analysis unavailable."
personas[author_name] = {
"name": author_name,
"bio": bio,
"sample_files": [filename],
"sample_text": sample_text[:500]
}
else:
utils.log("SYSTEM", f"Updating persona '{author_name}' with new sample.")
if 'sample_files' not in personas[author_name]: personas[author_name]['sample_files'] = []
if filename not in personas[author_name]['sample_files']:
personas[author_name]['sample_files'].append(filename)
with open(_personas_file, 'w') as f: json.dump(personas, f, indent=2)