ghost-logic commited on
Commit
1c99143
·
verified ·
1 Parent(s): 5180eee

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +630 -0
app.py ADDED
@@ -0,0 +1,630 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py - Flask D&D Campaign Manager for HuggingFace Spaces
2
+
3
+ import os
4
+ import logging
5
+ from flask import Flask, render_template, request, jsonify, send_file
6
+ import json
7
+ import tempfile
8
+ import random
9
+ from datetime import datetime
10
+ from typing import Dict, List, Optional
11
+ from dataclasses import dataclass, asdict
12
+ from enum import Enum
13
+
14
+ # Configure logging
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ # ===== D&D DATA MODELS =====
19
+ class Alignment(Enum):
20
+ LAWFUL_GOOD = "Lawful Good"
21
+ NEUTRAL_GOOD = "Neutral Good"
22
+ CHAOTIC_GOOD = "Chaotic Good"
23
+ LAWFUL_NEUTRAL = "Lawful Neutral"
24
+ TRUE_NEUTRAL = "True Neutral"
25
+ CHAOTIC_NEUTRAL = "Chaotic Neutral"
26
+ LAWFUL_EVIL = "Lawful Evil"
27
+ NEUTRAL_EVIL = "Neutral Evil"
28
+ CHAOTIC_EVIL = "Chaotic Evil"
29
+
30
+ @dataclass
31
+ class Character:
32
+ name: str
33
+ race: str
34
+ character_class: str
35
+ level: int
36
+ gender: str
37
+ alignment: str
38
+ abilities: Dict[str, int]
39
+ hit_points: int
40
+ background: str
41
+ backstory: str
42
+
43
+ # ===== AI AGENT CLASSES =====
44
+ class DungeonMasterAgent:
45
+ def generate_campaign_concept(self, theme: str, level: int, players: int) -> Dict:
46
+ try:
47
+ if os.getenv("OPENAI_API_KEY"):
48
+ from openai import OpenAI
49
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
50
+
51
+ prompt = f"""Create a {theme} D&D campaign for {players} level {level} characters.
52
+ Include: Campaign name, plot hook, main antagonist, 3 key locations, central conflict."""
53
+
54
+ response = client.chat.completions.create(
55
+ model="gpt-4",
56
+ messages=[{"role": "user", "content": prompt}],
57
+ max_tokens=500
58
+ )
59
+
60
+ return {"success": True, "content": response.choices[0].message.content}
61
+ else:
62
+ # Fallback content
63
+ return {
64
+ "success": True,
65
+ "content": f"**{theme} Campaign**\n\nA thrilling adventure awaits {players} brave heroes starting at level {level}. Ancient mysteries, dangerous foes, and legendary treasures lie ahead in this epic campaign designed to challenge and inspire."
66
+ }
67
+ except Exception as e:
68
+ logger.error(f"Campaign generation failed: {e}")
69
+ return {"success": False, "error": str(e)}
70
+
71
+ def generate_session_content(self, campaign_context: str, session_number: int) -> Dict:
72
+ try:
73
+ if os.getenv("OPENAI_API_KEY"):
74
+ from openai import OpenAI
75
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
76
+
77
+ prompt = f"""Create session {session_number} content for this campaign:
78
+ {campaign_context}
79
+
80
+ Generate:
81
+ 1. Session Opening (scene description)
82
+ 2. 3 Potential Encounters (combat, social, exploration)
83
+ 3. Key NPCs for this session
84
+ 4. Skill challenges or puzzles
85
+ 5. Cliffhanger ending options"""
86
+
87
+ response = client.chat.completions.create(
88
+ model="gpt-4",
89
+ messages=[{"role": "user", "content": prompt}],
90
+ max_tokens=600
91
+ )
92
+
93
+ return {"success": True, "content": response.choices[0].message.content}
94
+ else:
95
+ return {
96
+ "success": True,
97
+ "content": f"**Session {session_number}**\n\nThe adventure continues with new challenges, mysterious NPCs, and exciting encounters designed to test the heroes' resolve."
98
+ }
99
+ except Exception as e:
100
+ logger.error(f"Session generation failed: {e}")
101
+ return {"success": False, "error": str(e)}
102
+
103
+ class NPCAgent:
104
+ def generate_npc(self, context: str, role: str, importance: str, gender: str = "") -> Dict:
105
+ try:
106
+ if os.getenv("OPENAI_API_KEY"):
107
+ from openai import OpenAI
108
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
109
+
110
+ prompt = f"""Create a {importance} {role} NPC for: {context}
111
+ Gender: {gender if gender else 'Choose appropriate'}
112
+ Include: Name, personality, background, motivation, physical description, speech patterns."""
113
+
114
+ response = client.chat.completions.create(
115
+ model="gpt-4",
116
+ messages=[{"role": "user", "content": prompt}],
117
+ max_tokens=400
118
+ )
119
+
120
+ return {"success": True, "content": response.choices[0].message.content}
121
+ else:
122
+ return {
123
+ "success": True,
124
+ "content": f"**{gender if gender else 'Character'} {role}**\n\nA {importance.lower()} NPC perfect for your campaign context: {context}. This character brings depth and intrigue to your world."
125
+ }
126
+ except Exception as e:
127
+ logger.error(f"NPC generation failed: {e}")
128
+ return {"success": False, "error": str(e)}
129
+
130
+ def roleplay_npc(self, npc_description: str, player_input: str, context: str) -> Dict:
131
+ try:
132
+ if os.getenv("OPENAI_API_KEY"):
133
+ from openai import OpenAI
134
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
135
+
136
+ prompt = f"""You are roleplaying as this NPC:
137
+ {npc_description}
138
+
139
+ Context: {context}
140
+ Player says/does: {player_input}
141
+
142
+ Respond in character with:
143
+ 1. Dialogue (in quotes)
144
+ 2. Actions/body language (in italics)
145
+ 3. Internal thoughts/motivations (in parentheses)"""
146
+
147
+ response = client.chat.completions.create(
148
+ model="gpt-4",
149
+ messages=[{"role": "user", "content": prompt}],
150
+ max_tokens=200
151
+ )
152
+
153
+ return {"success": True, "content": response.choices[0].message.content}
154
+ else:
155
+ return {
156
+ "success": True,
157
+ "content": f"The NPC responds thoughtfully to your action: '{player_input}'. Their reaction fits their personality and the current situation."
158
+ }
159
+ except Exception as e:
160
+ logger.error(f"NPC roleplay failed: {e}")
161
+ return {"success": False, "error": str(e)}
162
+
163
+ class WorldBuilderAgent:
164
+ def generate_location(self, location_type: str, theme: str, purpose: str) -> Dict:
165
+ try:
166
+ if os.getenv("OPENAI_API_KEY"):
167
+ from openai import OpenAI
168
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
169
+
170
+ prompt = f"""Create a detailed {location_type} with:
171
+ Theme: {theme}
172
+ Purpose in campaign: {purpose}
173
+
174
+ Generate:
175
+ 1. Name and general description
176
+ 2. Key areas/rooms (at least 5)
177
+ 3. Notable inhabitants
178
+ 4. Hidden secrets or mysteries
179
+ 5. Potential dangers or challenges
180
+ 6. Valuable resources or rewards"""
181
+
182
+ response = client.chat.completions.create(
183
+ model="gpt-4",
184
+ messages=[{"role": "user", "content": prompt}],
185
+ max_tokens=500
186
+ )
187
+
188
+ return {"success": True, "content": response.choices[0].message.content}
189
+ else:
190
+ return {
191
+ "success": True,
192
+ "content": f"**{theme} {location_type}**\n\nA {theme.lower()} {location_type.lower()} designed for {purpose}. This location features multiple areas to explore, interesting NPCs to meet, and secrets to uncover."
193
+ }
194
+ except Exception as e:
195
+ logger.error(f"Location generation failed: {e}")
196
+ return {"success": False, "error": str(e)}
197
+
198
+ class LootMasterAgent:
199
+ def generate_loot_table(self, level: int, encounter_type: str, rarity: str) -> Dict:
200
+ try:
201
+ if os.getenv("OPENAI_API_KEY"):
202
+ from openai import OpenAI
203
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
204
+
205
+ prompt = f"""Create a balanced loot table for:
206
+ Party Level: {level}
207
+ Encounter Type: {encounter_type}
208
+ Rarity Level: {rarity}
209
+
210
+ Generate:
211
+ 1. Gold/Currency amounts
212
+ 2. Common items (consumables, gear)
213
+ 3. Uncommon magical items (if appropriate)
214
+ 4. Rare items (if high level)
215
+ 5. Unique/plot-relevant items"""
216
+
217
+ response = client.chat.completions.create(
218
+ model="gpt-4",
219
+ messages=[{"role": "user", "content": prompt}],
220
+ max_tokens=300
221
+ )
222
+
223
+ return {"success": True, "content": response.choices[0].message.content}
224
+ else:
225
+ return {
226
+ "success": True,
227
+ "content": f"**Level {level} {encounter_type} Loot ({rarity})**\n\nGold: {level * 10}-{level * 20} gp\nItems: Health potions, basic equipment\nSpecial: One rare item appropriate for the encounter"
228
+ }
229
+ except Exception as e:
230
+ logger.error(f"Loot generation failed: {e}")
231
+ return {"success": False, "error": str(e)}
232
+
233
+ def create_custom_magic_item(self, item_concept: str, power_level: str, campaign_theme: str) -> Dict:
234
+ try:
235
+ if os.getenv("OPENAI_API_KEY"):
236
+ from openai import OpenAI
237
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
238
+
239
+ prompt = f"""Design a custom magic item:
240
+ Concept: {item_concept}
241
+ Power Level: {power_level}
242
+ Campaign Theme: {campaign_theme}
243
+
244
+ Provide:
245
+ 1. Item name and basic description
246
+ 2. Mechanical effects (stats, abilities)
247
+ 3. Activation requirements
248
+ 4. Rarity and attunement needs
249
+ 5. Physical appearance
250
+ 6. Historical background/lore"""
251
+
252
+ response = client.chat.completions.create(
253
+ model="gpt-4",
254
+ messages=[{"role": "user", "content": prompt}],
255
+ max_tokens=400
256
+ )
257
+
258
+ return {"success": True, "content": response.choices[0].message.content}
259
+ else:
260
+ return {
261
+ "success": True,
262
+ "content": f"**{item_concept.title()} of {campaign_theme}**\n\nRarity: {power_level}\nA magical {item_concept} imbued with the essence of {campaign_theme}. This item grants special abilities and carries ancient power."
263
+ }
264
+ except Exception as e:
265
+ logger.error(f"Magic item creation failed: {e}")
266
+ return {"success": False, "error": str(e)}
267
+
268
+ class CharacterCreator:
269
+ def __init__(self):
270
+ self.classes = {
271
+ "Fighter": {"hit_die": 10},
272
+ "Wizard": {"hit_die": 6},
273
+ "Rogue": {"hit_die": 8},
274
+ "Cleric": {"hit_die": 8},
275
+ "Barbarian": {"hit_die": 12},
276
+ "Bard": {"hit_die": 8},
277
+ "Druid": {"hit_die": 8},
278
+ "Monk": {"hit_die": 8},
279
+ "Paladin": {"hit_die": 10},
280
+ "Ranger": {"hit_die": 10},
281
+ "Sorcerer": {"hit_die": 6},
282
+ "Warlock": {"hit_die": 8}
283
+ }
284
+
285
+ def roll_ability_scores(self) -> Dict[str, int]:
286
+ abilities = {}
287
+ for ability in ["Strength", "Dexterity", "Constitution", "Intelligence", "Wisdom", "Charisma"]:
288
+ rolls = [random.randint(1, 6) for _ in range(4)]
289
+ rolls.sort(reverse=True)
290
+ abilities[ability] = sum(rolls[:3])
291
+ return abilities
292
+
293
+ def generate_image(prompt: str) -> str:
294
+ """Generate image URL (placeholder for AI image generation)"""
295
+ try:
296
+ if os.getenv("OPENAI_API_KEY"):
297
+ from openai import OpenAI
298
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
299
+
300
+ response = client.images.generate(
301
+ model="dall-e-3",
302
+ prompt=prompt,
303
+ size="1024x1024",
304
+ quality="standard",
305
+ n=1,
306
+ )
307
+ return response.data[0].url
308
+ else:
309
+ # Placeholder image
310
+ return "https://via.placeholder.com/512x512/7c3aed/ffffff?text=AI+Image"
311
+ except Exception as e:
312
+ logger.error(f"Image generation failed: {e}")
313
+ return "https://via.placeholder.com/512x512/dc2626/ffffff?text=Image+Generation+Failed"
314
+
315
+ # ===== FLASK APP SETUP =====
316
+ app = Flask(__name__)
317
+ app.secret_key = os.getenv('SECRET_KEY', 'your-secret-key-for-hf-spaces')
318
+
319
+ # Initialize components
320
+ dm_agent = DungeonMasterAgent()
321
+ npc_agent = NPCAgent()
322
+ world_agent = WorldBuilderAgent()
323
+ loot_agent = LootMasterAgent()
324
+ character_creator = CharacterCreator()
325
+
326
+ @app.route('/')
327
+ def index():
328
+ """Serve the main page"""
329
+ return render_template('index.html')
330
+
331
+ # ===== API ROUTES =====
332
+
333
+ @app.route('/api/character/roll-abilities', methods=['POST'])
334
+ def roll_abilities():
335
+ try:
336
+ abilities = character_creator.roll_ability_scores()
337
+ return jsonify({'success': True, 'abilities': abilities})
338
+ except Exception as e:
339
+ return jsonify({'success': False, 'error': str(e)}), 500
340
+
341
+ @app.route('/api/character/generate-name', methods=['POST'])
342
+ def generate_character_name():
343
+ try:
344
+ data = request.json
345
+ race = data.get('race', 'Human')
346
+ gender = data.get('gender', 'Male')
347
+
348
+ # Try AI generation first
349
+ if os.getenv("OPENAI_API_KEY"):
350
+ try:
351
+ from openai import OpenAI
352
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
353
+
354
+ prompt = f"Generate a {gender} {race} name appropriate for D&D. Return only the name."
355
+
356
+ response = client.chat.completions.create(
357
+ model="gpt-4",
358
+ messages=[{"role": "user", "content": prompt}],
359
+ max_tokens=30
360
+ )
361
+
362
+ name = response.choices[0].message.content.strip()
363
+ return jsonify({'success': True, 'name': name})
364
+ except:
365
+ pass # Fall through to fallback
366
+
367
+ # Fallback name generation
368
+ names = {
369
+ "Human": {"Male": ["Garrett", "Marcus", "Thomas"], "Female": ["Elena", "Sarah", "Miranda"]},
370
+ "Elf": {"Male": ["Aelar", "Berrian", "Drannor"], "Female": ["Adrie", "Althaea", "Anastrianna"]},
371
+ "Dwarf": {"Male": ["Adrik", "Baern", "Darrak"], "Female": ["Amber", "Bardryn", "Diesa"]}
372
+ }
373
+
374
+ race_names = names.get(race, names["Human"])
375
+ gender_key = "Male" if gender in ["Male", "Transgender Male"] else "Female"
376
+ name_list = race_names.get(gender_key, race_names["Male"])
377
+
378
+ return jsonify({'success': True, 'name': random.choice(name_list)})
379
+ except Exception as e:
380
+ return jsonify({'success': False, 'error': str(e)}), 500
381
+
382
+ @app.route('/api/character/generate-backstory', methods=['POST'])
383
+ def generate_character_backstory():
384
+ try:
385
+ data = request.json
386
+ name = data.get('name', 'Character')
387
+ race = data.get('race', 'Human')
388
+ char_class = data.get('class', 'Fighter')
389
+ background = data.get('background', 'Folk Hero')
390
+
391
+ # Try AI generation first
392
+ if os.getenv("OPENAI_API_KEY"):
393
+ try:
394
+ from openai import OpenAI
395
+ client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
396
+
397
+ prompt = f"""Create a compelling backstory for {name}, a {race} {char_class} with a {background} background.
398
+ Write 2-3 paragraphs about their origins, motivations, and key life events."""
399
+
400
+ response = client.chat.completions.create(
401
+ model="gpt-4",
402
+ messages=[{"role": "user", "content": prompt}],
403
+ max_tokens=300
404
+ )
405
+
406
+ backstory = response.choices[0].message.content.strip()
407
+ return jsonify({'success': True, 'backstory': backstory})
408
+ except:
409
+ pass # Fall through to fallback
410
+
411
+ # Fallback backstory
412
+ backstory = f"{name} is a {race} {char_class} with a {background} background. Their journey began in their homeland, where they learned the skills that would define their path as an adventurer, embracing their destiny with courage and determination."
413
+
414
+ return jsonify({'success': True, 'backstory': backstory})
415
+ except Exception as e:
416
+ return jsonify({'success': False, 'error': str(e)}), 500
417
+
418
+ @app.route('/api/character/generate-portrait', methods=['POST'])
419
+ def generate_character_portrait():
420
+ try:
421
+ data = request.json
422
+ race = data.get('race', 'Human')
423
+ char_class = data.get('class', 'Fighter')
424
+ gender = data.get('gender', 'Male')
425
+
426
+ prompt = f"Fantasy portrait of a {gender.lower()} {race.lower()} {char_class.lower()}, professional D&D character art"
427
+ image_url = generate_image(prompt)
428
+
429
+ return jsonify({'success': True, 'image_url': image_url})
430
+ except Exception as e:
431
+ return jsonify({'success': False, 'error': str(e)}), 500
432
+
433
+ @app.route('/api/campaign/generate-concept', methods=['POST'])
434
+ def generate_campaign_concept():
435
+ try:
436
+ data = request.json
437
+ theme = data.get('theme', 'High Fantasy')
438
+ level = data.get('level', 5)
439
+ players = data.get('players', 4)
440
+
441
+ result = dm_agent.generate_campaign_concept(theme, level, players)
442
+ return jsonify(result)
443
+ except Exception as e:
444
+ return jsonify({'success': False, 'error': str(e)}), 500
445
+
446
+ @app.route('/api/campaign/generate-session', methods=['POST'])
447
+ def generate_session_content():
448
+ try:
449
+ data = request.json
450
+ campaign_context = data.get('campaign_context', 'General D&D campaign')
451
+ session_number = data.get('session_number', 1)
452
+
453
+ result = dm_agent.generate_session_content(campaign_context, session_number)
454
+ return jsonify(result)
455
+ except Exception as e:
456
+ return jsonify({'success': False, 'error': str(e)}), 500
457
+
458
+ @app.route('/api/campaign/generate-art', methods=['POST'])
459
+ def generate_campaign_art():
460
+ try:
461
+ data = request.json
462
+ theme = data.get('theme', 'High Fantasy')
463
+ level = data.get('level', 5)
464
+
465
+ prompt = f"{theme} D&D campaign art for level {level} adventurers, epic fantasy illustration"
466
+ image_url = generate_image(prompt)
467
+
468
+ return jsonify({'success': True, 'image_url': image_url})
469
+ except Exception as e:
470
+ return jsonify({'success': False, 'error': str(e)}), 500
471
+
472
+ @app.route('/api/npc/create', methods=['POST'])
473
+ def create_npc():
474
+ try:
475
+ data = request.json
476
+ context = data.get('context', '')
477
+ role = data.get('role', 'Neutral')
478
+ importance = data.get('importance', 'Moderate')
479
+ gender = data.get('gender', '')
480
+
481
+ result = npc_agent.generate_npc(context, role, importance, gender)
482
+ return jsonify(result)
483
+ except Exception as e:
484
+ return jsonify({'success': False, 'error': str(e)}), 500
485
+
486
+ @app.route('/api/npc/roleplay', methods=['POST'])
487
+ def npc_roleplay():
488
+ try:
489
+ data = request.json
490
+ npc_description = data.get('npc_description', '')
491
+ player_input = data.get('player_input', '')
492
+ context = data.get('context', '')
493
+
494
+ result = npc_agent.roleplay_npc(npc_description, player_input, context)
495
+ return jsonify(result)
496
+ except Exception as e:
497
+ return jsonify({'success': False, 'error': str(e)}), 500
498
+
499
+ @app.route('/api/world/create-location', methods=['POST'])
500
+ def create_location():
501
+ try:
502
+ data = request.json
503
+ location_type = data.get('type', 'Tavern')
504
+ theme = data.get('theme', 'Standard Fantasy')
505
+ purpose = data.get('purpose', '')
506
+
507
+ result = world_agent.generate_location(location_type, theme, purpose)
508
+ return jsonify(result)
509
+ except Exception as e:
510
+ return jsonify({'success': False, 'error': str(e)}), 500
511
+
512
+ @app.route('/api/loot/generate-table', methods=['POST'])
513
+ def generate_loot_table():
514
+ try:
515
+ data = request.json
516
+ level = data.get('level', 5)
517
+ encounter_type = data.get('encounter_type', 'Standard Combat')
518
+ rarity = data.get('rarity', 'Standard')
519
+
520
+ result = loot_agent.generate_loot_table(level, encounter_type, rarity)
521
+ return jsonify(result)
522
+ except Exception as e:
523
+ return jsonify({'success': False, 'error': str(e)}), 500
524
+
525
+ @app.route('/api/loot/create-magic-item', methods=['POST'])
526
+ def create_magic_item():
527
+ try:
528
+ data = request.json
529
+ concept = data.get('concept', '')
530
+ power_level = data.get('power_level', 'Uncommon')
531
+ theme = data.get('theme', '')
532
+
533
+ result = loot_agent.create_custom_magic_item(concept, power_level, theme)
534
+ return jsonify(result)
535
+ except Exception as e:
536
+ return jsonify({'success': False, 'error': str(e)}), 500
537
+
538
+ @app.route('/api/random/name', methods=['POST'])
539
+ def generate_random_name():
540
+ try:
541
+ data = request.json
542
+ name_type = data.get('type', 'Human Male')
543
+
544
+ names = {
545
+ "Human Male": ["Garrett", "Marcus", "Thomas", "William", "James"],
546
+ "Human Female": ["Elena", "Sarah", "Miranda", "Catherine", "Rose"],
547
+ "Elven": ["Aelar", "Berrian", "Drannor", "Enna", "Galinndan"],
548
+ "Dwarven": ["Adrik", "Baern", "Darrak", "Delg", "Eberk"],
549
+ "Fantasy Place": ["Ravenshollow", "Goldbrook", "Thornfield", "Mistral Keep"],
550
+ "Tavern": ["The Prancing Pony", "Dragon's Rest", "The Silver Tankard"]
551
+ }
552
+
553
+ selected_names = names.get(name_type, ["Unknown"])
554
+ random_name = random.choice(selected_names)
555
+
556
+ return jsonify({'success': True, 'name': random_name})
557
+ except Exception as e:
558
+ return jsonify({'success': False, 'error': str(e)}), 500
559
+
560
+ @app.route('/api/random/encounter', methods=['POST'])
561
+ def generate_random_encounter():
562
+ try:
563
+ data = request.json
564
+ level = data.get('level', 5)
565
+ difficulty = data.get('difficulty', 'Medium')
566
+
567
+ encounters = [
568
+ f"Bandit ambush (adapted for level {level})",
569
+ f"Wild animal encounter (CR {max(1, level//4)})",
570
+ "Mysterious traveler with a quest",
571
+ f"Ancient ruins with {difficulty.lower()} traps",
572
+ "Rival adventuring party",
573
+ "Magical phenomenon requiring investigation"
574
+ ]
575
+
576
+ random_encounter = random.choice(encounters)
577
+ return jsonify({'success': True, 'encounter': random_encounter})
578
+ except Exception as e:
579
+ return jsonify({'success': False, 'error': str(e)}), 500
580
+
581
+ @app.route('/api/random/plot-hook', methods=['POST'])
582
+ def generate_plot_hook():
583
+ try:
584
+ data = request.json
585
+ theme = data.get('theme', 'Adventure')
586
+
587
+ hooks = {
588
+ "Mystery": "A beloved local figure has vanished without a trace, leaving behind only cryptic clues.",
589
+ "Adventure": "Ancient maps surface pointing to a legendary treasure thought lost forever.",
590
+ "Political": "A diplomatic envoy requests secret protection during dangerous negotiations.",
591
+ "Personal": "A character's past catches up with them in an unexpected way.",
592
+ "Rescue": "Innocent people are trapped in a dangerous situation and need immediate help.",
593
+ "Exploration": "Uncharted territories beckon with promises of discovery and danger."
594
+ }
595
+
596
+ hook = hooks.get(theme, "A mysterious stranger approaches with an urgent request.")
597
+ return jsonify({'success': True, 'hook': hook})
598
+ except Exception as e:
599
+ return jsonify({'success': False, 'error': str(e)}), 500
600
+
601
+ @app.route('/api/random/weather', methods=['POST'])
602
+ def generate_weather():
603
+ try:
604
+ data = request.json
605
+ climate = data.get('climate', 'Temperate')
606
+
607
+ weather_options = {
608
+ "Temperate": ["Sunny and mild", "Light rain showers", "Overcast skies", "Gentle breeze"],
609
+ "Tropical": ["Hot and humid", "Sudden thunderstorm", "Sweltering heat", "Monsoon rains"],
610
+ "Arctic": ["Bitter cold winds", "Heavy snowfall", "Blizzard conditions", "Icy fog"],
611
+ "Desert": ["Scorching sun", "Sandstorm approaching", "Cool desert night", "Rare rainfall"],
612
+ "Mountainous": ["Mountain mist", "Alpine winds", "Rocky terrain", "Sudden weather change"]
613
+ }
614
+
615
+ weathers = weather_options.get(climate, ["Pleasant weather"])
616
+ random_weather = random.choice(weathers)
617
+
618
+ return jsonify({'success': True, 'weather': random_weather})
619
+ except Exception as e:
620
+ return jsonify({'success': False, 'error': str(e)}), 500
621
+
622
+ @app.route('/health')
623
+ def health_check():
624
+ """Health check endpoint for HF Spaces"""
625
+ return jsonify({"status": "healthy", "service": "D&D Campaign Manager"})
626
+
627
+ if __name__ == '__main__':
628
+ # HuggingFace Spaces requires port 7860
629
+ port = int(os.environ.get('PORT', 7860))
630
+ app.run(host='0.0.0.0', port=port, debug=False)