update
Browse files- client/src/components/StoryChoices.jsx +4 -2
- client/src/layouts/ComicLayout.jsx +2 -0
- server/core/constants.py +4 -0
- server/core/generators/image_prompt_generator.py +11 -6
- server/core/generators/metadata_generator.py +4 -2
- server/core/generators/story_segment_generator.py +4 -7
- server/core/story_generator.py +13 -3
client/src/components/StoryChoices.jsx
CHANGED
@@ -31,6 +31,8 @@ export function StoryChoices({
|
|
31 |
disabled = false,
|
32 |
isLastStep = false,
|
33 |
isGameOver = false,
|
|
|
|
|
34 |
containerRef,
|
35 |
}) {
|
36 |
const navigate = useNavigate();
|
@@ -53,13 +55,13 @@ export function StoryChoices({
|
|
53 |
<Typography
|
54 |
variant="h3"
|
55 |
sx={{
|
56 |
-
color: "
|
57 |
textAlign: "center",
|
58 |
mb: 2,
|
59 |
textTransform: "uppercase",
|
60 |
}}
|
61 |
>
|
62 |
-
|
63 |
</Typography>
|
64 |
<Button
|
65 |
variant="outlined"
|
|
|
31 |
disabled = false,
|
32 |
isLastStep = false,
|
33 |
isGameOver = false,
|
34 |
+
isDeath = false,
|
35 |
+
isVictory = false,
|
36 |
containerRef,
|
37 |
}) {
|
38 |
const navigate = useNavigate();
|
|
|
55 |
<Typography
|
56 |
variant="h3"
|
57 |
sx={{
|
58 |
+
color: isVictory ? "#4CAF50" : "#f44336",
|
59 |
textAlign: "center",
|
60 |
mb: 2,
|
61 |
textTransform: "uppercase",
|
62 |
}}
|
63 |
>
|
64 |
+
{isVictory ? "VICTORY" : "DEFEAT"}
|
65 |
</Typography>
|
66 |
<Button
|
67 |
variant="outlined"
|
client/src/layouts/ComicLayout.jsx
CHANGED
@@ -125,6 +125,8 @@ function ComicPage({
|
|
125 |
layout.segments[layout.segments.length - 1]?.isDeath ||
|
126 |
layout.segments[layout.segments.length - 1]?.isVictory
|
127 |
}
|
|
|
|
|
128 |
/>
|
129 |
</Box>
|
130 |
)}
|
|
|
125 |
layout.segments[layout.segments.length - 1]?.isDeath ||
|
126 |
layout.segments[layout.segments.length - 1]?.isVictory
|
127 |
}
|
128 |
+
isDeath={layout.segments[layout.segments.length - 1]?.isDeath}
|
129 |
+
isVictory={layout.segments[layout.segments.length - 1]?.isVictory}
|
130 |
/>
|
131 |
</Box>
|
132 |
)}
|
server/core/constants.py
CHANGED
@@ -6,6 +6,10 @@ class GameConfig:
|
|
6 |
# Story constraints
|
7 |
MIN_PANELS = 1
|
8 |
MAX_PANELS = 4
|
|
|
|
|
|
|
|
|
9 |
|
10 |
# Story progression
|
11 |
STORY_BEAT_INTRO = 0
|
|
|
6 |
# Story constraints
|
7 |
MIN_PANELS = 1
|
8 |
MAX_PANELS = 4
|
9 |
+
|
10 |
+
MIN_SEGMENTS_BEFORE_END = 2
|
11 |
+
MAX_SEGMENTS_BEFORE_END = 4
|
12 |
+
WINNING_STORY_CHANCE = 0.2
|
13 |
|
14 |
# Story progression
|
15 |
STORY_BEAT_INTRO = 0
|
server/core/generators/image_prompt_generator.py
CHANGED
@@ -70,9 +70,6 @@ class ImagePromptGenerator(BaseGenerator):
|
|
70 |
IMPORTANT RULES FOR IMAGE PROMPTS:
|
71 |
- If you are prompting only one panel, it must be an important panel. Dont use only one panel often. It should be a key moment in the story.
|
72 |
- If you are prompting more than one panel, they must be distinct and meaningful.
|
73 |
-
- For death scenes: Focus on the dramatic and emotional impact, not the gore or violence
|
74 |
-
- For victory scenes: Emphasize triumph, relief, and accomplishment
|
75 |
-
- For victory and death scenes, you MUST use 1 panel only
|
76 |
|
77 |
RESPONSE FORMAT:
|
78 |
You must return a valid JSON object that matches this Pydantic schema:
|
@@ -102,6 +99,13 @@ Story text: {story_text}
|
|
102 |
|
103 |
Generate panel descriptions that capture the key moments of this scene.
|
104 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
105 |
Story state: {is_end}
|
106 |
"""
|
107 |
|
@@ -162,7 +166,7 @@ Story state: {is_end}
|
|
162 |
metadata = f"[{time} - {location}] "
|
163 |
return f"{self.artist_style} -- {metadata}{prompt}"
|
164 |
|
165 |
-
async def generate(self, story_text: str, time: str, location: str, is_death: bool = False, is_victory: bool = False) -> ImagePromptResponse:
|
166 |
"""Generate image prompts based on story text.
|
167 |
|
168 |
Args:
|
@@ -178,9 +182,10 @@ Story state: {is_end}
|
|
178 |
|
179 |
is_end=""
|
180 |
if is_death:
|
181 |
-
is_end = "this is a death
|
182 |
elif is_victory:
|
183 |
-
is_end = "this is a victory
|
|
|
184 |
|
185 |
response = await super().generate(
|
186 |
story_text=story_text,
|
|
|
70 |
IMPORTANT RULES FOR IMAGE PROMPTS:
|
71 |
- If you are prompting only one panel, it must be an important panel. Dont use only one panel often. It should be a key moment in the story.
|
72 |
- If you are prompting more than one panel, they must be distinct and meaningful.
|
|
|
|
|
|
|
73 |
|
74 |
RESPONSE FORMAT:
|
75 |
You must return a valid JSON object that matches this Pydantic schema:
|
|
|
99 |
|
100 |
Generate panel descriptions that capture the key moments of this scene.
|
101 |
|
102 |
+
|
103 |
+
|
104 |
+
- For death scenes: Focus on the dramatic and emotional impact, not the gore or violence
|
105 |
+
- For victory scenes: Emphasize triumph, relief, and accomplishment
|
106 |
+
- For victory and death scenes, you MUST use 1 panel only
|
107 |
+
|
108 |
+
|
109 |
Story state: {is_end}
|
110 |
"""
|
111 |
|
|
|
166 |
metadata = f"[{time} - {location}] "
|
167 |
return f"{self.artist_style} -- {metadata}{prompt}"
|
168 |
|
169 |
+
async def generate(self, story_text: str, time: str, location: str, is_death: bool = False, is_victory: bool = False, turn_before_end: int = 0, is_winning_story: bool = False) -> ImagePromptResponse:
|
170 |
"""Generate image prompts based on story text.
|
171 |
|
172 |
Args:
|
|
|
182 |
|
183 |
is_end=""
|
184 |
if is_death:
|
185 |
+
is_end = "this is a death. just one panel, MANDATORY."
|
186 |
elif is_victory:
|
187 |
+
is_end = "this is a victory. just one panel, MANDATORY."
|
188 |
+
|
189 |
|
190 |
response = await super().generate(
|
191 |
story_text=story_text,
|
server/core/generators/metadata_generator.py
CHANGED
@@ -85,7 +85,7 @@ Current game state:
|
|
85 |
except ValueError as e:
|
86 |
raise ValueError(str(e))
|
87 |
|
88 |
-
async def generate(self, story_text: str, current_time: str, current_location: str, story_beat: int, error_feedback: str = "") -> StoryMetadataResponse:
|
89 |
"""Surcharge de generate pour inclure le error_feedback par défaut."""
|
90 |
|
91 |
is_end = "This should be close to the end of the story." if story_beat >= 5 else ""
|
@@ -95,5 +95,7 @@ Current game state:
|
|
95 |
current_location=current_location,
|
96 |
story_beat=story_beat,
|
97 |
error_feedback=error_feedback,
|
98 |
-
is_end=is_end
|
|
|
|
|
99 |
)
|
|
|
85 |
except ValueError as e:
|
86 |
raise ValueError(str(e))
|
87 |
|
88 |
+
async def generate(self, story_text: str, current_time: str, current_location: str, story_beat: int, error_feedback: str = "", turn_before_end: int = 0, is_winning_story: bool = False) -> StoryMetadataResponse:
|
89 |
"""Surcharge de generate pour inclure le error_feedback par défaut."""
|
90 |
|
91 |
is_end = "This should be close to the end of the story." if story_beat >= 5 else ""
|
|
|
95 |
current_location=current_location,
|
96 |
story_beat=story_beat,
|
97 |
error_feedback=error_feedback,
|
98 |
+
is_end=is_end,
|
99 |
+
turn_before_end=turn_before_end,
|
100 |
+
is_winning_story=is_winning_story
|
101 |
)
|
server/core/generators/story_segment_generator.py
CHANGED
@@ -57,7 +57,7 @@ Rules: {FORMATTING_RULES}
|
|
57 |
|
58 |
You must return a JSON object with the following format:
|
59 |
{{
|
60 |
-
"story_text": "Your story segment here
|
61 |
}}
|
62 |
"""
|
63 |
|
@@ -72,10 +72,6 @@ Current game state :
|
|
72 |
- Story beat: {story_beat}
|
73 |
|
74 |
{is_end}
|
75 |
-
You must return a JSON object with the following format:
|
76 |
-
{{
|
77 |
-
"story_text": "Your story segment here (15 words)"
|
78 |
-
}}
|
79 |
"""
|
80 |
return ChatPromptTemplate(
|
81 |
messages=[
|
@@ -109,10 +105,11 @@ You must return a JSON object with the following format:
|
|
109 |
"Example: {'story_text': 'Your story segment here'}"
|
110 |
)
|
111 |
|
112 |
-
async def generate(self, story_beat: int, current_time: str, current_location: str, previous_choice: str, story_history: str = "") -> StorySegmentResponse:
|
113 |
"""Generate the next story segment."""
|
114 |
|
115 |
-
|
|
|
116 |
|
117 |
return await super().generate(
|
118 |
HERO_DESCRIPTION=HERO_DESCRIPTION,
|
|
|
57 |
|
58 |
You must return a JSON object with the following format:
|
59 |
{{
|
60 |
+
"story_text": "Your story segment here"
|
61 |
}}
|
62 |
"""
|
63 |
|
|
|
72 |
- Story beat: {story_beat}
|
73 |
|
74 |
{is_end}
|
|
|
|
|
|
|
|
|
75 |
"""
|
76 |
return ChatPromptTemplate(
|
77 |
messages=[
|
|
|
105 |
"Example: {'story_text': 'Your story segment here'}"
|
106 |
)
|
107 |
|
108 |
+
async def generate(self, story_beat: int, current_time: str, current_location: str, previous_choice: str, story_history: str = "", turn_before_end: int = 0, is_winning_story: bool = False) -> StorySegmentResponse:
|
109 |
"""Generate the next story segment."""
|
110 |
|
111 |
+
what_to_represent =" this is a victory !" if is_winning_story else "this is a death !"
|
112 |
+
is_end = f"Generate the END of the story. {what_to_represent} in 30 words. THIS IS MANDATORY." if story_beat == turn_before_end else "Generate the next segment of the story in 15 words."
|
113 |
|
114 |
return await super().generate(
|
115 |
HERO_DESCRIPTION=HERO_DESCRIPTION,
|
server/core/story_generator.py
CHANGED
@@ -6,6 +6,8 @@ from core.generators.story_segment_generator import StorySegmentGenerator
|
|
6 |
from core.generators.image_prompt_generator import ImagePromptGenerator
|
7 |
from core.generators.metadata_generator import MetadataGenerator
|
8 |
from core.game_state import GameState
|
|
|
|
|
9 |
|
10 |
class StoryGenerator:
|
11 |
_instance = None
|
@@ -22,6 +24,8 @@ class StoryGenerator:
|
|
22 |
print("Initializing StoryGenerator singleton")
|
23 |
self.api_key = api_key
|
24 |
self.model_name = model_name
|
|
|
|
|
25 |
self.mistral_client = MistralClient(api_key=api_key, model_name=model_name)
|
26 |
self.image_prompt_generator = None # Will be initialized with the first universe style
|
27 |
self.metadata_generator = MetadataGenerator(self.mistral_client)
|
@@ -76,7 +80,9 @@ class StoryGenerator:
|
|
76 |
current_time=game_state.current_time,
|
77 |
current_location=game_state.current_location,
|
78 |
previous_choice=previous_choice,
|
79 |
-
story_history=story_history
|
|
|
|
|
80 |
)
|
81 |
|
82 |
# print(f"Generated story text: {segment_response}")
|
@@ -86,7 +92,9 @@ class StoryGenerator:
|
|
86 |
story_text=segment_response.story_text,
|
87 |
current_time=game_state.current_time,
|
88 |
current_location=game_state.current_location,
|
89 |
-
story_beat=game_state.story_beat
|
|
|
|
|
90 |
)
|
91 |
# print(f"Generated metadata_response: {metadata_response}")
|
92 |
|
@@ -96,7 +104,9 @@ class StoryGenerator:
|
|
96 |
time=metadata_response.time,
|
97 |
location=metadata_response.location,
|
98 |
is_death=metadata_response.is_death,
|
99 |
-
is_victory=metadata_response.is_victory
|
|
|
|
|
100 |
)
|
101 |
# print(f"Generated image prompts: {prompts_response}")
|
102 |
|
|
|
6 |
from core.generators.image_prompt_generator import ImagePromptGenerator
|
7 |
from core.generators.metadata_generator import MetadataGenerator
|
8 |
from core.game_state import GameState
|
9 |
+
import random
|
10 |
+
from core.constants import GameConfig
|
11 |
|
12 |
class StoryGenerator:
|
13 |
_instance = None
|
|
|
24 |
print("Initializing StoryGenerator singleton")
|
25 |
self.api_key = api_key
|
26 |
self.model_name = model_name
|
27 |
+
self.turn_before_end = random.randint(GameConfig.MIN_SEGMENTS_BEFORE_END, GameConfig.MAX_SEGMENTS_BEFORE_END)
|
28 |
+
self.is_winning_story = random.random() < GameConfig.WINNING_STORY_CHANCE
|
29 |
self.mistral_client = MistralClient(api_key=api_key, model_name=model_name)
|
30 |
self.image_prompt_generator = None # Will be initialized with the first universe style
|
31 |
self.metadata_generator = MetadataGenerator(self.mistral_client)
|
|
|
80 |
current_time=game_state.current_time,
|
81 |
current_location=game_state.current_location,
|
82 |
previous_choice=previous_choice,
|
83 |
+
story_history=story_history,
|
84 |
+
turn_before_end=self.turn_before_end,
|
85 |
+
is_winning_story=self.is_winning_story
|
86 |
)
|
87 |
|
88 |
# print(f"Generated story text: {segment_response}")
|
|
|
92 |
story_text=segment_response.story_text,
|
93 |
current_time=game_state.current_time,
|
94 |
current_location=game_state.current_location,
|
95 |
+
story_beat=game_state.story_beat,
|
96 |
+
turn_before_end=self.turn_before_end,
|
97 |
+
is_winning_story=self.is_winning_story
|
98 |
)
|
99 |
# print(f"Generated metadata_response: {metadata_response}")
|
100 |
|
|
|
104 |
time=metadata_response.time,
|
105 |
location=metadata_response.location,
|
106 |
is_death=metadata_response.is_death,
|
107 |
+
is_victory=metadata_response.is_victory,
|
108 |
+
turn_before_end=self.turn_before_end,
|
109 |
+
is_winning_story=self.is_winning_story
|
110 |
)
|
111 |
# print(f"Generated image prompts: {prompts_response}")
|
112 |
|