oussnaji commited on
Commit
27138e8
·
verified ·
1 Parent(s): e8f7028

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +752 -0
app.py ADDED
@@ -0,0 +1,752 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import anthropic
3
+ import requests
4
+ import json
5
+ import base64
6
+ import plotly.graph_objects as go
7
+ from typing import Dict, Any
8
+ import time
9
+ import random
10
+
11
+ # Page Config
12
+ st.set_page_config(
13
+ page_title="NarrativeCraft: Immersive Story & Character Design Studio",
14
+ layout="wide",
15
+ initial_sidebar_state="expanded",
16
+ page_icon="📚"
17
+ )
18
+
19
+ # Custom CSS for professional dark theme
20
+ st.markdown("""
21
+ <style>
22
+ .main {background-color: #0e1117; color: #ffffff;}
23
+ .stProgress > div > div > div > div {background-color: #1f77b4;}
24
+ .character-card {
25
+ background-color: #1e1e1e;
26
+ padding: 1.5rem;
27
+ border-radius: 0.5rem;
28
+ margin: 1rem 0;
29
+ border: 1px solid #2e2e2e;
30
+ }
31
+ .section-title {
32
+ font-size: 1.5rem;
33
+ font-weight: bold;
34
+ margin: 1.5rem 0 1rem 0;
35
+ padding-bottom: 0.5rem;
36
+ border-bottom: 2px solid #2e2e2e;
37
+ }
38
+ .subsection-title {
39
+ font-size: 1.2rem;
40
+ font-weight: bold;
41
+ margin: 1rem 0;
42
+ color: #4e8cff;
43
+ }
44
+ .story-section {
45
+ background-color: #1e1e1e;
46
+ padding: 2rem;
47
+ border-radius: 0.5rem;
48
+ margin: 1.5rem 0;
49
+ border: 1px solid #2e2e2e;
50
+ }
51
+ .audio-player {
52
+ margin: 1rem 0;
53
+ padding: 1rem;
54
+ background-color: #2e2e2e;
55
+ border-radius: 0.5rem;
56
+ }
57
+ .analysis-card {
58
+ background-color: #2e2e2e;
59
+ padding: 1rem;
60
+ border-radius: 0.5rem;
61
+ margin: 0.5rem 0;
62
+ }
63
+ </style>
64
+ """, unsafe_allow_html=True)
65
+
66
+ # Initialize session state
67
+ if 'character' not in st.session_state:
68
+ st.session_state.character = None
69
+ if 'story_params' not in st.session_state:
70
+ st.session_state.story_params = None
71
+ if 'generated_content' not in st.session_state:
72
+ st.session_state.generated_content = None
73
+ if 'voice_data' not in st.session_state:
74
+ st.session_state.voice_data = None
75
+
76
+ # Constants
77
+ ARCHETYPES = {
78
+ "The Hero": "Brave, determined protagonist driven by a noble cause",
79
+ "The Mentor": "Wise guide with deep knowledge and experience",
80
+ "The Trickster": "Clever, unpredictable character who challenges conventions",
81
+ "The Sage": "Philosophical, thoughtful character seeking truth",
82
+ "The Rebel": "Independent spirit fighting against the system",
83
+ "The Caregiver": "Nurturing, protective character driven by compassion",
84
+ "The Creator": "Innovative, artistic character driven by vision",
85
+ "The Explorer": "Adventure-seeking character driven by curiosity",
86
+ "The Ruler": "Leadership-focused character seeking control",
87
+ "The Innocent": "Pure-hearted character maintaining optimism"
88
+ }
89
+
90
+ GENRES = [
91
+ "Epic Fantasy",
92
+ "Science Fiction",
93
+ "Dark Fantasy",
94
+ "Historical Fiction",
95
+ "Magical Realism",
96
+ "Contemporary Drama",
97
+ "Mystery/Thriller",
98
+ "Romance",
99
+ "Political Intrigue",
100
+ "Cyberpunk",
101
+ "Space Opera",
102
+ "Urban Fantasy",
103
+ "Gothic Horror",
104
+ "Adventure",
105
+ "Literary Fiction"
106
+ ]
107
+
108
+ WRITING_STYLES = {
109
+ "Classical": "Elegant, formal prose with rich descriptions",
110
+ "Modern Minimalist": "Clean, precise language with impact",
111
+ "Lyrical": "Poetic, flowing prose with metaphorical depth",
112
+ "Gritty Realism": "Raw, direct style with stark honesty",
113
+ "Experimental": "Innovative structure and unique voice",
114
+ "Journalistic": "Clear, factual style with objectivity",
115
+ "Stream of Consciousness": "Free-flowing, internal narrative",
116
+ "Epic": "Grand, sweeping style with historical weight"
117
+ }
118
+ def create_character_section():
119
+ st.markdown('<p class="section-title">Character Design</p>', unsafe_allow_html=True)
120
+
121
+ col1, col2 = st.columns([2, 1])
122
+
123
+ with col1:
124
+ selected_archetype = st.selectbox(
125
+ "Choose Character Archetype",
126
+ list(ARCHETYPES.keys())
127
+ )
128
+
129
+ st.info(ARCHETYPES[selected_archetype])
130
+
131
+ age_range = st.select_slider(
132
+ "Age Range",
133
+ options=["Child", "Teen", "Young Adult", "Adult", "Middle-aged", "Elderly"],
134
+ value="Adult"
135
+ )
136
+
137
+ with col2:
138
+ st.markdown('<p class="subsection-title">Character Preview</p>', unsafe_allow_html=True)
139
+ st.markdown(f"**Archetype:** {selected_archetype}")
140
+ st.markdown(f"**Age:** {age_range}")
141
+
142
+ st.markdown('<p class="subsection-title">Personality Traits</p>', unsafe_allow_html=True)
143
+ trait_col1, trait_col2, trait_col3 = st.columns(3)
144
+
145
+ with trait_col1:
146
+ confidence = st.slider("Confidence", 1, 10, 5, help="Character's self-assurance level")
147
+ empathy = st.slider("Empathy", 1, 10, 5, help="Ability to understand others")
148
+ intelligence = st.slider("Intelligence", 1, 10, 5, help="Mental capability and wit")
149
+
150
+ with trait_col2:
151
+ courage = st.slider("Courage", 1, 10, 5, help="Bravery in face of adversity")
152
+ ambition = st.slider("Ambition", 1, 10, 5, help="Drive to achieve goals")
153
+ loyalty = st.slider("Loyalty", 1, 10, 5, help="Faithfulness to causes/people")
154
+
155
+ with trait_col3:
156
+ humor = st.slider("Humor", 1, 10, 5, help="Sense of humor and wit")
157
+ creativity = st.slider("Creativity", 1, 10, 5, help="Imaginative capability")
158
+ wisdom = st.slider("Wisdom", 1, 10, 5, help="Depth of understanding")
159
+
160
+ st.markdown('<p class="subsection-title">Voice & Speech</p>', unsafe_allow_html=True)
161
+ voice_col1, voice_col2 = st.columns(2)
162
+
163
+ with voice_col1:
164
+ voice_type = st.selectbox(
165
+ "Voice Quality",
166
+ ["Deep", "Melodious", "Rough", "Soft", "Commanding", "Gentle", "Energetic"],
167
+ help="Primary characteristic of the character's voice"
168
+ )
169
+
170
+ accent = st.selectbox(
171
+ "Accent",
172
+ ["Standard", "Regional", "Foreign", "Cultured", "Rustic"],
173
+ help="Character's accent or dialect"
174
+ )
175
+
176
+ with voice_col2:
177
+ speech_pattern = st.selectbox(
178
+ "Speech Pattern",
179
+ ["Formal", "Casual", "Educated", "Street-wise", "Poetic", "Technical", "Noble"],
180
+ help="How the character typically speaks"
181
+ )
182
+
183
+ speech_pacing = st.select_slider(
184
+ "Speech Pace",
185
+ options=["Very Slow", "Slow", "Moderate", "Quick", "Very Quick"],
186
+ value="Moderate",
187
+ help="Speed and rhythm of speech"
188
+ )
189
+
190
+ st.markdown('<p class="subsection-title">Emotional Profile</p>', unsafe_allow_html=True)
191
+ emo_col1, emo_col2 = st.columns(2)
192
+
193
+ with emo_col1:
194
+ primary_emotion = st.selectbox(
195
+ "Primary Emotional State",
196
+ ["Determined", "Serene", "Passionate", "Calculating", "Troubled", "Optimistic", "Reserved"]
197
+ )
198
+ emotional_stability = st.slider("Emotional Stability", 1, 10, 5)
199
+
200
+ with emo_col2:
201
+ emotional_expression = st.selectbox(
202
+ "Emotional Expression Style",
203
+ ["Open", "Guarded", "Volatile", "Controlled", "Subtle", "Dramatic"]
204
+ )
205
+ emotional_depth = st.slider("Emotional Depth", 1, 10, 5)
206
+
207
+ return {
208
+ "archetype": selected_archetype,
209
+ "age_range": age_range,
210
+ "traits": {
211
+ "confidence": confidence,
212
+ "empathy": empathy,
213
+ "intelligence": intelligence,
214
+ "courage": courage,
215
+ "ambition": ambition,
216
+ "loyalty": loyalty,
217
+ "humor": humor,
218
+ "creativity": creativity,
219
+ "wisdom": wisdom
220
+ },
221
+ "voice": {
222
+ "type": voice_type,
223
+ "accent": accent,
224
+ "pattern": speech_pattern,
225
+ "pacing": speech_pacing
226
+ },
227
+ "emotional_profile": {
228
+ "primary_emotion": primary_emotion,
229
+ "stability": emotional_stability,
230
+ "expression": emotional_expression,
231
+ "depth": emotional_depth
232
+ }
233
+ }
234
+
235
+ def create_voice_preview(description: str, text: str) -> Dict:
236
+ """Create voice preview using ElevenLabs API"""
237
+ url = 'https://api.elevenlabs.io/v1/text-to-voice/create-previews'
238
+
239
+ headers = {
240
+ 'xi-api-key': st.secrets["ELEVENLABS_API_KEY"],
241
+ 'Content-Type': 'application/json'
242
+ }
243
+
244
+ payload = {
245
+ 'voice_description': description,
246
+ 'text': text
247
+ }
248
+
249
+ try:
250
+ response = requests.post(url, headers=headers, json=payload)
251
+ if response.status_code == 200:
252
+ return response.json()
253
+ else:
254
+ st.error(f"Voice generation error: {response.status_code}")
255
+ st.write("Error details:", response.json())
256
+ return None
257
+ except Exception as e:
258
+ st.error(f"Error: {str(e)}")
259
+ return None
260
+
261
+ def display_character_profile(character: Dict, voice_data: Dict = None):
262
+ """Display character profile with voice playback"""
263
+ if not character:
264
+ st.warning("No character data available")
265
+ return
266
+
267
+ try:
268
+ with st.container():
269
+ st.markdown('<div class="character-card">', unsafe_allow_html=True)
270
+
271
+ # Character Title
272
+ if character.get("archetype"):
273
+ st.markdown(f'<p class="section-title">{character["archetype"]}</p>',
274
+ unsafe_allow_html=True)
275
+
276
+ col1, col2 = st.columns([2, 1])
277
+
278
+ with col1:
279
+ if character.get("traits"):
280
+ st.markdown("### Personality Profile")
281
+
282
+ # Personality Traits Radar Chart
283
+ traits = character['traits']
284
+ fig = go.Figure(data=go.Scatterpolar(
285
+ r=[traits[key] for key in traits.keys()],
286
+ theta=list(traits.keys()),
287
+ fill='toself',
288
+ line=dict(color='#4e8cff')
289
+ ))
290
+
291
+ fig.update_layout(
292
+ polar=dict(
293
+ radialaxis=dict(visible=True, range=[0, 10])),
294
+ showlegend=False,
295
+ paper_bgcolor='rgba(0,0,0,0)',
296
+ plot_bgcolor='rgba(0,0,0,0)',
297
+ font=dict(color='white')
298
+ )
299
+
300
+ st.plotly_chart(fig, use_container_width=True)
301
+
302
+ with col2:
303
+ st.markdown('<p class="subsection-title">Voice Sample</p>',
304
+ unsafe_allow_html=True)
305
+ if voice_data and 'previews' in voice_data and voice_data['previews']:
306
+ st.markdown('<div class="audio-player">', unsafe_allow_html=True)
307
+ audio_data = base64.b64decode(voice_data['previews'][0]['audio_base_64'])
308
+ st.audio(audio_data, format='audio/mp3')
309
+ st.markdown('</div>', unsafe_allow_html=True)
310
+
311
+ if character.get("voice"):
312
+ st.markdown('<p class="subsection-title">Core Traits</p>',
313
+ unsafe_allow_html=True)
314
+ st.markdown(f"**Age Range:** {character.get('age_range', 'N/A')}")
315
+ st.markdown(f"**Voice Type:** {character['voice'].get('type', 'N/A')}")
316
+ st.markdown(f"**Speech Pattern:** {character['voice'].get('pattern', 'N/A')}")
317
+
318
+ st.markdown('</div>', unsafe_allow_html=True)
319
+
320
+ except Exception as e:
321
+ st.error(f"Error displaying character profile: {str(e)}")
322
+
323
+ def create_story_parameters_section():
324
+ st.markdown('<p class="section-title">Story Configuration</p>', unsafe_allow_html=True)
325
+
326
+ # Main Story Parameters
327
+ col1, col2 = st.columns(2)
328
+
329
+ with col1:
330
+ genre = st.selectbox("Genre", GENRES)
331
+
332
+ writing_style = st.selectbox(
333
+ "Writing Style",
334
+ list(WRITING_STYLES.keys())
335
+ )
336
+ st.info(WRITING_STYLES[writing_style])
337
+
338
+ tone = st.select_slider(
339
+ "Story Tone",
340
+ options=["Light", "Hopeful", "Neutral", "Dark", "Gritty"],
341
+ value="Neutral"
342
+ )
343
+
344
+ with col2:
345
+ pacing = st.select_slider(
346
+ "Story Pacing",
347
+ options=["Slow & Thoughtful", "Balanced", "Fast & Intense"],
348
+ value="Balanced"
349
+ )
350
+
351
+ plot_complexity = st.slider("Plot Complexity", 1, 10, 5)
352
+
353
+ dialogue_focus = st.slider("Dialogue Emphasis", 1, 10, 5)
354
+
355
+ # Setting Details
356
+ st.markdown('<p class="subsection-title">Setting & Atmosphere</p>', unsafe_allow_html=True)
357
+ set_col1, set_col2 = st.columns(2)
358
+
359
+ with set_col1:
360
+ time_period = st.selectbox(
361
+ "Time Period",
362
+ ["Ancient", "Medieval", "Renaissance", "Industrial", "Modern",
363
+ "Contemporary", "Near Future", "Far Future", "Multiple Eras"]
364
+ )
365
+
366
+ setting_type = st.selectbox(
367
+ "Setting Type",
368
+ ["Urban", "Rural", "Wilderness", "Fantasy World", "Space",
369
+ "Underground", "Ocean", "Multiple Locations"]
370
+ )
371
+
372
+ with set_col2:
373
+ atmosphere = st.select_slider(
374
+ "Atmosphere",
375
+ options=["Mysterious", "Whimsical", "Tense", "Peaceful", "Epic", "Intimate"],
376
+ value="Tense"
377
+ )
378
+
379
+ realism_level = st.select_slider(
380
+ "Realism Level",
381
+ options=["Highly Realistic", "Balanced", "Fantastical"],
382
+ value="Balanced"
383
+ )
384
+
385
+ # Themes and Conflicts
386
+ st.markdown('<p class="subsection-title">Themes & Conflicts</p>', unsafe_allow_html=True)
387
+ theme_col1, theme_col2 = st.columns(2)
388
+
389
+ with theme_col1:
390
+ themes = st.multiselect(
391
+ "Main Themes",
392
+ ["Redemption", "Power", "Love", "Justice", "Identity", "Change",
393
+ "Good vs Evil", "Order vs Chaos", "Faith", "Family"],
394
+ default=["Identity"]
395
+ )
396
+
397
+ conflict_type = st.selectbox(
398
+ "Primary Conflict",
399
+ ["Person vs Person", "Person vs Nature", "Person vs Society",
400
+ "Person vs Self", "Person vs Technology", "Person vs Fate"]
401
+ )
402
+
403
+ with theme_col2:
404
+ moral_ambiguity = st.select_slider(
405
+ "Moral Ambiguity",
406
+ options=["Clear Good/Evil", "Some Gray Areas", "Morally Complex"],
407
+ value="Some Gray Areas"
408
+ )
409
+
410
+ theme_exploration = st.select_slider(
411
+ "Theme Exploration",
412
+ options=["Subtle", "Balanced", "Overt"],
413
+ value="Balanced"
414
+ )
415
+
416
+ return {
417
+ "main_params": {
418
+ "genre": genre,
419
+ "writing_style": writing_style,
420
+ "tone": tone,
421
+ "pacing": pacing,
422
+ "plot_complexity": plot_complexity,
423
+ "dialogue_focus": dialogue_focus
424
+ },
425
+ "setting": {
426
+ "time_period": time_period,
427
+ "type": setting_type,
428
+ "atmosphere": atmosphere,
429
+ "realism": realism_level
430
+ },
431
+ "themes": {
432
+ "main_themes": themes,
433
+ "conflict": conflict_type,
434
+ "moral_ambiguity": moral_ambiguity,
435
+ "exploration": theme_exploration
436
+ }
437
+ }
438
+
439
+ def display_story_preview(story_params: Dict, generated_content: Dict = None):
440
+ """Display generated story content"""
441
+ if not generated_content:
442
+ st.info("Generate preview to see content")
443
+ return
444
+
445
+ st.markdown('<div class="story-section">', unsafe_allow_html=True)
446
+
447
+ # Tabs for content sections
448
+ tabs = st.tabs(["Story", "Analysis"])
449
+
450
+ with tabs[0]:
451
+ # Prologue
452
+ st.markdown('<p class="subsection-title" style="color: #4e8cff;">Prologue</p>',
453
+ unsafe_allow_html=True)
454
+ st.markdown(generated_content.get('prologue', ''))
455
+
456
+ # Character Analysis
457
+ if generated_content.get('character_analysis'):
458
+ st.markdown('<p class="subsection-title" style="color: #4e8cff;">Character Analysis</p>',
459
+ unsafe_allow_html=True)
460
+ st.markdown(generated_content['character_analysis'])
461
+
462
+ # Scene Preview
463
+ if generated_content.get('scene_preview'):
464
+ st.markdown('<p class="subsection-title" style="color: #4e8cff;">Scene Preview</p>',
465
+ unsafe_allow_html=True)
466
+ st.markdown(generated_content['scene_preview'])
467
+
468
+ with tabs[1]:
469
+ col1, col2 = st.columns(2)
470
+
471
+ with col1:
472
+ # Themes
473
+ st.markdown('<p class="subsection-title" style="color: #4e8cff;">Themes & Motifs</p>',
474
+ unsafe_allow_html=True)
475
+ themes = generated_content.get('analysis', {}).get('themes', [])
476
+ if themes:
477
+ for theme in themes:
478
+ st.markdown(f"• {theme}")
479
+ else:
480
+ st.info("No themes specified")
481
+
482
+ with col2:
483
+ # Style Analysis
484
+ st.markdown('<p class="subsection-title" style="color: #4e8cff;">Style Analysis</p>',
485
+ unsafe_allow_html=True)
486
+ style = generated_content.get('analysis', {}).get('style', {})
487
+ if style:
488
+ for key, value in style.items():
489
+ st.markdown(f"**{key}:**")
490
+ st.markdown(value)
491
+ else:
492
+ st.info("No style analysis available")
493
+
494
+ st.markdown('</div>', unsafe_allow_html=True)
495
+
496
+ def generate_story_content(character: Dict, story_params: Dict) -> Dict:
497
+ """Generate story content using Claude"""
498
+ client = anthropic.Anthropic(api_key=st.secrets["ANTHROPIC_API_KEY"])
499
+
500
+ prompt = f"""Create a compelling and human-like story preview based on the following character and parameters:
501
+
502
+ Character Details:
503
+ {json.dumps(character, indent=2)}
504
+
505
+ Story Parameters:
506
+ {json.dumps(story_params, indent=2)}
507
+
508
+ Please provide:
509
+
510
+ 1. Prologue (2-3 engaging paragraphs)
511
+ - Set the tone and atmosphere
512
+ - Introduce the world/setting
513
+ - Create intrigue
514
+
515
+ 2. Character Analysis
516
+ - Deep psychological insights
517
+ - Key motivations and conflicts
518
+ - Unique traits and quirks
519
+
520
+ 3. Sample Scene
521
+ - Show character in action
522
+ - Demonstrate personality
523
+ - Include meaningful dialogue
524
+
525
+ 4. Thematic Analysis
526
+ - Provide 3-4 major themes
527
+ - Explain their significance
528
+ - Show their manifestation
529
+
530
+ 5. Style Elements
531
+ - Discuss narrative approach
532
+ - Note key stylistic choices
533
+ - Highlight unique features
534
+
535
+ Make the writing feel natural and professional, avoiding any AI-like patterns. Focus on depth and authenticity."""
536
+
537
+ try:
538
+ response = client.messages.create(
539
+ max_tokens=4096,
540
+ model="claude-3-5-sonnet-latest",
541
+ messages=[{"role": "user", "content": prompt}]
542
+ )
543
+
544
+ content = response.content[0].text
545
+
546
+ # Parse the content into structured sections
547
+ sections = parse_generated_content(content)
548
+
549
+ return sections
550
+ except Exception as e:
551
+ st.error(f"Error generating content: {str(e)}")
552
+ return None
553
+
554
+ def parse_generated_content(content: str) -> Dict:
555
+ """Parse Claude's response into structured sections"""
556
+ sections = {
557
+ "prologue": "",
558
+ "character_analysis": "",
559
+ "scene_preview": "",
560
+ "analysis": {
561
+ "themes": [],
562
+ "style": {}
563
+ }
564
+ }
565
+
566
+ try:
567
+ current_section = None
568
+ buffer = []
569
+
570
+ for line in content.split('\n'):
571
+ line = line.strip()
572
+ if not line:
573
+ continue
574
+
575
+ lower_line = line.lower()
576
+
577
+ # Section detection
578
+ if "prologue" in lower_line and len(line) < 30:
579
+ if buffer and current_section:
580
+ sections[current_section] = '\n'.join(buffer).strip()
581
+ current_section = "prologue"
582
+ buffer = []
583
+ continue
584
+
585
+ elif "character analysis" in lower_line and len(line) < 30:
586
+ if buffer and current_section:
587
+ sections[current_section] = '\n'.join(buffer).strip()
588
+ current_section = "character_analysis"
589
+ buffer = []
590
+ continue
591
+
592
+ elif "sample scene" in lower_line and len(line) < 30:
593
+ if buffer and current_section:
594
+ sections[current_section] = '\n'.join(buffer).strip()
595
+ current_section = "scene_preview"
596
+ buffer = []
597
+ continue
598
+
599
+ elif ("theme" in lower_line or "themes" in lower_line) and len(line) < 30:
600
+ if buffer and current_section:
601
+ sections[current_section] = '\n'.join(buffer).strip()
602
+ current_section = "themes"
603
+ buffer = []
604
+ continue
605
+
606
+ elif "style" in lower_line and len(line) < 30:
607
+ if buffer and current_section:
608
+ sections[current_section] = '\n'.join(buffer).strip()
609
+ current_section = "style"
610
+ buffer = []
611
+ continue
612
+
613
+ # Content processing
614
+ if current_section:
615
+ if current_section == "themes":
616
+ if line.startswith(('•', '-', '*')):
617
+ clean_line = line.lstrip('•-* ').strip()
618
+ sections['analysis']['themes'].append(clean_line)
619
+ elif line:
620
+ buffer.append(line)
621
+
622
+ elif current_section == "style":
623
+ if ':' in line:
624
+ key, value = line.split(':', 1)
625
+ key = key.strip().strip('0123456789.- ').strip()
626
+ sections['analysis']['style'][key] = value.strip()
627
+ elif line:
628
+ buffer.append(line)
629
+
630
+ else:
631
+ buffer.append(line)
632
+
633
+ # Process any remaining buffer
634
+ if buffer and current_section:
635
+ if current_section in ["themes", "style"]:
636
+ # Process remaining theme/style buffer if needed
637
+ pass
638
+ else:
639
+ sections[current_section] = '\n'.join(buffer).strip()
640
+
641
+ return sections
642
+
643
+ except Exception as e:
644
+ st.error(f"Error parsing content: {str(e)}")
645
+ return None
646
+
647
+ def main():
648
+ st.title("NarrativeCraft: Immersive Story & Character Design Studio")
649
+ st.markdown("#### Created by Oussama Naji")
650
+
651
+ with st.sidebar:
652
+ st.markdown("### Creation Process")
653
+ current_step = st.radio(
654
+ "Select Step:",
655
+ ["Character Creation", "Story Configuration", "Generate Preview"]
656
+ )
657
+
658
+ try:
659
+ if current_step == "Character Creation":
660
+ character = create_character_section()
661
+ if st.button("Save Character", type="primary"):
662
+ st.session_state.character = character
663
+ st.success("Character saved! Proceed to Story Configuration.")
664
+
665
+ elif current_step == "Story Configuration":
666
+ if 'character' not in st.session_state:
667
+ st.warning("Please create a character first!")
668
+ return
669
+
670
+ story_params = create_story_parameters_section()
671
+ if st.button("Save Story Parameters", type="primary"):
672
+ st.session_state.story_params = story_params
673
+ st.success("Story parameters saved! Proceed to Preview Generation.")
674
+
675
+ else: # Generate Preview
676
+ if 'character' not in st.session_state or 'story_params' not in st.session_state:
677
+ st.warning("Please complete character and story configuration first!")
678
+ return
679
+
680
+ if st.button("Generate Preview", type="primary"):
681
+ with st.spinner("🎨 Crafting your story..."):
682
+ progress_bar = st.progress(0)
683
+
684
+ try:
685
+ # Generate story content
686
+ progress_bar.progress(25)
687
+ st.info("🤖 Generating story content...")
688
+ st.session_state.generated_content = generate_story_content(
689
+ st.session_state.character,
690
+ st.session_state.story_params
691
+ )
692
+
693
+ if not st.session_state.generated_content:
694
+ st.error("Failed to generate story content")
695
+ return
696
+
697
+ # Generate voice
698
+ progress_bar.progress(50)
699
+ st.info("🎤 Creating character voice...")
700
+
701
+ voice_desc = f"A {st.session_state.character['voice']['type']} voice with {st.session_state.character['voice']['pattern']} speech pattern"
702
+ sample_text = "I stand here before you, ready to share my story. Each word carries the weight of my experiences, the essence of who I am."
703
+
704
+ st.session_state.voice_data = create_voice_preview(voice_desc, sample_text)
705
+
706
+ progress_bar.progress(75)
707
+ st.info("✨ Finalizing preview...")
708
+
709
+ # Display results
710
+ if st.session_state.character:
711
+ display_character_profile(
712
+ st.session_state.character,
713
+ st.session_state.voice_data
714
+ )
715
+
716
+ if st.session_state.story_params and st.session_state.generated_content:
717
+ display_story_preview(
718
+ st.session_state.story_params,
719
+ st.session_state.generated_content
720
+ )
721
+
722
+ progress_bar.progress(100)
723
+ st.success("✅ Preview generated successfully!")
724
+
725
+ except Exception as e:
726
+ st.error(f"An error occurred: {str(e)}")
727
+ return
728
+
729
+ # Display existing content if available
730
+ elif all(key in st.session_state for key in ['character', 'generated_content']):
731
+ display_character_profile(
732
+ st.session_state.character,
733
+ st.session_state.voice_data
734
+ )
735
+ display_story_preview(
736
+ st.session_state.story_params,
737
+ st.session_state.generated_content
738
+ )
739
+
740
+ # Reset button
741
+ if 'generated_content' in st.session_state:
742
+ if st.button("Start Over"):
743
+ for key in ['character', 'story_params', 'generated_content', 'voice_data']:
744
+ if key in st.session_state:
745
+ del st.session_state[key]
746
+ st.rerun()
747
+
748
+ except Exception as e:
749
+ st.error(f"An error occurred in main: {str(e)}")
750
+
751
+ if __name__ == "__main__":
752
+ main()