ghost-logic commited on
Commit
0ac3c34
Β·
verified Β·
1 Parent(s): 5786d24

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +1214 -4
app.py CHANGED
@@ -1,7 +1,1217 @@
 
 
1
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
- def greet(name):
4
- return "Hello " + name + "!!"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
- demo = gr.Interface(fn=greet, inputs="text", outputs="text")
7
- demo.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - Expanded D&D Campaign and Character Creator with AI Agents
2
+
3
  import gradio as gr
4
+ import logging
5
+ from typing import Dict, List, Tuple, Optional
6
+ import json
7
+ import os
8
+ from dotenv import load_dotenv
9
+ from dataclasses import dataclass
10
+ from enum import Enum
11
+ import random
12
+
13
+ # Load environment variables
14
+ load_dotenv()
15
+
16
+ # Set up logging
17
+ logging.basicConfig(level=logging.INFO)
18
+ logger = logging.getLogger(__name__)
19
+
20
+ # ===== DATA MODELS =====
21
+ class Alignment(Enum):
22
+ LAWFUL_GOOD = "Lawful Good"
23
+ NEUTRAL_GOOD = "Neutral Good"
24
+ CHAOTIC_GOOD = "Chaotic Good"
25
+ LAWFUL_NEUTRAL = "Lawful Neutral"
26
+ TRUE_NEUTRAL = "True Neutral"
27
+ CHAOTIC_NEUTRAL = "Chaotic Neutral"
28
+ LAWFUL_EVIL = "Lawful Evil"
29
+ NEUTRAL_EVIL = "Neutral Evil"
30
+ CHAOTIC_EVIL = "Chaotic Evil"
31
+
32
+ @dataclass
33
+ class CharacterClass:
34
+ name: str
35
+ hit_die: int
36
+ primary_ability: List[str]
37
+ saving_throws: List[str]
38
+ skills: List[str]
39
+
40
+ @dataclass
41
+ class Race:
42
+ name: str
43
+ ability_modifiers: Dict[str, int]
44
+ traits: List[str]
45
+ languages: List[str]
46
+
47
+ @dataclass
48
+ class Character:
49
+ name: str
50
+ race: str
51
+ character_class: str
52
+ level: int
53
+ alignment: Alignment
54
+ abilities: Dict[str, int]
55
+ hit_points: int
56
+ skills: List[str]
57
+ background: str
58
+ backstory: str
59
+ portrait_url: Optional[str] = None
60
+
61
+ @dataclass
62
+ class Campaign:
63
+ name: str
64
+ theme: str
65
+ level_range: str
66
+ description: str
67
+ locations: List[str]
68
+ npcs: List[str]
69
+ plot_hooks: List[str]
70
+
71
+ @dataclass
72
+ class NPC:
73
+ name: str
74
+ race: str
75
+ occupation: str
76
+ personality: str
77
+ secret: str
78
+ voice_description: str
79
+ relationship_to_party: str
80
+
81
+ # ===== AI AGENT CLASSES =====
82
+ class DungeonMasterAgent:
83
+ """AI agent that acts as a Dungeon Master"""
84
+
85
+ def __init__(self):
86
+ self.personality = "Creative, fair, and engaging storyteller"
87
+ self.knowledge_areas = ["D&D rules", "storytelling", "world-building", "character development"]
88
+
89
+ def generate_campaign_concept(self, theme: str, level: int, player_count: int) -> Dict:
90
+ """Generate a complete campaign concept"""
91
+ try:
92
+ from openai import OpenAI
93
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
94
+
95
+ prompt = f"""As an experienced D&D Dungeon Master, create a campaign concept for:
96
+ Theme: {theme}
97
+ Player Level: {level}
98
+ Number of Players: {player_count}
99
+
100
+ Provide:
101
+ 1. Campaign Name
102
+ 2. Core Plot Hook (2-3 sentences)
103
+ 3. Main Antagonist
104
+ 4. 3 Key Locations
105
+ 5. Central Conflict
106
+ 6. Estimated Campaign Length
107
+ 7. Unique Elements/Mechanics
108
+
109
+ Make it engaging and ready to play!"""
110
+
111
+ response = client.chat.completions.create(
112
+ model="gpt-4",
113
+ messages=[{"role": "system", "content": "You are an expert D&D Dungeon Master with 20 years of experience creating memorable campaigns."},
114
+ {"role": "user", "content": prompt}],
115
+ max_tokens=500,
116
+ temperature=0.8
117
+ )
118
+
119
+ content = response.choices[0].message.content
120
+ return {"success": True, "content": content}
121
+
122
+ except Exception as e:
123
+ logger.error(f"Campaign generation failed: {e}")
124
+ return {"success": False, "error": str(e)}
125
+
126
+ def generate_session_content(self, campaign_context: str, session_number: int) -> Dict:
127
+ """Generate content for a specific session"""
128
+ try:
129
+ from openai import OpenAI
130
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
131
+
132
+ prompt = f"""Create session {session_number} content for this campaign:
133
+ {campaign_context}
134
+
135
+ Generate:
136
+ 1. Session Opening (scene description)
137
+ 2. 3 Potential Encounters (combat, social, exploration)
138
+ 3. Key NPCs for this session
139
+ 4. Skill challenges or puzzles
140
+ 5. Potential plot developments
141
+ 6. Cliffhanger ending options
142
+
143
+ Make it detailed enough for a DM to run immediately."""
144
+
145
+ response = client.chat.completions.create(
146
+ model="gpt-4",
147
+ messages=[{"role": "system", "content": "You are a D&D Dungeon Master preparing detailed session content."},
148
+ {"role": "user", "content": prompt}],
149
+ max_tokens=600,
150
+ temperature=0.7
151
+ )
152
+
153
+ return {"success": True, "content": response.choices[0].message.content}
154
+
155
+ except Exception as e:
156
+ logger.error(f"Session generation failed: {e}")
157
+ return {"success": False, "error": str(e)}
158
+
159
+ class NPCAgent:
160
+ """AI agent specialized in creating and roleplaying NPCs"""
161
+
162
+ def __init__(self):
163
+ self.personality = "Versatile character actor with deep understanding of motivations"
164
+ self.specializations = ["Character creation", "Dialogue", "Motivations", "Voice acting"]
165
+
166
+ def generate_npc(self, context: str, role: str, importance: str) -> Dict:
167
+ """Generate a detailed NPC"""
168
+ try:
169
+ from openai import OpenAI
170
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
171
+
172
+ prompt = f"""Create a detailed NPC for:
173
+ Context: {context}
174
+ Role: {role}
175
+ Importance: {importance}
176
+
177
+ Generate:
178
+ 1. Name and basic demographics
179
+ 2. Personality traits (3-4 key traits)
180
+ 3. Background and motivation
181
+ 4. Speech patterns/accent description
182
+ 5. Physical description
183
+ 6. Secret or hidden agenda
184
+ 7. How they react to different party approaches
185
+ 8. Potential quest hooks they could provide
186
+
187
+ Make them memorable and three-dimensional!"""
188
+
189
+ response = client.chat.completions.create(
190
+ model="gpt-4",
191
+ messages=[{"role": "system", "content": "You are an expert at creating memorable, three-dimensional NPCs for D&D campaigns."},
192
+ {"role": "user", "content": prompt}],
193
+ max_tokens=400,
194
+ temperature=0.8
195
+ )
196
+
197
+ return {"success": True, "content": response.choices[0].message.content}
198
+
199
+ except Exception as e:
200
+ logger.error(f"NPC generation failed: {e}")
201
+ return {"success": False, "error": str(e)}
202
+
203
+ def roleplay_npc(self, npc_description: str, player_input: str, context: str) -> Dict:
204
+ """Roleplay as an NPC in response to player actions"""
205
+ try:
206
+ from openai import OpenAI
207
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
208
+
209
+ prompt = f"""You are roleplaying as this NPC:
210
+ {npc_description}
211
+
212
+ Context: {context}
213
+ Player says/does: {player_input}
214
+
215
+ Respond in character with:
216
+ 1. Dialogue (in quotes)
217
+ 2. Actions/body language (in italics)
218
+ 3. Internal thoughts/motivations (in parentheses)
219
+
220
+ Stay true to the character's personality and motivations!"""
221
+
222
+ response = client.chat.completions.create(
223
+ model="gpt-4",
224
+ messages=[{"role": "system", "content": "You are a skilled voice actor and D&D player, staying in character as NPCs."},
225
+ {"role": "user", "content": prompt}],
226
+ max_tokens=200,
227
+ temperature=0.9
228
+ )
229
+
230
+ return {"success": True, "content": response.choices[0].message.content}
231
+
232
+ except Exception as e:
233
+ logger.error(f"NPC roleplay failed: {e}")
234
+ return {"success": False, "error": str(e)}
235
+
236
+ class WorldBuilderAgent:
237
+ """AI agent focused on creating consistent world elements"""
238
+
239
+ def __init__(self):
240
+ self.personality = "Detail-oriented architect of fictional worlds"
241
+ self.specializations = ["Geography", "Politics", "Culture", "History", "Economics"]
242
+
243
+ def generate_location(self, location_type: str, theme: str, purpose: str) -> Dict:
244
+ """Generate a detailed location"""
245
+ try:
246
+ from openai import OpenAI
247
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
248
+
249
+ prompt = f"""Create a detailed {location_type} with:
250
+ Theme: {theme}
251
+ Purpose in campaign: {purpose}
252
+
253
+ Generate:
254
+ 1. Name and general description
255
+ 2. Key areas/rooms (at least 5)
256
+ 3. Notable inhabitants
257
+ 4. Hidden secrets or mysteries
258
+ 5. Potential dangers or challenges
259
+ 6. Valuable resources or rewards
260
+ 7. Connections to broader world/campaign
261
+ 8. Sensory details (sights, sounds, smells)
262
+
263
+ Make it feel lived-in and realistic!"""
264
+
265
+ response = client.chat.completions.create(
266
+ model="gpt-4",
267
+ messages=[{"role": "system", "content": "You are a master world-builder creating immersive D&D locations."},
268
+ {"role": "user", "content": prompt}],
269
+ max_tokens=500,
270
+ temperature=0.7
271
+ )
272
+
273
+ return {"success": True, "content": response.choices[0].message.content}
274
+
275
+ except Exception as e:
276
+ logger.error(f"Location generation failed: {e}")
277
+ return {"success": False, "error": str(e)}
278
+
279
+ class LootMasterAgent:
280
+ """AI agent specialized in creating balanced loot and magic items"""
281
+
282
+ def __init__(self):
283
+ self.personality = "Meticulous curator of magical treasures"
284
+ self.specializations = ["Game balance", "Magic item design", "Treasure distribution"]
285
+
286
+ def generate_loot_table(self, level: int, encounter_type: str, rarity: str) -> Dict:
287
+ """Generate a balanced loot table"""
288
+ try:
289
+ from openai import OpenAI
290
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
291
+
292
+ prompt = f"""Create a balanced loot table for:
293
+ Party Level: {level}
294
+ Encounter Type: {encounter_type}
295
+ Rarity Level: {rarity}
296
+
297
+ Generate:
298
+ 1. Gold/Currency amounts
299
+ 2. Common items (consumables, gear)
300
+ 3. Uncommon magical items (if appropriate)
301
+ 4. Rare items (if high level)
302
+ 5. Unique/plot-relevant items
303
+ 6. Alternative treasures (information, allies, etc.)
304
+
305
+ Ensure balance and appropriateness for the level!"""
306
+
307
+ response = client.chat.completions.create(
308
+ model="gpt-4",
309
+ messages=[{"role": "system", "content": "You are an expert at D&D game balance and treasure design."},
310
+ {"role": "user", "content": prompt}],
311
+ max_tokens=300,
312
+ temperature=0.6
313
+ )
314
+
315
+ return {"success": True, "content": response.choices[0].message.content}
316
+
317
+ except Exception as e:
318
+ logger.error(f"Loot generation failed: {e}")
319
+ return {"success": False, "error": str(e)}
320
+
321
+ def create_custom_magic_item(self, item_concept: str, power_level: str, campaign_theme: str) -> Dict:
322
+ """Create a custom magic item"""
323
+ try:
324
+ from openai import OpenAI
325
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
326
+
327
+ prompt = f"""Design a custom magic item:
328
+ Concept: {item_concept}
329
+ Power Level: {power_level}
330
+ Campaign Theme: {campaign_theme}
331
+
332
+ Provide:
333
+ 1. Item name and basic description
334
+ 2. Mechanical effects (stats, abilities)
335
+ 3. Activation requirements
336
+ 4. Rarity and attunement needs
337
+ 5. Physical appearance
338
+ 6. Historical background/lore
339
+ 7. Potential drawbacks or limitations
340
+ 8. How it fits the campaign theme
341
+
342
+ Make it balanced and interesting!"""
343
+
344
+ response = client.chat.completions.create(
345
+ model="gpt-4",
346
+ messages=[{"role": "system", "content": "You are a master craftsperson of magical items for D&D, balancing power with narrative interest."},
347
+ {"role": "user", "content": prompt}],
348
+ max_tokens=400,
349
+ temperature=0.7
350
+ )
351
+
352
+ return {"success": True, "content": response.choices[0].message.content}
353
+
354
+ except Exception as e:
355
+ logger.error(f"Magic item creation failed: {e}")
356
+ return {"success": False, "error": str(e)}
357
+
358
+ # ===== CHARACTER CREATOR CLASS =====
359
+ class CharacterCreator:
360
+ """Enhanced character creator with AI integration"""
361
+
362
+ def __init__(self):
363
+ self.classes = self._get_default_classes()
364
+ self.races = self._get_default_races()
365
+ self.backgrounds = self._get_backgrounds()
366
+
367
+ def _get_default_classes(self) -> Dict[str, CharacterClass]:
368
+ return {
369
+ "Fighter": CharacterClass(
370
+ name="Fighter", hit_die=10,
371
+ primary_ability=["Strength", "Dexterity"],
372
+ saving_throws=["Strength", "Constitution"],
373
+ skills=["Acrobatics", "Animal Handling", "Athletics", "History", "Insight", "Intimidation", "Perception", "Survival"]
374
+ ),
375
+ "Wizard": CharacterClass(
376
+ name="Wizard", hit_die=6,
377
+ primary_ability=["Intelligence"],
378
+ saving_throws=["Intelligence", "Wisdom"],
379
+ skills=["Arcana", "History", "Insight", "Investigation", "Medicine", "Religion"]
380
+ ),
381
+ "Rogue": CharacterClass(
382
+ name="Rogue", hit_die=8,
383
+ primary_ability=["Dexterity"],
384
+ saving_throws=["Dexterity", "Intelligence"],
385
+ skills=["Acrobatics", "Athletics", "Deception", "Insight", "Intimidation", "Investigation", "Perception", "Performance", "Persuasion", "Sleight of Hand", "Stealth"]
386
+ ),
387
+ "Cleric": CharacterClass(
388
+ name="Cleric", hit_die=8,
389
+ primary_ability=["Wisdom"],
390
+ saving_throws=["Wisdom", "Charisma"],
391
+ skills=["History", "Insight", "Medicine", "Persuasion", "Religion"]
392
+ ),
393
+ "Barbarian": CharacterClass(
394
+ name="Barbarian", hit_die=12,
395
+ primary_ability=["Strength"],
396
+ saving_throws=["Strength", "Constitution"],
397
+ skills=["Animal Handling", "Athletics", "Intimidation", "Nature", "Perception", "Survival"]
398
+ ),
399
+ "Bard": CharacterClass(
400
+ name="Bard", hit_die=8,
401
+ primary_ability=["Charisma"],
402
+ saving_throws=["Dexterity", "Charisma"],
403
+ skills=["Any three of your choice"]
404
+ ),
405
+ "Druid": CharacterClass(
406
+ name="Druid", hit_die=8,
407
+ primary_ability=["Wisdom"],
408
+ saving_throws=["Intelligence", "Wisdom"],
409
+ skills=["Arcana", "Animal Handling", "Insight", "Medicine", "Nature", "Perception", "Religion", "Survival"]
410
+ ),
411
+ "Monk": CharacterClass(
412
+ name="Monk", hit_die=8,
413
+ primary_ability=["Dexterity", "Wisdom"],
414
+ saving_throws=["Strength", "Dexterity"],
415
+ skills=["Acrobatics", "Athletics", "History", "Insight", "Religion", "Stealth"]
416
+ ),
417
+ "Paladin": CharacterClass(
418
+ name="Paladin", hit_die=10,
419
+ primary_ability=["Strength", "Charisma"],
420
+ saving_throws=["Wisdom", "Charisma"],
421
+ skills=["Athletics", "Insight", "Intimidation", "Medicine", "Persuasion", "Religion"]
422
+ ),
423
+ "Ranger": CharacterClass(
424
+ name="Ranger", hit_die=10,
425
+ primary_ability=["Dexterity", "Wisdom"],
426
+ saving_throws=["Strength", "Dexterity"],
427
+ skills=["Animal Handling", "Athletics", "Insight", "Investigation", "Nature", "Perception", "Stealth", "Survival"]
428
+ ),
429
+ "Sorcerer": CharacterClass(
430
+ name="Sorcerer", hit_die=6,
431
+ primary_ability=["Charisma"],
432
+ saving_throws=["Constitution", "Charisma"],
433
+ skills=["Arcana", "Deception", "Insight", "Intimidation", "Persuasion", "Religion"]
434
+ ),
435
+ "Warlock": CharacterClass(
436
+ name="Warlock", hit_die=8,
437
+ primary_ability=["Charisma"],
438
+ saving_throws=["Wisdom", "Charisma"],
439
+ skills=["Arcana", "Deception", "History", "Intimidation", "Investigation", "Nature", "Religion"]
440
+ )
441
+ }
442
+
443
+ def _get_default_races(self) -> Dict[str, Race]:
444
+ return {
445
+ "Human": Race(
446
+ name="Human",
447
+ ability_modifiers={"All": 1},
448
+ traits=["Extra Language", "Extra Skill", "Versatile"],
449
+ languages=["Common", "One other"]
450
+ ),
451
+ "Elf": Race(
452
+ name="Elf",
453
+ ability_modifiers={"Dexterity": 2},
454
+ traits=["Darkvision", "Keen Senses", "Fey Ancestry", "Trance"],
455
+ languages=["Common", "Elvish"]
456
+ ),
457
+ "Dwarf": Race(
458
+ name="Dwarf",
459
+ ability_modifiers={"Constitution": 2},
460
+ traits=["Darkvision", "Dwarven Resilience", "Stonecunning"],
461
+ languages=["Common", "Dwarvish"]
462
+ ),
463
+ "Halfling": Race(
464
+ name="Halfling",
465
+ ability_modifiers={"Dexterity": 2},
466
+ traits=["Lucky", "Brave", "Halfling Nimbleness"],
467
+ languages=["Common", "Halfling"]
468
+ ),
469
+ "Dragonborn": Race(
470
+ name="Dragonborn",
471
+ ability_modifiers={"Strength": 2, "Charisma": 1},
472
+ traits=["Draconic Ancestry", "Breath Weapon", "Damage Resistance"],
473
+ languages=["Common", "Draconic"]
474
+ ),
475
+ "Gnome": Race(
476
+ name="Gnome",
477
+ ability_modifiers={"Intelligence": 2},
478
+ traits=["Darkvision", "Gnome Cunning"],
479
+ languages=["Common", "Gnomish"]
480
+ ),
481
+ "Half-Elf": Race(
482
+ name="Half-Elf",
483
+ ability_modifiers={"Charisma": 2, "Choice": 1},
484
+ traits=["Darkvision", "Fey Ancestry", "Two Skills"],
485
+ languages=["Common", "Elvish", "One other"]
486
+ ),
487
+ "Half-Orc": Race(
488
+ name="Half-Orc",
489
+ ability_modifiers={"Strength": 2, "Constitution": 1},
490
+ traits=["Darkvision", "Relentless Endurance", "Savage Attacks"],
491
+ languages=["Common", "Orc"]
492
+ ),
493
+ "Tiefling": Race(
494
+ name="Tiefling",
495
+ ability_modifiers={"Intelligence": 1, "Charisma": 2},
496
+ traits=["Darkvision", "Hellish Resistance", "Infernal Legacy"],
497
+ languages=["Common", "Infernal"]
498
+ )
499
+ }
500
+
501
+ def _get_backgrounds(self) -> List[str]:
502
+ return [
503
+ "Acolyte", "Criminal", "Folk Hero", "Noble", "Sage", "Soldier",
504
+ "Charlatan", "Entertainer", "Guild Artisan", "Hermit", "Outlander", "Sailor"
505
+ ]
506
+
507
+ def roll_ability_scores(self) -> Dict[str, int]:
508
+ """Roll 4d6, drop lowest, for each ability score"""
509
+ abilities = {}
510
+ for ability in ["Strength", "Dexterity", "Constitution", "Intelligence", "Wisdom", "Charisma"]:
511
+ rolls = [random.randint(1, 6) for _ in range(4)]
512
+ rolls.sort(reverse=True)
513
+ abilities[ability] = sum(rolls[:3]) # Take top 3
514
+ return abilities
515
+
516
+ def calculate_ability_modifier(self, score: int) -> int:
517
+ """Calculate ability modifier from score"""
518
+ return (score - 10) // 2
519
+
520
+ def calculate_hit_points(self, char_class: str, level: int, constitution_modifier: int) -> int:
521
+ """Calculate hit points based on class, level, and CON modifier"""
522
+ hit_die = self.classes[char_class].hit_die
523
+ base_hp = hit_die + constitution_modifier # Max HP at level 1
524
+
525
+ # Add average HP for additional levels
526
+ for _ in range(level - 1):
527
+ base_hp += (hit_die // 2 + 1) + constitution_modifier
528
+
529
+ return max(1, base_hp) # Minimum 1 HP
530
+
531
+ def apply_racial_modifiers(self, base_abilities: Dict[str, int], race: str) -> Dict[str, int]:
532
+ """Apply racial ability score modifiers"""
533
+ modified_abilities = base_abilities.copy()
534
+ race_data = self.races[race]
535
+
536
+ for ability, modifier in race_data.ability_modifiers.items():
537
+ if ability == "All":
538
+ for ability_name in modified_abilities:
539
+ modified_abilities[ability_name] += modifier
540
+ elif ability == "Choice":
541
+ # For simplicity, add to lowest score
542
+ lowest_ability = min(modified_abilities, key=modified_abilities.get)
543
+ modified_abilities[lowest_ability] += modifier
544
+ else:
545
+ modified_abilities[ability] += modifier
546
+
547
+ return modified_abilities
548
+
549
+ # ===== IMAGE GENERATION =====
550
+ def generate_image(prompt: str) -> str:
551
+ """Generate image using OpenAI DALL-E"""
552
+ try:
553
+ from openai import OpenAI
554
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
555
+
556
+ response = client.images.generate(
557
+ model="dall-e-3",
558
+ prompt=prompt,
559
+ size="1024x1024",
560
+ quality="standard",
561
+ n=1,
562
+ )
563
+
564
+ return response.data[0].url
565
+
566
+ except Exception as e:
567
+ logger.error(f"Image generation failed: {e}")
568
+ return "https://via.placeholder.com/512x512/dc2626/ffffff?text=Image+Generation+Failed"
569
+
570
+ # ===== MAIN INTERFACE =====
571
+ def create_main_interface():
572
+ """Create the main D&D Campaign Manager interface"""
573
+
574
+ # Initialize agents
575
+ dm_agent = DungeonMasterAgent()
576
+ npc_agent = NPCAgent()
577
+ world_agent = WorldBuilderAgent()
578
+ loot_agent = LootMasterAgent()
579
+ character_creator = CharacterCreator()
580
+
581
+ custom_css = """
582
+ .agent-card {
583
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
584
+ padding: 20px;
585
+ border-radius: 15px;
586
+ margin: 10px 0;
587
+ color: white;
588
+ }
589
+
590
+ .output-box {
591
+ background: #f8f9fa;
592
+ border: 1px solid #dee2e6;
593
+ border-radius: 10px;
594
+ padding: 15px;
595
+ margin: 10px 0;
596
+ }
597
+ """
598
+
599
+ with gr.Blocks(css=custom_css, title="D&D Campaign Manager") as demo:
600
+ gr.Markdown("""
601
+ # 🏰 Advanced D&D Campaign Manager
602
+
603
+ *Your AI-powered toolkit for epic adventures*
604
+
605
+ **Features:**
606
+ - 🎭 AI Dungeon Master Assistant
607
+ - πŸ‘₯ Intelligent NPC Creator & Roleplay
608
+ - πŸ—ΊοΈ World Builder Agent
609
+ - πŸ’° Loot Master & Magic Item Designer
610
+ - πŸ‰ Enhanced Character Creator
611
+ """)
612
+
613
+ with gr.Tabs():
614
+ # ===== CHARACTER CREATOR TAB =====
615
+ with gr.TabItem("πŸ‰ Character Creator"):
616
+ with gr.Row():
617
+ with gr.Column(scale=2):
618
+ gr.Markdown("## πŸ“ Character Details")
619
+
620
+ character_name = gr.Textbox(label="Character Name", placeholder="Enter name...")
621
+
622
+ with gr.Row():
623
+ race_dropdown = gr.Dropdown(
624
+ choices=list(character_creator.races.keys()),
625
+ label="Race", value="Human"
626
+ )
627
+ class_dropdown = gr.Dropdown(
628
+ choices=list(character_creator.classes.keys()),
629
+ label="Class", value="Fighter"
630
+ )
631
+
632
+ with gr.Row():
633
+ level_slider = gr.Slider(minimum=1, maximum=20, step=1, value=1, label="Level")
634
+ alignment_dropdown = gr.Dropdown(
635
+ choices=[alignment.value for alignment in Alignment],
636
+ label="Alignment", value=Alignment.LAWFUL_GOOD.value
637
+ )
638
+
639
+ background_dropdown = gr.Dropdown(
640
+ choices=character_creator._get_backgrounds(),
641
+ label="Background", value="Folk Hero"
642
+ )
643
+
644
+ # AI Enhancement Buttons
645
+ with gr.Row():
646
+ generate_name_btn = gr.Button("✨ AI Generate Name", variant="secondary")
647
+ generate_backstory_btn = gr.Button("πŸ“š AI Generate Backstory", variant="secondary")
648
+
649
+ backstory = gr.Textbox(label="Backstory", lines=4, placeholder="Character background...")
650
+
651
+ # Ability Scores
652
+ gr.Markdown("## 🎲 Ability Scores")
653
+ roll_btn = gr.Button("🎲 Roll Ability Scores", variant="primary")
654
+
655
+ with gr.Row():
656
+ str_score = gr.Number(label="Strength", value=10, precision=0)
657
+ dex_score = gr.Number(label="Dexterity", value=10, precision=0)
658
+ con_score = gr.Number(label="Constitution", value=10, precision=0)
659
+
660
+ with gr.Row():
661
+ int_score = gr.Number(label="Intelligence", value=10, precision=0)
662
+ wis_score = gr.Number(label="Wisdom", value=10, precision=0)
663
+ cha_score = gr.Number(label="Charisma", value=10, precision=0)
664
+
665
+ with gr.Column(scale=1):
666
+ gr.Markdown("## πŸ“Š Character Summary")
667
+ character_summary = gr.Markdown("*Create character to see summary*")
668
+
669
+ gr.Markdown("## 🎨 Character Portrait")
670
+ portrait_btn = gr.Button("🎨 Generate AI Portrait", variant="primary")
671
+ character_portrait = gr.Image(label="Portrait", height=300)
672
+
673
+ gr.Markdown("## πŸ’Ύ Export")
674
+ export_btn = gr.Button("πŸ“₯ Export Character")
675
+ export_file = gr.File(label="Character JSON")
676
+
677
+ # Hidden state
678
+ character_data = gr.State()
679
+
680
+ # ===== CAMPAIGN CREATOR TAB =====
681
+ with gr.TabItem("🎭 AI Dungeon Master"):
682
+ gr.Markdown("## 🎯 Campaign Generation", elem_classes=["agent-card"])
683
+
684
+ with gr.Row():
685
+ with gr.Column():
686
+ campaign_theme = gr.Dropdown(
687
+ choices=["High Fantasy", "Dark Fantasy", "Urban Fantasy", "Steampunk", "Horror", "Comedy", "Political Intrigue", "Exploration"],
688
+ label="Campaign Theme", value="High Fantasy"
689
+ )
690
+ campaign_level = gr.Slider(minimum=1, maximum=20, value=5, label="Starting Level")
691
+ player_count = gr.Slider(minimum=1, maximum=8, value=4, label="Number of Players")
692
+
693
+ generate_campaign_btn = gr.Button("🎲 Generate Campaign Concept", variant="primary", size="lg")
694
+
695
+ with gr.Column():
696
+ campaign_visual_btn = gr.Button("πŸ–ΌοΈ Generate Campaign Art")
697
+ campaign_image = gr.Image(label="Campaign Visual", height=300)
698
+
699
+ campaign_output = gr.Textbox(label="Campaign Concept", lines=10, elem_classes=["output-box"])
700
+
701
+ gr.Markdown("## πŸ“… Session Planning")
702
+ with gr.Row():
703
+ session_number = gr.Number(label="Session Number", value=1, precision=0)
704
+ generate_session_btn = gr.Button("πŸ“ Generate Session Content", variant="secondary")
705
+
706
+ session_output = gr.Textbox(label="Session Content", lines=8, elem_classes=["output-box"])
707
+
708
+ # ===== NPC CREATOR TAB =====
709
+ with gr.TabItem("πŸ‘₯ NPC Agent"):
710
+ gr.Markdown("## 🎭 NPC Creator & Roleplay Assistant", elem_classes=["agent-card"])
711
+
712
+ with gr.Row():
713
+ with gr.Column():
714
+ gr.Markdown("### Create New NPC")
715
+ npc_context = gr.Textbox(label="Campaign/Scene Context", placeholder="Describe the setting or situation...")
716
+ npc_role = gr.Dropdown(
717
+ choices=["Ally", "Neutral", "Antagonist", "Quest Giver", "Merchant", "Authority Figure", "Mysterious Stranger"],
718
+ label="NPC Role", value="Neutral"
719
+ )
720
+ npc_importance = gr.Dropdown(
721
+ choices=["Minor", "Moderate", "Major", "Recurring"],
722
+ label="Importance Level", value="Moderate"
723
+ )
724
+
725
+ create_npc_btn = gr.Button("🎭 Generate NPC", variant="primary")
726
+
727
+ npc_portrait_btn = gr.Button("🎨 Generate NPC Portrait")
728
+ npc_portrait = gr.Image(label="NPC Portrait", height=300)
729
+
730
+ with gr.Column():
731
+ npc_output = gr.Textbox(label="Generated NPC", lines=12, elem_classes=["output-box"])
732
+
733
+ gr.Markdown("### πŸ’¬ NPC Roleplay")
734
+ with gr.Row():
735
+ with gr.Column(scale=2):
736
+ player_input = gr.Textbox(label="Player Action/Dialogue", placeholder="What does the player say or do?")
737
+ roleplay_context = gr.Textbox(label="Scene Context", placeholder="Describe the current situation...")
738
+
739
+ with gr.Column(scale=1):
740
+ roleplay_btn = gr.Button("🎭 NPC Response", variant="secondary", size="lg")
741
+
742
+ npc_response = gr.Textbox(label="NPC Response", lines=4, elem_classes=["output-box"])
743
+
744
+ # Hidden states for NPC
745
+ current_npc_data = gr.State()
746
+
747
+ # ===== WORLD BUILDER TAB =====
748
+ with gr.TabItem("πŸ—ΊοΈ World Builder"):
749
+ gr.Markdown("## πŸ—οΈ Location & World Generator", elem_classes=["agent-card"])
750
+
751
+ with gr.Row():
752
+ with gr.Column():
753
+ location_type = gr.Dropdown(
754
+ choices=["Tavern", "Dungeon", "City", "Forest", "Castle", "Temple", "Shop", "Wilderness", "Mansion", "Cave System"],
755
+ label="Location Type", value="Tavern"
756
+ )
757
+ location_theme = gr.Dropdown(
758
+ choices=["Standard Fantasy", "Dark/Gothic", "Mystical", "Abandoned/Ruined", "Luxurious", "Dangerous", "Peaceful", "Mysterious"],
759
+ label="Theme", value="Standard Fantasy"
760
+ )
761
+ location_purpose = gr.Textbox(label="Purpose in Campaign", placeholder="How will this location be used?")
762
+
763
+ generate_location_btn = gr.Button("πŸ—οΈ Generate Location", variant="primary")
764
+ location_art_btn = gr.Button("πŸ–ΌοΈ Generate Location Art")
765
+
766
+ with gr.Column():
767
+ location_image = gr.Image(label="Location Visual", height=300)
768
+
769
+ location_output = gr.Textbox(label="Location Details", lines=12, elem_classes=["output-box"])
770
+
771
+ # ===== LOOT MASTER TAB =====
772
+ with gr.TabItem("πŸ’° Loot Master"):
773
+ gr.Markdown("## πŸ’Ž Treasure & Magic Item Generator", elem_classes=["agent-card"])
774
+
775
+ with gr.Row():
776
+ with gr.Column():
777
+ gr.Markdown("### 🎲 Loot Table Generator")
778
+ loot_level = gr.Slider(minimum=1, maximum=20, value=5, label="Party Level")
779
+ encounter_type = gr.Dropdown(
780
+ choices=["Boss Fight", "Mini-boss", "Standard Combat", "Exploration Reward", "Quest Completion", "Treasure Hoard"],
781
+ label="Encounter Type", value="Standard Combat"
782
+ )
783
+ loot_rarity = gr.Dropdown(
784
+ choices=["Poor", "Standard", "Rich", "Legendary"],
785
+ label="Treasure Quality", value="Standard"
786
+ )
787
+
788
+ generate_loot_btn = gr.Button("πŸ’° Generate Loot Table", variant="primary")
789
+
790
+ with gr.Column():
791
+ gr.Markdown("### ✨ Custom Magic Item")
792
+ item_concept = gr.Textbox(label="Item Concept", placeholder="What kind of item? (sword, ring, staff, etc.)")
793
+ power_level = gr.Dropdown(
794
+ choices=["Common", "Uncommon", "Rare", "Very Rare", "Legendary", "Artifact"],
795
+ label="Power Level", value="Uncommon"
796
+ )
797
+ campaign_theme_item = gr.Textbox(label="Campaign Theme", placeholder="How should it fit your campaign?")
798
+
799
+ create_item_btn = gr.Button("✨ Create Magic Item", variant="secondary")
800
+ item_art_btn = gr.Button("🎨 Generate Item Art")
801
+
802
+ with gr.Row():
803
+ loot_output = gr.Textbox(label="Loot Table", lines=8, elem_classes=["output-box"])
804
+ magic_item_output = gr.Textbox(label="Magic Item Details", lines=8, elem_classes=["output-box"])
805
+
806
+ item_image = gr.Image(label="Magic Item Visual", height=250)
807
+
808
+ # ===== CAMPAIGN TOOLS TAB =====
809
+ with gr.TabItem("πŸ› οΈ Campaign Tools"):
810
+ gr.Markdown("## 🎯 Advanced Campaign Management")
811
+
812
+ with gr.Tabs():
813
+ with gr.TabItem("πŸ“Š Initiative Tracker"):
814
+ gr.Markdown("### βš”οΈ Combat Management")
815
+
816
+ with gr.Row():
817
+ add_character = gr.Textbox(label="Character Name", placeholder="Add to initiative...")
818
+ add_initiative = gr.Number(label="Initiative Roll", value=10)
819
+ add_btn = gr.Button("βž• Add to Initiative")
820
+
821
+ initiative_list = gr.Dataframe(
822
+ headers=["Name", "Initiative", "HP", "AC", "Status"],
823
+ label="Initiative Order",
824
+ interactive=True
825
+ )
826
+
827
+ with gr.Row():
828
+ next_turn_btn = gr.Button("⏭️ Next Turn", variant="primary")
829
+ reset_combat_btn = gr.Button("πŸ”„ Reset Combat")
830
+
831
+ with gr.TabItem("πŸ“ Session Notes"):
832
+ gr.Markdown("### πŸ“– Session Management")
833
+
834
+ session_date = gr.Textbox(label="Session Date", value="Session 1")
835
+
836
+ with gr.Row():
837
+ with gr.Column():
838
+ key_events = gr.Textbox(label="Key Events", lines=4, placeholder="What happened this session?")
839
+ npc_interactions = gr.Textbox(label="NPC Interactions", lines=3, placeholder="Who did they meet?")
840
+
841
+ with gr.Column():
842
+ player_actions = gr.Textbox(label="Notable Player Actions", lines=4, placeholder="What did the players do?")
843
+ next_session_prep = gr.Textbox(label="Next Session Prep", lines=3, placeholder="What to prepare for next time?")
844
+
845
+ save_notes_btn = gr.Button("πŸ’Ύ Save Session Notes", variant="primary")
846
+ notes_output = gr.File(label="Session Notes File")
847
+
848
+ with gr.TabItem("🎲 Random Generators"):
849
+ gr.Markdown("### 🎯 Quick Generators")
850
+
851
+ with gr.Row():
852
+ with gr.Column():
853
+ gr.Markdown("**Name Generators**")
854
+ name_type = gr.Dropdown(
855
+ choices=["Human Male", "Human Female", "Elven", "Dwarven", "Orcish", "Fantasy Place", "Tavern", "Shop"],
856
+ value="Human Male"
857
+ )
858
+ gen_name_btn = gr.Button("🎲 Generate Name")
859
+ random_name = gr.Textbox(label="Generated Name")
860
+
861
+ with gr.Column():
862
+ gr.Markdown("**Quick Encounters**")
863
+ encounter_level = gr.Slider(1, 20, value=5, label="Party Level")
864
+ encounter_difficulty = gr.Dropdown(
865
+ choices=["Easy", "Medium", "Hard", "Deadly"],
866
+ value="Medium"
867
+ )
868
+ gen_encounter_btn = gr.Button("βš”οΈ Generate Encounter")
869
+ random_encounter = gr.Textbox(label="Encounter", lines=3)
870
+
871
+ with gr.Row():
872
+ with gr.Column():
873
+ gr.Markdown("**Plot Hooks**")
874
+ hook_theme = gr.Dropdown(
875
+ choices=["Mystery", "Adventure", "Political", "Personal", "Rescue", "Exploration"],
876
+ value="Adventure"
877
+ )
878
+ gen_hook_btn = gr.Button("🎣 Generate Plot Hook")
879
+ plot_hook = gr.Textbox(label="Plot Hook", lines=2)
880
+
881
+ with gr.Column():
882
+ gr.Markdown("**Weather & Atmosphere**")
883
+ climate = gr.Dropdown(
884
+ choices=["Temperate", "Tropical", "Arctic", "Desert", "Mountainous"],
885
+ value="Temperate"
886
+ )
887
+ gen_weather_btn = gr.Button("🌀️ Generate Weather")
888
+ weather = gr.Textbox(label="Weather & Mood", lines=2)
889
+
890
+ # ===== EVENT HANDLERS =====
891
+
892
+ # Character Creator Events
893
+ def roll_abilities():
894
+ abilities = character_creator.roll_ability_scores()
895
+ return (
896
+ abilities["Strength"], abilities["Dexterity"], abilities["Constitution"],
897
+ abilities["Intelligence"], abilities["Wisdom"], abilities["Charisma"]
898
+ )
899
+
900
+ def generate_character_name(race, char_class, alignment):
901
+ try:
902
+ from openai import OpenAI
903
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
904
+
905
+ prompt = f"Generate a {race} {char_class} name appropriate for D&D. Return only the name."
906
+
907
+ response = client.chat.completions.create(
908
+ model="gpt-4",
909
+ messages=[{"role": "user", "content": prompt}],
910
+ max_tokens=30
911
+ )
912
+
913
+ return response.choices[0].message.content.strip()
914
+ except Exception as e:
915
+ return f"Error: {str(e)}"
916
+
917
+ def generate_character_backstory(name, race, char_class, alignment, background):
918
+ try:
919
+ from openai import OpenAI
920
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
921
+
922
+ prompt = f"""Create a compelling backstory for {name}, a {race} {char_class} with {alignment} alignment and {background} background.
923
+ Write 2-3 paragraphs about their origins, motivations, and key life events."""
924
+
925
+ response = client.chat.completions.create(
926
+ model="gpt-4",
927
+ messages=[{"role": "user", "content": prompt}],
928
+ max_tokens=300
929
+ )
930
+
931
+ return response.choices[0].message.content.strip()
932
+ except Exception as e:
933
+ return f"Error generating backstory: {str(e)}"
934
+
935
+ def generate_character_portrait(character_data):
936
+ if not character_data:
937
+ return "Please create a character first"
938
+
939
+ prompt = f"Fantasy portrait of a {character_data.race.lower()} {character_data.character_class.lower()}, professional D&D character art"
940
+ return generate_image(prompt)
941
+
942
+ def update_character_summary(name, race, char_class, level, alignment,
943
+ str_val, dex_val, con_val, int_val, wis_val, cha_val, background, backstory):
944
+ if not all([str_val, dex_val, con_val, int_val, wis_val, cha_val]):
945
+ return "*Roll ability scores to see summary*", None
946
+
947
+ base_abilities = {
948
+ "Strength": int(str_val), "Dexterity": int(dex_val), "Constitution": int(con_val),
949
+ "Intelligence": int(int_val), "Wisdom": int(wis_val), "Charisma": int(cha_val)
950
+ }
951
+
952
+ final_abilities = character_creator.apply_racial_modifiers(base_abilities, race)
953
+ con_modifier = character_creator.calculate_ability_modifier(final_abilities["Constitution"])
954
+ hit_points = character_creator.calculate_hit_points(char_class, int(level), con_modifier)
955
+
956
+ character = Character(
957
+ name=name or "Unnamed Character", race=race, character_class=char_class,
958
+ level=int(level), alignment=Alignment(alignment), abilities=final_abilities,
959
+ hit_points=hit_points, skills=character_creator.classes[char_class].skills[:2],
960
+ background=background, backstory=backstory
961
+ )
962
+
963
+ summary = f"""**{character.name}**
964
+ *Level {level} {race} {char_class} ({alignment})*
965
+
966
+ **Ability Scores:**
967
+ - STR: {final_abilities['Strength']} ({character_creator.calculate_ability_modifier(final_abilities['Strength']):+d})
968
+ - DEX: {final_abilities['Dexterity']} ({character_creator.calculate_ability_modifier(final_abilities['Dexterity']):+d})
969
+ - CON: {final_abilities['Constitution']} ({character_creator.calculate_ability_modifier(final_abilities['Constitution']):+d})
970
+ - INT: {final_abilities['Intelligence']} ({character_creator.calculate_ability_modifier(final_abilities['Intelligence']):+d})
971
+ - WIS: {final_abilities['Wisdom']} ({character_creator.calculate_ability_modifier(final_abilities['Wisdom']):+d})
972
+ - CHA: {final_abilities['Charisma']} ({character_creator.calculate_ability_modifier(final_abilities['Charisma']):+d})
973
+
974
+ **Combat Stats:**
975
+ - Hit Points: {hit_points}
976
+ - Hit Die: d{character_creator.classes[char_class].hit_die}
977
+
978
+ **Background:** {background}"""
979
+
980
+ return summary, character
981
+
982
+ # Campaign Events
983
+ def generate_campaign_concept(theme, level, players):
984
+ result = dm_agent.generate_campaign_concept(theme, level, players)
985
+ return result.get("content", "Error generating campaign") if result["success"] else f"Error: {result['error']}"
986
+
987
+ def generate_campaign_art(theme, level):
988
+ prompt = f"{theme} D&D campaign art for level {level} adventurers, epic fantasy illustration"
989
+ return generate_image(prompt)
990
+
991
+ def generate_session_content(campaign_output, session_num):
992
+ if not campaign_output:
993
+ return "Please generate a campaign concept first"
994
+ result = dm_agent.generate_session_content(campaign_output, session_num)
995
+ return result.get("content", "Error generating session") if result["success"] else f"Error: {result['error']}"
996
+
997
+ # NPC Events
998
+ def create_npc(context, role, importance):
999
+ result = npc_agent.generate_npc(context, role, importance)
1000
+ return result.get("content", "Error creating NPC") if result["success"] else f"Error: {result['error']}"
1001
+
1002
+ def generate_npc_portrait(npc_data):
1003
+ if not npc_data:
1004
+ return "Create an NPC first"
1005
+ prompt = "Fantasy portrait of a D&D NPC character, detailed face, professional RPG art"
1006
+ return generate_image(prompt)
1007
+
1008
+ def npc_roleplay_response(npc_data, player_input, context):
1009
+ if not npc_data or not player_input:
1010
+ return "Please create an NPC and enter player input"
1011
+ result = npc_agent.roleplay_npc(npc_data, player_input, context)
1012
+ return result.get("content", "Error in roleplay") if result["success"] else f"Error: {result['error']}"
1013
+
1014
+ # World Builder Events
1015
+ def create_location(loc_type, theme, purpose):
1016
+ result = world_agent.generate_location(loc_type, theme, purpose)
1017
+ return result.get("content", "Error creating location") if result["success"] else f"Error: {result['error']}"
1018
+
1019
+ def generate_location_art(loc_type, theme):
1020
+ prompt = f"{theme} {loc_type} fantasy location, detailed environment art, D&D setting"
1021
+ return generate_image(prompt)
1022
+
1023
+ # Loot Events
1024
+ def create_loot_table(level, encounter, rarity):
1025
+ result = loot_agent.generate_loot_table(level, encounter, rarity)
1026
+ return result.get("content", "Error creating loot") if result["success"] else f"Error: {result['error']}"
1027
+
1028
+ def create_magic_item(concept, power, theme):
1029
+ result = loot_agent.create_custom_magic_item(concept, power, theme)
1030
+ return result.get("content", "Error creating item") if result["success"] else f"Error: {result['error']}"
1031
+
1032
+ def generate_item_art(concept, power):
1033
+ prompt = f"{power} magical {concept}, fantasy item illustration, glowing magical effects"
1034
+ return generate_image(prompt)
1035
+
1036
+ # Random Generator Events
1037
+ def generate_random_name(name_type):
1038
+ names = {
1039
+ "Human Male": ["Garrett", "Marcus", "Thomas", "William", "James"],
1040
+ "Human Female": ["Elena", "Sarah", "Miranda", "Catherine", "Rose"],
1041
+ "Elven": ["Aelar", "Berrian", "Drannor", "Enna", "Galinndan"],
1042
+ "Dwarven": ["Adrik", "Baern", "Darrak", "Delg", "Eberk"],
1043
+ "Orcish": ["Grul", "Thark", "Dench", "Feng", "Gell"],
1044
+ "Fantasy Place": ["Ravenshollow", "Goldbrook", "Thornfield", "Mistral Keep"],
1045
+ "Tavern": ["The Prancing Pony", "Dragon's Rest", "The Silver Tankard"],
1046
+ "Shop": ["Magical Mysteries", "Fine Blades & More", "Potions & Remedies"]
1047
+ }
1048
+ return random.choice(names.get(name_type, ["Unknown"]))
1049
+
1050
+ def generate_random_encounter(level, difficulty):
1051
+ encounters = [
1052
+ f"Bandit ambush (adapted for level {level})",
1053
+ f"Wild animal encounter (CR {max(1, level//4)})",
1054
+ f"Mysterious traveler with a quest",
1055
+ f"Ancient ruins with {difficulty.lower()} traps",
1056
+ f"Rival adventuring party",
1057
+ f"Magical phenomenon requiring investigation"
1058
+ ]
1059
+ return random.choice(encounters)
1060
+
1061
+ def generate_plot_hook(theme):
1062
+ hooks = {
1063
+ "Mystery": "A beloved local figure has vanished without a trace, leaving behind only cryptic clues.",
1064
+ "Adventure": "Ancient maps surface pointing to a legendary treasure thought lost forever.",
1065
+ "Political": "A diplomatic envoy requests secret protection during dangerous negotiations.",
1066
+ "Personal": "A character's past catches up with them in an unexpected way.",
1067
+ "Rescue": "Innocent people are trapped in a dangerous situation and need immediate help.",
1068
+ "Exploration": "Uncharted territories beckon with promises of discovery and danger."
1069
+ }
1070
+ return hooks.get(theme, "A mysterious stranger approaches with an urgent request.")
1071
+
1072
+ def generate_weather(climate):
1073
+ weather_options = {
1074
+ "Temperate": ["Sunny and mild", "Light rain showers", "Overcast skies", "Gentle breeze"],
1075
+ "Tropical": ["Hot and humid", "Sudden thunderstorm", "Sweltering heat", "Monsoon rains"],
1076
+ "Arctic": ["Bitter cold winds", "Heavy snowfall", "Blizzard conditions", "Icy fog"],
1077
+ "Desert": ["Scorching sun", "Sandstorm approaching", "Cool desert night", "Rare rainfall"],
1078
+ "Mountainous": ["Mountain mist", "Alpine winds", "Rocky terrain", "Sudden weather change"]
1079
+ }
1080
+ return random.choice(weather_options.get(climate, ["Pleasant weather"]))
1081
 
1082
+ # Wire up all events
1083
+ roll_btn.click(roll_abilities, outputs=[str_score, dex_score, con_score, int_score, wis_score, cha_score])
1084
+
1085
+ generate_name_btn.click(
1086
+ generate_character_name,
1087
+ inputs=[race_dropdown, class_dropdown, alignment_dropdown],
1088
+ outputs=[character_name]
1089
+ )
1090
+
1091
+ generate_backstory_btn.click(
1092
+ generate_character_backstory,
1093
+ inputs=[character_name, race_dropdown, class_dropdown, alignment_dropdown, background_dropdown],
1094
+ outputs=[backstory]
1095
+ )
1096
+
1097
+ portrait_btn.click(
1098
+ generate_character_portrait,
1099
+ inputs=[character_data],
1100
+ outputs=[character_portrait]
1101
+ )
1102
+
1103
+ # Update character summary when inputs change
1104
+ for component in [character_name, race_dropdown, class_dropdown, level_slider, alignment_dropdown,
1105
+ str_score, dex_score, con_score, int_score, wis_score, cha_score, background_dropdown, backstory]:
1106
+ component.change(
1107
+ update_character_summary,
1108
+ inputs=[character_name, race_dropdown, class_dropdown, level_slider, alignment_dropdown,
1109
+ str_score, dex_score, con_score, int_score, wis_score, cha_score, background_dropdown, backstory],
1110
+ outputs=[character_summary, character_data]
1111
+ )
1112
+
1113
+ # Campaign events
1114
+ generate_campaign_btn.click(
1115
+ generate_campaign_concept,
1116
+ inputs=[campaign_theme, campaign_level, player_count],
1117
+ outputs=[campaign_output]
1118
+ )
1119
+
1120
+ campaign_visual_btn.click(
1121
+ generate_campaign_art,
1122
+ inputs=[campaign_theme, campaign_level],
1123
+ outputs=[campaign_image]
1124
+ )
1125
+
1126
+ generate_session_btn.click(
1127
+ generate_session_content,
1128
+ inputs=[campaign_output, session_number],
1129
+ outputs=[session_output]
1130
+ )
1131
+
1132
+ # NPC events
1133
+ create_npc_btn.click(
1134
+ create_npc,
1135
+ inputs=[npc_context, npc_role, npc_importance],
1136
+ outputs=[npc_output]
1137
+ )
1138
+
1139
+ npc_portrait_btn.click(
1140
+ generate_npc_portrait,
1141
+ inputs=[current_npc_data],
1142
+ outputs=[npc_portrait]
1143
+ )
1144
+
1145
+ roleplay_btn.click(
1146
+ npc_roleplay_response,
1147
+ inputs=[npc_output, player_input, roleplay_context],
1148
+ outputs=[npc_response]
1149
+ )
1150
+
1151
+ # World builder events
1152
+ generate_location_btn.click(
1153
+ create_location,
1154
+ inputs=[location_type, location_theme, location_purpose],
1155
+ outputs=[location_output]
1156
+ )
1157
+
1158
+ location_art_btn.click(
1159
+ generate_location_art,
1160
+ inputs=[location_type, location_theme],
1161
+ outputs=[location_image]
1162
+ )
1163
+
1164
+ # Loot events
1165
+ generate_loot_btn.click(
1166
+ create_loot_table,
1167
+ inputs=[loot_level, encounter_type, loot_rarity],
1168
+ outputs=[loot_output]
1169
+ )
1170
+
1171
+ create_item_btn.click(
1172
+ create_magic_item,
1173
+ inputs=[item_concept, power_level, campaign_theme_item],
1174
+ outputs=[magic_item_output]
1175
+ )
1176
+
1177
+ item_art_btn.click(
1178
+ generate_item_art,
1179
+ inputs=[item_concept, power_level],
1180
+ outputs=[item_image]
1181
+ )
1182
+
1183
+ # Random generator events
1184
+ gen_name_btn.click(generate_random_name, inputs=[name_type], outputs=[random_name])
1185
+ gen_encounter_btn.click(generate_random_encounter, inputs=[encounter_level, encounter_difficulty], outputs=[random_encounter])
1186
+ gen_hook_btn.click(generate_plot_hook, inputs=[hook_theme], outputs=[plot_hook])
1187
+ gen_weather_btn.click(generate_weather, inputs=[climate], outputs=[weather])
1188
+
1189
+ return demo
1190
 
1191
+ # Main entry point
1192
+ if __name__ == "__main__":
1193
+ logger.info("🏰 Starting Advanced D&D Campaign Manager...")
1194
+
1195
+ # Check for OpenAI API key
1196
+ api_key = os.getenv("OPENAI_API_KEY")
1197
+ if api_key:
1198
+ logger.info("βœ… OpenAI API key found - All AI features enabled!")
1199
+ else:
1200
+ logger.warning("⚠️ No OpenAI API key found - AI features will be limited")
1201
+ logger.info("πŸ’‘ Add OPENAI_API_KEY=your_key to a .env file to enable all AI features")
1202
+
1203
+ demo = create_main_interface()
1204
+
1205
+ try:
1206
+ demo.launch(
1207
+ share=True,
1208
+ server_name="0.0.0.0",
1209
+ server_port=7860,
1210
+ inbrowser=True
1211
+ )
1212
+ logger.info("🌐 App launched successfully!")
1213
+ logger.info("πŸ”— Local URL: http://localhost:7860")
1214
+
1215
+ except Exception as e:
1216
+ logger.error(f"❌ Launch failed: {e}")
1217
+ logger.info("πŸ’‘ Try: pip install --upgrade gradio")