update
Browse files
client/src/components/StoryChoices.jsx
CHANGED
|
@@ -129,7 +129,7 @@ export function StoryChoices() {
|
|
| 129 |
/>
|
| 130 |
) : (
|
| 131 |
<>
|
| 132 |
-
{choices
|
| 133 |
.filter((_, index) => !isMobile || index === 0)
|
| 134 |
.map((choice, index) => (
|
| 135 |
<Box
|
|
@@ -161,7 +161,36 @@ export function StoryChoices() {
|
|
| 161 |
{formatTextWithBold(choice.text)}
|
| 162 |
</Button>
|
| 163 |
</Box>
|
| 164 |
-
))}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
|
| 166 |
<Box
|
| 167 |
sx={{
|
|
|
|
| 129 |
/>
|
| 130 |
) : (
|
| 131 |
<>
|
| 132 |
+
{/* {choices
|
| 133 |
.filter((_, index) => !isMobile || index === 0)
|
| 134 |
.map((choice, index) => (
|
| 135 |
<Box
|
|
|
|
| 161 |
{formatTextWithBold(choice.text)}
|
| 162 |
</Button>
|
| 163 |
</Box>
|
| 164 |
+
))} */}
|
| 165 |
+
|
| 166 |
+
<Box
|
| 167 |
+
sx={{
|
| 168 |
+
display: "flex",
|
| 169 |
+
flexDirection: "column",
|
| 170 |
+
alignItems: "center",
|
| 171 |
+
gap: 1,
|
| 172 |
+
minWidth: "fit-content",
|
| 173 |
+
maxWidth: isMobile ? "90%" : "30%",
|
| 174 |
+
}}
|
| 175 |
+
>
|
| 176 |
+
<Button
|
| 177 |
+
variant="contained"
|
| 178 |
+
size="large"
|
| 179 |
+
onClick={() => {
|
| 180 |
+
initAudioContext();
|
| 181 |
+
playPageSound();
|
| 182 |
+
stopNarration();
|
| 183 |
+
onChoice(choices[0].id);
|
| 184 |
+
}}
|
| 185 |
+
disabled={isSarahActive || isLoading || isNarratorSpeaking}
|
| 186 |
+
sx={{
|
| 187 |
+
width: "auto",
|
| 188 |
+
minWidth: "fit-content",
|
| 189 |
+
}}
|
| 190 |
+
>
|
| 191 |
+
{formatTextWithBold(choices[0].text)}
|
| 192 |
+
</Button>
|
| 193 |
+
</Box>
|
| 194 |
|
| 195 |
<Box
|
| 196 |
sx={{
|
client/src/components/StyledText.jsx
CHANGED
|
@@ -1,31 +1,45 @@
|
|
| 1 |
-
import {
|
|
|
|
| 2 |
|
| 3 |
/**
|
| 4 |
* A component that renders text with styled words (bold, italic, etc.)
|
| 5 |
* It automatically handles spacing between words and styled segments
|
| 6 |
*/
|
| 7 |
-
export
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
})
|
| 13 |
-
// Split the text into segments, preserving spaces
|
| 14 |
-
const segments = text.split(/(<strong>.*?<\/strong>)/).filter(Boolean);
|
| 15 |
|
| 16 |
return (
|
| 17 |
-
<
|
| 18 |
-
{
|
| 19 |
-
if (
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
}
|
| 24 |
-
|
| 25 |
-
return segment;
|
| 26 |
})}
|
| 27 |
-
</
|
| 28 |
);
|
| 29 |
-
}
|
| 30 |
|
| 31 |
export default StyledText;
|
|
|
|
| 1 |
+
import { Box, Chip } from "@mui/material";
|
| 2 |
+
import { useGame } from "../contexts/GameContext";
|
| 3 |
|
| 4 |
/**
|
| 5 |
* A component that renders text with styled words (bold, italic, etc.)
|
| 6 |
* It automatically handles spacing between words and styled segments
|
| 7 |
*/
|
| 8 |
+
export function StyledText({ text, ...props }) {
|
| 9 |
+
const { heroName } = useGame();
|
| 10 |
+
|
| 11 |
+
if (!text || !heroName) return text;
|
| 12 |
+
|
| 13 |
+
const parts = text.split(new RegExp(`(${heroName})`, "i"));
|
|
|
|
|
|
|
| 14 |
|
| 15 |
return (
|
| 16 |
+
<Box component="span" sx={{ display: "inline", ...props.sx }}>
|
| 17 |
+
{parts.map((part, index) => {
|
| 18 |
+
if (part.toLowerCase() === heroName.toLowerCase()) {
|
| 19 |
+
return (
|
| 20 |
+
<Chip
|
| 21 |
+
key={index}
|
| 22 |
+
label={part}
|
| 23 |
+
size="small"
|
| 24 |
+
sx={{
|
| 25 |
+
mx: 0.1,
|
| 26 |
+
height: "auto",
|
| 27 |
+
padding: "0px 2px",
|
| 28 |
+
"& .MuiChip-label": {
|
| 29 |
+
padding: "0 2px",
|
| 30 |
+
fontSize: "inherit",
|
| 31 |
+
lineHeight: "inherit",
|
| 32 |
+
fontWeight: "900",
|
| 33 |
+
},
|
| 34 |
+
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
| 35 |
+
}}
|
| 36 |
+
/>
|
| 37 |
+
);
|
| 38 |
}
|
| 39 |
+
return part;
|
|
|
|
| 40 |
})}
|
| 41 |
+
</Box>
|
| 42 |
);
|
| 43 |
+
}
|
| 44 |
|
| 45 |
export default StyledText;
|
client/src/layouts/Panel.jsx
CHANGED
|
@@ -9,6 +9,7 @@ import RefreshIcon from "@mui/icons-material/Refresh";
|
|
| 9 |
import { useEffect, useState, useRef } from "react";
|
| 10 |
import { useGame } from "../contexts/GameContext";
|
| 11 |
import { keyframes } from "@mui/system";
|
|
|
|
| 12 |
|
| 13 |
// Animation de rotation complète
|
| 14 |
const spinFull = keyframes`
|
|
@@ -248,20 +249,13 @@ export function Panel({
|
|
| 248 |
borderRadius: "8px",
|
| 249 |
display: "flex",
|
| 250 |
alignItems: "center",
|
| 251 |
-
|
| 252 |
-
|
|
|
|
|
|
|
| 253 |
}}
|
| 254 |
>
|
| 255 |
-
<
|
| 256 |
-
variant="body1"
|
| 257 |
-
sx={{
|
| 258 |
-
fontSize: { xs: "0.775rem", sm: "1rem" }, // Responsive font size
|
| 259 |
-
color: "black",
|
| 260 |
-
lineHeight: 1.2,
|
| 261 |
-
}}
|
| 262 |
-
>
|
| 263 |
-
{segment.text}
|
| 264 |
-
</Typography>
|
| 265 |
</Box>
|
| 266 |
)}
|
| 267 |
</Box>
|
|
|
|
| 9 |
import { useEffect, useState, useRef } from "react";
|
| 10 |
import { useGame } from "../contexts/GameContext";
|
| 11 |
import { keyframes } from "@mui/system";
|
| 12 |
+
import { StyledText } from "../components/StyledText";
|
| 13 |
|
| 14 |
// Animation de rotation complète
|
| 15 |
const spinFull = keyframes`
|
|
|
|
| 249 |
borderRadius: "8px",
|
| 250 |
display: "flex",
|
| 251 |
alignItems: "center",
|
| 252 |
+
fontSize: { xs: "0.775rem", sm: "1rem" }, // Responsive font size
|
| 253 |
+
color: "black",
|
| 254 |
+
lineHeight: 1.1,
|
| 255 |
+
fontWeight: 900,
|
| 256 |
}}
|
| 257 |
>
|
| 258 |
+
<StyledText text={segment.text} />
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
</Box>
|
| 260 |
)}
|
| 261 |
</Box>
|
client/src/pages/Tutorial.jsx
CHANGED
|
@@ -88,7 +88,7 @@ export function Tutorial() {
|
|
| 88 |
the opportunity to write the next part of the story yourself.
|
| 89 |
<br />
|
| 90 |
<br />
|
| 91 |
-
|
| 92 |
</Typography>
|
| 93 |
|
| 94 |
<Button
|
|
|
|
| 88 |
the opportunity to write the next part of the story yourself.
|
| 89 |
<br />
|
| 90 |
<br />
|
| 91 |
+
Think of it more as a lucid dream than a traditional game. Enjoy!
|
| 92 |
</Typography>
|
| 93 |
|
| 94 |
<Button
|
server/core/generators/story_segment_generator.py
CHANGED
|
@@ -60,20 +60,27 @@ Universe Context:
|
|
| 60 |
- Genre: {self.universe_genre}
|
| 61 |
- Epoch: {self.universe_epoch}
|
| 62 |
|
| 63 |
-
|
| 64 |
-
-
|
| 65 |
-
-
|
| 66 |
-
-
|
| 67 |
-
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
-
|
| 71 |
-
-
|
| 72 |
-
-
|
| 73 |
-
-
|
| 74 |
-
-
|
| 75 |
-
-
|
| 76 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
|
| 78 |
Your task is to generate the next segment of the story, following these rules:
|
| 79 |
1. Keep the story consistent with the universe parameters
|
|
@@ -83,14 +90,22 @@ Your task is to generate the next segment of the story, following these rules:
|
|
| 83 |
|
| 84 |
Hero Description: {self.hero_desc}
|
| 85 |
|
| 86 |
-
- MANDATORY: Each segment must be close to 15 words, no exceptions
|
| 87 |
"""
|
| 88 |
|
| 89 |
human_template = """
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
-
|
| 93 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
|
| 95 |
Story history:
|
| 96 |
{story_history}
|
|
@@ -102,6 +117,10 @@ Be short. Never describes game variables.
|
|
| 102 |
|
| 103 |
IT MUST BE THE DIRECT CONTINUATION OF THE CURRENT STORY.
|
| 104 |
You MUST mention the previous situation and what is happening now with the new choice.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
"""
|
| 106 |
return ChatPromptTemplate(
|
| 107 |
messages=[
|
|
@@ -169,7 +188,7 @@ You MUST mention the previous situation and what is happening now with the new c
|
|
| 169 |
def _is_valid_length(self, text: str) -> bool:
|
| 170 |
"""Vérifie si le texte est dans la bonne plage de longueur."""
|
| 171 |
word_count = len(text.split())
|
| 172 |
-
return
|
| 173 |
|
| 174 |
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:
|
| 175 |
"""Generate the next story segment with length validation and retry."""
|
|
@@ -183,17 +202,17 @@ You MUST mention the previous situation and what is happening now with the new c
|
|
| 183 |
what_to_represent = self._get_what_to_represent(story_beat, is_death, is_victory)
|
| 184 |
|
| 185 |
# Si c'est un choix personnalisé, on l'utilise comme contexte pour générer la suite
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
-
|
| 196 |
-
|
| 197 |
|
| 198 |
# Créer les messages de base une seule fois
|
| 199 |
messages = self.prompt.format_messages(
|
|
@@ -224,9 +243,7 @@ You MUST mention the previous situation and what is happening now with the new c
|
|
| 224 |
retry_count += 1
|
| 225 |
if retry_count < self.max_retries:
|
| 226 |
# Créer un nouveau message avec le feedback sur la longueur
|
| 227 |
-
if word_count
|
| 228 |
-
feedback = f"The previous response was too short ({word_count} words). Here was your last attempt:\n\n{story_text}\n\nPlease generate a NEW and DIFFERENT story segment close to 15 words that continues from: {story_history}"
|
| 229 |
-
else:
|
| 230 |
feedback = f"The previous response was too long ({word_count} words). Here was your last attempt:\n\n{story_text}\n\nPlease generate a MUCH SHORTER story segment close to 15 words that continues from: {story_history}"
|
| 231 |
|
| 232 |
# Réinitialiser les messages avec les messages de base
|
|
|
|
| 60 |
- Genre: {self.universe_genre}
|
| 61 |
- Epoch: {self.universe_epoch}
|
| 62 |
|
| 63 |
+
EXAMPLES:
|
| 64 |
+
- Mateo inspects the relic after choosing to investigate the old house.
|
| 65 |
+
- A young woman finds a hidden door after exploring the alleyway.
|
| 66 |
+
- On a distant planet, an explorer uncovers an artifact after landing.
|
| 67 |
+
- In a medieval village, a blacksmith discovers a map after repairing a sword.
|
| 68 |
+
- A pilot notices a signal after taking a risky shortcut.
|
| 69 |
+
- In a mansion, a detective finds a passage after searching the library.
|
| 70 |
+
- Amidst a market, a thief spots an amulet after blending into the crowd.
|
| 71 |
+
- A diver encounters a ship after exploring uncharted waters.
|
| 72 |
+
- Mateo, the hero, finds a secret compartment after investigating the library.
|
| 73 |
+
- In a city, the hero deciphers a message after hacking the mainframe.
|
| 74 |
+
- A knight finds a hidden passage after examining the castle walls.
|
| 75 |
+
- An astronaut discovers a new planet after navigating through an asteroid field.
|
| 76 |
+
- A scientist uncovers a secret formula after analyzing ancient manuscripts.
|
| 77 |
+
- A warrior finds a mystical weapon after defeating a powerful enemy.
|
| 78 |
+
- A mage discovers a hidden spell after studying ancient runes.
|
| 79 |
+
- A ranger spots a hidden trail after scouting the forest.
|
| 80 |
+
- A sailor finds a treasure map after exploring a deserted island.
|
| 81 |
+
- A spy uncovers a conspiracy after infiltrating the enemy base.
|
| 82 |
+
- A historian finds a lost diary after searching the old archives.
|
| 83 |
+
- A musician discovers a hidden melody after playing an ancient instrument.
|
| 84 |
|
| 85 |
Your task is to generate the next segment of the story, following these rules:
|
| 86 |
1. Keep the story consistent with the universe parameters
|
|
|
|
| 90 |
|
| 91 |
Hero Description: {self.hero_desc}
|
| 92 |
|
| 93 |
+
- MANDATORY: Each segment must be close to 15 words, no exceptions.
|
| 94 |
"""
|
| 95 |
|
| 96 |
human_template = """
|
| 97 |
+
|
| 98 |
+
EXAMPLES:
|
| 99 |
+
- Mateo inspects the relic after choosing to investigate the old house.
|
| 100 |
+
- A young woman finds a hidden door after exploring the alleyway.
|
| 101 |
+
- On a distant planet, an explorer uncovers an artifact after landing.
|
| 102 |
+
- In a medieval village, a blacksmith discovers a map after repairing a sword.
|
| 103 |
+
- A pilot notices a signal after taking a risky shortcut.
|
| 104 |
+
- In a mansion, a detective finds a passage after searching the library.
|
| 105 |
+
|
| 106 |
+
BAD:
|
| 107 |
+
- In a mansion, a detective finds a passage after searching the library. [Choix du joueur: Choice 1, "the hero encounter a dragon"]
|
| 108 |
+
- [A town, 00h00] In a medieval village, a blacksmith discovers a map after repairing a sword.
|
| 109 |
|
| 110 |
Story history:
|
| 111 |
{story_history}
|
|
|
|
| 117 |
|
| 118 |
IT MUST BE THE DIRECT CONTINUATION OF THE CURRENT STORY.
|
| 119 |
You MUST mention the previous situation and what is happening now with the new choice.
|
| 120 |
+
Never propose choices or options. Never describe the game variables.
|
| 121 |
+
|
| 122 |
+
MANDATORY: Each segment must be close to 15 words, keep it concise.
|
| 123 |
+
Be short. Never describes game variables.
|
| 124 |
"""
|
| 125 |
return ChatPromptTemplate(
|
| 126 |
messages=[
|
|
|
|
| 188 |
def _is_valid_length(self, text: str) -> bool:
|
| 189 |
"""Vérifie si le texte est dans la bonne plage de longueur."""
|
| 190 |
word_count = len(text.split())
|
| 191 |
+
return 0 <= word_count <= 30
|
| 192 |
|
| 193 |
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:
|
| 194 |
"""Generate the next story segment with length validation and retry."""
|
|
|
|
| 202 |
what_to_represent = self._get_what_to_represent(story_beat, is_death, is_victory)
|
| 203 |
|
| 204 |
# Si c'est un choix personnalisé, on l'utilise comme contexte pour générer la suite
|
| 205 |
+
if previous_choice and not previous_choice.startswith("Choice "):
|
| 206 |
+
what_to_represent = f"""
|
| 207 |
+
Based on the player's custom choice: "{previous_choice}"
|
| 208 |
+
|
| 209 |
+
Write a story segment that:
|
| 210 |
+
1. Directly follows and incorporates the player's choice
|
| 211 |
+
2. Maintains consistency with the universe and story
|
| 212 |
+
3. Respects all previous rules about length and style
|
| 213 |
+
4. Naturally integrates the custom elements while staying true to the plot
|
| 214 |
+
Close to 15 words.
|
| 215 |
+
"""
|
| 216 |
|
| 217 |
# Créer les messages de base une seule fois
|
| 218 |
messages = self.prompt.format_messages(
|
|
|
|
| 243 |
retry_count += 1
|
| 244 |
if retry_count < self.max_retries:
|
| 245 |
# Créer un nouveau message avec le feedback sur la longueur
|
| 246 |
+
if word_count > 15:
|
|
|
|
|
|
|
| 247 |
feedback = f"The previous response was too long ({word_count} words). Here was your last attempt:\n\n{story_text}\n\nPlease generate a MUCH SHORTER story segment close to 15 words that continues from: {story_history}"
|
| 248 |
|
| 249 |
# Réinitialiser les messages avec les messages de base
|
server/core/generators/universe_generator.py
CHANGED
|
@@ -16,7 +16,8 @@ class UniverseGenerator(BaseGenerator):
|
|
| 16 |
def _create_prompt(self) -> ChatPromptTemplate:
|
| 17 |
|
| 18 |
system_template = """You are a creative writing assistant specialized in comic book universes.
|
| 19 |
-
Your task is to rewrite a story while keeping its exact structure and beats, but transposing it into a different universe.
|
|
|
|
| 20 |
|
| 21 |
human_template = """Transform the following story into a new universe with these parameters:
|
| 22 |
- Visual style: {style_name} (inspired by artists like {artists} with works such as {works})
|
|
@@ -51,6 +52,7 @@ The story must be atmospheric, magical, and focus on adventure and discovery. Ea
|
|
| 51 |
|
| 52 |
YOU HAVE. TOREWRITE THE STORY. ( one text including the constant part and the variable part )
|
| 53 |
YOU ONLY HAVE TO RIGHT AN INTRODUCTION. SETUP THE STORY AND DEFINE CLEARLY SARASH'S MISSION.
|
|
|
|
| 54 |
"""
|
| 55 |
|
| 56 |
return ChatPromptTemplate(
|
|
|
|
| 16 |
def _create_prompt(self) -> ChatPromptTemplate:
|
| 17 |
|
| 18 |
system_template = """You are a creative writing assistant specialized in comic book universes.
|
| 19 |
+
Your task is to rewrite a story while keeping its exact structure and beats, but transposing it into a different universe.
|
| 20 |
+
"""
|
| 21 |
|
| 22 |
human_template = """Transform the following story into a new universe with these parameters:
|
| 23 |
- Visual style: {style_name} (inspired by artists like {artists} with works such as {works})
|
|
|
|
| 52 |
|
| 53 |
YOU HAVE. TOREWRITE THE STORY. ( one text including the constant part and the variable part )
|
| 54 |
YOU ONLY HAVE TO RIGHT AN INTRODUCTION. SETUP THE STORY AND DEFINE CLEARLY SARASH'S MISSION.
|
| 55 |
+
|
| 56 |
"""
|
| 57 |
|
| 58 |
return ChatPromptTemplate(
|