yangzhitao commited on
Commit
0b237ab
·
1 Parent(s): b4929ca

feat: add backend health status indicator and update functionality

Browse files

- Implemented a backend health status indicator in the Gradio interface.
- Added JavaScript to automatically update the backend status every 30 seconds.
- Enhanced CSS for visual representation of backend health states (undefined, healthy, unhealthy).
- Updated app.py to include health check logic and integrate the new status indicator.
- Introduced a new backend_status.js file for managing status updates.

app.py CHANGED
@@ -3,6 +3,7 @@ import threading
3
  import gradio as gr
4
  import gradio.components as grc
5
  import pandas as pd
 
6
  import uvicorn
7
  from apscheduler.schedulers.background import BackgroundScheduler
8
  from huggingface_hub import snapshot_download
@@ -18,7 +19,7 @@ from src.about import (
18
  TITLE,
19
  )
20
  from src.backend.app import create_app
21
- from src.display.css_html_js import custom_css
22
  from src.display.utils import (
23
  BASE_COLS,
24
  BENCHMARK_COLS,
@@ -39,6 +40,58 @@ def restart_space():
39
  API.restart_space(repo_id=settings.REPO_ID)
40
 
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  print("///// --- Settings --- /////", settings.model_dump())
43
 
44
  # Space initialisation
@@ -282,6 +335,35 @@ with demo:
282
  gr.Markdown(LLM_BENCHMARKS_TEXT, elem_classes="markdown-text")
283
 
284
  with gr.TabItem("🚀 Submit here! ", elem_id="submit-tab", id=len(BENCHMARKS) + 1):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
  with gr.Column():
286
  with gr.Row():
287
  gr.Markdown(EVALUATION_QUEUE_TEXT, elem_classes="markdown-text")
@@ -413,13 +495,13 @@ if __name__ == "__main__":
413
  # Backend server - 在单独的线程中运行
414
  app = create_app()
415
 
416
- def run_fastapi():
417
- print("Starting FastAPI server on http://0.0.0.0:8000")
418
  uvicorn.run(
419
  app,
420
- host="0.0.0.0",
421
- port=8000,
422
- log_level="info",
423
  access_log=True,
424
  )
425
 
 
3
  import gradio as gr
4
  import gradio.components as grc
5
  import pandas as pd
6
+ import requests
7
  import uvicorn
8
  from apscheduler.schedulers.background import BackgroundScheduler
9
  from huggingface_hub import snapshot_download
 
19
  TITLE,
20
  )
21
  from src.backend.app import create_app
22
+ from src.display.css_html_js import backend_status_js, custom_css
23
  from src.display.utils import (
24
  BASE_COLS,
25
  BENCHMARK_COLS,
 
40
  API.restart_space(repo_id=settings.REPO_ID)
41
 
42
 
43
+ def get_backend_status_undefined_html() -> str:
44
+ """
45
+ 返回未定义状态(首次检查前)的 HTML
46
+ """
47
+ return """
48
+ <div id="backend-status-indicator">
49
+ <span class="backend-status-light undefined"></span>
50
+ <span>Backend Status: Checking...</span>
51
+ </div>
52
+ """
53
+
54
+
55
+ def check_backend_health() -> tuple[bool, str]:
56
+ """
57
+ 查询后端健康状态
58
+ 返回: (is_healthy, status_html)
59
+ """
60
+ try:
61
+ response = requests.get("http://localhost:8000/api/v1/health/", timeout=2)
62
+ if response.status_code == 200:
63
+ data = response.json()
64
+ if data.get("code") == 0:
65
+ return (
66
+ True,
67
+ """
68
+ <div id="backend-status-indicator">
69
+ <span class="backend-status-light healthy"></span>
70
+ <span>Backend Status: Healthy</span>
71
+ </div>
72
+ """,
73
+ )
74
+ return (
75
+ False,
76
+ """
77
+ <div id="backend-status-indicator">
78
+ <span class="backend-status-light unhealthy"></span>
79
+ <span>Backend Status: Unhealthy</span>
80
+ </div>
81
+ """,
82
+ )
83
+ except Exception:
84
+ return (
85
+ False,
86
+ """
87
+ <div id="backend-status-indicator">
88
+ <span class="backend-status-light unhealthy"></span>
89
+ <span>Backend Status: Unavailable</span>
90
+ </div>
91
+ """,
92
+ )
93
+
94
+
95
  print("///// --- Settings --- /////", settings.model_dump())
96
 
97
  # Space initialisation
 
335
  gr.Markdown(LLM_BENCHMARKS_TEXT, elem_classes="markdown-text")
336
 
337
  with gr.TabItem("🚀 Submit here! ", elem_id="submit-tab", id=len(BENCHMARKS) + 1):
338
+ # Backend status indicator - 初始状态为 undefined
339
+ backend_status = gr.HTML(value=get_backend_status_undefined_html(), elem_id="backend-status-container")
340
+
341
+ # 定时更新后端状态
342
+ def update_backend_status():
343
+ return check_backend_health()[1]
344
+
345
+ # 创建一个隐藏的按钮用于定时触发更新
346
+ status_trigger = gr.Button(visible=False, elem_id="backend-status-trigger-btn")
347
+
348
+ # 绑定按钮点击事件来更新状态
349
+ status_trigger.click(
350
+ fn=update_backend_status,
351
+ inputs=None,
352
+ outputs=backend_status,
353
+ )
354
+
355
+ # 加载外部 JavaScript 文件
356
+ js_content = backend_status_js.read_text(encoding="utf-8")
357
+ status_trigger_js_html = f'<script>{js_content}</script>'
358
+ gr.HTML(status_trigger_js_html)
359
+
360
+ # 页面加载时立即检查一次
361
+ demo.load(
362
+ fn=update_backend_status,
363
+ inputs=None,
364
+ outputs=backend_status,
365
+ )
366
+
367
  with gr.Column():
368
  with gr.Row():
369
  gr.Markdown(EVALUATION_QUEUE_TEXT, elem_classes="markdown-text")
 
495
  # Backend server - 在单独的线程中运行
496
  app = create_app()
497
 
498
+ def run_fastapi(host: str = "127.0.0.1", port: int = 8000):
499
+ print(f"Starting FastAPI server on http://{host}:{port}")
500
  uvicorn.run(
501
  app,
502
+ host=host,
503
+ port=port,
504
+ log_level="debug",
505
  access_log=True,
506
  )
507
 
src/assets/css/custom.css CHANGED
@@ -105,3 +105,45 @@
105
  overflow: hidden !important;
106
  text-overflow: ellipsis !important;
107
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  overflow: hidden !important;
106
  text-overflow: ellipsis !important;
107
  }
108
+
109
+ /* Backend status indicator - breathing light animation */
110
+ #backend-status-indicator {
111
+ display: flex;
112
+ align-items: center;
113
+ gap: 10px;
114
+ padding: 10px;
115
+ }
116
+
117
+ .backend-status-light {
118
+ width: 16px;
119
+ height: 16px;
120
+ border-radius: 50%;
121
+ display: inline-block;
122
+ animation: breathing 2s ease-in-out infinite;
123
+ }
124
+
125
+ .backend-status-light.undefined {
126
+ background-color: #6b7280;
127
+ box-shadow: 0 0 10px rgba(107, 114, 128, 0.5);
128
+ }
129
+
130
+ .backend-status-light.healthy {
131
+ background-color: #10b981;
132
+ box-shadow: 0 0 10px rgba(16, 185, 129, 0.5);
133
+ }
134
+
135
+ .backend-status-light.unhealthy {
136
+ background-color: #ef4444;
137
+ box-shadow: 0 0 10px rgba(239, 68, 68, 0.5);
138
+ }
139
+
140
+ @keyframes breathing {
141
+ 0%, 100% {
142
+ opacity: 1;
143
+ transform: scale(1);
144
+ }
145
+ 50% {
146
+ opacity: 0.6;
147
+ transform: scale(1.1);
148
+ }
149
+ }
src/assets/js/backend_status.js ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Backend status update script
3
+ * Automatically updates backend health status every 5 seconds
4
+ */
5
+ (function() {
6
+ function startStatusUpdate() {
7
+ const trigger = document.getElementById('backend-status-trigger-btn');
8
+ if (trigger) {
9
+ // 立即执行一次
10
+ trigger.click();
11
+ // 然后每5秒执行一次
12
+ setInterval(function() {
13
+ trigger.click();
14
+ }, 30_000);
15
+ } else {
16
+ // 如果按钮还没加载,等待一下再试
17
+ setTimeout(startStatusUpdate, 1_500);
18
+ }
19
+ }
20
+ // 页面加载完成后开始
21
+ if (document.readyState === 'complete') {
22
+ startStatusUpdate();
23
+ } else {
24
+ window.addEventListener('load', startStatusUpdate);
25
+ }
26
+ })();
src/backend/routes/hf.py CHANGED
@@ -1,17 +1,30 @@
1
  import io
2
  from dataclasses import asdict
 
3
 
4
- from fastapi import APIRouter
5
  from loguru import logger
6
 
7
  from src.backend.config import settings
8
- from src.backend.schemas import HfRepoUrl, ResponseData, Submit_Params, Submit_RespData
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
  router = APIRouter(tags=["huggingface"])
11
 
12
 
13
  @router.post("/community/submit/")
14
- async def submit(params: Submit_Params) -> ResponseData[Submit_RespData]:
15
  """Submit a new evaluation request to the Hugging Face repository."""
16
  file_obj = io.BytesIO(params.content.encode("utf-8"))
17
  commit_info = settings.hf_api.upload_file(
@@ -45,3 +58,21 @@ async def submit(params: Submit_Params) -> ResponseData[Submit_RespData]:
45
  msg=msg,
46
  data=Submit_RespData.model_validate(data_dict),
47
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import io
2
  from dataclasses import asdict
3
+ from typing import TYPE_CHECKING, Annotated
4
 
5
+ from fastapi import APIRouter, Depends
6
  from loguru import logger
7
 
8
  from src.backend.config import settings
9
+ from src.backend.schemas import (
10
+ CommitInfo,
11
+ GetModelInfo_QueryParams,
12
+ GetModelInfo_RespData,
13
+ HfRepoUrl,
14
+ ResponseData,
15
+ Submit_Params,
16
+ Submit_RespData,
17
+ )
18
+
19
+ if TYPE_CHECKING:
20
+ from huggingface_hub import ModelInfo
21
+
22
 
23
  router = APIRouter(tags=["huggingface"])
24
 
25
 
26
  @router.post("/community/submit/")
27
+ async def submit(params: Annotated[Submit_Params, Depends()]) -> ResponseData[Submit_RespData]:
28
  """Submit a new evaluation request to the Hugging Face repository."""
29
  file_obj = io.BytesIO(params.content.encode("utf-8"))
30
  commit_info = settings.hf_api.upload_file(
 
58
  msg=msg,
59
  data=Submit_RespData.model_validate(data_dict),
60
  )
61
+
62
+
63
+ # Get model info
64
+ @router.get("/models/info/")
65
+ async def get_model_info(
66
+ params: Annotated[GetModelInfo_QueryParams, Depends()],
67
+ ) -> ResponseData[GetModelInfo_RespData]:
68
+ # Get model info
69
+ model: ModelInfo = settings.hf_api.model_info(params.model_id, revision=params.revision or None)
70
+ # Get model commit history
71
+ commit_infos = settings.hf_api.list_repo_commits(repo_id=params.model_id, repo_type="model")
72
+ commits = [CommitInfo.model_validate(asdict(c)) for c in commit_infos]
73
+ # Response data
74
+ data = GetModelInfo_RespData.model_validate({
75
+ **asdict(model),
76
+ "commits": commits or None,
77
+ })
78
+ return ResponseData(data=data)
src/backend/schemas.py CHANGED
@@ -1,4 +1,5 @@
1
- from typing import Annotated, Generic, Literal, TypeVar
 
2
 
3
  from pydantic import BaseModel, ConfigDict, Field
4
 
@@ -42,3 +43,53 @@ class HfRepoUrl(BaseModel):
42
  repo_id: str
43
  repo_type: Literal["model", "dataset", "space"]
44
  url: str
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime
2
+ from typing import Annotated, Any, Generic, Literal, TypeVar
3
 
4
  from pydantic import BaseModel, ConfigDict, Field
5
 
 
43
  repo_id: str
44
  repo_type: Literal["model", "dataset", "space"]
45
  url: str
46
+
47
+
48
+ # --- Get Model Info ---
49
+ class GetModelInfo_QueryParams(BaseModel):
50
+ """Parameters for model info query."""
51
+
52
+ model_id: str
53
+ revision: str | None = None
54
+
55
+
56
+ class GetModelInfo_RespData(BaseModel):
57
+ model_config = ConfigDict(extra="allow")
58
+
59
+ id: str
60
+ author: str | None = None
61
+ downloads: int | None = None
62
+ likes: int | None = None
63
+ tags: list[str] | None = None
64
+ pipeline_tag: str | None = None
65
+ sha: str | None = None
66
+ created_at: datetime | None = None
67
+ last_modified: datetime | None = None
68
+ private: bool | None = None
69
+ disabled: bool | None = None
70
+ gated: bool | None = None
71
+ # ... others
72
+ safetensors: "SafetensorsField | None" = None
73
+ # commits
74
+ commits: list["CommitInfo"] | None = None
75
+
76
+
77
+ class SafetensorsField(BaseModel):
78
+ model_config = ConfigDict(extra="allow")
79
+
80
+ parameters: dict[str, Any] | None = None
81
+ total: int | None = None
82
+
83
+
84
+ class CommitInfo(BaseModel):
85
+ model_config = ConfigDict(extra="allow")
86
+
87
+ commit_id: str
88
+
89
+ authors: list[str]
90
+ created_at: datetime
91
+ title: str
92
+ message: str
93
+
94
+ formatted_title: str | None = None
95
+ formatted_message: str | None = None
src/display/css_html_js.py CHANGED
@@ -1,6 +1,7 @@
1
  from pathlib import Path
2
 
3
  custom_css = Path("src/assets/css/custom.css")
 
4
 
5
  # FIXME: seems deprecated
6
  get_window_url_params = """
 
1
  from pathlib import Path
2
 
3
  custom_css = Path("src/assets/css/custom.css")
4
+ backend_status_js = Path("src/assets/js/backend_status.js")
5
 
6
  # FIXME: seems deprecated
7
  get_window_url_params = """
src/leaderboard/read_evals.py CHANGED
@@ -62,7 +62,8 @@ class EvalResult(BaseModel):
62
  @classmethod
63
  def init_from_json_file(cls, json_filepath: str) -> Self:
64
  """Inits the result from the specific model result file"""
65
- data = EvalResultJson.model_validate_json(Path(json_filepath).read_text())
 
66
  config = data.config
67
 
68
  # Precision
 
62
  @classmethod
63
  def init_from_json_file(cls, json_filepath: str) -> Self:
64
  """Inits the result from the specific model result file"""
65
+ json_content = Path(json_filepath).read_text(encoding="utf-8")
66
+ data = EvalResultJson.model_validate_json(json_content)
67
  config = data.config
68
 
69
  # Precision