DnD_Campaign_Manager / src /ui /tabs /character_create_tab.py
official.ghost.logic
Fix GPU decorator placement - use simple dummy function
f0e06a6
"""
Character Create Tab for D'n'D Campaign Manager
"""
import gradio as gr
from typing import Tuple
import traceback
from src.agents.character_agent import CharacterAgent
from src.models.character import DnDRace, DnDClass, Alignment
from src.utils.validators import get_available_races, get_available_classes
from src.utils.image_generator import RACE_SKIN_TONES
class CharacterCreateTab:
"""Create Character tab for D&D character creation"""
def __init__(self, character_agent: CharacterAgent):
self.character_agent = character_agent
def _get_alignment_description(self, alignment: str) -> str:
"""Get personality guidance based on alignment"""
descriptions = {
"Lawful Good": "a strong sense of justice, honor, and desire to help others within the rules",
"Neutral Good": "genuine kindness and desire to help, but flexibility in how they achieve good",
"Chaotic Good": "rebellious goodness, fighting for freedom and helping others by breaking unjust rules",
"Lawful Neutral": "strict adherence to law, order, and tradition above good or evil",
"True Neutral": "balance and pragmatism, avoiding extreme positions",
"Chaotic Neutral": "unpredictability, freedom-loving nature, and self-interest",
"Lawful Evil": "tyrannical control, following their own code while causing harm",
"Neutral Evil": "pure self-interest and willingness to harm others for personal gain",
"Chaotic Evil": "destructive chaos, cruelty, and disregard for any rules or others' wellbeing"
}
return descriptions.get(alignment, "their moral compass")
def generate_name_ui(
self,
race: str,
character_class: str,
gender: str,
alignment: str,
) -> str:
"""
Generate a character name using AI
Alignment can influence name generation (e.g., darker names for evil characters)
"""
try:
race_enum = DnDRace(race)
class_enum = DnDClass(character_class)
# Add alignment hint to the generation prompt
alignment_hint = None
if "Evil" in alignment:
alignment_hint = "with a darker, more menacing tone"
elif "Good" in alignment:
alignment_hint = "with a heroic, noble tone"
elif alignment == "Chaotic Neutral":
alignment_hint = "with a wild, unpredictable feel"
# Build custom prompt if we have alignment influence
if alignment_hint:
prompt = f"""Generate a single fantasy character name for a D&D character.
Race: {race_enum.value}
Class: {class_enum.value}
Gender: {gender if gender != "Not specified" else "any"}
Alignment: {alignment} - name should reflect this {alignment_hint}
Requirements:
- Just the name, nothing else
- Make it sound appropriate for the race, gender, and alignment
- {alignment_hint}
- Make it memorable and fitting for an adventurer
- 2-3 words maximum
Examples:
- Evil: "Malakai Shadowbane", "Drusilla Nightwhisper"
- Good: "Elara Lightbringer", "Theron Brightheart"
- Chaotic: "Raven Wildfire", "Zephyr Stormblade"
Generate only the name:"""
name = self.character_agent.ai_client.generate_creative(prompt).strip()
name = name.split('\n')[0].strip('"\'')
return name
else:
# Use standard generation
name = self.character_agent.generate_name(
race=race_enum,
character_class=class_enum,
gender=gender if gender != "Not specified" else None
)
return name
except Exception as e:
return f"Error: {str(e)}"
def create_character_ui(
self,
name: str,
race: str,
character_class: str,
level: int,
gender: str,
skin_tone: str,
alignment: str,
background_dropdown: str,
custom_background: str,
personality_prompt: str,
stats_method: str,
use_ai_background: bool,
) -> Tuple[str, str]:
"""
Create character with UI inputs
Returns:
Tuple of (character_sheet_markdown, status_message)
"""
try:
# Validate inputs
if not name.strip():
return "", "❌ Error: Please provide a character name (use 'Generate Name' button or type one)"
if level < 1 or level > 20:
return "", "❌ Error: Level must be between 1 and 20"
# Convert race, class, and alignment
try:
race_enum = DnDRace(race)
class_enum = DnDClass(character_class)
alignment_enum = Alignment(alignment)
except ValueError as e:
return "", f"❌ Error: Invalid race, class, or alignment - {e}"
# Determine final background type
if background_dropdown == "Custom (enter below)":
final_background = custom_background.strip() if custom_background.strip() else "Adventurer"
else:
final_background = background_dropdown
# Create character with gender AND alignment in personality prompt
gender_hint = f"Character is {gender}. " if gender != "Not specified" else ""
alignment_hint = f"Character's alignment is {alignment}, so their personality and backstory should reflect {self._get_alignment_description(alignment)}. "
full_personality_prompt = gender_hint + alignment_hint + (personality_prompt if personality_prompt else "")
character = self.character_agent.create_character(
name=name,
race=race_enum,
character_class=class_enum,
level=level,
background_type=final_background,
personality_prompt=full_personality_prompt if use_ai_background else None,
stats_method=stats_method,
)
# Override alignment, gender, and skin tone if user specified
character.alignment = alignment_enum
character.gender = gender if gender != "Not specified" else None
character.skin_tone = skin_tone if skin_tone else None
# Generate markdown
markdown = character.to_markdown()
status = f"""✅ Character Created Successfully!
**ID:** {character.id}
**Name:** {character.name}
**Race:** {character.race.value}
**Class:** {character.character_class.value}
**Level:** {character.level}
Character has been saved to database."""
return markdown, status
except Exception as e:
error_msg = f"❌ Error creating character:\n\n{str(e)}\n\n{traceback.format_exc()}"
return "", error_msg
def create(self) -> None:
"""Create and return the Create Character tab component"""
with gr.Tab("Create Character"):
gr.Markdown("### Character Creation")
with gr.Row():
with gr.Column():
gr.Markdown("#### Basic Information")
with gr.Row():
name_input = gr.Textbox(
label="Character Name",
placeholder="Thorin Ironforge",
info="Type a name or generate one below",
scale=3
)
race_dropdown = gr.Dropdown(
choices=get_available_races(),
label="Race",
value="Human",
info="Character's race"
)
class_dropdown = gr.Dropdown(
choices=get_available_classes(),
label="Class",
value="Fighter",
info="Character's class"
)
gender_dropdown = gr.Dropdown(
choices=["Male", "Female", "Non-binary", "Not specified"],
label="Gender",
value="Not specified",
info="Character's gender"
)
skin_tone_dropdown = gr.Dropdown(
choices=RACE_SKIN_TONES[DnDRace.HUMAN], # Default to Human
label="Skin Tone / Color",
value=None,
info="Select appropriate color for the race"
)
generate_name_btn = gr.Button("🎲 Generate Name", variant="secondary", size="sm")
level_slider = gr.Slider(
minimum=1,
maximum=20,
value=1,
step=1,
label="Level",
info="Character level (1-20)"
)
alignment_dropdown = gr.Dropdown(
choices=[
"Lawful Good", "Neutral Good", "Chaotic Good",
"Lawful Neutral", "True Neutral", "Chaotic Neutral",
"Lawful Evil", "Neutral Evil", "Chaotic Evil"
],
label="Alignment",
value="True Neutral",
info="Character's moral alignment"
)
with gr.Column():
gr.Markdown("#### Background & Personality")
background_dropdown = gr.Dropdown(
choices=[
"Acolyte", "Charlatan", "Criminal", "Entertainer",
"Folk Hero", "Guild Artisan", "Hermit", "Noble",
"Outlander", "Sage", "Sailor", "Soldier",
"Urchin", "Custom (enter below)"
],
label="Background Type",
value="Soldier",
info="Select from D&D 5e backgrounds or choose Custom"
)
custom_background_input = gr.Textbox(
label="Custom Background",
placeholder="Enter your custom background...",
value="",
visible=False,
info="Only used if 'Custom' is selected above"
)
use_ai_background = gr.Checkbox(
label="Generate detailed backstory",
value=True,
info="Create a unique backstory for this character"
)
personality_input = gr.Textbox(
label="Personality Guidance (Optional)",
placeholder="A mysterious ranger who protects the forest...",
lines=3,
info="Guide AI in creating personality (if enabled)"
)
stats_method = gr.Radio(
choices=["standard_array", "roll", "point_buy"],
label="Ability Score Method",
value="standard_array",
info="How to generate ability scores"
)
create_btn = gr.Button("⚔️ Create Character", variant="primary", size="lg")
gr.Markdown("---")
with gr.Row():
character_output = gr.Markdown(label="Character Sheet")
status_output = gr.Textbox(label="Status", lines=8)
# Toggle custom background visibility
def toggle_custom_background(background_choice):
return gr.update(visible=background_choice == "Custom (enter below)")
# Update skin tone options when race changes
def update_skin_tone_choices(race: str):
try:
race_enum = DnDRace(race)
skin_tones = RACE_SKIN_TONES.get(race_enum, RACE_SKIN_TONES[DnDRace.HUMAN])
return gr.update(choices=skin_tones, value=skin_tones[0] if skin_tones else None)
except:
return gr.update(choices=RACE_SKIN_TONES[DnDRace.HUMAN], value=RACE_SKIN_TONES[DnDRace.HUMAN][0])
background_dropdown.change(
fn=toggle_custom_background,
inputs=[background_dropdown],
outputs=[custom_background_input]
)
race_dropdown.change(
fn=update_skin_tone_choices,
inputs=[race_dropdown],
outputs=[skin_tone_dropdown]
)
# Generate name action - includes alignment for more thematic names
generate_name_btn.click(
fn=self.generate_name_ui,
inputs=[race_dropdown, class_dropdown, gender_dropdown, alignment_dropdown],
outputs=[name_input]
)
# Create character action
create_btn.click(
fn=self.create_character_ui,
inputs=[
name_input,
race_dropdown,
class_dropdown,
level_slider,
gender_dropdown,
skin_tone_dropdown,
alignment_dropdown,
background_dropdown,
custom_background_input,
personality_input,
stats_method,
use_ai_background,
],
outputs=[character_output, status_output]
)