DnD_Campaign_Manager / src /ui /character_creator_ui.py
official.ghost.logic
Fix Gradio version compatibility - remove theme parameter
ddf162a
"""
Gradio UI for Character Creator
"""
import gradio as gr
from typing import Optional, Tuple
import traceback
from src.agents.character_agent import CharacterAgent
from src.agents.campaign_agent import CampaignAgent
from src.models.character import DnDRace, DnDClass
from src.models.campaign import CampaignTheme
from src.utils.validators import get_available_races, get_available_classes
from src.utils.image_generator import RACE_SKIN_TONES
from src.utils.character_sheet_exporter import CharacterSheetExporter
class CharacterCreatorUI:
"""Gradio interface for character creation"""
def __init__(self):
self.agent = CharacterAgent()
self.campaign_agent = CampaignAgent()
self.exporter = CharacterSheetExporter()
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
This is a self-contained function that calls the agent's generate_name method
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.agent.ai_client.generate_creative(prompt).strip()
name = name.split('\n')[0].strip('"\'')
return name
else:
# Use standard generation
name = self.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)
from src.models.character import Alignment
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.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 load_character_ui(self, character_id: str) -> Tuple[str, str]:
"""Load character by ID"""
try:
if not character_id.strip():
return "", "❌ Error: Please provide a character ID"
character = self.agent.load_character(character_id)
if character:
markdown = character.to_markdown()
status = f"βœ… Loaded character: {character.name}"
return markdown, status
else:
return "", f"❌ Character not found: {character_id}"
except Exception as e:
return "", f"❌ Error loading character: {e}"
def list_characters_ui(self) -> Tuple[str, str]:
"""List all saved characters"""
try:
characters = self.agent.list_characters()
if not characters:
return "", "No characters found in database."
# Create table
markdown = "# Saved Characters\n\n"
markdown += "| Name | Race | Class | Level | ID |\n"
markdown += "|------|------|-------|-------|----|\n"
for char in characters[-20:]: # Last 20 characters
markdown += f"| {char.name} | {char.race.value} | {char.character_class.value} | {char.level} | `{char.id}` |\n"
status = f"βœ… Found {len(characters)} character(s)"
return markdown, status
except Exception as e:
return "", f"❌ Error listing characters: {e}"
def delete_character_ui(self, character_id: str) -> str:
"""Delete character by ID"""
try:
if not character_id.strip():
return "❌ Error: Please provide a character ID"
# Check if exists
character = self.agent.load_character(character_id)
if not character:
return f"❌ Character not found: {character_id}"
# Delete
self.agent.delete_character(character_id)
return f"βœ… Deleted character: {character.name} ({character_id})"
except Exception as e:
return f"❌ Error deleting character: {e}"
def generate_portrait_ui(
self,
character_id: str,
style: str = "fantasy art",
quality: str = "standard",
provider: str = "auto"
) -> Tuple[Optional[str], str]:
"""
Generate character portrait
Returns:
Tuple of (image_path, status_message)
"""
try:
if not character_id.strip():
return None, "❌ Error: Please provide a character ID"
# Load character
character = self.agent.load_character(character_id)
if not character:
return None, f"❌ Character not found: {character_id}"
# Generate portrait
file_path, status = self.agent.generate_portrait(
character=character,
style=style,
quality=quality,
provider=provider
)
return file_path, status
except Exception as e:
import traceback
error_msg = f"❌ Error generating portrait:\n\n{str(e)}\n\n{traceback.format_exc()}"
return None, error_msg
def export_character_sheet_ui(
self,
character_id: str,
export_format: str = "markdown"
) -> str:
"""
Export character sheet to file
Returns:
Status message with file path
"""
try:
if not character_id.strip():
return "❌ Error: Please provide a character ID"
# Load character
character = self.agent.load_character(character_id)
if not character:
return f"❌ Character not found: {character_id}"
# Export to selected format
file_path = self.exporter.save_export(character, format=export_format)
return f"""βœ… Character sheet exported successfully!
**Character:** {character.name}
**Format:** {export_format.upper()}
**File:** {file_path}
You can find the exported file in the data/exports/ directory."""
except Exception as e:
return f"❌ Error exporting character sheet:\n\n{str(e)}\n\n{traceback.format_exc()}"
def preview_export_ui(
self,
character_id: str,
export_format: str = "markdown"
) -> Tuple[str, str]:
"""
Preview character sheet export without saving
Returns:
Tuple of (preview_content, status_message)
"""
try:
if not character_id.strip():
return "", "❌ Error: Please provide a character ID"
# Load character
character = self.agent.load_character(character_id)
if not character:
return "", f"❌ Character not found: {character_id}"
# Generate preview based on format
if export_format == "markdown":
preview = self.exporter.export_to_markdown(character)
elif export_format == "json":
preview = f"```json\n{self.exporter.export_to_json(character)}\n```"
elif export_format == "html":
preview = f"```html\n{self.exporter.export_to_html(character)}\n```"
else:
return "", f"❌ Unknown format: {export_format}"
status = f"βœ… Preview generated for {character.name}"
return preview, status
except Exception as e:
return "", f"❌ Error generating preview:\n\n{str(e)}\n\n{traceback.format_exc()}"
# Campaign Management UI Methods
def create_campaign_ui(
self,
name: str,
theme: str,
setting: str,
summary: str,
main_conflict: str,
game_master: str,
world_name: str,
starting_location: str,
level_range: str,
party_size: int
) -> str:
"""Create a new campaign"""
try:
if not name.strip():
return "❌ Error: Please provide a campaign name"
campaign = self.campaign_agent.create_campaign(
name=name,
theme=theme,
setting=setting,
summary=summary,
main_conflict=main_conflict,
game_master=game_master,
world_name=world_name,
starting_location=starting_location,
level_range=level_range,
party_size=party_size
)
return f"""βœ… Campaign Created Successfully!
**ID:** {campaign.id}
**Name:** {campaign.name}
**Theme:** {campaign.theme.value}
**Setting:** {campaign.setting}
Campaign has been saved to database.
Use the campaign ID to manage characters and sessions."""
except Exception as e:
return f"❌ Error creating campaign:\n\n{str(e)}\n\n{traceback.format_exc()}"
def list_campaigns_ui(self, active_only: bool = False) -> Tuple[str, str]:
"""List all campaigns"""
try:
campaigns = self.campaign_agent.list_campaigns(active_only=active_only)
if not campaigns:
return "", "No campaigns found in database."
# Create table
markdown = "# Campaigns\n\n"
markdown += "| Name | Theme | Session | Status | ID |\n"
markdown += "|------|-------|---------|--------|----|\n"
for campaign in campaigns[-20:]: # Last 20 campaigns
status = "Active" if campaign.is_active else "Inactive"
markdown += f"| {campaign.name} | {campaign.theme.value} | {campaign.current_session} | {status} | `{campaign.id}` |\n"
status = f"βœ… Found {len(campaigns)} campaign(s)"
return markdown, status
except Exception as e:
return "", f"❌ Error listing campaigns: {e}"
def load_campaign_ui(self, campaign_id: str) -> Tuple[str, str]:
"""Load campaign details"""
try:
if not campaign_id.strip():
return "", "❌ Error: Please provide a campaign ID"
campaign = self.campaign_agent.load_campaign(campaign_id)
if campaign:
markdown = campaign.to_markdown()
status = f"βœ… Loaded campaign: {campaign.name}"
return markdown, status
else:
return "", f"❌ Campaign not found: {campaign_id}"
except Exception as e:
return "", f"❌ Error loading campaign: {e}"
def add_character_to_campaign_ui(self, campaign_id: str, character_id: str) -> str:
"""Add a character to a campaign"""
try:
if not campaign_id.strip() or not character_id.strip():
return "❌ Error: Please provide both campaign ID and character ID"
# Verify character exists
character = self.agent.load_character(character_id)
if not character:
return f"❌ Character not found: {character_id}"
# Add to campaign
success = self.campaign_agent.add_character_to_campaign(campaign_id, character_id)
if success:
return f"βœ… Added {character.name} to campaign!"
else:
return f"❌ Campaign not found: {campaign_id}"
except Exception as e:
return f"❌ Error: {str(e)}"
def start_session_ui(self, campaign_id: str) -> str:
"""Start a new session"""
try:
if not campaign_id.strip():
return "❌ Error: Please provide a campaign ID"
campaign = self.campaign_agent.load_campaign(campaign_id)
if not campaign:
return f"❌ Campaign not found: {campaign_id}"
self.campaign_agent.start_new_session(campaign_id)
return f"""βœ… Started Session {campaign.current_session + 1}!
**Campaign:** {campaign.name}
**New Session Number:** {campaign.current_session + 1}
**Total Sessions:** {campaign.total_sessions + 1}"""
except Exception as e:
return f"❌ Error: {str(e)}"
def auto_generate_session_ui(self, campaign_id: str) -> str:
"""Auto-generate next session using AI"""
try:
if not campaign_id.strip():
return "❌ Error: Please select a campaign"
campaign = self.campaign_agent.load_campaign(campaign_id)
if not campaign:
return f"❌ Campaign not found: {campaign_id}"
# Generate session using autonomous AI
session_data = self.campaign_agent.auto_generate_next_session(campaign_id)
if 'error' in session_data:
return f"❌ Error: {session_data['error']}"
# Format output for display
output = []
output.append(f"# πŸ€– Auto-Generated Session {session_data.get('session_number', 'N/A')}")
output.append(f"\n**Campaign:** {campaign.name}")
output.append(f"\n**Session Title:** {session_data.get('session_title', 'Untitled')}")
output.append(f"\n---\n")
# Opening Scene
if 'opening_scene' in session_data:
output.append(f"## 🎬 Opening Scene\n\n{session_data['opening_scene']}\n\n")
# Key Encounters
if 'key_encounters' in session_data and session_data['key_encounters']:
output.append("## βš”οΈ Key Encounters\n\n")
for i, encounter in enumerate(session_data['key_encounters'], 1):
output.append(f"{i}. {encounter}\n")
output.append("\n")
# NPCs Featured
if 'npcs_featured' in session_data and session_data['npcs_featured']:
output.append("## πŸ‘₯ NPCs Featured\n\n")
for npc in session_data['npcs_featured']:
output.append(f"- {npc}\n")
output.append("\n")
# Locations
if 'locations' in session_data and session_data['locations']:
output.append("## πŸ—ΊοΈ Locations\n\n")
for loc in session_data['locations']:
output.append(f"- {loc}\n")
output.append("\n")
# Plot Developments
if 'plot_developments' in session_data and session_data['plot_developments']:
output.append("## πŸ“– Plot Developments\n\n")
for i, dev in enumerate(session_data['plot_developments'], 1):
output.append(f"{i}. {dev}\n")
output.append("\n")
# Potential Outcomes
if 'potential_outcomes' in session_data and session_data['potential_outcomes']:
output.append("## 🎲 Potential Outcomes\n\n")
for i, outcome in enumerate(session_data['potential_outcomes'], 1):
output.append(f"{i}. {outcome}\n")
output.append("\n")
# Rewards
if 'rewards' in session_data and session_data['rewards']:
output.append("## πŸ’° Rewards\n\n")
for reward in session_data['rewards']:
output.append(f"- {reward}\n")
output.append("\n")
# Cliffhanger
if 'cliffhanger' in session_data and session_data['cliffhanger']:
output.append(f"## 🎭 Cliffhanger\n\n{session_data['cliffhanger']}\n\n")
output.append("---\n\n")
output.append("βœ… **Session plan generated successfully!**\n\n")
output.append("πŸ’‘ **Next Steps:**\n")
output.append("- Review the session plan above\n")
output.append("- Adjust encounters/NPCs as needed for your table\n")
output.append("- Copy relevant sections to your session notes\n")
output.append("- Start the session when ready!\n")
return "".join(output)
except Exception as e:
import traceback
return f"❌ Error generating session:\n\n{str(e)}\n\n{traceback.format_exc()}"
def add_event_ui(
self,
campaign_id: str,
event_type: str,
title: str,
description: str,
importance: int
) -> str:
"""Add an event to the campaign"""
try:
if not campaign_id.strip():
return "❌ Error: Please provide a campaign ID"
if not title.strip() or not description.strip():
return "❌ Error: Please provide event title and description"
event = self.campaign_agent.add_event(
campaign_id=campaign_id,
event_type=event_type,
title=title,
description=description,
importance=importance
)
if event:
return f"""βœ… Event Added!
**Title:** {title}
**Type:** {event_type}
**Importance:** {'⭐' * importance}
Event has been recorded in campaign history."""
else:
return f"❌ Campaign not found: {campaign_id}"
except Exception as e:
return f"❌ Error: {str(e)}"
def get_character_choices_ui(self) -> list:
"""Get list of characters for selection"""
try:
characters = self.agent.list_characters()
if not characters:
return []
# Create choices as "Name (Race Class, Level X) - ID"
choices = []
for char in characters:
label = f"{char.name} ({char.race.value} {char.character_class.value}, Level {char.level})"
choices.append((label, char.id))
return choices
except Exception as e:
return []
def get_character_dropdown_choices(self) -> list:
"""Get character choices for dropdown (returns IDs only)"""
try:
characters = self.agent.list_characters()
if not characters:
return []
# Create dropdown choices with nice labels
choices = []
for char in characters:
label = f"{char.name} ({char.race.value} {char.character_class.value}, Lvl {char.level})"
choices.append(label)
return choices
except Exception as e:
return []
def get_character_id_from_label(self, label: str) -> str:
"""Extract character ID from dropdown label"""
try:
# Parse the label to get character name
if not label:
return ""
name = label.split(" (")[0] if " (" in label else label
# Find character by name
characters = self.agent.list_characters()
for char in characters:
if char.name == name:
return char.id
return ""
except Exception as e:
return ""
def get_campaign_dropdown_choices(self) -> list:
"""Get campaign choices for dropdown"""
try:
campaigns = self.campaign_agent.list_campaigns()
if not campaigns:
return []
choices = []
for campaign in campaigns:
label = f"{campaign.name} ({campaign.theme.value}, Session {campaign.current_session})"
choices.append(label)
return choices
except Exception as e:
return []
def get_campaign_id_from_label(self, label: str) -> str:
"""Extract campaign ID from dropdown label"""
try:
if not label:
return ""
name = label.split(" (")[0] if " (" in label else label
campaigns = self.campaign_agent.list_campaigns()
for campaign in campaigns:
if campaign.name == name:
return campaign.id
return ""
except Exception as e:
return ""
def synthesize_campaign_ui(
self,
selected_character_ids: list,
game_master: str,
additional_notes: str
) -> str:
"""Synthesize a campaign from selected characters"""
try:
# Check if any characters selected
if not selected_character_ids:
return "❌ Error: Please select at least one character"
# Load all characters
characters = []
for char_id in selected_character_ids:
char = self.agent.load_character(char_id)
if char:
characters.append(char)
if not characters:
return "❌ Error: No valid characters found"
# Synthesize campaign
campaign = self.campaign_agent.synthesize_campaign_from_characters(
characters=characters,
game_master=game_master,
additional_notes=additional_notes
)
# Create response with character list
char_list = "\n".join([f"- {char.name} (Level {char.level} {char.race.value} {char.character_class.value})" for char in characters])
# Build comprehensive output with all campaign details
output = [f"""βœ… Campaign Synthesized Successfully!
**Campaign ID:** {campaign.id}
**Campaign Name:** {campaign.name}
**Theme:** {campaign.theme.value}
**World:** {campaign.world_name}
**Starting Location:** {campaign.starting_location}
**Party Members ({len(characters)}):**
{char_list}
**Level Range:** {campaign.level_range}
---
## Campaign Overview
**Summary:**
{campaign.summary}
**Main Conflict:**
{campaign.main_conflict}
**Current Story Arc:**
{campaign.current_arc if campaign.current_arc else "See detailed notes below"}
"""]
# Add factions if present
if campaign.key_factions:
output.append("\n## Key Factions\n")
for faction in campaign.key_factions:
output.append(f"- {faction}\n")
# Add villains if present
if campaign.major_villains:
output.append("\n## Major Villains\n")
for villain in campaign.major_villains:
output.append(f"- {villain}\n")
# Add mysteries if present
if campaign.central_mysteries:
output.append("\n## Central Mysteries\n")
for mystery in campaign.central_mysteries:
output.append(f"- {mystery}\n")
# Add detailed campaign notes (includes character connections, hooks, sessions, NPCs, locations)
if campaign.notes:
output.append("\n---\n\n")
output.append(campaign.notes)
output.append(f"""
---
βœ… **Campaign Created!** All characters have been added to the campaign.
πŸ’‘ **Next Steps:**
- View full details in the "Manage Campaign" tab
- Start your first session in "Session Tracking"
- Add campaign events as your story unfolds""")
return "".join(output)
except Exception as e:
return f"❌ Error synthesizing campaign:\n\n{str(e)}\n\n{traceback.format_exc()}"
def create_interface(self) -> gr.Blocks:
"""Create Gradio interface"""
with gr.Blocks(title="D'n'D Campaign Manager - Character Creator") as interface:
gr.Markdown("""
# 🎲 D'n'D Campaign Manager
## Complete D&D Character Creator
Create and manage complete D&D 5e characters for your campaigns!
""")
with gr.Tabs():
# Tab 1: Create Character
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]
)
# Tab 2: Load Character
with gr.Tab("Load Character"):
gr.Markdown("### Load Saved Character")
load_char_refresh_btn = gr.Button("πŸ”„ Refresh Character List", variant="secondary")
character_dropdown = gr.Dropdown(
choices=[],
label="Select Character",
info="Choose a character from the list (type to search)",
allow_custom_value=False,
interactive=True
)
with gr.Row():
load_btn = gr.Button("πŸ“‚ Load Character", variant="primary")
list_btn = gr.Button("πŸ“‹ List All Characters")
gr.Markdown("---")
with gr.Row():
loaded_character_output = gr.Markdown(label="Character Sheet")
load_status_output = gr.Textbox(label="Status", lines=6)
# Refresh character dropdown
def refresh_character_dropdown():
choices = self.get_character_dropdown_choices()
return gr.update(choices=choices, value=None)
load_char_refresh_btn.click(
fn=refresh_character_dropdown,
inputs=[],
outputs=[character_dropdown]
)
# Load character action - convert dropdown label to ID
def load_character_from_dropdown(label):
char_id = self.get_character_id_from_label(label)
return self.load_character_ui(char_id)
load_btn.click(
fn=load_character_from_dropdown,
inputs=[character_dropdown],
outputs=[loaded_character_output, load_status_output]
)
# List characters action
list_btn.click(
fn=self.list_characters_ui,
inputs=[],
outputs=[loaded_character_output, load_status_output]
)
# Tab 3: Manage Characters
with gr.Tab("Manage Characters"):
gr.Markdown("### Character Management")
delete_refresh_btn = gr.Button("πŸ”„ Refresh Character List", variant="secondary")
delete_character_dropdown = gr.Dropdown(
choices=[],
label="Select Character to Delete",
info="⚠️ Warning: This action cannot be undone! (type to search)",
allow_custom_value=False,
interactive=True
)
delete_btn = gr.Button("πŸ—‘οΈ Delete Character", variant="stop")
delete_status_output = gr.Textbox(label="Status", lines=3)
# Refresh delete character dropdown
def refresh_delete_dropdown():
choices = self.get_character_dropdown_choices()
return gr.update(choices=choices, value=None)
delete_refresh_btn.click(
fn=refresh_delete_dropdown,
inputs=[],
outputs=[delete_character_dropdown]
)
# Delete character action - convert dropdown label to ID
def delete_character_from_dropdown(label):
char_id = self.get_character_id_from_label(label)
return self.delete_character_ui(char_id)
delete_btn.click(
fn=delete_character_from_dropdown,
inputs=[delete_character_dropdown],
outputs=[delete_status_output]
)
gr.Markdown("---")
with gr.Accordion("Quick Actions", open=False):
quick_list_btn = gr.Button("πŸ“‹ List All Characters")
quick_list_output = gr.Markdown(label="Character List")
quick_status = gr.Textbox(label="Status", lines=2)
quick_list_btn.click(
fn=self.list_characters_ui,
inputs=[],
outputs=[quick_list_output, quick_status]
)
# Tab 4: Generate Portrait
with gr.Tab("Generate Portrait"):
gr.Markdown("""
### 🎨 AI Character Portrait Generator
Generate stunning character portraits using DALL-E 3 or HuggingFace!
""")
with gr.Row():
with gr.Column():
portrait_refresh_btn = gr.Button("πŸ”„ Refresh Character List", variant="secondary")
portrait_character_dropdown = gr.Dropdown(
choices=[],
label="Select Character",
info="Choose a character to generate portrait for (type to search)",
allow_custom_value=False,
interactive=True
)
portrait_provider = gr.Radio(
choices=["auto", "openai", "huggingface"],
label="Image Provider",
value="auto",
info="Auto: Try OpenAI first, fallback to HuggingFace if needed"
)
portrait_style = gr.Dropdown(
choices=[
"fantasy art",
"digital painting",
"anime style",
"oil painting",
"watercolor",
"comic book art",
"concept art"
],
label="Art Style",
value="fantasy art",
info="Choose the artistic style"
)
portrait_quality = gr.Radio(
choices=["standard", "hd"],
label="Image Quality (OpenAI only)",
value="standard",
info="HD costs more tokens (OpenAI only)"
)
generate_portrait_btn = gr.Button(
"🎨 Generate Portrait",
variant="primary",
size="lg"
)
portrait_status = gr.Textbox(
label="Status",
lines=4
)
with gr.Column():
portrait_output = gr.Image(
label="Generated Portrait",
type="filepath",
height=512
)
gr.Markdown("""
**Providers:**
- **OpenAI DALL-E 3**: High quality, costs $0.04/image (standard) or $0.08/image (HD)
- **HuggingFace (Free!)**: Stable Diffusion XL, ~100 requests/day on free tier
- **Auto**: Tries OpenAI first, automatically falls back to HuggingFace if billing issues
Portraits are automatically saved to `data/portraits/` directory.
**Tips:**
- Use "auto" mode for seamless fallback
- OpenAI HD quality produces better results but costs 2x
- HuggingFace is free but may have a 30-60s warm-up time
- Different styles work better for different races/classes
""")
# Refresh portrait character dropdown
def refresh_portrait_dropdown():
choices = self.get_character_dropdown_choices()
return gr.update(choices=choices, value=None)
portrait_refresh_btn.click(
fn=refresh_portrait_dropdown,
inputs=[],
outputs=[portrait_character_dropdown]
)
# Generate portrait action - convert dropdown label to ID
def generate_portrait_from_dropdown(label, style, quality, provider):
char_id = self.get_character_id_from_label(label)
return self.generate_portrait_ui(char_id, style, quality, provider)
generate_portrait_btn.click(
fn=generate_portrait_from_dropdown,
inputs=[portrait_character_dropdown, portrait_style, portrait_quality, portrait_provider],
outputs=[portrait_output, portrait_status]
)
# Tab 5: Export Character Sheet
with gr.Tab("Export Character Sheet"):
gr.Markdown("""
### πŸ“„ Export Character Sheets
Export your characters to formatted character sheets in multiple formats!
""")
with gr.Row():
with gr.Column():
export_char_refresh_btn = gr.Button("πŸ”„ Refresh Character List", variant="secondary")
export_character_dropdown = gr.Dropdown(
choices=[],
label="Select Character",
info="Choose a character to export (type to search)",
allow_custom_value=False,
interactive=True
)
export_format = gr.Radio(
choices=["markdown", "json", "html"],
label="Export Format",
value="markdown",
info="Choose the format for your character sheet"
)
with gr.Row():
preview_btn = gr.Button("πŸ‘οΈ Preview", variant="secondary")
export_btn = gr.Button("πŸ’Ύ Export to File", variant="primary")
export_status = gr.Textbox(
label="Status",
lines=6
)
with gr.Column():
preview_output = gr.Markdown(
label="Preview",
value="Character sheet preview will appear here..."
)
gr.Markdown("""
### Format Descriptions
**Markdown (.md)**
- Clean, readable text format with tables
- Perfect for sharing in Discord, GitHub, or note apps
- Includes all character stats, features, and background
- Easy to read and edit
**JSON (.json)**
- Structured data format
- Perfect for importing into other tools or programs
- Contains all character data in a machine-readable format
- Great for backup or data transfer
**HTML (.html)**
- Styled character sheet that can be opened in a browser
- Print-ready format (mimics official D&D character sheet)
- Beautiful parchment styling with maroon borders
- Can be converted to PDF using browser's print function
All exports are saved to the `data/exports/` directory.
""")
# Refresh export character dropdown
def refresh_export_dropdown():
choices = self.get_character_dropdown_choices()
return gr.update(choices=choices, value=None)
export_char_refresh_btn.click(
fn=refresh_export_dropdown,
inputs=[],
outputs=[export_character_dropdown]
)
# Preview action - convert dropdown label to ID
def preview_from_dropdown(label, format):
char_id = self.get_character_id_from_label(label)
return self.preview_export_ui(char_id, format)
preview_btn.click(
fn=preview_from_dropdown,
inputs=[export_character_dropdown, export_format],
outputs=[preview_output, export_status]
)
# Export action - convert dropdown label to ID
def export_from_dropdown(label, format):
char_id = self.get_character_id_from_label(label)
return self.export_character_sheet_ui(char_id, format)
export_btn.click(
fn=export_from_dropdown,
inputs=[export_character_dropdown, export_format],
outputs=[export_status]
)
# Tab 6: Campaign Management
with gr.Tab("Campaign Management"):
gr.Markdown("""
### 🎲 Campaign Management
Create and manage your D&D campaigns, track sessions, and record events!
""")
with gr.Tabs():
# Sub-tab: Synthesize Campaign
with gr.Tab("πŸ€– Synthesize Campaign"):
gr.Markdown("""
### Campaign Synthesis
Select characters and automatically create a custom campaign tailored to your party!
""")
# Load character button
load_characters_btn = gr.Button("πŸ”„ Load Available Characters", variant="secondary")
with gr.Row():
with gr.Column():
synth_character_select = gr.CheckboxGroup(
choices=[],
label="Select Characters for Campaign",
info="Choose characters to include in your campaign"
)
synth_gm_name = gr.Textbox(
label="Game Master Name",
placeholder="Your name",
info="DM/GM running the campaign"
)
synth_notes = gr.Textbox(
label="Additional Notes (Optional)",
placeholder="Any specific themes, settings, or elements you'd like included...",
lines=4,
info="Guide the campaign creation process"
)
synthesize_btn = gr.Button("✨ Synthesize Campaign", variant="primary", size="lg")
with gr.Column():
gr.Markdown("""
### How It Works
1. **Load Characters**: Click "Load Available Characters" to see all your characters
2. **Select Party**: Check the boxes for characters you want in the campaign
3. **Add Context**: Optionally provide DM notes about themes or preferences
4. **Analyze Party**: The system analyzes:
- Character backstories and motivations
- Party composition and level
- Alignments and backgrounds
- Character personalities
5. **Campaign Created**: Get a fully-fledged campaign that:
- Ties into character backstories
- Provides appropriate challenges
- Creates opportunities for each character
- Includes factions, villains, and mysteries
**Example Party:**
- Thorin Ironforge (Dwarf Fighter, Level 3)
- Elara Moonwhisper (Elf Wizard, Level 3)
- Grimm Shadowstep (Halfling Rogue, Level 3)
The system will create a campaign that weaves their stories together!
""")
synth_status = gr.Textbox(label="Campaign Details", lines=20)
# Load characters button handler
def update_character_choices():
choices = self.get_character_choices_ui()
if not choices:
return gr.update(choices=[], value=[])
return gr.update(choices=choices, value=[])
load_characters_btn.click(
fn=update_character_choices,
inputs=[],
outputs=[synth_character_select]
)
synthesize_btn.click(
fn=self.synthesize_campaign_ui,
inputs=[synth_character_select, synth_gm_name, synth_notes],
outputs=[synth_status]
)
# Sub-tab: Create Campaign
with gr.Tab("Create Campaign"):
gr.Markdown("### Create New Campaign")
with gr.Row():
with gr.Column():
campaign_name = gr.Textbox(
label="Campaign Name",
placeholder="The Shattered Crown",
info="Name of your campaign"
)
campaign_theme = gr.Dropdown(
choices=[theme.value for theme in CampaignTheme],
label="Campaign Theme",
value="High Fantasy",
info="Select the theme of your campaign"
)
campaign_gm = gr.Textbox(
label="Game Master Name",
placeholder="Your name",
info="DM/GM running the campaign"
)
campaign_party_size = gr.Slider(
minimum=1,
maximum=10,
value=4,
step=1,
label="Party Size",
info="Expected number of players"
)
campaign_level_range = gr.Textbox(
label="Level Range",
value="1-5",
placeholder="1-5",
info="Expected level range for the campaign"
)
with gr.Column():
campaign_world = gr.Textbox(
label="World Name",
placeholder="Forgotten Realms",
info="Name of your world/realm"
)
campaign_starting = gr.Textbox(
label="Starting Location",
placeholder="Phandalin",
info="Where the adventure begins"
)
campaign_setting = gr.Textbox(
label="Setting Description",
placeholder="A war-torn kingdom...",
lines=3,
info="Describe the campaign setting"
)
campaign_summary = gr.Textbox(
label="Campaign Summary",
placeholder="The adventurers must...",
lines=3,
info="Brief campaign hook/summary"
)
campaign_conflict = gr.Textbox(
label="Main Conflict",
placeholder="A succession crisis threatens the kingdom",
lines=2,
info="Central conflict/tension"
)
create_campaign_btn = gr.Button("βš”οΈ Create Campaign", variant="primary", size="lg")
campaign_create_status = gr.Textbox(label="Status", lines=8)
create_campaign_btn.click(
fn=self.create_campaign_ui,
inputs=[
campaign_name,
campaign_theme,
campaign_setting,
campaign_summary,
campaign_conflict,
campaign_gm,
campaign_world,
campaign_starting,
campaign_level_range,
campaign_party_size
],
outputs=[campaign_create_status]
)
# Sub-tab: Manage Campaign
with gr.Tab("Manage Campaign"):
gr.Markdown("### Manage Campaign")
manage_campaign_refresh_btn = gr.Button("πŸ”„ Refresh Campaign List", variant="secondary")
manage_campaign_dropdown = gr.Dropdown(
choices=[],
label="Select Campaign",
info="Choose a campaign to view (type to search)",
allow_custom_value=False,
interactive=True
)
with gr.Row():
load_campaign_btn = gr.Button("πŸ“‚ Load Campaign", variant="primary")
list_campaigns_btn = gr.Button("πŸ“‹ List All Campaigns")
gr.Markdown("---")
with gr.Row():
campaign_details = gr.Markdown(label="Campaign Details")
campaign_status = gr.Textbox(label="Status", lines=6)
# Refresh campaign dropdown
def refresh_manage_campaign_dropdown():
choices = self.get_campaign_dropdown_choices()
return gr.update(choices=choices, value=None)
manage_campaign_refresh_btn.click(
fn=refresh_manage_campaign_dropdown,
inputs=[],
outputs=[manage_campaign_dropdown]
)
# Load campaign - convert dropdown label to ID
def load_campaign_from_dropdown(label):
campaign_id = self.get_campaign_id_from_label(label)
return self.load_campaign_ui(campaign_id)
load_campaign_btn.click(
fn=load_campaign_from_dropdown,
inputs=[manage_campaign_dropdown],
outputs=[campaign_details, campaign_status]
)
list_campaigns_btn.click(
fn=self.list_campaigns_ui,
inputs=[],
outputs=[campaign_details, campaign_status]
)
# Sub-tab: Add Characters
with gr.Tab("Add Characters"):
gr.Markdown("### Add Characters to Campaign")
add_char_refresh_btn = gr.Button("πŸ”„ Refresh Lists", variant="secondary")
with gr.Row():
add_char_campaign_dropdown = gr.Dropdown(
choices=[],
label="Select Campaign",
info="Choose the campaign to add characters to (type to search)",
allow_custom_value=False,
interactive=True
)
add_char_character_dropdown = gr.Dropdown(
choices=[],
label="Select Character",
info="Choose the character to add (type to search)",
allow_custom_value=False,
interactive=True
)
add_char_btn = gr.Button("βž• Add Character to Campaign", variant="primary")
add_char_status = gr.Textbox(label="Status", lines=4)
# Refresh both dropdowns
def refresh_add_char_dropdowns():
campaign_choices = self.get_campaign_dropdown_choices()
character_choices = self.get_character_dropdown_choices()
return (
gr.update(choices=campaign_choices, value=None),
gr.update(choices=character_choices, value=None)
)
add_char_refresh_btn.click(
fn=refresh_add_char_dropdowns,
inputs=[],
outputs=[add_char_campaign_dropdown, add_char_character_dropdown]
)
# Add character - convert dropdown labels to IDs
def add_char_from_dropdowns(campaign_label, character_label):
campaign_id = self.get_campaign_id_from_label(campaign_label)
character_id = self.get_character_id_from_label(character_label)
return self.add_character_to_campaign_ui(campaign_id, character_id)
add_char_btn.click(
fn=add_char_from_dropdowns,
inputs=[add_char_campaign_dropdown, add_char_character_dropdown],
outputs=[add_char_status]
)
gr.Markdown("""
**Tip:** Click "Refresh Lists" to load your campaigns and characters.
""")
# Sub-tab: Session Tracking
with gr.Tab("Session Tracking"):
gr.Markdown("### Track Campaign Sessions")
session_refresh_btn = gr.Button("πŸ”„ Refresh Campaign List", variant="secondary")
session_campaign_dropdown = gr.Dropdown(
choices=[],
label="Select Campaign",
info="Choose the campaign for session tracking (type to search)",
allow_custom_value=False,
interactive=True
)
start_session_btn = gr.Button("🎬 Start New Session", variant="primary")
session_status = gr.Textbox(label="Status", lines=4)
# Refresh session campaign dropdown
def refresh_session_dropdown():
choices = self.get_campaign_dropdown_choices()
return gr.update(choices=choices, value=None)
session_refresh_btn.click(
fn=refresh_session_dropdown,
inputs=[],
outputs=[session_campaign_dropdown]
)
# Start session - convert dropdown label to ID
def start_session_from_dropdown(label):
campaign_id = self.get_campaign_id_from_label(label)
return self.start_session_ui(campaign_id)
start_session_btn.click(
fn=start_session_from_dropdown,
inputs=[session_campaign_dropdown],
outputs=[session_status]
)
gr.Markdown("---")
gr.Markdown("### πŸ€– Auto-Generate Next Session")
gr.Markdown("""
**Autonomous Feature:** AI analyzes your campaign and automatically generates a complete session plan.
This includes:
- Opening scene narration
- Key encounters (combat, social, exploration)
- NPCs featured in the session
- Locations to visit
- Plot developments
- Potential outcomes and rewards
""")
auto_session_refresh_btn = gr.Button("πŸ”„ Refresh Campaign List", variant="secondary")
auto_session_campaign_dropdown = gr.Dropdown(
choices=[],
label="Select Campaign",
info="Choose campaign to generate next session for",
allow_custom_value=False,
interactive=True
)
auto_generate_session_btn = gr.Button("✨ Auto-Generate Next Session", variant="primary")
auto_session_output = gr.Textbox(label="Generated Session Plan", lines=20)
# Refresh auto-session campaign dropdown
def refresh_auto_session_dropdown():
choices = self.get_campaign_dropdown_choices()
return gr.update(choices=choices, value=None)
auto_session_refresh_btn.click(
fn=refresh_auto_session_dropdown,
inputs=[],
outputs=[auto_session_campaign_dropdown]
)
# Auto-generate session
def auto_generate_session_from_dropdown(label):
campaign_id = self.get_campaign_id_from_label(label)
return self.auto_generate_session_ui(campaign_id)
auto_generate_session_btn.click(
fn=auto_generate_session_from_dropdown,
inputs=[auto_session_campaign_dropdown],
outputs=[auto_session_output]
)
gr.Markdown("---")
gr.Markdown("### Add Session Event")
event_refresh_btn = gr.Button("πŸ”„ Refresh Campaign List", variant="secondary")
event_campaign_dropdown = gr.Dropdown(
choices=[],
label="Select Campaign",
info="Choose the campaign to add event to (type to search)",
allow_custom_value=False,
interactive=True
)
event_type = gr.Dropdown(
choices=["Combat", "Social", "Exploration", "Discovery", "Plot Development", "Character Moment", "NPC Interaction", "Quest Update"],
label="Event Type",
value="Combat",
info="Type of event"
)
event_title = gr.Textbox(
label="Event Title",
placeholder="Battle at the Bridge",
info="Short title for the event"
)
event_description = gr.Textbox(
label="Event Description",
placeholder="The party encountered a group of bandits...",
lines=4,
info="Detailed description of what happened"
)
event_importance = gr.Slider(
minimum=1,
maximum=5,
value=3,
step=1,
label="Importance",
info="How important is this event? (1-5 stars)"
)
add_event_btn = gr.Button("πŸ“ Add Event", variant="primary")
event_status = gr.Textbox(label="Status", lines=6)
# Refresh event campaign dropdown
def refresh_event_dropdown():
choices = self.get_campaign_dropdown_choices()
return gr.update(choices=choices, value=None)
event_refresh_btn.click(
fn=refresh_event_dropdown,
inputs=[],
outputs=[event_campaign_dropdown]
)
# Add event - convert dropdown label to ID
def add_event_from_dropdown(campaign_label, event_type_val, title, description, importance):
campaign_id = self.get_campaign_id_from_label(campaign_label)
return self.add_event_ui(campaign_id, event_type_val, title, description, importance)
add_event_btn.click(
fn=add_event_from_dropdown,
inputs=[
event_campaign_dropdown,
event_type,
event_title,
event_description,
event_importance
],
outputs=[event_status]
)
# Tab 7: About
with gr.Tab("About"):
gr.Markdown("""
## About D'n'D Campaign Manager
**Version:** 2.0.0
**Built for:** Gradio + Anthropic MCP Hackathon
### Features
- 🎲 Complete D&D 5e character creation
- πŸ“– Automated name and backstory generation
- 🎨 Character portrait generation (DALL-E 3 / HuggingFace SDXL)
- πŸ“Š Multiple ability score methods (Standard Array, Roll, Point Buy)
- πŸ’Ύ Database persistence
- πŸ“„ Markdown character sheet export
- βœ… Full data validation
### Stat Methods
- **Standard Array:** 15, 14, 13, 12, 10, 8 (balanced)
- **Roll:** 4d6 drop lowest (random)
- **Point Buy:** 27 points (customizable)
### Supported Races
Human, Elf, Dwarf, Halfling, Dragonborn, Gnome, Half-Elf, Half-Orc, Tiefling
### Supported Classes
Barbarian, Bard, Cleric, Druid, Fighter, Monk, Paladin, Ranger, Rogue, Sorcerer, Warlock, Wizard
### Tech Stack
- **AI:** Anthropic Claude / Google Gemini / OpenAI DALL-E 3
- **Framework:** Gradio
- **Database:** SQLite
- **Validation:** Pydantic
---
*Built with ❀️ for the TTRPG community*
""")
gr.Markdown("""
---
### Tips
- Enable "Use AI" options for more creative and detailed characters
- Use Standard Array for balanced characters
- Use Roll for random variation
- Save your character ID to load it later
- Check the character list to find previously created characters
""")
# Auto-populate dropdowns on interface load
def populate_all_dropdowns():
"""Populate all dropdowns when interface loads"""
campaign_choices = self.get_campaign_dropdown_choices()
character_choices = self.get_character_dropdown_choices()
return [
gr.update(choices=character_choices), # character_dropdown
gr.update(choices=character_choices), # delete_character_dropdown
gr.update(choices=character_choices), # portrait_character_dropdown
gr.update(choices=character_choices), # export_character_dropdown
gr.update(choices=campaign_choices), # manage_campaign_dropdown
gr.update(choices=campaign_choices), # add_char_campaign_dropdown
gr.update(choices=character_choices), # add_char_character_dropdown
gr.update(choices=campaign_choices), # session_campaign_dropdown
gr.update(choices=campaign_choices), # auto_session_campaign_dropdown
gr.update(choices=campaign_choices), # event_campaign_dropdown
]
interface.load(
fn=populate_all_dropdowns,
inputs=[],
outputs=[
character_dropdown,
delete_character_dropdown,
portrait_character_dropdown,
export_character_dropdown,
manage_campaign_dropdown,
add_char_campaign_dropdown,
add_char_character_dropdown,
session_campaign_dropdown,
auto_session_campaign_dropdown,
event_campaign_dropdown,
]
)
return interface
def launch_ui():
"""Launch the Gradio interface"""
ui = CharacterCreatorUI()
interface = ui.create_interface()
interface.launch(
server_name="0.0.0.0",
server_port=7860,
share=False,
show_error=True
)
if __name__ == "__main__":
launch_ui()