Files
bookapp/web/routes/persona.py

156 lines
4.9 KiB
Python

import json
from flask import Blueprint, render_template, request, redirect, url_for, flash
from flask_login import login_required
from core import utils
from ai import models as ai_models
from ai import setup as ai_setup
from web.db import db, Persona
persona_bp = Blueprint('persona', __name__)
def _all_personas_dict():
"""Return all personas as a dict keyed by name, matching the old personas.json structure."""
records = Persona.query.all()
result = {}
for rec in records:
try:
details = json.loads(rec.details_json) if rec.details_json else {}
except Exception:
details = {}
result[rec.name] = details
return result
@persona_bp.route('/personas')
@login_required
def list_personas():
personas = _all_personas_dict()
return render_template('personas.html', personas=personas)
@persona_bp.route('/persona/new')
@login_required
def new_persona():
return render_template('persona_edit.html', persona={}, name="")
@persona_bp.route('/persona/<string:name>')
@login_required
def edit_persona(name):
record = Persona.query.filter_by(name=name).first()
if not record:
flash(f"Persona '{name}' not found.")
return redirect(url_for('persona.list_personas'))
try:
persona = json.loads(record.details_json) if record.details_json else {}
except Exception:
persona = {}
return render_template('persona_edit.html', persona=persona, name=name)
@persona_bp.route('/persona/save', methods=['POST'])
@login_required
def save_persona():
old_name = request.form.get('old_name')
name = request.form.get('name')
if not name:
flash("Persona name is required.")
return redirect(url_for('persona.list_personas'))
persona_data = {
"name": name,
"bio": request.form.get('bio'),
"age": request.form.get('age'),
"gender": request.form.get('gender'),
"race": request.form.get('race'),
"nationality": request.form.get('nationality'),
"language": request.form.get('language'),
"sample_text": request.form.get('sample_text'),
"voice_keywords": request.form.get('voice_keywords'),
"style_inspirations": request.form.get('style_inspirations')
}
# If name changed, remove old record
if old_name and old_name != name:
old_record = Persona.query.filter_by(name=old_name).first()
if old_record:
db.session.delete(old_record)
db.session.flush()
record = Persona.query.filter_by(name=name).first()
if record:
record.details_json = json.dumps(persona_data)
else:
record = Persona(name=name, details_json=json.dumps(persona_data))
db.session.add(record)
db.session.commit()
flash(f"Persona '{name}' saved.")
return redirect(url_for('persona.list_personas'))
@persona_bp.route('/persona/delete/<string:name>', methods=['POST'])
@login_required
def delete_persona(name):
record = Persona.query.filter_by(name=name).first()
if record:
db.session.delete(record)
db.session.commit()
flash(f"Persona '{name}' deleted.")
return redirect(url_for('persona.list_personas'))
@persona_bp.route('/persona/analyze', methods=['POST'])
@login_required
def analyze_persona():
try: ai_setup.init_models()
except: pass
if not ai_models.model_logic:
return {"error": "AI models not initialized."}, 500
data = request.json
sample = data.get('sample_text', '')
# Cache by a hash of the inputs to avoid redundant API calls for unchanged data
cache_key = utils.make_cache_key(
"persona_analyze",
data.get('name', ''),
data.get('age', ''),
data.get('gender', ''),
data.get('nationality', ''),
sample[:500]
)
cached = utils.get_ai_cache(cache_key)
if cached:
return cached
prompt = f"""
ROLE: Literary Analyst
TASK: Create or analyze an Author Persona profile.
INPUT_DATA:
- NAME: {data.get('name')}
- DEMOGRAPHICS: Age: {data.get('age')} | Gender: {data.get('gender')} | Nationality: {data.get('nationality')}
- SAMPLE_TEXT: {utils.truncate_to_tokens(sample, 750)}
INSTRUCTIONS:
1. BIO: Write a 2-3 sentence description of the writing style. If sample is provided, analyze it. If not, invent a style that fits the demographics/name.
2. KEYWORDS: Comma-separated list of 3-5 adjectives describing the voice (e.g. Gritty, Whimsical, Sarcastic).
3. INSPIRATIONS: Comma-separated list of 1-3 famous authors or genres that this style resembles.
OUTPUT_FORMAT (JSON): {{ "bio": "String", "voice_keywords": "String", "style_inspirations": "String" }}
"""
try:
response = ai_models.model_logic.generate_content(prompt)
result = json.loads(utils.clean_json(response.text))
utils.set_ai_cache(cache_key, result)
return result
except Exception as e:
return {"error": str(e)}, 500