tfrere commited on
Commit
b4fbe2e
·
1 Parent(s): 6bf12a4

fix healthcheck

Browse files
client/src/components/ServiceStatus.jsx CHANGED
@@ -1,4 +1,4 @@
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";
@@ -10,8 +10,6 @@ const StatusDot = styled("div")(({ status }) => ({
10
  backgroundColor:
11
  status === "healthy"
12
  ? "#4caf50"
13
- : status === "initializing"
14
- ? "#FFA726"
15
  : status === "unhealthy"
16
  ? "#f44336"
17
  : "#9e9e9e",
@@ -28,82 +26,30 @@ const ServiceLabel = styled(Box)({
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
  }
 
1
+ import React from "react";
2
  import { Box, Typography } from "@mui/material";
3
  import { styled } from "@mui/system";
4
  import { useServiceStatus } from "../contexts/ServiceStatusContext";
 
10
  backgroundColor:
11
  status === "healthy"
12
  ? "#4caf50"
 
 
13
  : status === "unhealthy"
14
  ? "#f44336"
15
  : "#9e9e9e",
 
26
  },
27
  });
28
 
 
 
 
 
 
 
 
 
 
 
29
  export function ServiceStatus() {
30
  const { services } = useServiceStatus();
31
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  return (
33
+ <Box
34
+ sx={{
35
+ position: "absolute",
36
+ top: 16,
37
+ left: 16,
38
+ display: "flex",
39
+ alignItems: "center",
40
+ zIndex: 1000,
41
+ }}
42
+ >
43
  {Object.entries(services).map(([service, { status, latency, error }]) => (
44
  <ServiceLabel key={service}>
45
  <StatusDot status={status} />
46
  <Typography>
47
  {service.charAt(0).toUpperCase() + service.slice(1)}
48
+ {/* {status === "healthy" && latency && ` (${latency}ms)`} */}
49
+ {/* {error && ` - ${error}`} */}
 
50
  </Typography>
51
  </ServiceLabel>
52
  ))}
53
+ </Box>
54
  );
55
  }
client/src/contexts/ServiceStatusContext.jsx CHANGED
@@ -63,26 +63,11 @@ export function ServiceStatusProvider({ children }) {
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 }}>
 
63
  };
64
 
65
  useEffect(() => {
66
+ console.log("ServiceStatusProvider mounted, checking services health...");
67
+ // Un seul check au montage du composant
68
+ fetchServiceHealth("mistral");
69
+ fetchServiceHealth("flux");
70
+ }, []); // Pas d'intervalle, juste une vérification au montage
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  return (
73
  <ServiceStatusContext.Provider value={{ services, areServicesHealthy }}>
client/src/pages/Tutorial.jsx CHANGED
@@ -75,7 +75,7 @@ export function Tutorial() {
75
  zIndex: 10,
76
  textAlign: "center",
77
  mt: 2,
78
- maxWidth: isMobile ? "85%" : "50%",
79
  opacity: 0.8,
80
  color: "white",
81
  px: isMobile ? 3 : 0,
@@ -105,7 +105,7 @@ export function Tutorial() {
105
  sx={{
106
  position: "relative",
107
  flex: { xs: "none", sm: 1 },
108
- width: { xs: "50%", sm: "auto" },
109
  maxWidth: { xs: "160px", sm: "200px" },
110
  "&::before": {
111
  content: '""',
 
75
  zIndex: 10,
76
  textAlign: "center",
77
  mt: 2,
78
+ maxWidth: isMobile ? "85%" : "75%",
79
  opacity: 0.8,
80
  color: "white",
81
  px: isMobile ? 3 : 0,
 
105
  sx={{
106
  position: "relative",
107
  flex: { xs: "none", sm: 1 },
108
+ width: { xs: "75%", sm: "auto" },
109
  maxWidth: { xs: "160px", sm: "200px" },
110
  "&::before": {
111
  content: '""',
server/api/models.py CHANGED
@@ -2,7 +2,6 @@ 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
@@ -85,13 +84,8 @@ class StoryResponse(BaseModel):
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
 
2
  from typing import List, Optional, Dict, Any, Union
3
  from core.constants import GameConfig
4
  import re
 
5
 
6
  class Choice(BaseModel):
7
  id: int
 
84
  raise ValueError('Must have exactly 2 choices for story progression')
85
  return v
86
 
 
 
 
 
 
87
  class HealthCheckResponse(BaseModel):
88
+ status: str = Field(..., description="The health status of the service (healthy/unhealthy)")
89
+ service: str = Field(..., description="The name of the service being checked")
90
+ latency: Optional[float] = Field(None, description="The latency of the service in milliseconds")
91
+ error: Optional[str] = Field(None, description="Error message if the service is unhealthy")
server/api/routes/health.py CHANGED
@@ -58,43 +58,21 @@ def get_health_router(mistral_client: MistralClient, flux_client: FluxClient) ->
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(
 
58
  start_time = time.time()
59
  try:
60
  # Try to generate a test image with a timeout
61
+ is_healthy = await asyncio.wait_for(
62
  flux_client.check_health(),
63
  timeout=5.0 # Même timeout que Mistral
64
  )
65
 
66
+ if not is_healthy:
67
+ raise Exception("Failed to generate test image")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
 
69
+ latency = (time.time() - start_time) * 1000 # Convert to milliseconds
70
+ print(f"Flux health check successful. Latency: {latency}ms")
71
+ return HealthCheckResponse(
72
+ status="healthy",
73
+ service="flux",
74
+ latency=latency
75
+ )
76
  except asyncio.TimeoutError:
77
  print("Flux health check failed: timeout")
78
  raise HTTPException(
server/services/flux_client.py CHANGED
@@ -1,6 +1,6 @@
1
  import os
2
  import aiohttp
3
- from typing import Optional, Tuple
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) -> 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,6 +29,7 @@ class FluxClient:
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,59 +50,51 @@ class FluxClient:
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)
 
1
  import os
2
  import aiohttp
3
+ from typing import Optional
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) -> Optional[bytes]:
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
+
33
  session = await self._get_session()
34
  async with session.post(
35
  self.endpoint,
 
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
80
 
81
+ async def check_health(self) -> bool:
82
  """
83
  Vérifie la disponibilité du service Flux en tentant de générer une petite image.
84
 
85
  Returns:
86
+ bool: True si le service est disponible, False sinon
 
 
87
  """
88
  try:
89
  # Test simple prompt pour générer une petite image
90
+ test_image = await self.generate_image(
91
  prompt="test image, simple circle",
92
  width=64, # Petite image pour le test
93
  height=64,
94
  num_inference_steps=1 # Minimum d'étapes pour être rapide
95
  )
96
 
97
+ return test_image is not None
 
 
 
 
 
 
98
  except Exception as e:
99
  print(f"Health check failed: {str(e)}")
100
+ raise