Chronicle / modules /core_logic.py
topguy's picture
UX Polish, Metadata Embedding & Local Name Generation
de132df
import yaml
import random
import os
import json
import tempfile
import gradio as gr
from .config import FEATURES_FILE, FEATURE_SEQUENCE, SECTIONS, EXAMPLES_DIR
def load_features():
with open(FEATURES_FILE, "r") as f:
return yaml.safe_load(f)
features_data = load_features()
def get_detail(category, subcategory, key):
"""Retrieves the detailed description for a given key in a category/subcategory."""
return features_data.get(category, {}).get(subcategory, {}).get(key, key)
def generate_prompt(*args):
"""
Assembles the prompt based on dropdown selections and extra text info.
"""
character_name = args[0]
num_features = len(FEATURE_SEQUENCE)
feature_keys = args[1:num_features+1]
# Checkboxes are at args[num_features+1 : num_features+1 + num_features]
# Extra info starts after the name, dropdowns, and checkboxes
extra_infos = args[1 + num_features*2 : 1 + num_features*2 + len(SECTIONS)]
template = features_data.get("templates", {}).get("default", "")
context = {}
for i, (cat, subcat, t_key) in enumerate(FEATURE_SEQUENCE):
key = feature_keys[i]
context[t_key] = get_detail(cat, subcat, key)
# Handle multiple accessories dynamically
acc_list = []
# Identify accessory keys in context
for i, (cat, subcat, t_key) in enumerate(FEATURE_SEQUENCE):
if subcat == 'accessory' and feature_keys[i] != "None":
acc_list.append(context[t_key])
if acc_list:
context['accessories'] = ", and has " + " as well as ".join(acc_list)
else:
context['accessories'] = ""
# Inject extra info into the context
if extra_infos[0]: # Identity
context['age'] += f", {extra_infos[0]}"
if extra_infos[1]: # Appearance
context['distinguishing_feature'] += f", also {extra_infos[1]}"
if extra_infos[2]: # Equipment
if context['accessories']:
context['accessories'] += f", further complemented by {extra_infos[2]}"
else:
context['accessories'] = f", complemented by {extra_infos[2]}"
if extra_infos[3]: # Environment
context['atmosphere'] += f", additionally {extra_infos[3]}"
if extra_infos[4]: # Style
context['camera'] += f", art style notes: {extra_infos[4]}"
try:
base_prompt = template.format(**context)
if character_name and character_name.strip() and character_name != "Unnamed Hero":
return f"A portrait of {character_name}, {base_prompt}"
return base_prompt
except Exception as e:
return f"Error building prompt: {e}"
def handle_regeneration(*args):
"""Randomizes checkboxes and returns new values for dropdowns."""
num_features = len(FEATURE_SEQUENCE)
current_values = list(args[:num_features])
checkboxes = args[num_features : num_features*2]
new_values = []
for i, (is_random, (cat, subcat, t_key)) in enumerate(zip(checkboxes, FEATURE_SEQUENCE)):
if is_random:
choices = list(features_data[cat][subcat].keys())
new_values.append(random.choice(choices))
else:
new_values.append(current_values[i])
return new_values
def save_character(*args):
"""Saves all current UI values to a JSON file."""
character_name = args[0]
num_features = len(FEATURE_SEQUENCE)
feature_keys = args[1:num_features+1]
checkboxes = args[num_features+1 : num_features+1 + num_features]
extra_infos = args[num_features+1 + num_features : num_features+1 + num_features + len(SECTIONS)]
data = {
"name": character_name,
"features": {FEATURE_SEQUENCE[i][2]: feature_keys[i] for i in range(num_features)},
"randomization": {FEATURE_SEQUENCE[i][2]: checkboxes[i] for i in range(num_features)},
"extra_info": {SECTIONS[i].lower(): extra_infos[i] for i in range(len(SECTIONS))}
}
# Save to a file with a friendly name in a temp directory
safe_name = "".join([c if c.isalnum() else "_" for c in character_name]).strip("_")
filename = f"{safe_name}_data.json" if safe_name else "rpg_character_data.json"
temp_dir = tempfile.mkdtemp()
path = os.path.join(temp_dir, filename)
with open(path, 'w') as f:
json.dump(data, f, indent=4)
return path
def load_character(file):
"""Loads UI values from a JSON file."""
if file is None:
return [gr.update()] * (1 + len(FEATURE_SEQUENCE) * 2 + len(SECTIONS))
try:
file_path = file.name if hasattr(file, 'name') else file
with open(file_path, 'r') as f:
data = json.load(f)
updates = []
# Update name
updates.append(data.get("name", "Unnamed Hero"))
# Update dropdowns
for _, _, t_key in FEATURE_SEQUENCE:
updates.append(data.get("features", {}).get(t_key, gr.update()))
# Update checkboxes (randomization flags)
for _, _, t_key in FEATURE_SEQUENCE:
updates.append(data.get("randomization", {}).get(t_key, gr.update()))
# Update extra info textboxes
for section in SECTIONS:
updates.append(data.get("extra_info", {}).get(section.lower(), ""))
return updates
except Exception as e:
print(f"Error loading character: {e}")
return [gr.update()] * (1 + len(FEATURE_SEQUENCE) * 2 + len(SECTIONS))
def get_example_list():
"""Returns a list of JSON filenames in the examples directory."""
if not os.path.exists(EXAMPLES_DIR):
return []
return [f for f in os.listdir(EXAMPLES_DIR) if f.endswith('.json')]
def load_example_character(filename):
"""Loads a character config from the examples directory."""
if not filename:
return [gr.update()] * (1 + len(FEATURE_SEQUENCE) * 2 + len(SECTIONS))
path = os.path.join(EXAMPLES_DIR, filename)
return load_character(path)