Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -85,12 +85,25 @@ class Campaign:
|
|
85 |
class NPC:
|
86 |
name: str
|
87 |
race: str
|
|
|
88 |
occupation: str
|
89 |
personality: str
|
90 |
secret: str
|
91 |
voice_description: str
|
92 |
relationship_to_party: str
|
93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
# ===== AI AGENT CLASSES =====
|
95 |
class DungeonMasterAgent:
|
96 |
"""AI agent that acts as a Dungeon Master"""
|
@@ -176,19 +189,22 @@ class NPCAgent:
|
|
176 |
self.personality = "Versatile character actor with deep understanding of motivations"
|
177 |
self.specializations = ["Character creation", "Dialogue", "Motivations", "Voice acting"]
|
178 |
|
179 |
-
def generate_npc(self, context: str, role: str, importance: str) -> Dict:
|
180 |
"""Generate a detailed NPC"""
|
181 |
try:
|
182 |
from openai import OpenAI
|
183 |
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
184 |
|
|
|
|
|
185 |
prompt = f"""Create a detailed NPC for:
|
186 |
Context: {context}
|
187 |
Role: {role}
|
188 |
Importance: {importance}
|
|
|
189 |
|
190 |
Generate:
|
191 |
-
1. Name and basic demographics
|
192 |
2. Personality traits (3-4 key traits)
|
193 |
3. Background and motivation
|
194 |
4. Speech patterns/accent description
|
@@ -201,7 +217,7 @@ class NPCAgent:
|
|
201 |
|
202 |
response = client.chat.completions.create(
|
203 |
model="gpt-4",
|
204 |
-
messages=[{"role": "system", "content": "You are an expert at creating memorable, three-dimensional NPCs for D&D campaigns."},
|
205 |
{"role": "user", "content": prompt}],
|
206 |
max_tokens=400,
|
207 |
temperature=0.8
|
@@ -211,7 +227,7 @@ class NPCAgent:
|
|
211 |
|
212 |
except Exception as e:
|
213 |
logger.error(f"NPC generation failed: {e}")
|
214 |
-
return {"success": False, "error": str(e), "content": f"Mock NPC: {role}
|
215 |
|
216 |
def roleplay_npc(self, npc_description: str, player_input: str, context: str) -> Dict:
|
217 |
"""Roleplay as an NPC in response to player actions"""
|
@@ -289,6 +305,90 @@ class WorldBuilderAgent:
|
|
289 |
logger.error(f"Location generation failed: {e}")
|
290 |
return {"success": False, "error": str(e), "content": f"Mock Location: {theme} {location_type} for {purpose}"}
|
291 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
292 |
class LootMasterAgent:
|
293 |
"""AI agent specialized in creating balanced loot and magic items"""
|
294 |
|
@@ -588,6 +688,7 @@ def create_main_interface():
|
|
588 |
dm_agent = DungeonMasterAgent()
|
589 |
npc_agent = NPCAgent()
|
590 |
world_agent = WorldBuilderAgent()
|
|
|
591 |
loot_agent = LootMasterAgent()
|
592 |
character_creator = CharacterCreator()
|
593 |
|
@@ -621,6 +722,7 @@ def create_main_interface():
|
|
621 |
- 🗺️ World Builder Agent
|
622 |
- 💰 Loot Master & Magic Item Designer
|
623 |
- 🐉 Enhanced Character Creator
|
|
|
624 |
""")
|
625 |
|
626 |
with gr.Tabs():
|
@@ -738,6 +840,10 @@ def create_main_interface():
|
|
738 |
choices=["Minor", "Moderate", "Major", "Recurring"],
|
739 |
label="Importance Level", value="Moderate"
|
740 |
)
|
|
|
|
|
|
|
|
|
741 |
|
742 |
create_npc_btn = gr.Button("🎭 Generate NPC", variant="primary")
|
743 |
|
@@ -785,6 +891,57 @@ def create_main_interface():
|
|
785 |
|
786 |
location_output = gr.Textbox(label="Location Details", lines=12, elem_classes=["output-box"])
|
787 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
788 |
# ===== LOOT MASTER TAB =====
|
789 |
with gr.TabItem("💰 Loot Master"):
|
790 |
gr.Markdown("## 💎 Treasure & Magic Item Generator", elem_classes=["agent-card"])
|
@@ -1052,14 +1209,16 @@ def create_main_interface():
|
|
1052 |
return result.get("content", "Error generating session")
|
1053 |
|
1054 |
# NPC Events
|
1055 |
-
def create_npc(context, role, importance):
|
1056 |
-
result = npc_agent.generate_npc(context, role, importance)
|
1057 |
return result.get("content", "Error creating NPC")
|
1058 |
|
1059 |
def generate_npc_portrait(npc_data):
|
1060 |
if not npc_data:
|
1061 |
-
|
1062 |
-
|
|
|
|
|
1063 |
return generate_image(prompt)
|
1064 |
|
1065 |
def npc_roleplay_response(npc_data, player_input, context):
|
@@ -1077,6 +1236,23 @@ def create_main_interface():
|
|
1077 |
prompt = f"{theme} {loc_type} fantasy location, detailed environment art, D&D setting"
|
1078 |
return generate_image(prompt)
|
1079 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1080 |
# Loot Events
|
1081 |
def create_loot_table(level, encounter, rarity):
|
1082 |
result = loot_agent.generate_loot_table(level, encounter, rarity)
|
@@ -1189,13 +1365,13 @@ def create_main_interface():
|
|
1189 |
# NPC events
|
1190 |
create_npc_btn.click(
|
1191 |
create_npc,
|
1192 |
-
inputs=[npc_context, npc_role, npc_importance],
|
1193 |
outputs=[npc_output]
|
1194 |
)
|
1195 |
|
1196 |
npc_portrait_btn.click(
|
1197 |
generate_npc_portrait,
|
1198 |
-
inputs=[
|
1199 |
outputs=[npc_portrait]
|
1200 |
)
|
1201 |
|
@@ -1218,6 +1394,31 @@ def create_main_interface():
|
|
1218 |
outputs=[location_image]
|
1219 |
)
|
1220 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1221 |
# Loot events
|
1222 |
generate_loot_btn.click(
|
1223 |
create_loot_table,
|
@@ -1242,6 +1443,124 @@ def create_main_interface():
|
|
1242 |
gen_encounter_btn.click(generate_random_encounter, inputs=[encounter_level, encounter_difficulty], outputs=[random_encounter])
|
1243 |
gen_hook_btn.click(generate_plot_hook, inputs=[hook_theme], outputs=[plot_hook])
|
1244 |
gen_weather_btn.click(generate_weather, inputs=[climate], outputs=[weather])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1245 |
|
1246 |
return demo
|
1247 |
|
@@ -1270,4 +1589,4 @@ if __name__ == "__main__":
|
|
1270 |
except Exception as e:
|
1271 |
logger.error(f"❌ Launch failed: {e}")
|
1272 |
# Fallback launch for HF Spaces
|
1273 |
-
demo.launch()
|
|
|
85 |
class NPC:
|
86 |
name: str
|
87 |
race: str
|
88 |
+
gender: str
|
89 |
occupation: str
|
90 |
personality: str
|
91 |
secret: str
|
92 |
voice_description: str
|
93 |
relationship_to_party: str
|
94 |
|
95 |
+
@dataclass
|
96 |
+
class Deity:
|
97 |
+
name: str
|
98 |
+
domain: str
|
99 |
+
alignment: Alignment
|
100 |
+
gender: str
|
101 |
+
description: str
|
102 |
+
holy_symbol: str
|
103 |
+
followers: str
|
104 |
+
tenets: List[str]
|
105 |
+
clergy: str
|
106 |
+
|
107 |
# ===== AI AGENT CLASSES =====
|
108 |
class DungeonMasterAgent:
|
109 |
"""AI agent that acts as a Dungeon Master"""
|
|
|
189 |
self.personality = "Versatile character actor with deep understanding of motivations"
|
190 |
self.specializations = ["Character creation", "Dialogue", "Motivations", "Voice acting"]
|
191 |
|
192 |
+
def generate_npc(self, context: str, role: str, importance: str, gender: str = "") -> Dict:
|
193 |
"""Generate a detailed NPC"""
|
194 |
try:
|
195 |
from openai import OpenAI
|
196 |
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
197 |
|
198 |
+
gender_text = f"Gender: {gender}" if gender else "Gender: Choose appropriate gender"
|
199 |
+
|
200 |
prompt = f"""Create a detailed NPC for:
|
201 |
Context: {context}
|
202 |
Role: {role}
|
203 |
Importance: {importance}
|
204 |
+
{gender_text}
|
205 |
|
206 |
Generate:
|
207 |
+
1. Name and basic demographics (including gender)
|
208 |
2. Personality traits (3-4 key traits)
|
209 |
3. Background and motivation
|
210 |
4. Speech patterns/accent description
|
|
|
217 |
|
218 |
response = client.chat.completions.create(
|
219 |
model="gpt-4",
|
220 |
+
messages=[{"role": "system", "content": "You are an expert at creating memorable, three-dimensional NPCs for D&D campaigns with diverse and inclusive representation."},
|
221 |
{"role": "user", "content": prompt}],
|
222 |
max_tokens=400,
|
223 |
temperature=0.8
|
|
|
227 |
|
228 |
except Exception as e:
|
229 |
logger.error(f"NPC generation failed: {e}")
|
230 |
+
return {"success": False, "error": str(e), "content": f"Mock NPC: {gender if gender else 'Character'} {role} for {context}"}
|
231 |
|
232 |
def roleplay_npc(self, npc_description: str, player_input: str, context: str) -> Dict:
|
233 |
"""Roleplay as an NPC in response to player actions"""
|
|
|
305 |
logger.error(f"Location generation failed: {e}")
|
306 |
return {"success": False, "error": str(e), "content": f"Mock Location: {theme} {location_type} for {purpose}"}
|
307 |
|
308 |
+
class DeityAgent:
|
309 |
+
"""AI agent specialized in creating deities and pantheons"""
|
310 |
+
|
311 |
+
def __init__(self):
|
312 |
+
self.personality = "Divine architect of mythological systems"
|
313 |
+
self.specializations = ["Mythology", "Religion", "Divine domains", "Pantheon design"]
|
314 |
+
|
315 |
+
def generate_deity(self, domain: str, alignment: str, gender: str, pantheon_theme: str) -> Dict:
|
316 |
+
"""Generate a detailed deity"""
|
317 |
+
try:
|
318 |
+
from openai import OpenAI
|
319 |
+
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
320 |
+
|
321 |
+
prompt = f"""Create a detailed deity for D&D:
|
322 |
+
Domain: {domain}
|
323 |
+
Alignment: {alignment}
|
324 |
+
Gender: {gender}
|
325 |
+
Pantheon Theme: {pantheon_theme}
|
326 |
+
|
327 |
+
Generate:
|
328 |
+
1. Name and titles
|
329 |
+
2. Physical description and manifestation
|
330 |
+
3. Personality and temperament
|
331 |
+
4. Core domains and portfolio
|
332 |
+
5. Holy symbol and sacred items
|
333 |
+
6. Typical followers and clergy
|
334 |
+
7. 3-4 key tenets or commandments
|
335 |
+
8. Relationships with other deities
|
336 |
+
9. Myths or legends about them
|
337 |
+
10. How they interact with mortals
|
338 |
+
|
339 |
+
Make them feel divine and mythologically rich!"""
|
340 |
+
|
341 |
+
response = client.chat.completions.create(
|
342 |
+
model="gpt-4",
|
343 |
+
messages=[{"role": "system", "content": "You are an expert mythologist and world-builder creating rich, detailed deities for fantasy settings."},
|
344 |
+
{"role": "user", "content": prompt}],
|
345 |
+
max_tokens=600,
|
346 |
+
temperature=0.8
|
347 |
+
)
|
348 |
+
|
349 |
+
return {"success": True, "content": response.choices[0].message.content}
|
350 |
+
|
351 |
+
except Exception as e:
|
352 |
+
logger.error(f"Deity generation failed: {e}")
|
353 |
+
return {"success": False, "error": str(e), "content": f"Mock Deity: {gender} deity of {domain} with {alignment} alignment"}
|
354 |
+
|
355 |
+
def generate_pantheon(self, theme: str, size: str, culture: str) -> Dict:
|
356 |
+
"""Generate an entire pantheon"""
|
357 |
+
try:
|
358 |
+
from openai import OpenAI
|
359 |
+
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
360 |
+
|
361 |
+
prompt = f"""Create a complete pantheon for D&D:
|
362 |
+
Theme: {theme}
|
363 |
+
Size: {size}
|
364 |
+
Cultural Influence: {culture}
|
365 |
+
|
366 |
+
Generate:
|
367 |
+
1. Pantheon name and overview
|
368 |
+
2. Creation myth
|
369 |
+
3. 5-8 major deities with names, domains, and relationships
|
370 |
+
4. 3-5 lesser deities or demigods
|
371 |
+
5. Pantheon hierarchy and conflicts
|
372 |
+
6. How mortals worship them
|
373 |
+
7. Religious festivals and holy days
|
374 |
+
8. Sacred sites and temples
|
375 |
+
|
376 |
+
Make it cohesive and interconnected!"""
|
377 |
+
|
378 |
+
response = client.chat.completions.create(
|
379 |
+
model="gpt-4",
|
380 |
+
messages=[{"role": "system", "content": "You are creating interconnected pantheons with rich mythological depth."},
|
381 |
+
{"role": "user", "content": prompt}],
|
382 |
+
max_tokens=800,
|
383 |
+
temperature=0.7
|
384 |
+
)
|
385 |
+
|
386 |
+
return {"success": True, "content": response.choices[0].message.content}
|
387 |
+
|
388 |
+
except Exception as e:
|
389 |
+
logger.error(f"Pantheon generation failed: {e}")
|
390 |
+
return {"success": False, "error": str(e), "content": f"Mock Pantheon: {size} {theme} pantheon inspired by {culture}"}
|
391 |
+
|
392 |
class LootMasterAgent:
|
393 |
"""AI agent specialized in creating balanced loot and magic items"""
|
394 |
|
|
|
688 |
dm_agent = DungeonMasterAgent()
|
689 |
npc_agent = NPCAgent()
|
690 |
world_agent = WorldBuilderAgent()
|
691 |
+
deity_agent = DeityAgent()
|
692 |
loot_agent = LootMasterAgent()
|
693 |
character_creator = CharacterCreator()
|
694 |
|
|
|
722 |
- 🗺️ World Builder Agent
|
723 |
- 💰 Loot Master & Magic Item Designer
|
724 |
- 🐉 Enhanced Character Creator
|
725 |
+
- ⚡ Deity & Pantheon Generator
|
726 |
""")
|
727 |
|
728 |
with gr.Tabs():
|
|
|
840 |
choices=["Minor", "Moderate", "Major", "Recurring"],
|
841 |
label="Importance Level", value="Moderate"
|
842 |
)
|
843 |
+
npc_gender = gr.Dropdown(
|
844 |
+
choices=["", "Male", "Female", "Non-binary", "Transgender Male", "Transgender Female", "Genderfluid", "Agender", "Other"],
|
845 |
+
label="Gender (Optional)", value=""
|
846 |
+
)
|
847 |
|
848 |
create_npc_btn = gr.Button("🎭 Generate NPC", variant="primary")
|
849 |
|
|
|
891 |
|
892 |
location_output = gr.Textbox(label="Location Details", lines=12, elem_classes=["output-box"])
|
893 |
|
894 |
+
# ===== DEITY CREATOR TAB =====
|
895 |
+
with gr.TabItem("⚡ Deity Creator"):
|
896 |
+
gr.Markdown("## ⚡ Divine Pantheon Generator", elem_classes=["agent-card"])
|
897 |
+
|
898 |
+
with gr.Tabs():
|
899 |
+
with gr.TabItem("🌟 Single Deity"):
|
900 |
+
with gr.Row():
|
901 |
+
with gr.Column():
|
902 |
+
deity_domain = gr.Dropdown(
|
903 |
+
choices=["War", "Death", "Life", "Knowledge", "Nature", "Tempest", "Trickery", "Light", "Forge", "Grave", "Order", "Peace", "Twilight"],
|
904 |
+
label="Primary Domain", value="Life"
|
905 |
+
)
|
906 |
+
deity_alignment = gr.Dropdown(
|
907 |
+
choices=[alignment.value for alignment in Alignment],
|
908 |
+
label="Alignment", value="Neutral Good"
|
909 |
+
)
|
910 |
+
deity_gender = gr.Dropdown(
|
911 |
+
choices=["Male", "Female", "Non-binary", "Genderfluid", "Agender", "Beyond Gender", "Dual-Gendered", "Other"],
|
912 |
+
label="Gender", value="Female"
|
913 |
+
)
|
914 |
+
pantheon_theme = gr.Textbox(label="Pantheon Theme", placeholder="Greek, Norse, Egyptian, Original, etc.")
|
915 |
+
|
916 |
+
generate_deity_btn = gr.Button("⚡ Generate Deity", variant="primary")
|
917 |
+
deity_art_btn = gr.Button("🎨 Generate Deity Art")
|
918 |
+
|
919 |
+
with gr.Column():
|
920 |
+
deity_image = gr.Image(label="Deity Visual", height=300)
|
921 |
+
|
922 |
+
deity_output = gr.Textbox(label="Deity Details", lines=15, elem_classes=["output-box"])
|
923 |
+
|
924 |
+
with gr.TabItem("🏛️ Full Pantheon"):
|
925 |
+
with gr.Row():
|
926 |
+
with gr.Column():
|
927 |
+
pantheon_theme_full = gr.Dropdown(
|
928 |
+
choices=["Greek-inspired", "Norse-inspired", "Egyptian-inspired", "Celtic-inspired", "Hindu-inspired", "Mesopotamian-inspired", "Original Fantasy", "Dark Pantheon", "Nature Pantheon", "Elemental Pantheon"],
|
929 |
+
label="Pantheon Style", value="Original Fantasy"
|
930 |
+
)
|
931 |
+
pantheon_size = gr.Dropdown(
|
932 |
+
choices=["Small (5-8 deities)", "Medium (9-12 deities)", "Large (13-16 deities)", "Epic (17+ deities)"],
|
933 |
+
label="Pantheon Size", value="Medium (9-12 deities)"
|
934 |
+
)
|
935 |
+
cultural_influence = gr.Textbox(label="Cultural Influence", placeholder="What mortal culture do they influence?")
|
936 |
+
|
937 |
+
generate_pantheon_btn = gr.Button("🏛️ Generate Pantheon", variant="primary")
|
938 |
+
|
939 |
+
with gr.Column():
|
940 |
+
pantheon_visual_btn = gr.Button("🖼️ Generate Pantheon Art")
|
941 |
+
pantheon_image = gr.Image(label="Pantheon Visual", height=300)
|
942 |
+
|
943 |
+
pantheon_output = gr.Textbox(label="Pantheon Details", lines=20, elem_classes=["output-box"])
|
944 |
+
|
945 |
# ===== LOOT MASTER TAB =====
|
946 |
with gr.TabItem("💰 Loot Master"):
|
947 |
gr.Markdown("## 💎 Treasure & Magic Item Generator", elem_classes=["agent-card"])
|
|
|
1209 |
return result.get("content", "Error generating session")
|
1210 |
|
1211 |
# NPC Events
|
1212 |
+
def create_npc(context, role, importance, gender):
|
1213 |
+
result = npc_agent.generate_npc(context, role, importance, gender)
|
1214 |
return result.get("content", "Error creating NPC")
|
1215 |
|
1216 |
def generate_npc_portrait(npc_data):
|
1217 |
if not npc_data:
|
1218 |
+
prompt = "Fantasy portrait of a D&D NPC character, detailed face, professional RPG art"
|
1219 |
+
else:
|
1220 |
+
# Extract key details from NPC description for better portraits
|
1221 |
+
prompt = f"Fantasy portrait of a D&D NPC character based on: {npc_data[:200]}..., detailed face, professional RPG art"
|
1222 |
return generate_image(prompt)
|
1223 |
|
1224 |
def npc_roleplay_response(npc_data, player_input, context):
|
|
|
1236 |
prompt = f"{theme} {loc_type} fantasy location, detailed environment art, D&D setting"
|
1237 |
return generate_image(prompt)
|
1238 |
|
1239 |
+
# Deity Events
|
1240 |
+
def create_deity(domain, alignment, gender, theme):
|
1241 |
+
result = deity_agent.generate_deity(domain, alignment, gender, theme)
|
1242 |
+
return result.get("content", "Error creating deity")
|
1243 |
+
|
1244 |
+
def generate_deity_art(domain, gender, theme):
|
1245 |
+
prompt = f"{gender} deity of {domain}, {theme} mythology style, divine fantasy art, glowing divine aura"
|
1246 |
+
return generate_image(prompt)
|
1247 |
+
|
1248 |
+
def create_pantheon(theme, size, culture):
|
1249 |
+
result = deity_agent.generate_pantheon(theme, size, culture)
|
1250 |
+
return result.get("content", "Error creating pantheon")
|
1251 |
+
|
1252 |
+
def generate_pantheon_art(theme, culture):
|
1253 |
+
prompt = f"{theme} pantheon temple complex, {culture} architectural style, multiple divine statues, epic fantasy art"
|
1254 |
+
return generate_image(prompt)
|
1255 |
+
|
1256 |
# Loot Events
|
1257 |
def create_loot_table(level, encounter, rarity):
|
1258 |
result = loot_agent.generate_loot_table(level, encounter, rarity)
|
|
|
1365 |
# NPC events
|
1366 |
create_npc_btn.click(
|
1367 |
create_npc,
|
1368 |
+
inputs=[npc_context, npc_role, npc_importance, npc_gender],
|
1369 |
outputs=[npc_output]
|
1370 |
)
|
1371 |
|
1372 |
npc_portrait_btn.click(
|
1373 |
generate_npc_portrait,
|
1374 |
+
inputs=[npc_output], # Changed from current_npc_data to npc_output
|
1375 |
outputs=[npc_portrait]
|
1376 |
)
|
1377 |
|
|
|
1394 |
outputs=[location_image]
|
1395 |
)
|
1396 |
|
1397 |
+
# Deity events
|
1398 |
+
generate_deity_btn.click(
|
1399 |
+
create_deity,
|
1400 |
+
inputs=[deity_domain, deity_alignment, deity_gender, pantheon_theme],
|
1401 |
+
outputs=[deity_output]
|
1402 |
+
)
|
1403 |
+
|
1404 |
+
deity_art_btn.click(
|
1405 |
+
generate_deity_art,
|
1406 |
+
inputs=[deity_domain, deity_gender, pantheon_theme],
|
1407 |
+
outputs=[deity_image]
|
1408 |
+
)
|
1409 |
+
|
1410 |
+
generate_pantheon_btn.click(
|
1411 |
+
create_pantheon,
|
1412 |
+
inputs=[pantheon_theme_full, pantheon_size, cultural_influence],
|
1413 |
+
outputs=[pantheon_output]
|
1414 |
+
)
|
1415 |
+
|
1416 |
+
pantheon_visual_btn.click(
|
1417 |
+
generate_pantheon_art,
|
1418 |
+
inputs=[pantheon_theme_full, cultural_influence],
|
1419 |
+
outputs=[pantheon_image]
|
1420 |
+
)
|
1421 |
+
|
1422 |
# Loot events
|
1423 |
generate_loot_btn.click(
|
1424 |
create_loot_table,
|
|
|
1443 |
gen_encounter_btn.click(generate_random_encounter, inputs=[encounter_level, encounter_difficulty], outputs=[random_encounter])
|
1444 |
gen_hook_btn.click(generate_plot_hook, inputs=[hook_theme], outputs=[plot_hook])
|
1445 |
gen_weather_btn.click(generate_weather, inputs=[climate], outputs=[weather])
|
1446 |
+
|
1447 |
+
# Additional missing event handlers
|
1448 |
+
|
1449 |
+
# Export character functionality
|
1450 |
+
def export_character_json(character_data):
|
1451 |
+
if not character_data:
|
1452 |
+
return None
|
1453 |
+
|
1454 |
+
try:
|
1455 |
+
# Convert character to dict for JSON export
|
1456 |
+
char_dict = {
|
1457 |
+
"name": character_data.name,
|
1458 |
+
"race": character_data.race,
|
1459 |
+
"class": character_data.character_class,
|
1460 |
+
"level": character_data.level,
|
1461 |
+
"gender": character_data.gender,
|
1462 |
+
"alignment": character_data.alignment.value,
|
1463 |
+
"abilities": character_data.abilities,
|
1464 |
+
"hit_points": character_data.hit_points,
|
1465 |
+
"skills": character_data.skills,
|
1466 |
+
"background": character_data.background,
|
1467 |
+
"backstory": character_data.backstory
|
1468 |
+
}
|
1469 |
+
|
1470 |
+
# Create temporary file
|
1471 |
+
import tempfile
|
1472 |
+
import json
|
1473 |
+
|
1474 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
|
1475 |
+
json.dump(char_dict, f, indent=2)
|
1476 |
+
return f.name
|
1477 |
+
|
1478 |
+
except Exception as e:
|
1479 |
+
logger.error(f"Character export failed: {e}")
|
1480 |
+
return None
|
1481 |
+
|
1482 |
+
export_btn.click(
|
1483 |
+
export_character_json,
|
1484 |
+
inputs=[character_data],
|
1485 |
+
outputs=[export_file]
|
1486 |
+
)
|
1487 |
+
|
1488 |
+
# Session notes functionality
|
1489 |
+
def save_session_notes(session_date, key_events, npc_interactions, player_actions, next_session_prep):
|
1490 |
+
try:
|
1491 |
+
import tempfile
|
1492 |
+
|
1493 |
+
notes_content = f"""# {session_date}
|
1494 |
+
|
1495 |
+
## Key Events
|
1496 |
+
{key_events}
|
1497 |
+
|
1498 |
+
## NPC Interactions
|
1499 |
+
{npc_interactions}
|
1500 |
+
|
1501 |
+
## Notable Player Actions
|
1502 |
+
{player_actions}
|
1503 |
+
|
1504 |
+
## Next Session Preparation
|
1505 |
+
{next_session_prep}
|
1506 |
+
|
1507 |
+
---
|
1508 |
+
*Generated by D&D Campaign Manager*
|
1509 |
+
"""
|
1510 |
+
|
1511 |
+
with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False) as f:
|
1512 |
+
f.write(notes_content)
|
1513 |
+
return f.name
|
1514 |
+
|
1515 |
+
except Exception as e:
|
1516 |
+
logger.error(f"Session notes save failed: {e}")
|
1517 |
+
return None
|
1518 |
+
|
1519 |
+
save_notes_btn.click(
|
1520 |
+
save_session_notes,
|
1521 |
+
inputs=[session_date, key_events, npc_interactions, player_actions, next_session_prep],
|
1522 |
+
outputs=[notes_output]
|
1523 |
+
)
|
1524 |
+
|
1525 |
+
# Initiative tracker functionality
|
1526 |
+
initiative_data = gr.State([])
|
1527 |
+
|
1528 |
+
def add_to_initiative(current_data, char_name, initiative_roll):
|
1529 |
+
if not char_name:
|
1530 |
+
return current_data
|
1531 |
+
|
1532 |
+
if current_data is None:
|
1533 |
+
current_data = []
|
1534 |
+
|
1535 |
+
# Add new character
|
1536 |
+
new_entry = [char_name, int(initiative_roll), "Full", "10", "Active"]
|
1537 |
+
current_data.append(new_entry)
|
1538 |
+
|
1539 |
+
# Sort by initiative (descending)
|
1540 |
+
current_data.sort(key=lambda x: x[1], reverse=True)
|
1541 |
+
|
1542 |
+
return current_data
|
1543 |
+
|
1544 |
+
def reset_initiative():
|
1545 |
+
return []
|
1546 |
+
|
1547 |
+
add_btn.click(
|
1548 |
+
add_to_initiative,
|
1549 |
+
inputs=[initiative_data, add_character, add_initiative],
|
1550 |
+
outputs=[initiative_data]
|
1551 |
+
)
|
1552 |
+
|
1553 |
+
# Update the dataframe when initiative_data changes
|
1554 |
+
initiative_data.change(
|
1555 |
+
lambda data: data,
|
1556 |
+
inputs=[initiative_data],
|
1557 |
+
outputs=[initiative_list]
|
1558 |
+
)
|
1559 |
+
|
1560 |
+
reset_combat_btn.click(
|
1561 |
+
reset_initiative,
|
1562 |
+
outputs=[initiative_data]
|
1563 |
+
)
|
1564 |
|
1565 |
return demo
|
1566 |
|
|
|
1589 |
except Exception as e:
|
1590 |
logger.error(f"❌ Launch failed: {e}")
|
1591 |
# Fallback launch for HF Spaces
|
1592 |
+
demo.launch()
|