tfrere commited on
Commit
33d38e5
·
1 Parent(s): eca7f7a
client/src/layouts/utils.js CHANGED
@@ -1,7 +1,7 @@
1
  import { LAYOUTS, getNextLayoutType } from "./config";
2
 
3
- // Map to store layout types for each page
4
- const pageLayoutMap = new Map();
5
 
6
  // Function to group segments into layouts
7
  export function groupSegmentsIntoLayouts(segments) {
@@ -9,7 +9,7 @@ export function groupSegmentsIntoLayouts(segments) {
9
 
10
  const layouts = [];
11
 
12
- segments.forEach((segment) => {
13
  const imageCount = segment.images?.length || 0;
14
 
15
  // Si c'est le premier segment ou le dernier (mort/victoire), créer un layout COVER
@@ -18,8 +18,12 @@ export function groupSegmentsIntoLayouts(segments) {
18
  return;
19
  }
20
 
21
- // Pour tous les autres segments, créer un layout adapté au nombre d'images
22
- const layoutType = getNextLayoutType(layouts.length, imageCount);
 
 
 
 
23
  layouts.push({ type: layoutType, segments: [segment] });
24
  });
25
 
@@ -40,18 +44,19 @@ export function getNextPanelDimensions(segments) {
40
  return LAYOUTS.COVER.panels[0];
41
  }
42
 
43
- // Pour les segments du milieu, déterminer le layout en fonction du nombre d'images
44
  const lastSegment = nonChoiceSegments[nonChoiceSegments.length - 1];
45
  const imageCount = lastSegment.images?.length || 0;
46
- const layoutType = getNextLayoutType(
47
- nonChoiceSegments.length - 1,
48
- imageCount
49
- );
 
50
 
51
  return LAYOUTS[layoutType].panels[0];
52
  }
53
 
54
  // Function to reset layout map (call this when starting a new story)
55
  export function resetLayoutMap() {
56
- pageLayoutMap.clear();
57
  }
 
1
  import { LAYOUTS, getNextLayoutType } from "./config";
2
 
3
+ // Map to store layout types for each segment
4
+ const segmentLayoutMap = new Map();
5
 
6
  // Function to group segments into layouts
7
  export function groupSegmentsIntoLayouts(segments) {
 
9
 
10
  const layouts = [];
11
 
12
+ segments.forEach((segment, index) => {
13
  const imageCount = segment.images?.length || 0;
14
 
15
  // Si c'est le premier segment ou le dernier (mort/victoire), créer un layout COVER
 
18
  return;
19
  }
20
 
21
+ // Pour tous les autres segments, utiliser le layout existant ou en créer un nouveau
22
+ let layoutType = segmentLayoutMap.get(segment.text);
23
+ if (!layoutType) {
24
+ layoutType = getNextLayoutType(layouts.length, imageCount);
25
+ segmentLayoutMap.set(segment.text, layoutType);
26
+ }
27
  layouts.push({ type: layoutType, segments: [segment] });
28
  });
29
 
 
44
  return LAYOUTS.COVER.panels[0];
45
  }
46
 
47
+ // Pour les segments du milieu, utiliser le layout existant ou en créer un nouveau
48
  const lastSegment = nonChoiceSegments[nonChoiceSegments.length - 1];
49
  const imageCount = lastSegment.images?.length || 0;
50
+ let layoutType = segmentLayoutMap.get(lastSegment.text);
51
+ if (!layoutType) {
52
+ layoutType = getNextLayoutType(nonChoiceSegments.length - 1, imageCount);
53
+ segmentLayoutMap.set(lastSegment.text, layoutType);
54
+ }
55
 
56
  return LAYOUTS[layoutType].panels[0];
57
  }
58
 
59
  // Function to reset layout map (call this when starting a new story)
60
  export function resetLayoutMap() {
61
+ segmentLayoutMap.clear();
62
  }
client/src/pages/Game.jsx CHANGED
@@ -149,13 +149,14 @@ export function Game() {
149
  }
150
  } catch (error) {
151
  console.error("Error in handleStoryAction:", error);
 
 
 
 
 
152
  const errorSegment = {
153
- text:
154
- error.message ||
155
- "Le conteur d'histoires est temporairement indisponible. Veuillez réessayer dans quelques instants...",
156
- rawText:
157
- error.message ||
158
- "Le conteur d'histoires est temporairement indisponible. Veuillez réessayer dans quelques instants...",
159
  isChoice: false,
160
  isDeath: false,
161
  isVictory: false,
@@ -164,16 +165,18 @@ export function Game() {
164
  ? storySegments[storySegments.length - 1].radiationLevel
165
  : 0,
166
  images: [],
 
167
  };
168
 
169
  if (action === "restart") {
170
  setStorySegments([errorSegment]);
171
  } else {
172
- setStorySegments((prev) => [...prev, errorSegment]);
 
173
  }
174
 
175
  // Set retry choice
176
- setCurrentChoices([{ id: 1, text: "Réessayer" }]);
177
 
178
  // Play error message
179
  await playNarration(errorSegment.rawText);
 
149
  }
150
  } catch (error) {
151
  console.error("Error in handleStoryAction:", error);
152
+ const errorMessage =
153
+ error.response?.data?.detail ||
154
+ error.message ||
155
+ "Le conteur d'histoires est temporairement indisponible. Veuillez réessayer dans quelques instants...";
156
+
157
  const errorSegment = {
158
+ text: errorMessage,
159
+ rawText: errorMessage,
 
 
 
 
160
  isChoice: false,
161
  isDeath: false,
162
  isVictory: false,
 
165
  ? storySegments[storySegments.length - 1].radiationLevel
166
  : 0,
167
  images: [],
168
+ isLoading: false,
169
  };
170
 
171
  if (action === "restart") {
172
  setStorySegments([errorSegment]);
173
  } else {
174
+ // En cas d'erreur sur un choix, on garde le segment précédent
175
+ setStorySegments((prev) => [...prev.slice(0, -1), errorSegment]);
176
  }
177
 
178
  // Set retry choice
179
+ setCurrentChoices([{ id: "retry", text: "Réessayer" }]);
180
 
181
  // Play error message
182
  await playNarration(errorSegment.rawText);
server/api/models.py CHANGED
@@ -3,11 +3,11 @@ from typing import List, Optional
3
 
4
  class Choice(BaseModel):
5
  id: int
6
- text: str = Field(description="The text of the choice with proper nouns in bold using ** markdown. No more than 6 words.")
7
 
8
  # New response models for story generation steps
9
  class StoryTextResponse(BaseModel):
10
- story_text: str = Field(description="The story text with proper nouns in bold using ** markdown. No more than 15 words.")
11
 
12
  class StoryPromptsResponse(BaseModel):
13
  image_prompts: List[str] = Field(description="List of 2 to 4 comic panel descriptions that illustrate the key moments of the scene. Use the word 'Sarah' only when referring to her.", min_items=1, max_items=4)
@@ -18,11 +18,11 @@ class StoryMetadataResponse(BaseModel):
18
  radiation_increase: int = Field(description="How much radiation this segment adds (0-3)", ge=0, le=3, default=1)
19
  is_last_step: bool = Field(description="Whether this is the last step (victory or death)", default=False)
20
  time: str = Field(description="Current in-game time in 24h format (HH:MM). Time passes realistically based on actions.")
21
- location: str = Field(description="Current location, using bold for proper nouns (e.g., 'Inside **Vault 15**', 'Streets of **New Haven**').")
22
 
23
  # Complete story response combining all parts
24
  class StoryResponse(BaseModel):
25
- story_text: str = Field(description="The story text with proper nouns in bold using ** markdown. No more than 15 words.")
26
  choices: List[Choice]
27
  radiation_level: int = Field(description="Current radiation level from 0 to 10")
28
  is_victory: bool = Field(description="Whether this segment ends in Sarah's victory", default=False)
 
3
 
4
  class Choice(BaseModel):
5
  id: int
6
+ text: str = Field(description="The text of the choice. No more than 6 words.")
7
 
8
  # New response models for story generation steps
9
  class StoryTextResponse(BaseModel):
10
+ story_text: str = Field(description="The story text. No more than 15 words.")
11
 
12
  class StoryPromptsResponse(BaseModel):
13
  image_prompts: List[str] = Field(description="List of 2 to 4 comic panel descriptions that illustrate the key moments of the scene. Use the word 'Sarah' only when referring to her.", min_items=1, max_items=4)
 
18
  radiation_increase: int = Field(description="How much radiation this segment adds (0-3)", ge=0, le=3, default=1)
19
  is_last_step: bool = Field(description="Whether this is the last step (victory or death)", default=False)
20
  time: str = Field(description="Current in-game time in 24h format (HH:MM). Time passes realistically based on actions.")
21
+ location: str = Field(description="Current location.")
22
 
23
  # Complete story response combining all parts
24
  class StoryResponse(BaseModel):
25
+ story_text: str = Field(description="The story text. No more than 15 words.")
26
  choices: List[Choice]
27
  radiation_level: int = Field(description="Current radiation level from 0 to 10")
28
  is_victory: bool = Field(description="Whether this segment ends in Sarah's victory", default=False)
server/core/game_logic.py CHANGED
@@ -15,7 +15,7 @@ from core.story_generators import TextGenerator, ImagePromptsGenerator, Metadata
15
  # Game constants
16
  MAX_RADIATION = 10
17
  STARTING_TIME = "18:00" # Game starts at sunset
18
- STARTING_LOCATION = "Outskirts of **New Haven**"
19
 
20
  def enrich_prompt_with_sarah_description(prompt: str) -> str:
21
  """Add Sarah's visual description to prompts that mention her."""
@@ -56,14 +56,14 @@ class GameState:
56
 
57
  # Story output structure
58
  class StoryLLMResponse(BaseModel):
59
- story_text: str = Field(description="The next segment of the story. No more than 12 words THIS IS MANDATORY. Use bold formatting (like **this**) ONLY for proper nouns (like **Sarah**, **hospital**) and important locations.")
60
  choices: List[str] = Field(description="Exactly two possible choices for the player", min_items=2, max_items=2)
61
  is_victory: bool = Field(description="Whether this segment ends in Sarah's victory", default=False)
62
  radiation_increase: int = Field(description="How much radiation this segment adds (0-3)", ge=0, le=3, default=1)
63
  image_prompts: List[str] = Field(description="List of 1 to 4 comic panel descriptions that illustrate the key moments of the scene", min_items=1, max_items=4)
64
  is_last_step: bool = Field(description="Whether this is the last step (victory or death)", default=False)
65
  time: str = Field(description="Current in-game time in 24h format (HH:MM). Time passes realistically based on actions.", default=STARTING_TIME)
66
- location: str = Field(description="Current location, using bold for proper nouns (e.g., 'Inside **Vault 15**', 'Streets of **New Haven**').", default=STARTING_LOCATION)
67
 
68
  # Story generator
69
  class StoryGenerator:
 
15
  # Game constants
16
  MAX_RADIATION = 10
17
  STARTING_TIME = "18:00" # Game starts at sunset
18
+ STARTING_LOCATION = "Outskirts of New Haven"
19
 
20
  def enrich_prompt_with_sarah_description(prompt: str) -> str:
21
  """Add Sarah's visual description to prompts that mention her."""
 
56
 
57
  # Story output structure
58
  class StoryLLMResponse(BaseModel):
59
+ story_text: str = Field(description="The next segment of the story. No more than 12 words THIS IS MANDATORY. Use bold formatting (like this) ONLY for proper nouns (like Sarah, hospital) and important locations.")
60
  choices: List[str] = Field(description="Exactly two possible choices for the player", min_items=2, max_items=2)
61
  is_victory: bool = Field(description="Whether this segment ends in Sarah's victory", default=False)
62
  radiation_increase: int = Field(description="How much radiation this segment adds (0-3)", ge=0, le=3, default=1)
63
  image_prompts: List[str] = Field(description="List of 1 to 4 comic panel descriptions that illustrate the key moments of the scene", min_items=1, max_items=4)
64
  is_last_step: bool = Field(description="Whether this is the last step (victory or death)", default=False)
65
  time: str = Field(description="Current in-game time in 24h format (HH:MM). Time passes realistically based on actions.", default=STARTING_TIME)
66
+ location: str = Field(description="Current location, using bold for proper nouns (e.g., 'Inside Vault 15', 'Streets of New Haven').", default=STARTING_LOCATION)
67
 
68
  # Story generator
69
  class StoryGenerator:
server/core/prompts/system.py CHANGED
@@ -12,6 +12,7 @@ FORMATTING_RULES ( MANDATORY )
12
  - Never use TIME: 18:30 or other time-related information
13
  - Never use LOCATION: the city or other location-related information
14
  - Never use RADIATION: 10* or other radiation-related information
 
15
  """
16
 
17
  STORY_RULES = """
 
12
  - Never use TIME: 18:30 or other time-related information
13
  - Never use LOCATION: the city or other location-related information
14
  - Never use RADIATION: 10* or other radiation-related information
15
+ - NEVER USE BOLD FOR ANYTHING
16
  """
17
 
18
  STORY_RULES = """
server/core/prompts/text_prompts.py CHANGED
@@ -19,12 +19,12 @@ Be consistent with the story's tone and previous context.
19
 
20
  You must return a JSON object with the following format:
21
  {{{{
22
- "choices": ["Go to the **hospital**", "Get back to the **warehouse**"],
23
  "is_victory": false,
24
  "radiation_increase": 1,
25
  "is_last_step": false,
26
  "time": "HH:MM",
27
- "location": "Location name with **proper nouns** in bold"
28
  }}}}
29
  """
30
 
 
19
 
20
  You must return a JSON object with the following format:
21
  {{{{
22
+ "choices": ["Go to the hospital", "Get back to the warehouse"],
23
  "is_victory": false,
24
  "radiation_increase": 1,
25
  "is_last_step": false,
26
  "time": "HH:MM",
27
+ "location": "Location name with proper nouns in bold"
28
  }}}}
29
  """
30