update
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .DS_Store +0 -0
- Dockerfile +1 -1
- client/src/App.jsx +313 -173
- client/src/fonts/Action-Man/Action-Man-Bold-Italic.woff2 +0 -0
- client/src/fonts/Action-Man/Action-Man-Bold.woff2 +0 -0
- client/src/fonts/Action-Man/Action-Man-Italic.woff2 +0 -0
- client/src/fonts/Action-Man/Action-Man.woff2 +0 -0
- client/src/fonts/Action-Man/Action_Man_Extended-webfont.woff +0 -0
- client/src/fonts/Action-Man/Action_Man_Extended_Bold-webfont.woff2 +0 -0
- client/src/fonts/Action-Man/Action_Man_Extended_Bold_Italic-webfont.woff +0 -0
- client/src/fonts/Action-Man/Action_Man_Extended_Italic-webfont.woff +0 -0
- client/src/fonts/Action-Man/Action_Man_Shaded-webfont.woff +0 -0
- client/src/fonts/Action-Man/Action_Man_Shaded_Italic-webfont.woff +0 -0
- client/src/fonts/DigitalStripBB/DigitalStripBB_BoldItal.woff2 +0 -0
- client/src/fonts/DigitalStripBB/DigitalStripBB_Ital.woff2 +0 -0
- client/src/fonts/DigitalStripBB/DigitalStripBB_Reg.woff2 +0 -0
- client/src/fonts/Karantula/Karantula-Bold.woff2 +0 -0
- client/src/fonts/Karantula/Karantula-Italic-Bold.woff2 +0 -0
- client/src/fonts/Karantula/Karantula-Italic.woff2 +0 -0
- client/src/fonts/Karantula/Karantula.woff2 +0 -0
- client/src/fonts/Komika-Display/Komika-Display.woff +0 -0
- client/src/fonts/Komika-Display/Komika_display_bold-webfont.woff +0 -0
- client/src/fonts/Komika-Display/Komika_display_kaps-webfont.woff +0 -0
- client/src/fonts/Komika-Display/Komika_display_kaps_bold-webfont.woff +0 -0
- client/src/fonts/Komika-Hand/Komika-Hand-Bold-Italic.woff2 +0 -0
- client/src/fonts/Komika-Hand/Komika-Hand-Bold.woff2 +0 -0
- client/src/fonts/Komika-Hand/Komika-Hand-Italic.woff2 +0 -0
- client/src/fonts/Komika-Hand/Komika-Hand.woff2 +0 -0
- client/src/fonts/Komika-Hand/Komika_Parch.woff2 +0 -0
- client/src/fonts/Komika-Text/KOMTXKBI-webfont.woff +0 -0
- client/src/fonts/Komika-Text/KOMTXTBI-webfont.woff +0 -0
- client/src/fonts/Komika-Text/KOMTXTB_-webfont.woff +0 -0
- client/src/fonts/Komika-Text/KOMTXTI_-webfont.woff +0 -0
- client/src/fonts/Komika-Text/KOMTXTKB-webfont.woff +0 -0
- client/src/fonts/Komika-Text/KOMTXTKI-webfont.woff +0 -0
- client/src/fonts/Komika-Text/KOMTXTK_-webfont.woff +0 -0
- client/src/fonts/Komika-Text/KOMTXTTI-webfont.woff +0 -0
- client/src/fonts/Komika-Text/KOMTXTT_-webfont.woff +0 -0
- client/src/fonts/Komika-Text/KOMTXT__-webfont.woff +0 -0
- client/src/fonts/Manoskope/MANOSKOPE-Bold.woff2 +0 -0
- client/src/fonts/Paete-Round/Paete-Round-Bold-Italic.woff2 +0 -0
- client/src/fonts/Paete-Round/Paete-Round-Bold.woff2 +0 -0
- client/src/fonts/Paete-Round/Paete-Round-Italic.woff2 +0 -0
- client/src/fonts/Paete-Round/Paete-Round.woff2 +0 -0
- client/src/fonts/Qarmic-Sans/Qarmic-Sans-Abridged.woff2 +0 -0
- client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Bold-Italic.woff2 +0 -0
- client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Bold.woff2 +0 -0
- client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Extended-Bold-Italic.woff2 +0 -0
- client/src/fonts/SF-Arch-Rival/SF-Arch-Rival-Extended-Bold.woff2 +0 -0
- 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
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
|
34 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
return null;
|
44 |
}
|
45 |
};
|
@@ -47,51 +123,91 @@ function App() {
|
|
47 |
const handleStoryAction = async (action, choiceId = null) => {
|
48 |
setIsLoading(true);
|
49 |
try {
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
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 |
-
|
76 |
-
|
77 |
-
|
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 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
109 |
setStorySegments((prev) => [
|
110 |
...prev,
|
111 |
{
|
112 |
-
text:
|
113 |
isChoice: true,
|
|
|
114 |
},
|
115 |
]);
|
|
|
116 |
// Continue the story with this choice
|
117 |
await handleStoryAction("choice", choiceId);
|
118 |
};
|
119 |
|
|
|
|
|
|
|
|
|
|
|
120 |
return (
|
121 |
-
<
|
122 |
-
|
123 |
-
|
124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
>
|
126 |
-
<Box
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
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 |
-
|
179 |
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
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 |
-
|
259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|