tfrere commited on
Commit
d9ce58c
·
1 Parent(s): 5633299
.DS_Store CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
 
client/src/components/ServiceStatus.jsx ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from "react";
2
+ import { Box, Typography } from "@mui/material";
3
+ import { styled } from "@mui/system";
4
+ import { useServiceStatus } from "../contexts/ServiceStatusContext";
5
+
6
+ const StatusDot = styled("div")(({ status }) => ({
7
+ width: 8,
8
+ height: 8,
9
+ borderRadius: "50%",
10
+ backgroundColor:
11
+ status === "healthy"
12
+ ? "#4caf50"
13
+ : status === "initializing"
14
+ ? "#FFA726"
15
+ : status === "unhealthy"
16
+ ? "#f44336"
17
+ : "#9e9e9e",
18
+ marginRight: 8,
19
+ }));
20
+
21
+ const ServiceLabel = styled(Box)({
22
+ display: "flex",
23
+ alignItems: "center",
24
+ marginRight: 16,
25
+ "& .MuiTypography-root": {
26
+ fontSize: "0.75rem",
27
+ color: "rgba(255, 255, 255, 0.7)",
28
+ },
29
+ });
30
+
31
+ const ServiceStatusContainer = styled("div")({
32
+ position: "absolute",
33
+ top: 16,
34
+ left: 16,
35
+ padding: 8,
36
+ borderRadius: 4,
37
+ boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
38
+ zIndex: 1000,
39
+ });
40
+
41
+ export function ServiceStatus() {
42
+ const { services } = useServiceStatus();
43
+
44
+ const fetchServiceHealth = async (service) => {
45
+ try {
46
+ const response = await fetch(`/api/health/${service}`);
47
+ const data = await response.json();
48
+
49
+ if (response.ok) {
50
+ return {
51
+ status: data.status,
52
+ latency: data.latency,
53
+ error: null,
54
+ };
55
+ } else {
56
+ return {
57
+ status: data.status || "unhealthy",
58
+ latency: null,
59
+ error: data.error || "Service unavailable",
60
+ };
61
+ }
62
+ } catch (error) {
63
+ console.error(`Error checking ${service} health:`, error);
64
+ return {
65
+ status: "unhealthy",
66
+ latency: null,
67
+ error: error.message,
68
+ };
69
+ }
70
+ };
71
+
72
+ useEffect(() => {
73
+ const checkHealth = async () => {
74
+ const mistralHealth = await fetchServiceHealth("mistral");
75
+ const fluxHealth = await fetchServiceHealth("flux");
76
+
77
+ // Assuming you want to update the state with the fetched health data
78
+ // This is a placeholder and should be replaced with actual state management logic
79
+ console.log("Updating health status:", {
80
+ mistral: mistralHealth,
81
+ flux: fluxHealth,
82
+ });
83
+ };
84
+
85
+ // Initial check
86
+ checkHealth();
87
+
88
+ // Check every 30 seconds
89
+ const interval = setInterval(checkHealth, 30000);
90
+
91
+ return () => clearInterval(interval);
92
+ }, []);
93
+
94
+ return (
95
+ <ServiceStatusContainer>
96
+ {Object.entries(services).map(([service, { status, latency, error }]) => (
97
+ <ServiceLabel key={service}>
98
+ <StatusDot status={status} />
99
+ <Typography>
100
+ {service.charAt(0).toUpperCase() + service.slice(1)}
101
+ {/* {status === "healthy" && latency && ` (${Math.round(latency)}ms)`} */}
102
+ {/* {status === "initializing" && " (initializing...)"}
103
+ {status === "unhealthy" && error && ` (${error})`} */}
104
+ </Typography>
105
+ </ServiceLabel>
106
+ ))}
107
+ </ServiceStatusContainer>
108
+ );
109
+ }
client/src/contexts/ServiceStatusContext.jsx ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { createContext, useContext, useState, useEffect } from "react";
2
+
3
+ const ServiceStatusContext = createContext();
4
+
5
+ export function ServiceStatusProvider({ children }) {
6
+ const [services, setServices] = useState({
7
+ mistral: { status: "loading", latency: null },
8
+ flux: { status: "loading", latency: null },
9
+ });
10
+
11
+ const areServicesHealthy = () => {
12
+ return Object.values(services).every(
13
+ (service) => service.status === "healthy"
14
+ );
15
+ };
16
+
17
+ const fetchServiceHealth = async (service) => {
18
+ console.log(`Checking health for ${service} service...`);
19
+ try {
20
+ const response = await fetch(`/api/health/${service}`, {
21
+ method: "GET",
22
+ headers: {
23
+ Accept: "application/json",
24
+ },
25
+ });
26
+ console.log(`Response status for ${service}:`, response.status);
27
+
28
+ const data = await response.json();
29
+ console.log(`Health data for ${service}:`, data);
30
+
31
+ if (response.ok) {
32
+ setServices((prev) => ({
33
+ ...prev,
34
+ [service]: {
35
+ status: data.status,
36
+ latency: data.latency,
37
+ error: data.error,
38
+ },
39
+ }));
40
+ } else {
41
+ const errorData = data?.detail || data;
42
+ console.error(`Error checking ${service} health:`, errorData);
43
+ setServices((prev) => ({
44
+ ...prev,
45
+ [service]: {
46
+ status: "unhealthy",
47
+ latency: null,
48
+ error: errorData?.error || "Service unavailable",
49
+ },
50
+ }));
51
+ }
52
+ } catch (error) {
53
+ console.error(`Failed to check ${service} health:`, error);
54
+ setServices((prev) => ({
55
+ ...prev,
56
+ [service]: {
57
+ status: "unhealthy",
58
+ latency: null,
59
+ error: "Connection error",
60
+ },
61
+ }));
62
+ }
63
+ };
64
+
65
+ useEffect(() => {
66
+ console.log("ServiceStatusProvider mounted, initializing health checks...");
67
+ const checkHealth = () => {
68
+ console.log("Running health checks...");
69
+ fetchServiceHealth("mistral");
70
+ fetchServiceHealth("flux");
71
+ };
72
+
73
+ // Premier check immédiat
74
+ checkHealth();
75
+
76
+ // Mettre en place l'intervalle
77
+ console.log("Setting up health check interval...");
78
+ const interval = setInterval(checkHealth, 30000);
79
+
80
+ // Cleanup
81
+ return () => {
82
+ console.log("Cleaning up health check interval...");
83
+ clearInterval(interval);
84
+ };
85
+ }, []);
86
+
87
+ return (
88
+ <ServiceStatusContext.Provider value={{ services, areServicesHealthy }}>
89
+ {children}
90
+ </ServiceStatusContext.Provider>
91
+ );
92
+ }
93
+
94
+ export function useServiceStatus() {
95
+ const context = useContext(ServiceStatusContext);
96
+ if (!context) {
97
+ throw new Error(
98
+ "useServiceStatus must be used within a ServiceStatusProvider"
99
+ );
100
+ }
101
+ return context;
102
+ }
client/src/main.jsx CHANGED
@@ -10,6 +10,7 @@ import { Tutorial } from "./pages/Tutorial";
10
  import Debug from "./pages/Debug";
11
  import { Universe } from "./pages/Universe";
12
  import { SoundProvider } from "./contexts/SoundContext";
 
13
  import { GameNavigation } from "./components/GameNavigation";
14
  import { AppBackground } from "./components/AppBackground";
15
  import "./index.css";
@@ -18,17 +19,19 @@ ReactDOM.createRoot(document.getElementById("root")).render(
18
  <ThemeProvider theme={theme}>
19
  <CssBaseline />
20
  <SoundProvider>
21
- <BrowserRouter>
22
- <GameNavigation />
23
- <AppBackground />
24
- <Routes>
25
- <Route path="/" element={<Home />} />
26
- <Route path="/game" element={<Game />} />
27
- <Route path="/tutorial" element={<Tutorial />} />
28
- <Route path="/debug" element={<Debug />} />
29
- <Route path="/universe" element={<Universe />} />
30
- </Routes>
31
- </BrowserRouter>
 
 
32
  </SoundProvider>
33
  </ThemeProvider>
34
  );
 
10
  import Debug from "./pages/Debug";
11
  import { Universe } from "./pages/Universe";
12
  import { SoundProvider } from "./contexts/SoundContext";
13
+ import { ServiceStatusProvider } from "./contexts/ServiceStatusContext";
14
  import { GameNavigation } from "./components/GameNavigation";
15
  import { AppBackground } from "./components/AppBackground";
16
  import "./index.css";
 
19
  <ThemeProvider theme={theme}>
20
  <CssBaseline />
21
  <SoundProvider>
22
+ <ServiceStatusProvider>
23
+ <BrowserRouter>
24
+ <GameNavigation />
25
+ <AppBackground />
26
+ <Routes>
27
+ <Route path="/" element={<Home />} />
28
+ <Route path="/game" element={<Game />} />
29
+ <Route path="/tutorial" element={<Tutorial />} />
30
+ <Route path="/debug" element={<Debug />} />
31
+ <Route path="/universe" element={<Universe />} />
32
+ </Routes>
33
+ </BrowserRouter>
34
+ </ServiceStatusProvider>
35
  </SoundProvider>
36
  </ThemeProvider>
37
  );
client/src/pages/Home.jsx CHANGED
@@ -4,16 +4,20 @@ import {
4
  Typography,
5
  useTheme,
6
  useMediaQuery,
 
7
  } from "@mui/material";
8
  import { motion } from "framer-motion";
9
  import { useNavigate } from "react-router-dom";
10
  import { useSoundSystem } from "../contexts/SoundContext";
 
11
  import { BlinkingText } from "../components/BlinkingText";
12
  import { BookPages } from "../components/BookPages";
 
13
 
14
  export function Home() {
15
  const navigate = useNavigate();
16
  const { playSound } = useSoundSystem();
 
17
  const theme = useTheme();
18
  const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
19
 
@@ -22,6 +26,24 @@ export function Home() {
22
  navigate("/tutorial");
23
  };
24
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  return (
26
  <motion.div
27
  initial={{ opacity: 0 }}
@@ -35,6 +57,7 @@ export function Home() {
35
  overflow: "hidden",
36
  }}
37
  >
 
38
  <Box
39
  sx={{
40
  display: "flex",
@@ -43,7 +66,7 @@ export function Home() {
43
  justifyContent: "center",
44
  minHeight: "100vh",
45
  height: "100%",
46
- width: isMobile ? "80%" : "40%", // Adjust the width of the containing block
47
  margin: "auto",
48
  position: "relative",
49
  }}
@@ -96,20 +119,16 @@ export function Home() {
96
  Experience a unique comic book where artificial intelligence brings
97
  your choices to life, shaping the narrative as you explore.
98
  </Typography>
99
- <Button
100
- color="primary"
101
- size="large"
102
- variant="contained"
103
- onClick={handlePlay}
104
- sx={{
105
- mt: 4,
106
- fontSize: isMobile ? "1rem" : "1.2rem",
107
- padding: isMobile ? "8px 24px" : "12px 36px",
108
- zIndex: 10,
109
- }}
110
- >
111
- Play
112
- </Button>
113
  </Box>
114
  </motion.div>
115
  );
 
4
  Typography,
5
  useTheme,
6
  useMediaQuery,
7
+ Tooltip,
8
  } from "@mui/material";
9
  import { motion } from "framer-motion";
10
  import { useNavigate } from "react-router-dom";
11
  import { useSoundSystem } from "../contexts/SoundContext";
12
+ import { useServiceStatus } from "../contexts/ServiceStatusContext";
13
  import { BlinkingText } from "../components/BlinkingText";
14
  import { BookPages } from "../components/BookPages";
15
+ import { ServiceStatus } from "../components/ServiceStatus";
16
 
17
  export function Home() {
18
  const navigate = useNavigate();
19
  const { playSound } = useSoundSystem();
20
+ const { areServicesHealthy } = useServiceStatus();
21
  const theme = useTheme();
22
  const isMobile = useMediaQuery(theme.breakpoints.down("sm"));
23
 
 
26
  navigate("/tutorial");
27
  };
28
 
29
+ const playButton = (
30
+ <Button
31
+ color="primary"
32
+ size="large"
33
+ variant="contained"
34
+ onClick={handlePlay}
35
+ disabled={!areServicesHealthy()}
36
+ sx={{
37
+ mt: 4,
38
+ fontSize: isMobile ? "1rem" : "1.2rem",
39
+ padding: isMobile ? "8px 24px" : "12px 36px",
40
+ zIndex: 10,
41
+ }}
42
+ >
43
+ Play
44
+ </Button>
45
+ );
46
+
47
  return (
48
  <motion.div
49
  initial={{ opacity: 0 }}
 
57
  overflow: "hidden",
58
  }}
59
  >
60
+ <ServiceStatus />
61
  <Box
62
  sx={{
63
  display: "flex",
 
66
  justifyContent: "center",
67
  minHeight: "100vh",
68
  height: "100%",
69
+ width: isMobile ? "80%" : "40%",
70
  margin: "auto",
71
  position: "relative",
72
  }}
 
119
  Experience a unique comic book where artificial intelligence brings
120
  your choices to life, shaping the narrative as you explore.
121
  </Typography>
122
+ {areServicesHealthy() ? (
123
+ playButton
124
+ ) : (
125
+ <Tooltip
126
+ title="Services are currently unavailable. Please wait..."
127
+ arrow
128
+ >
129
+ <span>{playButton}</span>
130
+ </Tooltip>
131
+ )}
 
 
 
 
132
  </Box>
133
  </motion.div>
134
  );
client/vite.config.js CHANGED
@@ -4,4 +4,13 @@ import react from "@vitejs/plugin-react";
4
  // https://vite.dev/config/
5
  export default defineConfig({
6
  plugins: [react()],
 
 
 
 
 
 
 
 
 
7
  });
 
4
  // https://vite.dev/config/
5
  export default defineConfig({
6
  plugins: [react()],
7
+ server: {
8
+ proxy: {
9
+ "/api": {
10
+ target: "http://localhost:8000",
11
+ changeOrigin: true,
12
+ secure: false,
13
+ },
14
+ },
15
+ },
16
  });
server/api/models.py CHANGED
@@ -1,6 +1,8 @@
1
  from pydantic import BaseModel, Field, validator
2
- from typing import List, Optional
3
  from core.constants import GameConfig
 
 
4
 
5
  class Choice(BaseModel):
6
  id: int
@@ -82,3 +84,14 @@ class StoryResponse(BaseModel):
82
  if len(v) != 2:
83
  raise ValueError('Must have exactly 2 choices for story progression')
84
  return v
 
 
 
 
 
 
 
 
 
 
 
 
1
  from pydantic import BaseModel, Field, validator
2
+ from typing import List, Optional, Dict, Any, Union
3
  from core.constants import GameConfig
4
+ import re
5
+ from enum import Enum
6
 
7
  class Choice(BaseModel):
8
  id: int
 
84
  if len(v) != 2:
85
  raise ValueError('Must have exactly 2 choices for story progression')
86
  return v
87
+
88
+ class ServiceStatus(str, Enum):
89
+ HEALTHY = "healthy"
90
+ UNHEALTHY = "unhealthy"
91
+ INITIALIZING = "initializing"
92
+
93
+ class HealthCheckResponse(BaseModel):
94
+ status: ServiceStatus
95
+ service: str
96
+ latency: Optional[float] = None
97
+ error: Optional[str] = None
server/api/routes/health.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import time
2
+ import asyncio
3
+ from fastapi import APIRouter, HTTPException
4
+ from langchain.schema import SystemMessage
5
+ from api.models import HealthCheckResponse
6
+ from services.mistral_client import MistralClient
7
+ from services.flux_client import FluxClient
8
+
9
+ def get_health_router(mistral_client: MistralClient, flux_client: FluxClient) -> APIRouter:
10
+ router = APIRouter()
11
+
12
+ @router.get("/health/mistral", response_model=HealthCheckResponse)
13
+ async def check_mistral_health():
14
+ """Vérifie la disponibilité du service Mistral."""
15
+ print("Checking Mistral health...")
16
+ start_time = time.time()
17
+ try:
18
+ # Try to make a simple request to Mistral with a 5 second timeout
19
+ await asyncio.wait_for(
20
+ mistral_client.check_health(),
21
+ timeout=5.0
22
+ )
23
+
24
+ latency = (time.time() - start_time) * 1000 # Convert to milliseconds
25
+ print(f"Mistral health check successful. Latency: {latency}ms")
26
+ return HealthCheckResponse(
27
+ status="healthy",
28
+ service="mistral",
29
+ latency=latency
30
+ )
31
+ except asyncio.TimeoutError:
32
+ print("Mistral health check failed: timeout")
33
+ raise HTTPException(
34
+ status_code=503,
35
+ detail=HealthCheckResponse(
36
+ status="unhealthy",
37
+ service="mistral",
38
+ latency=None,
39
+ error="Request timed out after 5 seconds"
40
+ ).dict()
41
+ )
42
+ except Exception as e:
43
+ print(f"Mistral health check failed: {str(e)}")
44
+ raise HTTPException(
45
+ status_code=503,
46
+ detail=HealthCheckResponse(
47
+ status="unhealthy",
48
+ service="mistral",
49
+ latency=None,
50
+ error=str(e)
51
+ ).dict()
52
+ )
53
+
54
+ @router.get("/health/flux", response_model=HealthCheckResponse)
55
+ async def check_flux_health():
56
+ """Vérifie la disponibilité du service Flux."""
57
+ print("Checking Flux health...")
58
+ start_time = time.time()
59
+ try:
60
+ # Try to generate a test image with a timeout
61
+ is_healthy, status = await asyncio.wait_for(
62
+ flux_client.check_health(),
63
+ timeout=5.0 # Même timeout que Mistral
64
+ )
65
+
66
+ latency = (time.time() - start_time) * 1000 # Convert to milliseconds
67
+
68
+ if is_healthy:
69
+ print(f"Flux health check successful. Latency: {latency}ms")
70
+ return HealthCheckResponse(
71
+ status="healthy",
72
+ service="flux",
73
+ latency=latency
74
+ )
75
+ elif status == "initializing":
76
+ print("Flux service is initializing")
77
+ raise HTTPException(
78
+ status_code=503,
79
+ detail=HealthCheckResponse(
80
+ status="initializing",
81
+ service="flux",
82
+ latency=None,
83
+ error="Service is initializing"
84
+ ).dict()
85
+ )
86
+ else:
87
+ print(f"Flux health check failed: {status}")
88
+ raise HTTPException(
89
+ status_code=503,
90
+ detail=HealthCheckResponse(
91
+ status="unhealthy",
92
+ service="flux",
93
+ latency=None,
94
+ error=status
95
+ ).dict()
96
+ )
97
+
98
+ except asyncio.TimeoutError:
99
+ print("Flux health check failed: timeout")
100
+ raise HTTPException(
101
+ status_code=503,
102
+ detail=HealthCheckResponse(
103
+ status="unhealthy",
104
+ service="flux",
105
+ latency=None,
106
+ error="Image generation timed out after 5 seconds"
107
+ ).dict()
108
+ )
109
+ except Exception as e:
110
+ print(f"Flux health check failed: {str(e)}")
111
+ raise HTTPException(
112
+ status_code=503,
113
+ detail=HealthCheckResponse(
114
+ status="unhealthy",
115
+ service="flux",
116
+ latency=None,
117
+ error=str(e)
118
+ ).dict()
119
+ )
120
+
121
+ return router
server/core/setup.py CHANGED
@@ -5,7 +5,7 @@ from core.story_generator import StoryGenerator
5
  # Initialize generators with None - they will be set up when needed
6
  universe_generator = None
7
 
8
- def setup_game(api_key: str, model_name: str = "mistral-medium"):
9
  """Setup all game components with the provided API key."""
10
  global universe_generator
11
 
 
5
  # Initialize generators with None - they will be set up when needed
6
  universe_generator = None
7
 
8
+ def setup_game(api_key: str, model_name: str = "mistral-small"):
9
  """Setup all game components with the provided API key."""
10
  global universe_generator
11
 
server/server.py CHANGED
@@ -10,10 +10,12 @@ from core.story_generator import StoryGenerator
10
  from core.setup import setup_game, get_universe_generator
11
  from core.session_manager import SessionManager
12
  from services.flux_client import FluxClient
 
13
  from api.routes.chat import get_chat_router
14
  from api.routes.image import get_image_router
15
  from api.routes.speech import get_speech_router
16
  from api.routes.universe import get_universe_router
 
17
 
18
  # Load environment variables
19
  load_dotenv()
@@ -52,6 +54,7 @@ print("Creating global SessionManager")
52
  session_manager = SessionManager()
53
  story_generator = StoryGenerator(api_key=mistral_api_key)
54
  flux_client = FluxClient(api_key=HF_API_KEY)
 
55
 
56
  # Health check endpoint
57
  @app.get("/api/health")
@@ -65,6 +68,7 @@ app.include_router(get_chat_router(session_manager, story_generator), prefix="/a
65
  app.include_router(get_image_router(flux_client), prefix="/api")
66
  app.include_router(get_speech_router(), prefix="/api")
67
  app.include_router(get_universe_router(session_manager, story_generator), prefix="/api")
 
68
 
69
  @app.on_event("startup")
70
  async def startup_event():
 
10
  from core.setup import setup_game, get_universe_generator
11
  from core.session_manager import SessionManager
12
  from services.flux_client import FluxClient
13
+ from services.mistral_client import MistralClient
14
  from api.routes.chat import get_chat_router
15
  from api.routes.image import get_image_router
16
  from api.routes.speech import get_speech_router
17
  from api.routes.universe import get_universe_router
18
+ from api.routes.health import get_health_router
19
 
20
  # Load environment variables
21
  load_dotenv()
 
54
  session_manager = SessionManager()
55
  story_generator = StoryGenerator(api_key=mistral_api_key)
56
  flux_client = FluxClient(api_key=HF_API_KEY)
57
+ mistral_client = MistralClient(api_key=mistral_api_key)
58
 
59
  # Health check endpoint
60
  @app.get("/api/health")
 
68
  app.include_router(get_image_router(flux_client), prefix="/api")
69
  app.include_router(get_speech_router(), prefix="/api")
70
  app.include_router(get_universe_router(session_manager, story_generator), prefix="/api")
71
+ app.include_router(get_health_router(mistral_client, flux_client), prefix="/api")
72
 
73
  @app.on_event("startup")
74
  async def startup_event():
server/services/flux_client.py CHANGED
@@ -1,6 +1,6 @@
1
  import os
2
  import aiohttp
3
- from typing import Optional
4
 
5
  class FluxClient:
6
  def __init__(self, api_key: str):
@@ -18,7 +18,7 @@ class FluxClient:
18
  width: int,
19
  height: int,
20
  num_inference_steps: int = 5,
21
- guidance_scale: float = 9.0) -> Optional[bytes]:
22
  """Génère une image à partir d'un prompt."""
23
  try:
24
  # Ensure dimensions are multiples of 8
@@ -29,7 +29,6 @@ class FluxClient:
29
  print(f"Headers: Authorization: Bearer {self.api_key[:4]}...")
30
  print(f"Request body: {prompt[:100]}...")
31
 
32
-
33
  session = await self._get_session()
34
  async with session.post(
35
  self.endpoint,
@@ -50,30 +49,59 @@ class FluxClient:
50
  ) as response:
51
  print(f"Response status code: {response.status}")
52
  print(f"Response headers: {response.headers}")
53
- print(f"Response content type: {response.headers.get('content-type', 'unknown')}")
 
 
 
 
 
 
54
 
55
  if response.status == 200:
56
  content = await response.read()
57
- content_length = len(content)
58
- print(f"Received successful response with content length: {content_length}")
59
- if isinstance(content, bytes):
60
- print("Response content is bytes (correct)")
61
- else:
62
- print(f"Warning: Response content is {type(content)}")
63
- return content
64
  else:
65
  error_content = await response.text()
66
  print(f"Error from Flux API: {response.status}")
67
  print(f"Response content: {error_content}")
68
- return None
69
 
70
  except Exception as e:
71
  print(f"Error in FluxClient.generate_image: {str(e)}")
72
  import traceback
73
  print(f"Traceback: {traceback.format_exc()}")
74
- return None
75
 
76
  async def close(self):
77
  if self._session:
78
  await self._session.close()
79
- self._session = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import aiohttp
3
+ from typing import Optional, Tuple
4
 
5
  class FluxClient:
6
  def __init__(self, api_key: str):
 
18
  width: int,
19
  height: int,
20
  num_inference_steps: int = 5,
21
+ guidance_scale: float = 9.0) -> Tuple[Optional[bytes], Optional[str]]:
22
  """Génère une image à partir d'un prompt."""
23
  try:
24
  # Ensure dimensions are multiples of 8
 
29
  print(f"Headers: Authorization: Bearer {self.api_key[:4]}...")
30
  print(f"Request body: {prompt[:100]}...")
31
 
 
32
  session = await self._get_session()
33
  async with session.post(
34
  self.endpoint,
 
49
  ) as response:
50
  print(f"Response status code: {response.status}")
51
  print(f"Response headers: {response.headers}")
52
+
53
+ # Vérifier si le modèle est en cours d'initialisation
54
+ if response.status == 503:
55
+ error_content = await response.text()
56
+ if "currently loading" in error_content.lower() or "initializing" in error_content.lower():
57
+ return None, "initializing"
58
+ return None, "unavailable"
59
 
60
  if response.status == 200:
61
  content = await response.read()
62
+ return content, None
 
 
 
 
 
 
63
  else:
64
  error_content = await response.text()
65
  print(f"Error from Flux API: {response.status}")
66
  print(f"Response content: {error_content}")
67
+ return None, error_content
68
 
69
  except Exception as e:
70
  print(f"Error in FluxClient.generate_image: {str(e)}")
71
  import traceback
72
  print(f"Traceback: {traceback.format_exc()}")
73
+ return None, str(e)
74
 
75
  async def close(self):
76
  if self._session:
77
  await self._session.close()
78
+ self._session = None
79
+
80
+ async def check_health(self) -> Tuple[bool, Optional[str]]:
81
+ """
82
+ Vérifie la disponibilité du service Flux en tentant de générer une petite image.
83
+
84
+ Returns:
85
+ Tuple[bool, Optional[str]]: (is_healthy, status)
86
+ - is_healthy: True si le service est disponible
87
+ - status: "healthy", "initializing", ou message d'erreur
88
+ """
89
+ try:
90
+ # Test simple prompt pour générer une petite image
91
+ test_image, status = await self.generate_image(
92
+ prompt="test image, simple circle",
93
+ width=64, # Petite image pour le test
94
+ height=64,
95
+ num_inference_steps=1 # Minimum d'étapes pour être rapide
96
+ )
97
+
98
+ if test_image is not None:
99
+ return True, "healthy"
100
+ elif status == "initializing":
101
+ return False, "initializing"
102
+ else:
103
+ return False, status or "unavailable"
104
+
105
+ except Exception as e:
106
+ print(f"Health check failed: {str(e)}")
107
+ return False, str(e)
server/services/mistral_client.py CHANGED
@@ -47,7 +47,7 @@ class MistralValidationError(MistralAPIError):
47
  pass
48
 
49
  class MistralClient:
50
- def __init__(self, api_key: str, model_name: str = "mistral-large-latest", max_tokens: int = 1000):
51
  logger.info(f"Initializing MistralClient with model: {model_name}, max_tokens: {max_tokens}")
52
  self.model = ChatMistralAI(
53
  mistral_api_key=api_key,
@@ -213,4 +213,18 @@ class MistralClient:
213
  continue
214
 
215
  logger.error(f"Failed after {self.max_retries} attempts. Last error: {last_error or str(e)}")
216
- raise Exception(f"Failed after {self.max_retries} attempts. Last error: {last_error or str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  pass
48
 
49
  class MistralClient:
50
+ def __init__(self, api_key: str, model_name: str = "mistral-small-latest", max_tokens: int = 1000):
51
  logger.info(f"Initializing MistralClient with model: {model_name}, max_tokens: {max_tokens}")
52
  self.model = ChatMistralAI(
53
  mistral_api_key=api_key,
 
213
  continue
214
 
215
  logger.error(f"Failed after {self.max_retries} attempts. Last error: {last_error or str(e)}")
216
+ raise Exception(f"Failed after {self.max_retries} attempts. Last error: {last_error or str(e)}")
217
+
218
+ async def check_health(self) -> bool:
219
+ """
220
+ Vérifie la disponibilité du service Mistral avec un appel simple sans retry.
221
+
222
+ Returns:
223
+ bool: True si le service est disponible, False sinon
224
+ """
225
+ try:
226
+ response = await self.model.ainvoke([SystemMessage(content="Hi")])
227
+ return True
228
+ except Exception as e:
229
+ logger.error(f"Health check failed: {str(e)}")
230
+ raise