tfrere commited on
Commit
d091da8
·
1 Parent(s): 5c28e74
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .DS_Store +0 -0
  2. Dockerfile +1 -1
  3. client/src/App.jsx +313 -173
  4. client/src/fonts/Action-Man/Action-Man-Bold-Italic.woff2 +0 -0
  5. client/src/fonts/Action-Man/Action-Man-Bold.woff2 +0 -0
  6. client/src/fonts/Action-Man/Action-Man-Italic.woff2 +0 -0
  7. client/src/fonts/Action-Man/Action-Man.woff2 +0 -0
  8. client/src/fonts/Action-Man/Action_Man_Extended-webfont.woff +0 -0
  9. client/src/fonts/Action-Man/Action_Man_Extended_Bold-webfont.woff2 +0 -0
  10. client/src/fonts/Action-Man/Action_Man_Extended_Bold_Italic-webfont.woff +0 -0
  11. client/src/fonts/Action-Man/Action_Man_Extended_Italic-webfont.woff +0 -0
  12. client/src/fonts/Action-Man/Action_Man_Shaded-webfont.woff +0 -0
  13. client/src/fonts/Action-Man/Action_Man_Shaded_Italic-webfont.woff +0 -0
  14. client/src/fonts/DigitalStripBB/DigitalStripBB_BoldItal.woff2 +0 -0
  15. client/src/fonts/DigitalStripBB/DigitalStripBB_Ital.woff2 +0 -0
  16. client/src/fonts/DigitalStripBB/DigitalStripBB_Reg.woff2 +0 -0
  17. client/src/fonts/Karantula/Karantula-Bold.woff2 +0 -0
  18. client/src/fonts/Karantula/Karantula-Italic-Bold.woff2 +0 -0
  19. client/src/fonts/Karantula/Karantula-Italic.woff2 +0 -0
  20. client/src/fonts/Karantula/Karantula.woff2 +0 -0
  21. client/src/fonts/Komika-Display/Komika-Display.woff +0 -0
  22. client/src/fonts/Komika-Display/Komika_display_bold-webfont.woff +0 -0
  23. client/src/fonts/Komika-Display/Komika_display_kaps-webfont.woff +0 -0
  24. client/src/fonts/Komika-Display/Komika_display_kaps_bold-webfont.woff +0 -0
  25. client/src/fonts/Komika-Hand/Komika-Hand-Bold-Italic.woff2 +0 -0
  26. client/src/fonts/Komika-Hand/Komika-Hand-Bold.woff2 +0 -0
  27. client/src/fonts/Komika-Hand/Komika-Hand-Italic.woff2 +0 -0
  28. client/src/fonts/Komika-Hand/Komika-Hand.woff2 +0 -0
  29. client/src/fonts/Komika-Hand/Komika_Parch.woff2 +0 -0
  30. client/src/fonts/Komika-Text/KOMTXKBI-webfont.woff +0 -0
  31. client/src/fonts/Komika-Text/KOMTXTBI-webfont.woff +0 -0
  32. client/src/fonts/Komika-Text/KOMTXTB_-webfont.woff +0 -0
  33. client/src/fonts/Komika-Text/KOMTXTI_-webfont.woff +0 -0
  34. client/src/fonts/Komika-Text/KOMTXTKB-webfont.woff +0 -0
  35. client/src/fonts/Komika-Text/KOMTXTKI-webfont.woff +0 -0
  36. client/src/fonts/Komika-Text/KOMTXTK_-webfont.woff +0 -0
  37. client/src/fonts/Komika-Text/KOMTXTTI-webfont.woff +0 -0
  38. client/src/fonts/Komika-Text/KOMTXTT_-webfont.woff +0 -0
  39. client/src/fonts/Komika-Text/KOMTXT__-webfont.woff +0 -0
  40. client/src/fonts/Manoskope/MANOSKOPE-Bold.woff2 +0 -0
  41. client/src/fonts/Paete-Round/Paete-Round-Bold-Italic.woff2 +0 -0
  42. client/src/fonts/Paete-Round/Paete-Round-Bold.woff2 +0 -0
  43. client/src/fonts/Paete-Round/Paete-Round-Italic.woff2 +0 -0
  44. client/src/fonts/Paete-Round/Paete-Round.woff2 +0 -0
  45. client/src/fonts/Qarmic-Sans/Qarmic-Sans-Abridged.woff2 +0 -0
  46. client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Bold-Italic.woff2 +0 -0
  47. client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Bold.woff2 +0 -0
  48. client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Extended-Bold-Italic.woff2 +0 -0
  49. client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Extended-Bold.woff2 +0 -0
  50. client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Extended-Italic.woff2 +0 -0
.DS_Store ADDED
Binary file (6.15 kB). View file
 
Dockerfile CHANGED
@@ -4,7 +4,7 @@ COPY client/package*.json ./
4
  RUN npm install
5
  COPY client/ ./
6
  ENV VITE_API_URL=https://mistral-ai-game-jam-dont-lookup.hf.space
7
- RUN npm run build
8
 
9
  FROM python:3.9-slim
10
  WORKDIR /app
 
4
  RUN npm install
5
  COPY client/ ./
6
  ENV VITE_API_URL=https://mistral-ai-game-jam-dont-lookup.hf.space
7
+ RUN mkdir -p dist && npm run build
8
 
9
  FROM python:3.9-slim
10
  WORKDIR /app
client/src/App.jsx CHANGED
@@ -5,41 +5,117 @@ import {
5
  Button,
6
  Box,
7
  Typography,
8
- List,
9
- ListItem,
10
- ListItemText,
11
  LinearProgress,
12
  } from "@mui/material";
13
  import RestartAltIcon from "@mui/icons-material/RestartAlt";
14
  import axios from "axios";
 
 
15
 
16
  // Get API URL from environment or default to localhost in development
17
  const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8000";
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  function App() {
20
  const [storySegments, setStorySegments] = useState([]);
21
  const [currentChoices, setCurrentChoices] = useState([]);
22
  const [isLoading, setIsLoading] = useState(false);
 
23
  const isInitializedRef = useRef(false);
 
 
24
 
25
- const generateImageForStory = async (storyText) => {
26
  try {
 
 
 
 
 
 
 
 
27
  console.log("Generating image for story:", storyText);
28
- const response = await axios.post(`${API_URL}/api/generate-image`, {
29
- prompt: `Comic book style scene: ${storyText}`,
30
- width: 512,
31
- height: 512,
32
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- console.log("Image generation response:", response.data);
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
  if (response.data.success) {
37
- console.log("Image URL length:", response.data.image_base64.length);
38
  return response.data.image_base64;
39
  }
40
  return null;
41
  } catch (error) {
42
- console.error("Error generating image:", error);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  return null;
44
  }
45
  };
@@ -47,51 +123,91 @@ function App() {
47
  const handleStoryAction = async (action, choiceId = null) => {
48
  setIsLoading(true);
49
  try {
50
- const response = await axios.post(`${API_URL}/api/chat`, {
51
- message: action,
52
- choice_id: choiceId,
53
- });
54
-
55
- // Générer l'image pour ce segment
56
- const imageUrl = await generateImageForStory(response.data.story_text);
57
- console.log(
58
- "Generated image URL:",
59
- imageUrl ? "Image received" : "No image"
60
  );
61
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  if (action === "restart") {
63
- setStorySegments([
64
- {
65
- text: response.data.story_text,
66
- isChoice: false,
67
- isDeath: response.data.is_death,
68
- isVictory: response.data.is_victory,
69
- radiationLevel: response.data.radiation_level,
70
- imageUrl: imageUrl,
71
- },
72
- ]);
73
  } else {
74
- setStorySegments((prev) => [
75
- ...prev,
76
- {
77
- text: response.data.story_text,
78
- isChoice: false,
79
- isDeath: response.data.is_death,
80
- isVictory: response.data.is_victory,
81
- radiationLevel: response.data.radiation_level,
82
- imageUrl: imageUrl,
83
- },
84
- ]);
85
  }
86
 
 
87
  setCurrentChoices(response.data.choices);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  } catch (error) {
89
  console.error("Error:", error);
90
- setStorySegments((prev) => [
91
- ...prev,
92
- { text: "Connection lost with the storyteller...", isChoice: false },
93
- ]);
94
- } finally {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  setIsLoading(false);
96
  }
97
  };
@@ -105,158 +221,182 @@ function App() {
105
  }, []); // Empty dependency array since we're using a ref
106
 
107
  const handleChoice = async (choiceId) => {
108
- // Add the chosen option to the story
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  setStorySegments((prev) => [
110
  ...prev,
111
  {
112
- text: currentChoices.find((c) => c.id === choiceId).text,
113
  isChoice: true,
 
114
  },
115
  ]);
 
116
  // Continue the story with this choice
117
  await handleStoryAction("choice", choiceId);
118
  };
119
 
 
 
 
 
 
120
  return (
121
- <Container maxWidth="md" sx={{ mt: 4 }}>
122
- <Paper
123
- elevation={3}
124
- sx={{ height: "80vh", display: "flex", flexDirection: "column", p: 2 }}
 
 
 
 
 
 
 
 
 
 
 
125
  >
126
- <Box
127
- sx={{
128
- display: "flex",
129
- justifyContent: "space-between",
130
- alignItems: "center",
131
- mb: 2,
132
- }}
133
- >
134
- <Typography variant="h4" component="h1">
135
- Echoes of Influence
136
- </Typography>
137
- <Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
138
- <Box
139
- sx={{
140
- display: "flex",
141
- alignItems: "center",
142
- bgcolor: "warning.main",
143
- color: "white",
144
- px: 2,
145
- py: 1,
146
- borderRadius: 1,
147
- "& .radiation-value": {
148
- color:
149
- storySegments.length > 0 &&
150
- storySegments[storySegments.length - 1].radiationLevel >= 7
151
- ? "error.light"
152
- : "inherit",
153
- },
154
- }}
155
- >
156
- <Typography variant="body1" component="span">
157
- Radiation:{" "}
158
- <span className="radiation-value">
159
- {storySegments.length > 0
160
- ? `${
161
- storySegments[storySegments.length - 1].radiationLevel
162
- }/10`
163
- : "0/10"}
164
- </span>
165
- </Typography>
166
- </Box>
167
- <Button
168
- variant="outlined"
169
- startIcon={<RestartAltIcon />}
170
- onClick={() => handleStoryAction("restart")}
171
- disabled={isLoading}
172
- >
173
- Restart
174
- </Button>
175
  </Box>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  </Box>
 
177
 
178
- {isLoading && <LinearProgress sx={{ mb: 2 }} />}
179
 
180
- <List sx={{ flexGrow: 1, overflow: "auto", mb: 2 }}>
181
- {storySegments.map((segment, index) => (
182
- <ListItem
183
- key={index}
184
- sx={{
185
- justifyContent: segment.isChoice ? "flex-end" : "flex-start",
186
- display: "flex",
187
- flexDirection: "column",
188
- alignItems: segment.isChoice ? "flex-end" : "flex-start",
189
- }}
190
- >
191
- <Paper
192
- elevation={1}
193
- sx={{
194
- p: 2,
195
- maxWidth: "80%",
196
- bgcolor: segment.isDeath
197
- ? "error.light"
198
- : segment.isVictory
199
- ? "success.light"
200
- : segment.isChoice
201
- ? "primary.light"
202
- : "grey.100",
203
- color:
204
- segment.isDeath || segment.isVictory || segment.isChoice
205
- ? "white"
206
- : "text.primary",
207
- }}
208
- >
209
- <ListItemText
210
- primary={
211
- segment.isDeath
212
- ? "DEATH"
213
- : segment.isVictory
214
- ? "VICTOIRE"
215
- : segment.isChoice
216
- ? "Your Choice"
217
- : "Story"
218
- }
219
- secondary={segment.text}
220
- primaryTypographyProps={{
221
- variant: "subtitle2",
222
- color: segment.isChoice ? "inherit" : "primary",
223
- }}
224
- />
225
- {!segment.isChoice && segment.imageUrl && (
226
- <Box sx={{ mt: 2, width: "100%", textAlign: "center" }}>
227
- <img
228
- src={segment.imageUrl}
229
- alt="Story scene"
230
- style={{
231
- maxWidth: "100%",
232
- height: "auto",
233
- borderRadius: "4px",
234
- }}
235
- />
236
- </Box>
237
- )}
238
- </Paper>
239
- </ListItem>
240
- ))}
241
- </List>
242
-
243
- {currentChoices.length > 0 && (
244
- <Box sx={{ display: "flex", justifyContent: "center", gap: 2 }}>
245
  {currentChoices.map((choice) => (
246
  <Button
247
  key={choice.id}
248
  variant="contained"
 
249
  onClick={() => handleChoice(choice.id)}
250
  disabled={isLoading}
251
  sx={{ minWidth: "200px" }}
252
  >
253
- {choice.text}
254
  </Button>
255
  ))}
256
  </Box>
257
- )}
258
- </Paper>
259
- </Container>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  );
261
  }
262
 
 
5
  Button,
6
  Box,
7
  Typography,
 
 
 
8
  LinearProgress,
9
  } from "@mui/material";
10
  import RestartAltIcon from "@mui/icons-material/RestartAlt";
11
  import axios from "axios";
12
+ import { ComicLayout } from "./layouts/ComicLayout";
13
+ import { getNextPanelDimensions } from "./layouts/utils";
14
 
15
  // Get API URL from environment or default to localhost in development
16
  const API_URL = import.meta.env.VITE_API_URL || "http://localhost:8000";
17
 
18
+ // Generate a unique client ID
19
+ const CLIENT_ID = `client_${Math.random().toString(36).substring(2)}`;
20
+
21
+ // Create axios instance with default config
22
+ const api = axios.create({
23
+ headers: {
24
+ "x-client-id": CLIENT_ID,
25
+ },
26
+ });
27
+
28
+ // Function to convert text with ** to bold elements
29
+ const formatTextWithBold = (text) => {
30
+ if (!text) return "";
31
+ const parts = text.split(/(\*\*.*?\*\*)/g);
32
+ return parts.map((part, index) => {
33
+ if (part.startsWith("**") && part.endsWith("**")) {
34
+ // Remove the ** and wrap in bold
35
+ return <strong key={index}>{part.slice(2, -2)}</strong>;
36
+ }
37
+ return part;
38
+ });
39
+ };
40
+
41
  function App() {
42
  const [storySegments, setStorySegments] = useState([]);
43
  const [currentChoices, setCurrentChoices] = useState([]);
44
  const [isLoading, setIsLoading] = useState(false);
45
+ const [isDebugMode, setIsDebugMode] = useState(false);
46
  const isInitializedRef = useRef(false);
47
+ const currentImageRequestRef = useRef(null);
48
+ const pendingImageRequests = useRef(new Set()); // Track pending image requests
49
 
50
+ const generateImageForStory = async (storyText, segmentIndex) => {
51
  try {
52
+ // Cancel previous request if it exists
53
+ if (currentImageRequestRef.current) {
54
+ currentImageRequestRef.current.abort();
55
+ }
56
+
57
+ // Add this segment to pending requests
58
+ pendingImageRequests.current.add(segmentIndex);
59
+
60
  console.log("Generating image for story:", storyText);
61
+ const dimensions = getNextPanelDimensions(storySegments);
62
+ console.log("[DEBUG] Story segments:", storySegments);
63
+ console.log("[DEBUG] Dimensions object:", dimensions);
64
+ console.log(
65
+ "[DEBUG] Width:",
66
+ dimensions?.width,
67
+ "Height:",
68
+ dimensions?.height
69
+ );
70
+
71
+ if (!dimensions || !dimensions.width || !dimensions.height) {
72
+ console.error("[ERROR] Invalid dimensions:", dimensions);
73
+ pendingImageRequests.current.delete(segmentIndex);
74
+ return null;
75
+ }
76
+
77
+ // Create new AbortController for this request
78
+ const abortController = new AbortController();
79
+ currentImageRequestRef.current = abortController;
80
 
81
+ const response = await api.post(
82
+ `${API_URL}/api/${isDebugMode ? "test/" : ""}generate-image`,
83
+ {
84
+ prompt: `Comic book style scene: ${storyText}`,
85
+ width: dimensions.width,
86
+ height: dimensions.height,
87
+ },
88
+ {
89
+ signal: abortController.signal,
90
+ }
91
+ );
92
+
93
+ // Remove from pending requests
94
+ pendingImageRequests.current.delete(segmentIndex);
95
 
96
  if (response.data.success) {
 
97
  return response.data.image_base64;
98
  }
99
  return null;
100
  } catch (error) {
101
+ if (axios.isCancel(error)) {
102
+ console.log("Image request cancelled for segment", segmentIndex);
103
+ // On met quand même à jour le segment pour arrêter le spinner
104
+ setStorySegments((prev) => {
105
+ const updatedSegments = [...prev];
106
+ if (updatedSegments[segmentIndex]) {
107
+ updatedSegments[segmentIndex] = {
108
+ ...updatedSegments[segmentIndex],
109
+ image_base64: null,
110
+ imageRequestCancelled: true, // Flag pour indiquer que la requête a été annulée
111
+ };
112
+ }
113
+ return updatedSegments;
114
+ });
115
+ } else {
116
+ console.error("Error generating image:", error);
117
+ }
118
+ pendingImageRequests.current.delete(segmentIndex);
119
  return null;
120
  }
121
  };
 
123
  const handleStoryAction = async (action, choiceId = null) => {
124
  setIsLoading(true);
125
  try {
126
+ // 1. D'abord, obtenir l'histoire
127
+ const response = await api.post(
128
+ `${API_URL}/api/${isDebugMode ? "test/" : ""}chat`,
129
+ {
130
+ message: action,
131
+ choice_id: choiceId,
132
+ }
 
 
 
133
  );
134
 
135
+ // 2. Créer le nouveau segment sans image
136
+ const newSegment = {
137
+ text: formatTextWithBold(response.data.story_text),
138
+ isChoice: false,
139
+ isDeath: response.data.is_death,
140
+ isVictory: response.data.is_victory,
141
+ radiationLevel: response.data.radiation_level,
142
+ is_first_step: response.data.is_first_step,
143
+ is_last_step: response.data.is_last_step,
144
+ image_base64: null,
145
+ };
146
+
147
+ let segmentIndex;
148
+ // 3. Mettre à jour l'état avec le nouveau segment
149
  if (action === "restart") {
150
+ setStorySegments([newSegment]);
151
+ segmentIndex = 0;
 
 
 
 
 
 
 
 
152
  } else {
153
+ setStorySegments((prev) => {
154
+ segmentIndex = prev.length;
155
+ return [...prev, newSegment];
156
+ });
 
 
 
 
 
 
 
157
  }
158
 
159
+ // 4. Mettre à jour les choix immédiatement
160
  setCurrentChoices(response.data.choices);
161
+
162
+ // 5. Désactiver le loading car l'histoire est affichée
163
+ setIsLoading(false);
164
+
165
+ // 6. Tenter de générer l'image en arrière-plan
166
+ try {
167
+ const image_base64 = await generateImageForStory(
168
+ response.data.story_text,
169
+ segmentIndex
170
+ );
171
+ if (image_base64) {
172
+ setStorySegments((prev) => {
173
+ const updatedSegments = [...prev];
174
+ if (updatedSegments[segmentIndex]) {
175
+ updatedSegments[segmentIndex] = {
176
+ ...updatedSegments[segmentIndex],
177
+ image_base64: image_base64,
178
+ };
179
+ }
180
+ return updatedSegments;
181
+ });
182
+ }
183
+ } catch (imageError) {
184
+ console.error("Error generating image:", imageError);
185
+ }
186
  } catch (error) {
187
  console.error("Error:", error);
188
+ // En cas d'erreur, créer un segment d'erreur qui permet de continuer
189
+ const errorSegment = {
190
+ text: "Le conteur d'histoires est temporairement indisponible. Veuillez réessayer dans quelques instants...",
191
+ isChoice: false,
192
+ isDeath: false,
193
+ isVictory: false,
194
+ radiationLevel:
195
+ storySegments.length > 0
196
+ ? storySegments[storySegments.length - 1].radiationLevel
197
+ : 0,
198
+ image_base64: null,
199
+ };
200
+
201
+ // Ajouter le segment d'erreur et permettre de réessayer
202
+ if (action === "restart") {
203
+ setStorySegments([errorSegment]);
204
+ } else {
205
+ setStorySegments((prev) => [...prev, errorSegment]);
206
+ }
207
+
208
+ // Donner l'option de réessayer
209
+ setCurrentChoices([{ id: 1, text: "Réessayer" }]);
210
+
211
  setIsLoading(false);
212
  }
213
  };
 
221
  }, []); // Empty dependency array since we're using a ref
222
 
223
  const handleChoice = async (choiceId) => {
224
+ // Si c'est l'option "Réessayer", on relance la dernière action
225
+ if (currentChoices.length === 1 && currentChoices[0].text === "Réessayer") {
226
+ // Supprimer le segment d'erreur
227
+ setStorySegments((prev) => prev.slice(0, -1));
228
+ // Réessayer la dernière action
229
+ await handleStoryAction(
230
+ "choice",
231
+ storySegments[storySegments.length - 2]?.choiceId || null
232
+ );
233
+ return;
234
+ }
235
+
236
+ // Comportement normal pour les autres choix
237
+ const choice = currentChoices.find((c) => c.id === choiceId);
238
  setStorySegments((prev) => [
239
  ...prev,
240
  {
241
+ text: choice.text,
242
  isChoice: true,
243
+ choiceId: choiceId, // Stocker l'ID du choix pour pouvoir réessayer
244
  },
245
  ]);
246
+
247
  // Continue the story with this choice
248
  await handleStoryAction("choice", choiceId);
249
  };
250
 
251
+ // Filter out choice segments
252
+ const nonChoiceSegments = storySegments.filter(
253
+ (segment) => !segment.isChoice
254
+ );
255
+
256
  return (
257
+ <Box
258
+ sx={{
259
+ height: "100vh",
260
+ width: "100%",
261
+ display: "flex",
262
+ flexDirection: "column",
263
+ }}
264
+ >
265
+ {/* <Box
266
+ sx={{
267
+ p: 2,
268
+ display: "flex",
269
+ justifyContent: "space-between",
270
+ alignItems: "center",
271
+ }}
272
  >
273
+ <Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
274
+ <Box
275
+ sx={{
276
+ display: "flex",
277
+ alignItems: "center",
278
+ bgcolor: "warning.main",
279
+ color: "white",
280
+ px: 2,
281
+ py: 1,
282
+ borderRadius: 1,
283
+ "& .radiation-value": {
284
+ color:
285
+ storySegments.length > 0 &&
286
+ storySegments[storySegments.length - 1].radiationLevel >= 7
287
+ ? "error.light"
288
+ : "inherit",
289
+ },
290
+ }}
291
+ >
292
+ <Typography variant="body1" component="span">
293
+ Radiation:{" "}
294
+ <span className="radiation-value">
295
+ {storySegments.length > 0
296
+ ? `${
297
+ storySegments[storySegments.length - 1].radiationLevel
298
+ }/10`
299
+ : "0/10"}
300
+ </span>
301
+ </Typography>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
  </Box>
303
+ <Button
304
+ variant="outlined"
305
+ startIcon={<RestartAltIcon />}
306
+ onClick={() => handleStoryAction("restart")}
307
+ disabled={isLoading}
308
+ >
309
+ Restart
310
+ </Button>
311
+ <Button
312
+ variant={isDebugMode ? "contained" : "outlined"}
313
+ color={isDebugMode ? "secondary" : "primary"}
314
+ onClick={() => {
315
+ setIsDebugMode(!isDebugMode);
316
+ // Redémarrer l'histoire en mode debug
317
+ if (!isDebugMode) {
318
+ handleStoryAction("restart");
319
+ }
320
+ }}
321
+ sx={{ ml: 2 }}
322
+ >
323
+ {isDebugMode ? "Mode Debug" : "Mode Normal"}
324
+ </Button>
325
  </Box>
326
+ </Box> */}
327
 
328
+ {/* {isLoading && <LinearProgress sx={{ mb: 2 }} />} */}
329
 
330
+ <Box
331
+ sx={{
332
+ flexGrow: 1,
333
+ display: "flex",
334
+ gap: 4,
335
+ p: 2,
336
+ width: "100%",
337
+ height: "90vh",
338
+ }}
339
+ >
340
+ <ComicLayout segments={nonChoiceSegments} />
341
+ </Box>
342
+
343
+ <Box
344
+ sx={{
345
+ py: 3,
346
+ borderColor: "divider",
347
+ backgroundColor: "background.paper",
348
+ }}
349
+ >
350
+ {currentChoices.length > 0 ? (
351
+ <Box
352
+ sx={{
353
+ display: "flex",
354
+ justifyContent: "center",
355
+ gap: 2,
356
+ minHeight: "40px",
357
+ }}
358
+ >
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
359
  {currentChoices.map((choice) => (
360
  <Button
361
  key={choice.id}
362
  variant="contained"
363
+ size="large"
364
  onClick={() => handleChoice(choice.id)}
365
  disabled={isLoading}
366
  sx={{ minWidth: "200px" }}
367
  >
368
+ {formatTextWithBold(choice.text)}
369
  </Button>
370
  ))}
371
  </Box>
372
+ ) : storySegments.length > 0 &&
373
+ storySegments[storySegments.length - 1].is_last_step ? (
374
+ <Box
375
+ sx={{
376
+ display: "flex",
377
+ justifyContent: "center",
378
+ gap: 2,
379
+ minHeight: "40px",
380
+ }}
381
+ >
382
+ <Button
383
+ variant="text"
384
+ size="large"
385
+ onClick={() => handleStoryAction("restart")}
386
+ startIcon={<RestartAltIcon />}
387
+ sx={{
388
+ color: "text.secondary",
389
+ "&:hover": {
390
+ color: "text.primary",
391
+ },
392
+ }}
393
+ >
394
+ Replay
395
+ </Button>
396
+ </Box>
397
+ ) : null}
398
+ </Box>
399
+ </Box>
400
  );
401
  }
402
 
client/src/fonts/Action-Man/Action-Man-Bold-Italic.woff2 ADDED
Binary file (30.4 kB). View file
 
client/src/fonts/Action-Man/Action-Man-Bold.woff2 ADDED
Binary file (30.6 kB). View file
 
client/src/fonts/Action-Man/Action-Man-Italic.woff2 ADDED
Binary file (30.7 kB). View file
 
client/src/fonts/Action-Man/Action-Man.woff2 ADDED
Binary file (31.3 kB). View file
 
client/src/fonts/Action-Man/Action_Man_Extended-webfont.woff ADDED
Binary file (40.3 kB). View file
 
client/src/fonts/Action-Man/Action_Man_Extended_Bold-webfont.woff2 ADDED
Binary file (35.4 kB). View file
 
client/src/fonts/Action-Man/Action_Man_Extended_Bold_Italic-webfont.woff ADDED
Binary file (41 kB). View file
 
client/src/fonts/Action-Man/Action_Man_Extended_Italic-webfont.woff ADDED
Binary file (39.7 kB). View file
 
client/src/fonts/Action-Man/Action_Man_Shaded-webfont.woff ADDED
Binary file (68.4 kB). View file
 
client/src/fonts/Action-Man/Action_Man_Shaded_Italic-webfont.woff ADDED
Binary file (67.7 kB). View file
 
client/src/fonts/DigitalStripBB/DigitalStripBB_BoldItal.woff2 ADDED
Binary file (13.2 kB). View file
 
client/src/fonts/DigitalStripBB/DigitalStripBB_Ital.woff2 ADDED
Binary file (11.8 kB). View file
 
client/src/fonts/DigitalStripBB/DigitalStripBB_Reg.woff2 ADDED
Binary file (11.3 kB). View file
 
client/src/fonts/Karantula/Karantula-Bold.woff2 ADDED
Binary file (16 kB). View file
 
client/src/fonts/Karantula/Karantula-Italic-Bold.woff2 ADDED
Binary file (15.6 kB). View file
 
client/src/fonts/Karantula/Karantula-Italic.woff2 ADDED
Binary file (17.4 kB). View file
 
client/src/fonts/Karantula/Karantula.woff2 ADDED
Binary file (17 kB). View file
 
client/src/fonts/Komika-Display/Komika-Display.woff ADDED
Binary file (53.4 kB). View file
 
client/src/fonts/Komika-Display/Komika_display_bold-webfont.woff ADDED
Binary file (62.1 kB). View file
 
client/src/fonts/Komika-Display/Komika_display_kaps-webfont.woff ADDED
Binary file (53.1 kB). View file
 
client/src/fonts/Komika-Display/Komika_display_kaps_bold-webfont.woff ADDED
Binary file (59.9 kB). View file
 
client/src/fonts/Komika-Hand/Komika-Hand-Bold-Italic.woff2 ADDED
Binary file (42.5 kB). View file
 
client/src/fonts/Komika-Hand/Komika-Hand-Bold.woff2 ADDED
Binary file (39.5 kB). View file
 
client/src/fonts/Komika-Hand/Komika-Hand-Italic.woff2 ADDED
Binary file (69.7 kB). View file
 
client/src/fonts/Komika-Hand/Komika-Hand.woff2 ADDED
Binary file (69.4 kB). View file
 
client/src/fonts/Komika-Hand/Komika_Parch.woff2 ADDED
Binary file (226 kB). View file
 
client/src/fonts/Komika-Text/KOMTXKBI-webfont.woff ADDED
Binary file (46.4 kB). View file
 
client/src/fonts/Komika-Text/KOMTXTBI-webfont.woff ADDED
Binary file (48.7 kB). View file
 
client/src/fonts/Komika-Text/KOMTXTB_-webfont.woff ADDED
Binary file (47.3 kB). View file
 
client/src/fonts/Komika-Text/KOMTXTI_-webfont.woff ADDED
Binary file (35.4 kB). View file
 
client/src/fonts/Komika-Text/KOMTXTKB-webfont.woff ADDED
Binary file (44.8 kB). View file
 
client/src/fonts/Komika-Text/KOMTXTKI-webfont.woff ADDED
Binary file (42.2 kB). View file
 
client/src/fonts/Komika-Text/KOMTXTK_-webfont.woff ADDED
Binary file (41.2 kB). View file
 
client/src/fonts/Komika-Text/KOMTXTTI-webfont.woff ADDED
Binary file (35.4 kB). View file
 
client/src/fonts/Komika-Text/KOMTXTT_-webfont.woff ADDED
Binary file (35.1 kB). View file
 
client/src/fonts/Komika-Text/KOMTXT__-webfont.woff ADDED
Binary file (36.3 kB). View file
 
client/src/fonts/Manoskope/MANOSKOPE-Bold.woff2 ADDED
Binary file (6.6 kB). View file
 
client/src/fonts/Paete-Round/Paete-Round-Bold-Italic.woff2 ADDED
Binary file (37.5 kB). View file
 
client/src/fonts/Paete-Round/Paete-Round-Bold.woff2 ADDED
Binary file (33.2 kB). View file
 
client/src/fonts/Paete-Round/Paete-Round-Italic.woff2 ADDED
Binary file (30 kB). View file
 
client/src/fonts/Paete-Round/Paete-Round.woff2 ADDED
Binary file (34.5 kB). View file
 
client/src/fonts/Qarmic-Sans/Qarmic-Sans-Abridged.woff2 ADDED
Binary file (23 kB). View file
 
client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Bold-Italic.woff2 ADDED
Binary file (38.6 kB). View file
 
client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Bold.woff2 ADDED
Binary file (37.9 kB). View file
 
client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Extended-Bold-Italic.woff2 ADDED
Binary file (39.4 kB). View file
 
client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Extended-Bold.woff2 ADDED
Binary file (38.6 kB). View file
 
client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Extended-Italic.woff2 ADDED
Binary file (39.9 kB). View file