DhominickJ commited on
Commit
4e7673e
·
1 Parent(s): b73ba10

Successfully creating the application that allows for story generation based on the given parameters

Browse files
Files changed (3) hide show
  1. .gitignore +3 -0
  2. app.py +613 -0
  3. requirements.txt +1 -0
.gitignore ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+
2
+ #Ignore secrets
3
+ /.streamlit
app.py ADDED
@@ -0,0 +1,613 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import json
3
+ from dataclasses import dataclass
4
+ from typing import List, Dict, Optional
5
+ from pathlib import Path
6
+ import requests
7
+ from datetime import datetime
8
+ import re
9
+ from huggingface_hub import InferenceClient
10
+
11
+ # Creating an OpenAI call
12
+ from openai import OpenAI
13
+
14
+ @dataclass
15
+ class Character:
16
+ """Represents a character in the visual novel, storing their attributes and state"""
17
+ name: str
18
+ description: str
19
+ personality: str
20
+ relationships: Dict[str, int] # Character name -> relationship value
21
+ current_mood: str = "neutral"
22
+ background_story: str = ""
23
+
24
+ def __init__(self, name: str, description: str, personality: str, relationships: Dict[str, int] = None,
25
+ current_mood: str = "neutral", background_story: str = ""):
26
+ self.name = name
27
+ self.description = description
28
+ self.personality = personality
29
+ self.relationships = relationships if relationships is not None else {}
30
+ self.current_mood = current_mood
31
+ self.background_story = background_story
32
+
33
+ @dataclass
34
+ class StoryState:
35
+ """Maintains the current state of the visual novel, including scene and character information"""
36
+ current_scene: str
37
+ characters: Dict[str, Character]
38
+ player_choices: List[str]
39
+ story_branch: str
40
+ mood: str
41
+ setting: str
42
+ genre: str
43
+ chapter: int = 1
44
+ scene_number: int = 1
45
+
46
+ class VisualNovelGenerator:
47
+ def __init__(self):
48
+ """Initialize the visual novel generator with API configuration"""
49
+ self.setup_streamlit_config()
50
+ self.initialize_session_state()
51
+ self.setup_api()
52
+
53
+ def setup_api(self):
54
+ """Configure the OpenAI API client"""
55
+ # Load API key from Streamlit secrets or environment variables
56
+ self.api_key = st.secrets.get("API_KEY") or st.session_state.get("api_key")
57
+
58
+ # Initialize OpenAI client
59
+ self.client = OpenAI(
60
+ base_url = "https://integrate.api.nvidia.com/v1",
61
+ api_key = self.api_key
62
+ )
63
+
64
+ def api_request(self, messages: List[Dict[str, str]], max_tokens: int = 1000, temperature=0.7) -> str:
65
+ """Send a request to the API and handle the response"""
66
+ try:
67
+ # temperature = st.slider("Creativity: ", 0.0, 1.0, 0.7, key='_tempslider')
68
+ response = self.client.chat.completions.create(
69
+ model="meta/llama-3.1-405b-instruct",
70
+ messages=messages,
71
+ max_tokens=max_tokens,
72
+ temperature=temperature
73
+ )
74
+ return response.choices[0].message.content
75
+ except Exception as e:
76
+ st.error(f"API Error: {str(e)}")
77
+ return None
78
+
79
+ def setup_streamlit_config(self):
80
+ """Configure the Streamlit page settings for optimal display"""
81
+ st.set_page_config(
82
+ page_title="Visual Novel Generator",
83
+ page_icon="📚",
84
+ layout="wide",
85
+ initial_sidebar_state="expanded"
86
+ )
87
+
88
+ def image_api_request(prompt: str) -> str:
89
+ """Send a request to generate an image based on the prompt"""
90
+ image_api_key = st.secrets.get("IMG_API_KEY") or st.session_state.get("img_api_key")
91
+ client = InferenceClient("black-forest-labs/FLUX.1-dev", token=image_api_key)
92
+ try:
93
+ image = client.text_to_image(prompt)
94
+ return image
95
+ except Exception as e:
96
+ st.error(f"Image Generation Error: {str(e)}")
97
+ return None
98
+
99
+ def initialize_session_state(self):
100
+ """Set up the initial session state variables"""
101
+ if 'api_key' not in st.session_state:
102
+ st.session_state.api_key = None
103
+ if 'story_state' not in st.session_state:
104
+ st.session_state.story_state = None
105
+ if 'story_history' not in st.session_state:
106
+ st.session_state.story_history = []
107
+ if 'save_states' not in st.session_state:
108
+ st.session_state.save_states = {}
109
+
110
+ def create_api_key_input(self):
111
+ """Create an input field for the API key if not already set"""
112
+ if not self.api_key:
113
+ st.sidebar.markdown("### API Configuration")
114
+ api_key = st.sidebar.text_input("Enter your API key:", type="password")
115
+ if api_key:
116
+ st.session_state.api_key = api_key
117
+ self.api_key = api_key
118
+ self.setup_api()
119
+ st.sidebar.success("API key set successfully!")
120
+ st.experimental_experimental_rerun()
121
+ return False
122
+ return True
123
+
124
+ def create_story_setup_interface(self):
125
+ """Create the initial story setup interface with enhanced options"""
126
+ if not self.create_api_key_input():
127
+ return
128
+
129
+ st.title("📚 Interactive Visual Novel Generator")
130
+ st.markdown("### Create Your Story")
131
+
132
+ with st.form("story_setup"):
133
+ # Advanced story settings
134
+ col1, col2, col3 = st.columns(3)
135
+
136
+ with col1:
137
+ genre = st.selectbox(
138
+ "Genre",
139
+ ["Romance", "Mystery", "Fantasy", "Science Fiction", "Horror", "Adventure"]
140
+ )
141
+ story_theme = st.text_area(
142
+ "Theme/Topic",
143
+ help="Main theme or concept of your story"
144
+ )
145
+ story_theme = st.selectbox(
146
+ "Theme/Topic",
147
+ ["Coming of Age", "Good vs Evil", "Power of Friendship", "Forbidden Love",
148
+ "Quest for Identity", "Revenge", "Redemption", "Personal Growth",
149
+ "Family Bonds", "Justice vs Mercy"],
150
+ help="Main theme or concept of your story"
151
+ )
152
+
153
+ with col2:
154
+ setting = st.select_slider(
155
+ "Time Period",
156
+ options=["Prehistoric", "Ancient", "Medieval", "Renaissance", "Modern", "Future"],
157
+ value="Modern",
158
+ help="Choose the time period for your story"
159
+ )
160
+ tone = st.selectbox(
161
+ "Tone",
162
+ ["Light and Humorous", "Dark and Serious", "Dramatic", "Mysterious", "Romantic"]
163
+ )
164
+
165
+ with col3:
166
+ pacing = st.selectbox(
167
+ "Story Pacing",
168
+ ["Fast-paced", "Moderate", "Slow-burn"]
169
+ )
170
+ narrative_style = st.selectbox(
171
+ "Narrative Style",
172
+ ["First Person", "Third Person Limited", "Third Person Omniscient"]
173
+ )
174
+
175
+ # Enhanced character creation
176
+ st.markdown("### Create Your Characters")
177
+ num_characters = st.number_input("Number of Characters", min_value=1, max_value=5, value=2)
178
+
179
+ characters = {}
180
+ for i in range(num_characters):
181
+ with st.expander(f"Character {i+1}"):
182
+ char_name = st.text_input("Name", key=f"char_name_{i}")
183
+
184
+ # Let's generate character details using AI for each character
185
+ if char_name:
186
+ prompt = f"""Return a JSON object for a character named {char_name} in this format exactly:
187
+ {{
188
+ "description": "detailed physical appearance",
189
+ "personality": "personality traits",
190
+ "background": "background story",
191
+ "goals": "character goals"
192
+ }}"""
193
+
194
+ response = self.api_request([
195
+ {"role": "system", "content": "You are a character designer that only returns valid JSON."},
196
+ {"role": "user", "content": prompt}
197
+ ], temperature=0.5)
198
+
199
+ if response:
200
+ try:
201
+ # Remove any non-JSON text before and after the JSON object
202
+ json_start = response.find("{")
203
+ json_end = response.rfind("}") + 1
204
+ if json_start >= 0 and json_end > json_start:
205
+ json_str = response[json_start:json_end]
206
+ char_info = json.loads(json_str)
207
+ characters[char_name] = char_info
208
+ st.success(f"Character {char_name} details generated!")
209
+ except:
210
+ st.error(f"Error generating details for {char_name}")
211
+
212
+ # Advanced story options
213
+ with st.expander("Advanced Story Options"):
214
+ story_length = st.selectbox(
215
+ "Story Length",
216
+ ["Short (3-5 scenes)", "Medium (6-10 scenes)", "Long (11-15 scenes)"]
217
+ )
218
+ complexity = st.selectbox(
219
+ "Story Complexity",
220
+ ["Simple", "Moderate", "Complex"]
221
+ )
222
+ themes = st.multiselect(
223
+ "Additional Themes",
224
+ ["Friendship", "Betrayal", "Love", "Loss", "Redemption", "Growth"]
225
+ )
226
+
227
+ submit_button = st.form_submit_button("Generate Story")
228
+
229
+ if submit_button and characters:
230
+ with st.spinner("Crafting your story..."):
231
+ self.generate_story(
232
+ genre, story_theme, setting, tone, characters,
233
+ pacing, narrative_style, story_length, complexity, themes
234
+ )
235
+
236
+ def generate_story(self, genre, theme, setting, tone, characters, pacing,
237
+ narrative_style, story_length, complexity, themes):
238
+ """Generate the initial story state and content using the API"""
239
+ # Create a detailed story prompt
240
+ story_prompt = f"""
241
+ Create an engaging visual novel opening with these parameters:
242
+ Genre: {genre}
243
+ Theme: {theme}
244
+ Setting: {setting}
245
+ Tone: {tone}
246
+ Pacing: {pacing}
247
+ Narrative Style: {narrative_style}
248
+ Story Length: {story_length}
249
+ Complexity: {complexity}
250
+ Additional Themes: {', '.join(themes)}
251
+
252
+ Characters:
253
+ {json.dumps(characters, indent=2)}
254
+
255
+ Create:
256
+ 1. A vivid opening scene (2-3 paragraphs)
257
+ 2. Initial emotional states for each character
258
+ 3. Three meaningful dialogue options that will impact the story
259
+ 4. Brief background context for the scene
260
+
261
+ Format as JSON:
262
+ {{
263
+ "opening_scene": "scene description",
264
+ "background_context": "context",
265
+ "character_emotions": {{"character_name": "emotion"}},
266
+ "dialogue_options": ["option1", "option2", "option3"]
267
+ }}
268
+ """
269
+
270
+ try:
271
+ # Generate story content through API
272
+ response = self.api_request([
273
+ {"role": "system", "content": "You are a professional visual novel writer specializing in creating engaging, character-driven narratives."},
274
+ {"role": "user", "content": story_prompt}
275
+ ])
276
+
277
+ if not response:
278
+ return
279
+
280
+ try:
281
+ # Clean the response string to ensure valid JSON
282
+ json_str = response.strip()
283
+ # Find the first '{' and last '}' to extract just the JSON object
284
+ start = json_str.find('{')
285
+ end = json_str.rfind('}') + 1
286
+ if start == -1 or end == 0:
287
+ raise ValueError("No valid JSON object found in response")
288
+ clean_json = json_str[start:end]
289
+
290
+ story_data = json.loads(clean_json)
291
+ except json.JSONDecodeError as e:
292
+ st.error(f"Invalid JSON response from API: {str(e)}")
293
+ return
294
+ except ValueError as e:
295
+ st.error(f"Could not find valid JSON in response: {str(e)}")
296
+ return
297
+
298
+ # Initialize enhanced character objects
299
+ char_objects = {}
300
+ for char_name, char_info in characters.items():
301
+ char_objects[char_name] = Character(
302
+ name=char_name,
303
+ description=char_info["description"],
304
+ personality=char_info["personality"],
305
+ relationships={},
306
+ current_mood=story_data["character_emotions"].get(char_name, "neutral"),
307
+ background_story=char_info["background"]
308
+ )
309
+
310
+ # Create detailed story state
311
+ st.session_state.story_state = StoryState(
312
+ current_scene=f"{story_data['background_context']}\n\n{story_data['opening_scene']}",
313
+ characters=char_objects,
314
+ player_choices=story_data["dialogue_options"],
315
+ story_branch="main",
316
+ mood=tone,
317
+ setting=setting,
318
+ genre=genre
319
+ )
320
+
321
+ # Initialize story history
322
+ st.session_state.story_history.append({
323
+ "scene": st.session_state.story_state.current_scene,
324
+ "choices": story_data["dialogue_options"],
325
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
326
+ })
327
+
328
+ except Exception as e:
329
+ st.error(f"Error generating story: {str(e)}")
330
+ st.error("Please try again with different inputs.")
331
+
332
+ def display_story_interface(self):
333
+ """Display the main story interface with enhanced features"""
334
+ if not st.session_state.story_state:
335
+ return
336
+
337
+ st.title("📖 Your Visual Novel")
338
+
339
+ # Main story display
340
+ col1, col2 = st.columns([2, 1])
341
+
342
+ with col1:
343
+ # Scene information
344
+ st.markdown(f"### Chapter {st.session_state.story_state.chapter}, Scene {st.session_state.story_state.scene_number}")
345
+ st.write(st.session_state.story_state.current_scene)
346
+
347
+ # Enhanced choice system
348
+ st.markdown("### What will you do?")
349
+ for option in st.session_state.story_state.player_choices:
350
+ if st.button(option, key=f"choice_{option}"):
351
+ self.process_player_choice(option)
352
+
353
+ with col2:
354
+ # Character status and relationships
355
+ st.markdown("### Characters")
356
+ for char_name, char in st.session_state.story_state.characters.items():
357
+ with st.expander(char_name):
358
+ st.write(f"Current Mood: {char.current_mood}")
359
+ st.markdown("#### Personality")
360
+ st.write(char.personality)
361
+ st.markdown("#### Background")
362
+ st.write(char.background_story)
363
+ if char.relationships:
364
+ st.markdown("#### Relationships")
365
+ for other_char, value in char.relationships.items():
366
+ st.write(f"- {other_char}: {value}")
367
+
368
+ # Story management features
369
+ col3, col4 = st.columns([1, 1])
370
+ with col3:
371
+ if st.button("Save Game"):
372
+ save_name = f"Save_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
373
+ st.session_state.save_states[save_name] = {
374
+ "story_state": st.session_state.story_state,
375
+ "history": st.session_state.story_history
376
+ }
377
+ st.success(f"Game saved as: {save_name}")
378
+
379
+ with col4:
380
+ if st.session_state.save_states:
381
+ save_files = list(st.session_state.save_states.keys())
382
+ selected_save = st.selectbox("Load Save File:", save_files)
383
+ if st.button("Load Game"):
384
+ save_data = st.session_state.save_states[selected_save]
385
+ st.session_state.story_state = save_data["story_state"]
386
+ st.session_state.story_history = save_data["history"]
387
+ st.success("Game loaded successfully!")
388
+ st.experimental_rerun()
389
+
390
+ # Story history with timestamps
391
+ with st.expander("Story History"):
392
+ for i, history_item in enumerate(st.session_state.story_history):
393
+ st.markdown(f"### Scene {i+1} ({history_item['timestamp']})")
394
+ st.write(history_item["scene"])
395
+ st.markdown("Choices:")
396
+ for choice in history_item["choices"]:
397
+ st.write(f"- {choice}")
398
+
399
+ def process_player_choice(self, choice):
400
+ """Process the player's choice and generate the next scene using the API"""
401
+ try:
402
+ # Create a detailed context for the next scene
403
+ context = {
404
+ "current_scene": st.session_state.story_state.current_scene,
405
+ "choice": choice,
406
+ "genre": st.session_state.story_state.genre,
407
+ "mood": st.session_state.story_state.mood,
408
+ "characters": {name: {"mood": char.current_mood, "personality": char.personality}
409
+ for name, char in st.session_state.story_state.characters.items()},
410
+ "chapter": st.session_state.story_state.chapter,
411
+ "scene_number": st.session_state.story_state.scene_number
412
+ }
413
+
414
+ prompt = f"""
415
+ Story Context: {json.dumps(context, indent=2)}
416
+
417
+ Generate the next scene based on the player's choice. Include:
418
+ 1. Scene description
419
+ 2. Updated character emotions
420
+ 3. Three new meaningful choices
421
+ 4. Any relationship changes between characters.
422
+ 5. You should not add any additonal comments on the json file.
423
+
424
+ Format as JSON:
425
+ {{
426
+ "scene": "scene description",
427
+ "character_emotions": {{"character_name": "emotion"}},
428
+ "dialogue_options": ["option1", "option2", "option3"],
429
+ "relationship_changes": {{"character1": {{"character2": "change_value"}}}}
430
+ }}
431
+ """
432
+
433
+ # Generate next scene through API
434
+ response = self.api_request([
435
+ {"role": "system", "content": "You are a professional visual novel writer continuing an ongoing story."},
436
+ {"role": "user", "content": prompt}
437
+ ])
438
+
439
+ if not response:
440
+ return
441
+
442
+ try:
443
+ # Clean the response string to ensure valid JSON
444
+ json_str = response.strip()
445
+ # Find the first '{' and last '}' to extract just the JSON object
446
+ start = json_str.find('{')
447
+ end = json_str.rfind('}') + 1
448
+ if start == -1 or end == 0:
449
+ raise ValueError("No valid JSON object found in response")
450
+ clean_json = json_str[start:end]
451
+ scene_data = json.loads(clean_json)
452
+ except json.JSONDecodeError as e:
453
+ st.error(f"Invalid JSON response from API: {str(e)}")
454
+ st.write(response)
455
+ return
456
+ except ValueError as e:
457
+ st.error(f"Could not find valid JSON in response: {str(e)}")
458
+ return
459
+
460
+ # Update character states
461
+ for char_name, emotion in scene_data["character_emotions"].items():
462
+ if char_name in st.session_state.story_state.characters:
463
+ st.session_state.story_state.characters[char_name].current_mood = emotion
464
+
465
+ # Update relationships
466
+ for char1, relations in scene_data.get("relationship_changes", {}).items():
467
+ for char2, change in relations.items():
468
+ if char1 in st.session_state.story_state.characters and char2 in st.session_state.story_state.characters:
469
+ current_value = st.session_state.story_state.characters[char1].relationships.get(char2, 0)
470
+ # Extract numeric value from the change string using regex
471
+ numeric_change = int(''.join(filter(str.isdigit, str(change))))
472
+ # Apply negative if "decrease" or "-" is in the change string
473
+ if any(term in str(change).lower() for term in ['decrease', '-']):
474
+ numeric_change = -numeric_change
475
+ st.session_state.story_state.characters[char1].relationships[char2] = current_value + numeric_change
476
+
477
+ # Update story state
478
+ st.session_state.story_state.current_scene = scene_data["scene"]
479
+ st.session_state.story_state.player_choices = scene_data["dialogue_options"]
480
+ st.session_state.story_state.scene_number += 1
481
+
482
+ # Check if we should advance to next chapter
483
+ if st.session_state.story_state.scene_number > 3: # Advance chapter every 3 scenes
484
+ st.session_state.story_state.chapter += 1
485
+ st.session_state.story_state.scene_number = 1
486
+
487
+ # Add to history
488
+ st.session_state.story_history.append({
489
+ "scene": scene_data["scene"],
490
+ "choices": scene_data["dialogue_options"],
491
+ "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
492
+ })
493
+
494
+ # Force a experimental_rerun to update the interface
495
+ st.experimental_rerun()
496
+
497
+ except Exception as e:
498
+ st.error(f"Error processing choice: {str(e)}")
499
+ return
500
+
501
+ def save_story_to_file(self):
502
+ """Save the current story state to a JSON file"""
503
+ if not st.session_state.story_state:
504
+ return False
505
+
506
+ try:
507
+ save_data = {
508
+ "story_state": {
509
+ "current_scene": st.session_state.story_state.current_scene,
510
+ "chapter": st.session_state.story_state.chapter,
511
+ "scene_number": st.session_state.story_state.scene_number,
512
+ "genre": st.session_state.story_state.genre,
513
+ "mood": st.session_state.story_state.mood,
514
+ "setting": st.session_state.story_state.setting
515
+ },
516
+ "characters": {
517
+ name: {
518
+ "name": char.name,
519
+ "description": char.description,
520
+ "personality": char.personality,
521
+ "current_mood": char.current_mood,
522
+ "background_story": char.background_story,
523
+ "relationships": char.relationships
524
+ }
525
+ for name, char in st.session_state.story_state.characters.items()
526
+ },
527
+ "history": st.session_state.story_history
528
+ }
529
+
530
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
531
+ filename = f"visual_novel_save_{timestamp}.json"
532
+
533
+ with open(filename, 'w') as f:
534
+ json.dump(save_data, f, indent=2)
535
+
536
+ return filename
537
+
538
+ except Exception as e:
539
+ st.error(f"Error saving story: {str(e)}")
540
+ return False
541
+
542
+ def load_story_from_file(self, filename):
543
+ """Load a story state from a JSON file"""
544
+ try:
545
+ with open(filename, 'r') as f:
546
+ save_data = json.load(f)
547
+
548
+ # Reconstruct characters
549
+ characters = {}
550
+ for name, char_data in save_data["characters"].items():
551
+ characters[name] = Character(
552
+ name=char_data["name"],
553
+ description=char_data["description"],
554
+ personality=char_data["personality"],
555
+ current_mood=char_data["current_mood"],
556
+ background_story=char_data["background_story"],
557
+ relationships=char_data["relationships"]
558
+ )
559
+
560
+ # Reconstruct story state
561
+ st.session_state.story_state = StoryState(
562
+ current_scene=save_data["story_state"]["current_scene"],
563
+ characters=characters,
564
+ player_choices=save_data["story_state"].get("player_choices", []),
565
+ story_branch=save_data["story_state"].get("story_branch", "main"),
566
+ mood=save_data["story_state"]["mood"],
567
+ setting=save_data["story_state"]["setting"],
568
+ genre=save_data["story_state"]["genre"],
569
+ chapter=save_data["story_state"]["chapter"],
570
+ scene_number=save_data["story_state"]["scene_number"]
571
+ )
572
+
573
+ # Load history
574
+ st.session_state.story_history = save_data["history"]
575
+
576
+ return True
577
+
578
+ except Exception as e:
579
+ st.error(f"Error loading story: {str(e)}")
580
+ return False
581
+
582
+ def run(self):
583
+ """Main application loop with enhanced save/load functionality"""
584
+ # Sidebar options
585
+ with st.sidebar:
586
+ st.markdown("### Story Management")
587
+ if st.button("Start New Story"):
588
+ st.session_state.story_state = None
589
+ st.session_state.story_history = []
590
+ st.experimental_rerun()
591
+
592
+ if st.session_state.story_state:
593
+ if st.button("Save Story to File"):
594
+ filename = self.save_story_to_file()
595
+ if filename:
596
+ st.success(f"Story saved to {filename}")
597
+
598
+ uploaded_file = st.file_uploader("Load Story from File", type="json")
599
+ if uploaded_file:
600
+ if self.load_story_from_file(uploaded_file):
601
+ st.success("Story loaded successfully!")
602
+ st.experimental_rerun()
603
+
604
+ # Main interface
605
+ if not st.session_state.story_state:
606
+ self.create_story_setup_interface()
607
+ else:
608
+ self.display_story_interface()
609
+
610
+ # Run the application
611
+ if __name__ == "__main__":
612
+ app = VisualNovelGenerator()
613
+ app.run()
requirements.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ streamlit