File size: 11,051 Bytes
1e8f4c6
 
 
 
 
 
 
 
 
 
ddf672c
 
 
1e8f4c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ddf672c
 
 
 
 
 
 
 
 
 
 
 
1e8f4c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ddf672c
1e8f4c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ddf672c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1e8f4c6
 
ddf672c
1e8f4c6
 
 
 
 
 
 
 
 
 
 
 
 
ddf672c
 
 
 
 
 
 
 
 
1e8f4c6
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ddf672c
1e8f4c6
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
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
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
import {
  createContext,
  useContext,
  useState,
  useCallback,
  useEffect,
} from "react";
import { storyApi } from "../utils/api";
import { getNextLayoutType, LAYOUTS } from "../layouts/config";

// Constants
const DISABLE_NARRATOR = true; // Désactive complètement le narrateur

const GameContext = createContext(null);

export function GameProvider({ children }) {
  const [segments, setSegments] = useState([]);
  const [choices, setChoices] = useState([]);
  const [isLoading, setIsLoading] = useState(false);
  const [isNarratorSpeaking, setIsNarratorSpeaking] = useState(false);
  const [heroName, setHeroName] = useState("");
  const [loadedPages, setLoadedPages] = useState(new Set());
  const [showChoices, setShowChoices] = useState(true);
  const [showTransitionSpinner, setShowTransitionSpinner] = useState(false);
  const [error, setError] = useState(null);
  const [gameState, setGameState] = useState(null);
  const [currentStory, setCurrentStory] = useState(null);
  const [universe, setUniverse] = useState(null);
  const [slotMachineState, setSlotMachineState] = useState({
    style: null,
    genre: null,
    epoch: null,
    activeIndex: -1,
  });
  const [showSlotMachine, setShowSlotMachine] = useState(() => {
    return !localStorage.getItem("game_initialized");
  });
  const [isInitialLoading, setIsInitialLoading] = useState(true);
  const [showLoadingMessages, setShowLoadingMessages] = useState(false);
  const [isTransitionLoading, setIsTransitionLoading] = useState(false);
  const [layoutCounter, setLayoutCounter] = useState(0);

  // Gestion de la narration
  const stopNarration = useCallback(() => {
    storyApi.stopNarration();
    setIsNarratorSpeaking(false);
  }, []);

  const playNarration = useCallback(async (text) => {
    if (DISABLE_NARRATOR) return; // Early return si le narrateur est désactivé

    try {
      setIsNarratorSpeaking(true);
      await storyApi.playNarration(text);
    } catch (error) {
      console.error("Error playing narration:", error);
    } finally {
      setIsNarratorSpeaking(false);
    }
  }, []);

  // Effect pour arrêter la narration quand le composant est démonté
  useEffect(() => {
    return () => {
      stopNarration();
    };
  }, [stopNarration]);

  // Gestion du chargement des pages
  const handlePageLoaded = useCallback((pageIndex) => {
    setLoadedPages((prev) => {
      const newSet = new Set(prev);
      newSet.add(pageIndex);
      return newSet;
    });
  }, []);

  // Générer les images pour un segment
  const generateImagesForStory = useCallback(
    async (imagePrompts, segmentIndex, currentSegments) => {
      try {
        let localSegments = [...currentSegments];
        const images = Array(imagePrompts.length).fill(null);

        // Obtenir le session_id du segment actuel
        const session_id = localSegments[segmentIndex].session_id;
        if (!session_id) {
          throw new Error("No session_id available for image generation");
        }

        // Déterminer le layout en fonction du nombre d'images
        const layoutType = getNextLayoutType(
          layoutCounter,
          imagePrompts.length
        );
        setLayoutCounter((prev) => prev + 1);

        // Initialiser le segment avec le layout type
        localSegments[segmentIndex] = {
          ...localSegments[segmentIndex],
          layoutType,
          images: Array(imagePrompts.length).fill(null),
          imagePrompts,
          isLoading: true,
        };

        // Mettre à jour les segments et cacher le spinner de transition
        setSegments([...localSegments]);
        setShowTransitionSpinner(false);

        // Générer toutes les images
        for (
          let promptIndex = 0;
          promptIndex < imagePrompts.length;
          promptIndex++
        ) {
          let retryCount = 0;
          const maxRetries = 3;
          let success = false;

          // Obtenir les dimensions pour ce panneau
          const panelDimensions = LAYOUTS[layoutType].panels[promptIndex];
          if (!panelDimensions) {
            console.error(
              `No panel dimensions found for index ${promptIndex} in layout ${layoutType}`
            );
            continue;
          }

          while (retryCount < maxRetries && !success) {
            try {
              const result = await storyApi.generateImage(
                imagePrompts[promptIndex],
                panelDimensions.width,
                panelDimensions.height,
                session_id
              );

              if (!result) {
                throw new Error("Pas de résultat de génération d'image");
              }

              if (result.success) {
                images[promptIndex] = result.image_base64;

                // Mettre à jour le segment avec la nouvelle image
                localSegments[segmentIndex] = {
                  ...localSegments[segmentIndex],
                  images: [...images],
                  isLoading: true, // On garde isLoading à true jusqu'à ce que toutes les images soient générées
                };
                setSegments([...localSegments]);

                success = true;
              } else {
                console.warn(
                  `Failed to generate image ${promptIndex + 1}, attempt ${
                    retryCount + 1
                  }`
                );
                retryCount++;
              }
            } catch (error) {
              console.error(
                `Error generating image ${promptIndex + 1}:`,
                error
              );
              retryCount++;
            }
          }
        }

        // Une fois toutes les images générées, marquer le segment comme chargé
        localSegments[segmentIndex] = {
          ...localSegments[segmentIndex],
          isLoading: false,
        };
        setSegments([...localSegments]);
      } catch (error) {
        console.error("Error in generateImagesForStory:", error);
      }
    },
    [layoutCounter, setLayoutCounter, setSegments]
  );

  const regenerateImage = async (prompt, session_id) => {
    try {
      if (!session_id) {
        console.error("No session_id provided for image regeneration");
        return null;
      }

      const response = await storyApi.generateImage(
        prompt,
        512,
        512,
        session_id
      );
      if (response.success && response.image_base64) {
        return response.image_base64;
      }
      return null;
    } catch (error) {
      console.error("Error regenerating image:", error);
      return null;
    }
  };

  // Gestion des choix
  const handleChoice = useCallback(
    async (choiceId, customText) => {
      if (isLoading) return;

      // Arrêter toute narration en cours avant de faire un nouveau choix
      stopNarration();

      // Montrer le spinner seulement si ce n'est pas la première page
      if (segments.length > 0) {
        setShowTransitionSpinner(true);
      }
      setIsLoading(true);
      setShowChoices(false);

      try {
        let response;
        if (choiceId === "custom") {
          response = await storyApi.makeCustomChoice(
            customText,
            universe?.session_id
          );
        } else {
          response = await storyApi.makeChoice(choiceId, universe?.session_id);
        }

        // Mettre à jour les choix (mais ne pas les afficher encore)
        setChoices(response.choices);

        // Formater le segment avec le bon format
        const formattedSegment = {
          text: response.story_text,
          rawText: response.story_text,
          choices: response.choices || [],
          isLoading: true,
          images: [],
          isDeath: response.is_death || false,
          isVictory: response.is_victory || false,
          time: response.time,
          location: response.location,
          session_id: universe?.session_id,
          is_last_step: response.is_last_step,
          hasBeenRead: false,
        };

        // Si pas d'images à générer
        if (!response.image_prompts || response.image_prompts.length === 0) {
          formattedSegment.isLoading = false;
          setSegments((prev) => [...prev, formattedSegment]);
          setIsLoading(false);
          setShowChoices(true);
          setShowTransitionSpinner(false);
          return;
        }

        // Sinon, générer les images
        const currentSegments = [...segments];
        const newSegmentIndex = currentSegments.length;

        await generateImagesForStory(response.image_prompts, newSegmentIndex, [
          ...currentSegments,
          formattedSegment,
        ]);

        // Une fois toutes les images générées
        setIsLoading(false);
        setShowChoices(true);
      } catch (error) {
        console.error("Error making choice:", error);
        setError(error);
        setIsLoading(false);
        setShowTransitionSpinner(false);
        setShowChoices(true);
      }
    },
    [
      isLoading,
      universe?.session_id,
      generateImagesForStory,
      segments,
      stopNarration,
    ]
  );

  // Reset du jeu
  const resetGame = useCallback(() => {
    setSegments([]);
    setChoices([]);
    setIsLoading(false);
    setIsNarratorSpeaking(false);
    setLoadedPages(new Set());
  }, []);

  // Obtenir le dernier segment
  const getLastSegment = useCallback(() => {
    if (!segments || segments.length === 0) return null;
    return segments[segments.length - 1];
  }, [segments]);

  // Vérifier si le jeu est terminé
  const isGameOver = useCallback(() => {
    const lastSegment = getLastSegment();
    return lastSegment?.isDeath || lastSegment?.isVictory;
  }, [getLastSegment]);

  const value = {
    // État
    segments,
    setSegments,
    choices,
    setChoices,
    isLoading,
    setIsLoading,
    isNarratorSpeaking,
    setIsNarratorSpeaking,
    heroName,
    setHeroName,
    loadedPages,
    showChoices,
    setShowChoices,
    showTransitionSpinner,
    error,
    setError,
    gameState,
    setGameState,
    currentStory,
    setCurrentStory,
    universe,
    setUniverse,
    slotMachineState,
    setSlotMachineState,
    showSlotMachine,
    setShowSlotMachine,
    isInitialLoading,
    setIsInitialLoading,
    showLoadingMessages,
    setShowLoadingMessages,
    isTransitionLoading,
    setIsTransitionLoading,
    layoutCounter,
    setLayoutCounter,

    // Actions
    handlePageLoaded,
    onChoice: handleChoice,
    stopNarration,
    playNarration,
    resetGame,
    getLastSegment,
    isGameOver,

    // Helpers
    isPageLoaded: (pageIndex) => loadedPages.has(pageIndex),
    areAllPagesLoaded: (totalPages) => loadedPages.size === totalPages,
    generateImagesForStory,
    regenerateImage,
  };

  return <GameContext.Provider value={value}>{children}</GameContext.Provider>;
}

export const useGame = () => {
  const context = useContext(GameContext);
  if (!context) {
    throw new Error("useGame must be used within a GameProvider");
  }
  return context;
};