cpayne1303 commited on
Commit
96b2ae3
·
1 Parent(s): 05e52e1

initial commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.example +3 -0
  2. .gitignore +45 -0
  3. Dockerfile +62 -0
  4. README.md +86 -7
  5. backend/Dockerfile.dev +25 -0
  6. backend/README.md +352 -0
  7. backend/app/api/__init__.py +5 -0
  8. backend/app/api/dependencies.py +34 -0
  9. backend/app/api/endpoints/leaderboard.py +49 -0
  10. backend/app/api/endpoints/models.py +116 -0
  11. backend/app/api/endpoints/votes.py +126 -0
  12. backend/app/api/router.py +9 -0
  13. backend/app/asgi.py +106 -0
  14. backend/app/config/__init__.py +6 -0
  15. backend/app/config/base.py +38 -0
  16. backend/app/config/hf_config.py +30 -0
  17. backend/app/config/logging_config.py +38 -0
  18. backend/app/core/cache.py +109 -0
  19. backend/app/core/fastapi_cache.py +76 -0
  20. backend/app/core/formatting.py +104 -0
  21. backend/app/main.py +18 -0
  22. backend/app/services/__init__.py +3 -0
  23. backend/app/services/hf_service.py +50 -0
  24. backend/app/services/leaderboard.py +208 -0
  25. backend/app/services/models.py +668 -0
  26. backend/app/services/rate_limiter.py +72 -0
  27. backend/app/services/votes.py +441 -0
  28. backend/app/utils/__init__.py +3 -0
  29. backend/app/utils/logging.py +3 -0
  30. backend/app/utils/model_validation.py +266 -0
  31. backend/pyproject.toml +31 -0
  32. backend/utils/analyze_prod_datasets.py +170 -0
  33. backend/utils/analyze_prod_models.py +106 -0
  34. backend/utils/fix_wrong_model_size.py +110 -0
  35. backend/utils/last_activity.py +164 -0
  36. backend/utils/sync_datasets_locally.py +130 -0
  37. docker-compose.yml +33 -0
  38. frontend/Dockerfile.dev +15 -0
  39. frontend/README.md +80 -0
  40. frontend/package.json +55 -0
  41. frontend/public/index.html +96 -0
  42. frontend/public/logo256.png +0 -0
  43. frontend/public/logo32.png +0 -0
  44. frontend/public/og-image.jpg +0 -0
  45. frontend/public/robots.txt +3 -0
  46. frontend/server.js +85 -0
  47. frontend/src/App.js +121 -0
  48. frontend/src/components/Footer/Footer.js +30 -0
  49. frontend/src/components/Logo/HFLogo.js +19 -0
  50. frontend/src/components/Logo/Logo.js +56 -0
.env.example ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ ENVIRONMENT=development
2
+ HF_TOKEN=xxx
3
+ HF_HOME=.cache
.gitignore ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ __pycache__
4
+ .cache/
5
+
6
+ # dependencies
7
+
8
+ frontend/node_modules
9
+ /.pnp
10
+ .pnp.js
11
+
12
+ # testing
13
+
14
+ /coverage
15
+
16
+ # production
17
+
18
+ /build
19
+
20
+ # misc
21
+
22
+ .DS_Store
23
+ .env.local
24
+ .env.development.local
25
+ .env.test.local
26
+ .env.production.local
27
+
28
+ npm-debug.log*
29
+ yarn-debug.log*
30
+ yarn-error.log\*
31
+
32
+ src/dataframe.json
33
+
34
+ yarn.lock
35
+ package-lock.json
36
+
37
+ /public
38
+
39
+ .claudesync/
40
+
41
+ # Environment variables
42
+ .env
43
+ .env.*
44
+ !.env.example
45
+
Dockerfile ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Build frontend
2
+ FROM node:18 as frontend-build
3
+ WORKDIR /app
4
+ COPY frontend/package*.json ./
5
+ RUN npm install
6
+ COPY frontend/ ./
7
+
8
+ RUN npm run build
9
+
10
+ # Build backend
11
+ FROM python:3.12-slim
12
+ WORKDIR /app
13
+
14
+ # Create non-root user
15
+ RUN useradd -m -u 1000 user
16
+
17
+ # Install poetry
18
+ RUN pip install poetry
19
+
20
+ # Create and configure cache directory
21
+ RUN mkdir -p /app/.cache && \
22
+ chown -R user:user /app
23
+
24
+ # Copy and install backend dependencies
25
+ COPY backend/pyproject.toml backend/poetry.lock* ./
26
+ RUN poetry config virtualenvs.create false \
27
+ && poetry install --no-interaction --no-ansi --no-root --only main
28
+
29
+ # Copy backend code
30
+ COPY backend/ .
31
+
32
+ # Install Node.js and npm
33
+ RUN apt-get update && apt-get install -y \
34
+ curl \
35
+ netcat-openbsd \
36
+ && curl -fsSL https://deb.nodesource.com/setup_18.x | bash - \
37
+ && apt-get install -y nodejs \
38
+ && rm -rf /var/lib/apt/lists/*
39
+
40
+ # Copy frontend server and build
41
+ COPY --from=frontend-build /app/build ./frontend/build
42
+ COPY --from=frontend-build /app/package*.json ./frontend/
43
+ COPY --from=frontend-build /app/server.js ./frontend/
44
+
45
+ # Install frontend production dependencies
46
+ WORKDIR /app/frontend
47
+ RUN npm install --production
48
+ WORKDIR /app
49
+
50
+ # Environment variables
51
+ ENV HF_HOME=/app/.cache \
52
+ HF_DATASETS_CACHE=/app/.cache \
53
+ INTERNAL_API_PORT=7861 \
54
+ PORT=7860 \
55
+ NODE_ENV=production
56
+
57
+ # Note: HF_TOKEN should be provided at runtime, not build time
58
+ USER user
59
+ EXPOSE 7860
60
+
61
+ # Start both servers with wait-for
62
+ CMD ["sh", "-c", "uvicorn app.asgi:app --host 0.0.0.0 --port 7861 & while ! nc -z localhost 7861; do sleep 1; done && cd frontend && npm run serve"]
README.md CHANGED
@@ -1,12 +1,91 @@
1
  ---
2
- title: Open Llm Leaderboard
3
- emoji: 👀
4
- colorFrom: red
5
- colorTo: blue
6
  sdk: docker
7
- pinned: false
 
8
  license: apache-2.0
9
- short_description: copy of official llm leaderboard
 
 
 
 
 
 
 
 
 
10
  ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Open LLM Leaderboard
3
+ emoji: 🏆
4
+ colorFrom: blue
5
+ colorTo: red
6
  sdk: docker
7
+ hf_oauth: true
8
+ pinned: true
9
  license: apache-2.0
10
+ duplicated_from: open-llm-leaderboard/open_llm_leaderboard
11
+ tags:
12
+ - leaderboard
13
+ - modality:text
14
+ - submission:automatic
15
+ - test:public
16
+ - language:english
17
+ - eval:code
18
+ - eval:math
19
+ short_description: Track, rank and evaluate open LLMs and chatbots
20
  ---
21
 
22
+ # Open LLM Leaderboard
23
+
24
+ Modern React interface for comparing Large Language Models (LLMs) in an open and reproducible way.
25
+
26
+ ## Features
27
+
28
+ - 📊 Interactive table with advanced sorting and filtering
29
+ - 🔍 Semantic model search
30
+ - 📌 Pin models for comparison
31
+ - 📱 Responsive and modern interface
32
+ - 🎨 Dark/Light mode
33
+ - ⚡️ Optimized performance with virtualization
34
+
35
+ ## Architecture
36
+
37
+ The project is split into two main parts:
38
+
39
+ ### Frontend (React)
40
+
41
+ ```
42
+ frontend/
43
+ ├── src/
44
+ │ ├── components/ # Reusable UI components
45
+ │ ├── pages/ # Application pages
46
+ │ ├── hooks/ # Custom React hooks
47
+ │ ├── context/ # React contexts
48
+ │ └── constants/ # Constants and configurations
49
+ ├── public/ # Static assets
50
+ └── server.js # Express server for production
51
+ ```
52
+
53
+ ### Backend (FastAPI)
54
+
55
+ ```
56
+ backend/
57
+ ├── app/
58
+ │ ├── api/ # API router and endpoints
59
+ │ │ └── endpoints/ # Specific API endpoints
60
+ │ ├── core/ # Core functionality
61
+ │ ├── config/ # Configuration
62
+ │ └── services/ # Business logic services
63
+ │ ├── leaderboard.py
64
+ │ ├── models.py
65
+ │ ├── votes.py
66
+ │ └── hf_service.py
67
+ └── utils/ # Utility functions
68
+ ```
69
+
70
+ ## Technologies
71
+
72
+ ### Frontend
73
+
74
+ - React
75
+ - Material-UI
76
+ - TanStack Table & Virtual
77
+ - Express.js
78
+
79
+ ### Backend
80
+
81
+ - FastAPI
82
+ - Hugging Face API
83
+ - Docker
84
+
85
+ ## Development
86
+
87
+ The application is containerized using Docker and can be run using:
88
+
89
+ ```bash
90
+ docker-compose up
91
+ ```
backend/Dockerfile.dev ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.12-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install required system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Install poetry
11
+ RUN pip install poetry
12
+
13
+ # Copy Poetry configuration files
14
+ COPY pyproject.toml poetry.lock* ./
15
+
16
+ # Install dependencies
17
+ RUN poetry config virtualenvs.create false && \
18
+ poetry install --no-interaction --no-ansi --no-root
19
+
20
+ # Environment variables configuration for logs
21
+ ENV PYTHONUNBUFFERED=1
22
+ ENV LOG_LEVEL=INFO
23
+
24
+ # In dev, mount volume directly
25
+ CMD ["uvicorn", "app.asgi:app", "--host", "0.0.0.0", "--port", "7860", "--reload", "--log-level", "warning", "--no-access-log"]
backend/README.md ADDED
@@ -0,0 +1,352 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Backend - Open LLM Leaderboard 🏆
2
+
3
+ FastAPI backend for the Open LLM Leaderboard. This service is part of a larger architecture that includes a React frontend. For complete project installation, see the [main README](../README.md).
4
+
5
+ ## ✨ Features
6
+
7
+ - 📊 REST API for LLM models leaderboard management
8
+ - 🗳️ Voting and ranking system
9
+ - 🔄 HuggingFace Hub integration
10
+ - 🚀 Caching and performance optimizations
11
+
12
+ ## 🏗 Architecture
13
+
14
+ ```mermaid
15
+ flowchart TD
16
+ Client(["**Frontend**<br><br>React Application"]) --> API["**API Server**<br><br>FastAPI REST Endpoints"]
17
+
18
+ subgraph Backend
19
+ API --> Core["**Core Layer**<br><br>• Middleware<br>• Cache<br>• Rate Limiting"]
20
+ Core --> Services["**Services Layer**<br><br>• Business Logic<br>• Data Processing"]
21
+
22
+ subgraph Services Layer
23
+ Services --> Models["**Model Service**<br><br>• Model Submission<br>• Evaluation Pipeline"]
24
+ Services --> Votes["**Vote Service**<br><br>• Vote Management<br>• Data Synchronization"]
25
+ Services --> Board["**Leaderboard Service**<br><br>• Rankings<br>• Performance Metrics"]
26
+ end
27
+
28
+ Models --> Cache["**Cache Layer**<br><br>• In-Memory Store<br>• Auto Invalidation"]
29
+ Votes --> Cache
30
+ Board --> Cache
31
+
32
+ Models --> HF["**HuggingFace Hub**<br><br>• Models Repository<br>• Datasets Access"]
33
+ Votes --> HF
34
+ Board --> HF
35
+ end
36
+
37
+ style Client fill:#f9f,stroke:#333,stroke-width:2px
38
+ style Models fill:#bbf,stroke:#333,stroke-width:2px
39
+ style Votes fill:#bbf,stroke:#333,stroke-width:2px
40
+ style Board fill:#bbf,stroke:#333,stroke-width:2px
41
+ style HF fill:#bfb,stroke:#333,stroke-width:2px
42
+ ```
43
+
44
+ ## 🛠️ HuggingFace Datasets
45
+
46
+ The application uses several datasets on the HuggingFace Hub:
47
+
48
+ ### 1. Requests Dataset (`{HF_ORGANIZATION}/requests`)
49
+
50
+ - **Operations**:
51
+ - 📤 `POST /api/models/submit`: Adds a JSON file for each new model submission
52
+ - 📥 `GET /api/models/status`: Reads files to get models status
53
+ - **Format**: One JSON file per model with submission details
54
+ - **Updates**: On each new model submission
55
+
56
+ ### 2. Votes Dataset (`{HF_ORGANIZATION}/votes`)
57
+
58
+ - **Operations**:
59
+ - 📤 `POST /api/votes/{model_id}`: Adds a new vote
60
+ - 📥 `GET /api/votes/model/{provider}/{model}`: Reads model votes
61
+ - 📥 `GET /api/votes/user/{user_id}`: Reads user votes
62
+ - **Format**: JSONL with one vote per line
63
+ - **Sync**: Bidirectional between local cache and Hub
64
+
65
+ ### 3. Contents Dataset (`{HF_ORGANIZATION}/contents`)
66
+
67
+ - **Operations**:
68
+ - 📥 `GET /api/leaderboard`: Reads raw data
69
+ - 📥 `GET /api/leaderboard/formatted`: Reads and formats data
70
+ - **Format**: Main dataset containing all scores and metrics
71
+ - **Updates**: Automatic after model evaluations
72
+
73
+ ### 4. Official Providers Dataset (`{HF_ORGANIZATION}/official-providers`)
74
+
75
+ - **Operations**:
76
+ - 📥 Read-only access for highlighted models
77
+ - **Format**: List of models selected by maintainers
78
+ - **Updates**: Manual by maintainers
79
+
80
+ ## 🛠 Local Development
81
+
82
+ ### Prerequisites
83
+
84
+ - Python 3.9+
85
+ - [Poetry](https://python-poetry.org/docs/#installation)
86
+
87
+ ### Standalone Installation (without Docker)
88
+
89
+ ```bash
90
+ # Install dependencies
91
+ poetry install
92
+
93
+ # Setup configuration
94
+ cp .env.example .env
95
+
96
+ # Start development server
97
+ poetry run uvicorn app.asgi:app --host 0.0.0.0 --port 7860 --reload
98
+ ```
99
+
100
+ Server will be available at http://localhost:7860
101
+
102
+ ## ⚙️ Configuration
103
+
104
+ | Variable | Description | Default |
105
+ | ------------ | ------------------------------------ | ----------- |
106
+ | ENVIRONMENT | Environment (development/production) | development |
107
+ | HF_TOKEN | HuggingFace authentication token | - |
108
+ | PORT | Server port | 7860 |
109
+ | LOG_LEVEL | Logging level (INFO/DEBUG/WARNING) | INFO |
110
+ | CORS_ORIGINS | Allowed CORS origins | ["*"] |
111
+ | CACHE_TTL | Cache Time To Live in seconds | 300 |
112
+
113
+ ## 🔧 Middleware
114
+
115
+ The backend uses several middleware layers for optimal performance and security:
116
+
117
+ - **CORS Middleware**: Handles Cross-Origin Resource Sharing
118
+ - **GZIP Middleware**: Compresses responses > 500 bytes
119
+ - **Rate Limiting**: Prevents API abuse
120
+ - **Caching**: In-memory caching with automatic invalidation
121
+
122
+ ## 📝 Logging
123
+
124
+ The application uses a structured logging system with:
125
+
126
+ - Formatted console output
127
+ - Different log levels per component
128
+ - Request/Response logging
129
+ - Performance metrics
130
+ - Error tracking
131
+
132
+ ## 📁 File Structure
133
+
134
+ ```
135
+ backend/
136
+ ├── app/ # Source code
137
+ │ ├── api/ # Routes and endpoints
138
+ │ │ └── endpoints/ # Endpoint handlers
139
+ │ ├── core/ # Configurations
140
+ │ ├── services/ # Business logic
141
+ │ └── utils/ # Utilities
142
+ └── tests/ # Tests
143
+ ```
144
+
145
+ ## 📚 API
146
+
147
+ Swagger documentation available at http://localhost:7860/docs
148
+
149
+ ### Main Endpoints & Data Structures
150
+
151
+ #### Leaderboard
152
+
153
+ - `GET /api/leaderboard/formatted` - Formatted data with computed fields and metadata
154
+
155
+ ```typescript
156
+ Response {
157
+ models: [{
158
+ id: string, // eval_name
159
+ model: {
160
+ name: string, // fullname
161
+ sha: string, // Model sha
162
+ precision: string, // e.g. "fp16", "int8"
163
+ type: string, // e.g. "fined-tuned-on-domain-specific-dataset"
164
+ weight_type: string,
165
+ architecture: string,
166
+ average_score: number,
167
+ has_chat_template: boolean
168
+ },
169
+ evaluations: {
170
+ ifeval: {
171
+ name: "IFEval",
172
+ value: number, // Raw score
173
+ normalized_score: number
174
+ },
175
+ bbh: {
176
+ name: "BBH",
177
+ value: number,
178
+ normalized_score: number
179
+ },
180
+ math: {
181
+ name: "MATH Level 5",
182
+ value: number,
183
+ normalized_score: number
184
+ },
185
+ gpqa: {
186
+ name: "GPQA",
187
+ value: number,
188
+ normalized_score: number
189
+ },
190
+ musr: {
191
+ name: "MUSR",
192
+ value: number,
193
+ normalized_score: number
194
+ },
195
+ mmlu_pro: {
196
+ name: "MMLU-PRO",
197
+ value: number,
198
+ normalized_score: number
199
+ }
200
+ },
201
+ features: {
202
+ is_not_available_on_hub: boolean,
203
+ is_merged: boolean,
204
+ is_moe: boolean,
205
+ is_flagged: boolean,
206
+ is_official_provider: boolean
207
+ },
208
+ metadata: {
209
+ upload_date: string,
210
+ submission_date: string,
211
+ generation: string,
212
+ base_model: string,
213
+ hub_license: string,
214
+ hub_hearts: number,
215
+ params_billions: number,
216
+ co2_cost: number // CO₂ cost in kg
217
+ }
218
+ }]
219
+ }
220
+ ```
221
+
222
+ - `GET /api/leaderboard` - Raw data from the HuggingFace dataset
223
+ ```typescript
224
+ Response {
225
+ models: [{
226
+ eval_name: string,
227
+ Precision: string,
228
+ Type: string,
229
+ "Weight type": string,
230
+ Architecture: string,
231
+ Model: string,
232
+ fullname: string,
233
+ "Model sha": string,
234
+ "Average ⬆️": number,
235
+ "Hub License": string,
236
+ "Hub ❤️": number,
237
+ "#Params (B)": number,
238
+ "Available on the hub": boolean,
239
+ Merged: boolean,
240
+ MoE: boolean,
241
+ Flagged: boolean,
242
+ "Chat Template": boolean,
243
+ "CO₂ cost (kg)": number,
244
+ "IFEval Raw": number,
245
+ IFEval: number,
246
+ "BBH Raw": number,
247
+ BBH: number,
248
+ "MATH Lvl 5 Raw": number,
249
+ "MATH Lvl 5": number,
250
+ "GPQA Raw": number,
251
+ GPQA: number,
252
+ "MUSR Raw": number,
253
+ MUSR: number,
254
+ "MMLU-PRO Raw": number,
255
+ "MMLU-PRO": number,
256
+ "Maintainer's Highlight": boolean,
257
+ "Upload To Hub Date": string,
258
+ "Submission Date": string,
259
+ Generation: string,
260
+ "Base Model": string
261
+ }]
262
+ }
263
+ ```
264
+
265
+ #### Models
266
+
267
+ - `GET /api/models/status` - Get all models grouped by status
268
+ ```typescript
269
+ Response {
270
+ pending: [{
271
+ name: string,
272
+ submitter: string,
273
+ revision: string,
274
+ wait_time: string,
275
+ submission_time: string,
276
+ status: "PENDING" | "EVALUATING" | "FINISHED",
277
+ precision: string
278
+ }],
279
+ evaluating: Array<Model>,
280
+ finished: Array<Model>
281
+ }
282
+ ```
283
+ - `GET /api/models/pending` - Get pending models only
284
+ - `POST /api/models/submit` - Submit model
285
+
286
+ ```typescript
287
+ Request {
288
+ user_id: string,
289
+ model_id: string,
290
+ base_model?: string,
291
+ precision?: string,
292
+ model_type: string
293
+ }
294
+
295
+ Response {
296
+ status: string,
297
+ message: string
298
+ }
299
+ ```
300
+
301
+ - `GET /api/models/{model_id}/status` - Get model status
302
+
303
+ #### Votes
304
+
305
+ - `POST /api/votes/{model_id}` - Vote
306
+
307
+ ```typescript
308
+ Request {
309
+ vote_type: "up" | "down",
310
+ user_id: string // HuggingFace username
311
+ }
312
+
313
+ Response {
314
+ success: boolean,
315
+ message: string
316
+ }
317
+ ```
318
+
319
+ - `GET /api/votes/model/{provider}/{model}` - Get model votes
320
+ ```typescript
321
+ Response {
322
+ total_votes: number,
323
+ up_votes: number,
324
+ down_votes: number
325
+ }
326
+ ```
327
+ - `GET /api/votes/user/{user_id}` - Get user votes
328
+ ```typescript
329
+ Response Array<{
330
+ model_id: string,
331
+ vote_type: string,
332
+ timestamp: string
333
+ }>
334
+ ```
335
+
336
+ ## 🔒 Authentication
337
+
338
+ The backend uses HuggingFace token-based authentication for secure API access. Make sure to:
339
+
340
+ 1. Set your HF_TOKEN in the .env file
341
+ 2. Include the token in API requests via Bearer authentication
342
+ 3. Keep your token secure and never commit it to version control
343
+
344
+ ## 🚀 Performance
345
+
346
+ The backend implements several optimizations:
347
+
348
+ - In-memory caching with configurable TTL (Time To Live)
349
+ - Batch processing for model evaluations
350
+ - Rate limiting for API endpoints
351
+ - Efficient database queries with proper indexing
352
+ - Automatic cache invalidation for votes
backend/app/api/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """
2
+ API package initialization
3
+ """
4
+
5
+ __all__ = ["endpoints"]
backend/app/api/dependencies.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import Depends, HTTPException
2
+ import logging
3
+ from app.services.models import ModelService
4
+ from app.services.votes import VoteService
5
+ from app.core.formatting import LogFormatter
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ model_service = ModelService()
10
+ vote_service = VoteService()
11
+
12
+ async def get_model_service() -> ModelService:
13
+ """Dependency to get ModelService instance"""
14
+ try:
15
+ logger.info(LogFormatter.info("Initializing model service dependency"))
16
+ await model_service.initialize()
17
+ logger.info(LogFormatter.success("Model service initialized"))
18
+ return model_service
19
+ except Exception as e:
20
+ error_msg = "Failed to initialize model service"
21
+ logger.error(LogFormatter.error(error_msg, e))
22
+ raise HTTPException(status_code=500, detail=str(e))
23
+
24
+ async def get_vote_service() -> VoteService:
25
+ """Dependency to get VoteService instance"""
26
+ try:
27
+ logger.info(LogFormatter.info("Initializing vote service dependency"))
28
+ await vote_service.initialize()
29
+ logger.info(LogFormatter.success("Vote service initialized"))
30
+ return vote_service
31
+ except Exception as e:
32
+ error_msg = "Failed to initialize vote service"
33
+ logger.error(LogFormatter.error(error_msg, e))
34
+ raise HTTPException(status_code=500, detail=str(e))
backend/app/api/endpoints/leaderboard.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+ from typing import List, Dict, Any
3
+ from app.services.leaderboard import LeaderboardService
4
+ from app.core.fastapi_cache import cached, build_cache_key
5
+ import logging
6
+ from app.core.formatting import LogFormatter
7
+
8
+ logger = logging.getLogger(__name__)
9
+ router = APIRouter()
10
+ leaderboard_service = LeaderboardService()
11
+
12
+ def leaderboard_key_builder(func, namespace: str = "leaderboard", **kwargs):
13
+ """Build cache key for leaderboard data"""
14
+ key_type = "raw" if func.__name__ == "get_leaderboard" else "formatted"
15
+ key = build_cache_key(namespace, key_type)
16
+ logger.debug(LogFormatter.info(f"Built leaderboard cache key: {key}"))
17
+ return key
18
+
19
+ @router.get("")
20
+ @cached(expire=300, key_builder=leaderboard_key_builder)
21
+ async def get_leaderboard() -> List[Dict[str, Any]]:
22
+ """
23
+ Get raw leaderboard data
24
+ Response will be automatically GZIP compressed if size > 500 bytes
25
+ """
26
+ try:
27
+ logger.info(LogFormatter.info("Fetching raw leaderboard data"))
28
+ data = await leaderboard_service.fetch_raw_data()
29
+ logger.info(LogFormatter.success(f"Retrieved {len(data)} leaderboard entries"))
30
+ return data
31
+ except Exception as e:
32
+ logger.error(LogFormatter.error("Failed to fetch raw leaderboard data", e))
33
+ raise
34
+
35
+ @router.get("/formatted")
36
+ @cached(expire=300, key_builder=leaderboard_key_builder)
37
+ async def get_formatted_leaderboard() -> List[Dict[str, Any]]:
38
+ """
39
+ Get formatted leaderboard data with restructured objects
40
+ Response will be automatically GZIP compressed if size > 500 bytes
41
+ """
42
+ try:
43
+ logger.info(LogFormatter.info("Fetching formatted leaderboard data"))
44
+ data = await leaderboard_service.get_formatted_data()
45
+ logger.info(LogFormatter.success(f"Retrieved {len(data)} formatted entries"))
46
+ return data
47
+ except Exception as e:
48
+ logger.error(LogFormatter.error("Failed to fetch formatted leaderboard data", e))
49
+ raise
backend/app/api/endpoints/models.py ADDED
@@ -0,0 +1,116 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, Depends, Query
2
+ from typing import Dict, Any, List
3
+ import logging
4
+ from app.services.models import ModelService
5
+ from app.api.dependencies import get_model_service
6
+ from app.core.fastapi_cache import cached
7
+ from app.core.formatting import LogFormatter
8
+
9
+ logger = logging.getLogger(__name__)
10
+ router = APIRouter(tags=["models"])
11
+
12
+ @router.get("/status")
13
+ @cached(expire=300)
14
+ async def get_models_status(
15
+ model_service: ModelService = Depends(get_model_service)
16
+ ) -> Dict[str, List[Dict[str, Any]]]:
17
+ """Get all models grouped by status"""
18
+ try:
19
+ logger.info(LogFormatter.info("Fetching status for all models"))
20
+ result = await model_service.get_models()
21
+ stats = {
22
+ status: len(models) for status, models in result.items()
23
+ }
24
+ for line in LogFormatter.stats(stats, "Models by Status"):
25
+ logger.info(line)
26
+ return result
27
+ except Exception as e:
28
+ logger.error(LogFormatter.error("Failed to get models status", e))
29
+ raise HTTPException(status_code=500, detail=str(e))
30
+
31
+ @router.get("/pending")
32
+ @cached(expire=60)
33
+ async def get_pending_models(
34
+ model_service: ModelService = Depends(get_model_service)
35
+ ) -> List[Dict[str, Any]]:
36
+ """Get all models waiting for evaluation"""
37
+ try:
38
+ logger.info(LogFormatter.info("Fetching pending models"))
39
+ models = await model_service.get_models()
40
+ pending = models.get("pending", [])
41
+ logger.info(LogFormatter.success(f"Found {len(pending)} pending models"))
42
+ return pending
43
+ except Exception as e:
44
+ logger.error(LogFormatter.error("Failed to get pending models", e))
45
+ raise HTTPException(status_code=500, detail=str(e))
46
+
47
+ @router.post("/submit")
48
+ async def submit_model(
49
+ model_data: Dict[str, Any],
50
+ model_service: ModelService = Depends(get_model_service)
51
+ ) -> Dict[str, Any]:
52
+ try:
53
+ logger.info(LogFormatter.section("MODEL SUBMISSION"))
54
+
55
+ user_id = model_data.pop('user_id', None)
56
+ if not user_id:
57
+ error_msg = "user_id is required"
58
+ logger.error(LogFormatter.error("Validation failed", error_msg))
59
+ raise ValueError(error_msg)
60
+
61
+ # Log submission details
62
+ submission_info = {
63
+ "Model_ID": model_data.get("model_id"),
64
+ "User": user_id,
65
+ "Base_Model": model_data.get("base_model"),
66
+ "Precision": model_data.get("precision"),
67
+ "Model_Type": model_data.get("model_type")
68
+ }
69
+ for line in LogFormatter.tree(submission_info, "Submission Details"):
70
+ logger.info(line)
71
+
72
+ result = await model_service.submit_model(model_data, user_id)
73
+ logger.info(LogFormatter.success("Model submitted successfully"))
74
+ return result
75
+
76
+ except ValueError as e:
77
+ logger.error(LogFormatter.error("Invalid submission data", e))
78
+ raise HTTPException(status_code=400, detail=str(e))
79
+ except Exception as e:
80
+ logger.error(LogFormatter.error("Submission failed", e))
81
+ raise HTTPException(status_code=500, detail=str(e))
82
+
83
+ @router.get("/organization/{organization}/submissions")
84
+ async def get_organization_submissions(
85
+ organization: str,
86
+ days: int = Query(default=7, ge=1, le=30),
87
+ model_service: ModelService = Depends(get_model_service)
88
+ ) -> List[Dict[str, Any]]:
89
+ """Get all submissions from an organization in the last n days"""
90
+ try:
91
+ submissions = await model_service.get_organization_submissions(organization, days)
92
+ return submissions
93
+ except Exception as e:
94
+ raise HTTPException(status_code=500, detail=str(e))
95
+
96
+ @router.get("/{model_id}/status")
97
+ async def get_model_status(
98
+ model_id: str,
99
+ model_service: ModelService = Depends(get_model_service)
100
+ ) -> Dict[str, Any]:
101
+ try:
102
+ logger.info(LogFormatter.info(f"Checking status for model: {model_id}"))
103
+ status = await model_service.get_model_status(model_id)
104
+
105
+ if status["status"] != "not_found":
106
+ logger.info(LogFormatter.success("Status found"))
107
+ for line in LogFormatter.tree(status, "Model Status"):
108
+ logger.info(line)
109
+ else:
110
+ logger.warning(LogFormatter.warning(f"No status found for model: {model_id}"))
111
+
112
+ return status
113
+
114
+ except Exception as e:
115
+ logger.error(LogFormatter.error("Failed to get model status", e))
116
+ raise HTTPException(status_code=500, detail=str(e))
backend/app/api/endpoints/votes.py ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, HTTPException, Query, Depends, Response
2
+ from typing import Dict, Any, List
3
+ from app.services.votes import VoteService
4
+ from app.core.fastapi_cache import cached, build_cache_key, invalidate_cache_key
5
+ import logging
6
+ from app.core.formatting import LogFormatter
7
+ from datetime import datetime, timezone
8
+
9
+ logger = logging.getLogger(__name__)
10
+ router = APIRouter()
11
+ vote_service = VoteService()
12
+
13
+ CACHE_TTL = 30 # 30 seconds cache
14
+
15
+ def model_votes_key_builder(func, namespace: str = "model_votes", **kwargs):
16
+ """Build cache key for model votes"""
17
+ provider = kwargs.get('provider')
18
+ model = kwargs.get('model')
19
+ key = build_cache_key(namespace, provider, model)
20
+ logger.debug(LogFormatter.info(f"Built model votes cache key: {key}"))
21
+ return key
22
+
23
+ def user_votes_key_builder(func, namespace: str = "user_votes", **kwargs):
24
+ """Build cache key for user votes"""
25
+ user_id = kwargs.get('user_id')
26
+ key = build_cache_key(namespace, user_id)
27
+ logger.debug(LogFormatter.info(f"Built user votes cache key: {key}"))
28
+ return key
29
+
30
+ @router.post("/{model_id:path}")
31
+ async def add_vote(
32
+ response: Response,
33
+ model_id: str,
34
+ vote_type: str = Query(..., description="Type of vote (up/down)"),
35
+ user_id: str = Query(..., description="HuggingFace username"),
36
+ vote_data: Dict[str, Any] = None
37
+ ) -> Dict[str, Any]:
38
+ try:
39
+ logger.info(LogFormatter.section("ADDING VOTE"))
40
+ stats = {
41
+ "Model": model_id,
42
+ "User": user_id,
43
+ "Type": vote_type,
44
+ "Config": vote_data or {}
45
+ }
46
+ for line in LogFormatter.tree(stats, "Vote Details"):
47
+ logger.info(line)
48
+
49
+ await vote_service.initialize()
50
+ result = await vote_service.add_vote(model_id, user_id, vote_type, vote_data)
51
+
52
+ # Invalidate affected caches
53
+ try:
54
+ logger.info(LogFormatter.subsection("CACHE INVALIDATION"))
55
+ provider, model = model_id.split('/', 1)
56
+
57
+ # Build and invalidate cache keys
58
+ model_cache_key = build_cache_key("model_votes", provider, model)
59
+ user_cache_key = build_cache_key("user_votes", user_id)
60
+
61
+ await invalidate_cache_key(model_cache_key)
62
+ await invalidate_cache_key(user_cache_key)
63
+
64
+ cache_stats = {
65
+ "Model_Cache": model_cache_key,
66
+ "User_Cache": user_cache_key
67
+ }
68
+ for line in LogFormatter.tree(cache_stats, "Invalidated Caches"):
69
+ logger.info(line)
70
+
71
+ except Exception as e:
72
+ logger.error(LogFormatter.error("Failed to invalidate cache", e))
73
+
74
+ # Add cache control headers
75
+ response.headers["Cache-Control"] = "no-cache"
76
+
77
+ return result
78
+ except Exception as e:
79
+ logger.error(LogFormatter.error("Failed to add vote", e))
80
+ raise HTTPException(status_code=400, detail=str(e))
81
+
82
+ @router.get("/model/{provider}/{model}")
83
+ @cached(expire=CACHE_TTL, key_builder=model_votes_key_builder)
84
+ async def get_model_votes(
85
+ response: Response,
86
+ provider: str,
87
+ model: str
88
+ ) -> Dict[str, Any]:
89
+ """Get all votes for a specific model"""
90
+ try:
91
+ logger.info(LogFormatter.info(f"Fetching votes for model: {provider}/{model}"))
92
+ await vote_service.initialize()
93
+ model_id = f"{provider}/{model}"
94
+ result = await vote_service.get_model_votes(model_id)
95
+
96
+ # Add cache control headers
97
+ response.headers["Cache-Control"] = f"max-age={CACHE_TTL}"
98
+ response.headers["Last-Modified"] = vote_service._last_sync.strftime("%a, %d %b %Y %H:%M:%S GMT")
99
+
100
+ logger.info(LogFormatter.success(f"Found {result.get('total_votes', 0)} votes"))
101
+ return result
102
+ except Exception as e:
103
+ logger.error(LogFormatter.error("Failed to get model votes", e))
104
+ raise HTTPException(status_code=400, detail=str(e))
105
+
106
+ @router.get("/user/{user_id}")
107
+ @cached(expire=CACHE_TTL, key_builder=user_votes_key_builder)
108
+ async def get_user_votes(
109
+ response: Response,
110
+ user_id: str
111
+ ) -> List[Dict[str, Any]]:
112
+ """Get all votes from a specific user"""
113
+ try:
114
+ logger.info(LogFormatter.info(f"Fetching votes for user: {user_id}"))
115
+ await vote_service.initialize()
116
+ votes = await vote_service.get_user_votes(user_id)
117
+
118
+ # Add cache control headers
119
+ response.headers["Cache-Control"] = f"max-age={CACHE_TTL}"
120
+ response.headers["Last-Modified"] = vote_service._last_sync.strftime("%a, %d %b %Y %H:%M:%S GMT")
121
+
122
+ logger.info(LogFormatter.success(f"Found {len(votes)} votes"))
123
+ return votes
124
+ except Exception as e:
125
+ logger.error(LogFormatter.error("Failed to get user votes", e))
126
+ raise HTTPException(status_code=400, detail=str(e))
backend/app/api/router.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+
3
+ from app.api.endpoints import leaderboard, votes, models
4
+
5
+ router = APIRouter()
6
+
7
+ router.include_router(leaderboard.router, prefix="/leaderboard", tags=["leaderboard"])
8
+ router.include_router(votes.router, prefix="/votes", tags=["votes"])
9
+ router.include_router(models.router, prefix="/models", tags=["models"])
backend/app/asgi.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ASGI entry point for the Open LLM Leaderboard API.
3
+ """
4
+ import os
5
+ import uvicorn
6
+ import logging
7
+ import logging.config
8
+ from fastapi import FastAPI
9
+ from fastapi.middleware.cors import CORSMiddleware
10
+ from fastapi.middleware.gzip import GZipMiddleware
11
+ import sys
12
+
13
+ from app.api.router import router
14
+ from app.core.fastapi_cache import setup_cache
15
+ from app.core.formatting import LogFormatter
16
+ from app.config import hf_config
17
+
18
+ # Configure logging before anything else
19
+ LOGGING_CONFIG = {
20
+ "version": 1,
21
+ "disable_existing_loggers": True,
22
+ "formatters": {
23
+ "default": {
24
+ "format": "%(name)s - %(levelname)s - %(message)s",
25
+ }
26
+ },
27
+ "handlers": {
28
+ "default": {
29
+ "formatter": "default",
30
+ "class": "logging.StreamHandler",
31
+ "stream": "ext://sys.stdout",
32
+ }
33
+ },
34
+ "loggers": {
35
+ "uvicorn": {
36
+ "handlers": ["default"],
37
+ "level": "WARNING",
38
+ "propagate": False,
39
+ },
40
+ "uvicorn.error": {
41
+ "level": "WARNING",
42
+ "handlers": ["default"],
43
+ "propagate": False,
44
+ },
45
+ "uvicorn.access": {
46
+ "handlers": ["default"],
47
+ "level": "WARNING",
48
+ "propagate": False,
49
+ },
50
+ "app": {
51
+ "handlers": ["default"],
52
+ "level": "WARNING",
53
+ "propagate": False,
54
+ }
55
+ },
56
+ "root": {
57
+ "handlers": ["default"],
58
+ "level": "WARNING",
59
+ }
60
+ }
61
+
62
+ # Apply logging configuration
63
+ logging.config.dictConfig(LOGGING_CONFIG)
64
+ logger = logging.getLogger("app")
65
+
66
+ # Create FastAPI application
67
+ app = FastAPI(
68
+ title="Open LLM Leaderboard",
69
+ version="1.0.0",
70
+ docs_url="/docs",
71
+ )
72
+
73
+ # Add CORS middleware
74
+ app.add_middleware(
75
+ CORSMiddleware,
76
+ allow_origins=["*"],
77
+ allow_credentials=True,
78
+ allow_methods=["*"],
79
+ allow_headers=["*"],
80
+ )
81
+
82
+ # Add GZIP compression
83
+ app.add_middleware(GZipMiddleware, minimum_size=500)
84
+
85
+ # Include API router
86
+ app.include_router(router, prefix="/api")
87
+
88
+ @app.on_event("startup")
89
+ async def startup_event():
90
+ """Initialize services on startup"""
91
+ logger.info("\n")
92
+ logger.info(LogFormatter.section("APPLICATION STARTUP"))
93
+
94
+ # Log HF configuration
95
+ logger.info(LogFormatter.section("HUGGING FACE CONFIGURATION"))
96
+ logger.info(LogFormatter.info(f"Organization: {hf_config.HF_ORGANIZATION}"))
97
+ logger.info(LogFormatter.info(f"Token Status: {'Present' if hf_config.HF_TOKEN else 'Missing'}"))
98
+ logger.info(LogFormatter.info(f"Using repositories:"))
99
+ logger.info(LogFormatter.info(f" - Queue: {hf_config.QUEUE_REPO}"))
100
+ logger.info(LogFormatter.info(f" - Aggregated: {hf_config.AGGREGATED_REPO}"))
101
+ logger.info(LogFormatter.info(f" - Votes: {hf_config.VOTES_REPO}"))
102
+ logger.info(LogFormatter.info(f" - Official Providers: {hf_config.OFFICIAL_PROVIDERS_REPO}"))
103
+
104
+ # Setup cache
105
+ setup_cache()
106
+ logger.info(LogFormatter.success("FastAPI Cache initialized with in-memory backend"))
backend/app/config/__init__.py ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ """
2
+ Configuration module for the Open LLM Leaderboard backend.
3
+ All configuration values are imported from base.py to avoid circular dependencies.
4
+ """
5
+
6
+ from .base import *
backend/app/config/base.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from pathlib import Path
3
+
4
+ # Server configuration
5
+ HOST = "0.0.0.0"
6
+ PORT = 7860
7
+ WORKERS = 4
8
+ RELOAD = True if os.environ.get("ENVIRONMENT") == "development" else False
9
+
10
+ # CORS configuration
11
+ ORIGINS = ["http://localhost:3000"] if os.getenv("ENVIRONMENT") == "development" else ["*"]
12
+
13
+ # Cache configuration
14
+ CACHE_TTL = int(os.environ.get("CACHE_TTL", 300)) # 5 minutes default
15
+
16
+ # Rate limiting
17
+ RATE_LIMIT_PERIOD = 7 # days
18
+ RATE_LIMIT_QUOTA = 5
19
+ HAS_HIGHER_RATE_LIMIT = []
20
+
21
+ # HuggingFace configuration
22
+ HF_TOKEN = os.environ.get("HF_TOKEN")
23
+ HF_ORGANIZATION = "open-llm-leaderboard"
24
+ API = {
25
+ "INFERENCE": "https://api-inference.huggingface.co/models",
26
+ "HUB": "https://huggingface.co"
27
+ }
28
+
29
+ # Cache paths
30
+ CACHE_ROOT = Path(os.environ.get("HF_HOME", ".cache"))
31
+ DATASETS_CACHE = CACHE_ROOT / "datasets"
32
+ MODELS_CACHE = CACHE_ROOT / "models"
33
+ VOTES_CACHE = CACHE_ROOT / "votes"
34
+ EVAL_CACHE = CACHE_ROOT / "eval-queue"
35
+
36
+ # Repository configuration
37
+ QUEUE_REPO = f"{HF_ORGANIZATION}/requests"
38
+ EVAL_REQUESTS_PATH = EVAL_CACHE / "eval_requests.jsonl"
backend/app/config/hf_config.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ from typing import Optional
4
+ from huggingface_hub import HfApi
5
+ from pathlib import Path
6
+ from app.core.cache import cache_config
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # Organization or user who owns the datasets
11
+ HF_ORGANIZATION = "open-llm-leaderboard"
12
+
13
+ # Get HF token directly from environment
14
+ HF_TOKEN = os.environ.get("HF_TOKEN")
15
+ if not HF_TOKEN:
16
+ logger.warning("HF_TOKEN not found in environment variables. Some features may be limited.")
17
+
18
+ # Initialize HF API
19
+ API = HfApi(token=HF_TOKEN)
20
+
21
+ # Repository configuration
22
+ QUEUE_REPO = f"{HF_ORGANIZATION}/requests"
23
+ AGGREGATED_REPO = f"{HF_ORGANIZATION}/contents"
24
+ VOTES_REPO = f"{HF_ORGANIZATION}/votes"
25
+ OFFICIAL_PROVIDERS_REPO = f"{HF_ORGANIZATION}/official-providers"
26
+
27
+ # File paths from cache config
28
+ VOTES_PATH = cache_config.votes_file
29
+ EVAL_REQUESTS_PATH = cache_config.eval_requests_file
30
+ MODEL_CACHE_DIR = cache_config.models_cache
backend/app/config/logging_config.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import sys
3
+ from tqdm import tqdm
4
+
5
+ def get_tqdm_handler():
6
+ """
7
+ Creates a special handler for tqdm that doesn't interfere with other logs.
8
+ """
9
+ class TqdmLoggingHandler(logging.Handler):
10
+ def emit(self, record):
11
+ try:
12
+ msg = self.format(record)
13
+ tqdm.write(msg)
14
+ self.flush()
15
+ except Exception:
16
+ self.handleError(record)
17
+
18
+ return TqdmLoggingHandler()
19
+
20
+ def setup_service_logger(service_name: str) -> logging.Logger:
21
+ """
22
+ Configure a specific logger for a given service.
23
+ """
24
+ logger = logging.getLogger(f"app.services.{service_name}")
25
+
26
+ # If the logger already has handlers, don't reconfigure it
27
+ if logger.handlers:
28
+ return logger
29
+
30
+ # Add tqdm handler for this service
31
+ tqdm_handler = get_tqdm_handler()
32
+ tqdm_handler.setFormatter(logging.Formatter('%(name)s - %(levelname)s - %(message)s'))
33
+ logger.addHandler(tqdm_handler)
34
+
35
+ # Don't propagate logs to parent loggers
36
+ logger.propagate = False
37
+
38
+ return logger
backend/app/core/cache.py ADDED
@@ -0,0 +1,109 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ from pathlib import Path
4
+ from datetime import timedelta
5
+ import logging
6
+ from app.core.formatting import LogFormatter
7
+ from app.config.base import (
8
+ CACHE_ROOT,
9
+ DATASETS_CACHE,
10
+ MODELS_CACHE,
11
+ VOTES_CACHE,
12
+ EVAL_CACHE,
13
+ CACHE_TTL
14
+ )
15
+
16
+ logger = logging.getLogger(__name__)
17
+
18
+ class CacheConfig:
19
+ def __init__(self):
20
+ # Get cache paths from config
21
+ self.cache_root = CACHE_ROOT
22
+ self.datasets_cache = DATASETS_CACHE
23
+ self.models_cache = MODELS_CACHE
24
+ self.votes_cache = VOTES_CACHE
25
+ self.eval_cache = EVAL_CACHE
26
+
27
+ # Specific files
28
+ self.votes_file = self.votes_cache / "votes_data.jsonl"
29
+ self.eval_requests_file = self.eval_cache / "eval_requests.jsonl"
30
+
31
+ # Cache TTL
32
+ self.cache_ttl = timedelta(seconds=CACHE_TTL)
33
+
34
+ self._initialize_cache_dirs()
35
+ self._setup_environment()
36
+
37
+ def _initialize_cache_dirs(self):
38
+ """Initialize all necessary cache directories"""
39
+ try:
40
+ logger.info(LogFormatter.section("CACHE INITIALIZATION"))
41
+
42
+ cache_dirs = {
43
+ "Root": self.cache_root,
44
+ "Datasets": self.datasets_cache,
45
+ "Models": self.models_cache,
46
+ "Votes": self.votes_cache,
47
+ "Eval": self.eval_cache
48
+ }
49
+
50
+ for name, cache_dir in cache_dirs.items():
51
+ cache_dir.mkdir(parents=True, exist_ok=True)
52
+ logger.info(LogFormatter.success(f"{name} cache directory: {cache_dir}"))
53
+
54
+ except Exception as e:
55
+ logger.error(LogFormatter.error("Failed to create cache directories", e))
56
+ raise
57
+
58
+ def _setup_environment(self):
59
+ """Configure HuggingFace environment variables"""
60
+ logger.info(LogFormatter.subsection("ENVIRONMENT SETUP"))
61
+
62
+ env_vars = {
63
+ "HF_HOME": str(self.cache_root),
64
+ "HF_DATASETS_CACHE": str(self.datasets_cache)
65
+ }
66
+
67
+ for var, value in env_vars.items():
68
+ os.environ[var] = value
69
+ logger.info(LogFormatter.info(f"Set {var}={value}"))
70
+
71
+
72
+ def get_cache_path(self, cache_type: str) -> Path:
73
+ """Returns the path for a specific cache type"""
74
+ cache_paths = {
75
+ "datasets": self.datasets_cache,
76
+ "models": self.models_cache,
77
+ "votes": self.votes_cache,
78
+ "eval": self.eval_cache
79
+ }
80
+ return cache_paths.get(cache_type, self.cache_root)
81
+
82
+ def flush_cache(self, cache_type: str = None):
83
+ """Flush specified cache or all caches if no type is specified"""
84
+ try:
85
+ if cache_type:
86
+ logger.info(LogFormatter.section(f"FLUSHING {cache_type.upper()} CACHE"))
87
+ cache_dir = self.get_cache_path(cache_type)
88
+ if cache_dir.exists():
89
+ stats = {
90
+ "Cache_Type": cache_type,
91
+ "Directory": str(cache_dir)
92
+ }
93
+ for line in LogFormatter.tree(stats, "Cache Details"):
94
+ logger.info(line)
95
+ shutil.rmtree(cache_dir)
96
+ cache_dir.mkdir(parents=True, exist_ok=True)
97
+ logger.info(LogFormatter.success("Cache cleared successfully"))
98
+ else:
99
+ logger.info(LogFormatter.section("FLUSHING ALL CACHES"))
100
+ for cache_type in ["datasets", "models", "votes", "eval"]:
101
+ self.flush_cache(cache_type)
102
+ logger.info(LogFormatter.success("All caches cleared successfully"))
103
+
104
+ except Exception as e:
105
+ logger.error(LogFormatter.error("Failed to flush cache", e))
106
+ raise
107
+
108
+ # Singleton instance of cache configuration
109
+ cache_config = CacheConfig()
backend/app/core/fastapi_cache.py ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi_cache import FastAPICache
2
+ from fastapi_cache.backends.inmemory import InMemoryBackend
3
+ from fastapi_cache.decorator import cache
4
+ from datetime import timedelta
5
+ from app.config import CACHE_TTL
6
+ import logging
7
+ from app.core.formatting import LogFormatter
8
+ from typing import Optional, Any
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class CustomInMemoryBackend(InMemoryBackend):
13
+ def __init__(self):
14
+ """Initialize the cache backend"""
15
+ super().__init__()
16
+ self.cache = {}
17
+
18
+ async def delete(self, key: str) -> bool:
19
+ """Delete a key from the cache"""
20
+ try:
21
+ if key in self.cache:
22
+ del self.cache[key]
23
+ return True
24
+ return False
25
+ except Exception as e:
26
+ logger.error(LogFormatter.error(f"Failed to delete key {key} from cache", e))
27
+ return False
28
+
29
+ async def get(self, key: str) -> Any:
30
+ """Get a value from the cache"""
31
+ return self.cache.get(key)
32
+
33
+ async def set(self, key: str, value: Any, expire: Optional[int] = None) -> None:
34
+ """Set a value in the cache"""
35
+ self.cache[key] = value
36
+
37
+ def setup_cache():
38
+ """Initialize FastAPI Cache with in-memory backend"""
39
+ try:
40
+ logger.info(LogFormatter.section("CACHE INITIALIZATION"))
41
+ FastAPICache.init(
42
+ backend=CustomInMemoryBackend(),
43
+ prefix="fastapi-cache"
44
+ )
45
+ logger.info(LogFormatter.success("Cache initialized successfully"))
46
+ except Exception as e:
47
+ logger.error(LogFormatter.error("Failed to initialize cache", e))
48
+ raise
49
+
50
+ async def invalidate_cache_key(key: str):
51
+ """Invalidate a specific cache key"""
52
+ try:
53
+ backend = FastAPICache.get_backend()
54
+ if hasattr(backend, 'delete'):
55
+ await backend.delete(key)
56
+ logger.info(LogFormatter.success(f"Cache invalidated for key: {key}"))
57
+ else:
58
+ logger.warning(LogFormatter.warning("Cache backend does not support deletion"))
59
+ except Exception as e:
60
+ logger.error(LogFormatter.error(f"Failed to invalidate cache key: {key}", e))
61
+
62
+ def build_cache_key(*args) -> str:
63
+ """Build a cache key from multiple arguments"""
64
+ return ":".join(str(arg) for arg in args if arg is not None)
65
+
66
+ def cached(expire: int = CACHE_TTL, key_builder=None):
67
+ """Decorator for caching endpoint responses
68
+
69
+ Args:
70
+ expire (int): Cache TTL in seconds
71
+ key_builder (callable, optional): Custom key builder function
72
+ """
73
+ return cache(
74
+ expire=expire,
75
+ key_builder=key_builder
76
+ )
backend/app/core/formatting.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Dict, Any, List, Optional
3
+
4
+ logger = logging.getLogger(__name__)
5
+
6
+ class LogFormatter:
7
+ """Utility class for consistent log formatting across the application"""
8
+
9
+ @staticmethod
10
+ def section(title: str) -> str:
11
+ """Create a section header"""
12
+ return f"\n{'='*20} {title.upper()} {'='*20}"
13
+
14
+ @staticmethod
15
+ def subsection(title: str) -> str:
16
+ """Create a subsection header"""
17
+ return f"\n{'─'*20} {title} {'─'*20}"
18
+
19
+ @staticmethod
20
+ def tree(items: Dict[str, Any], title: str = None) -> List[str]:
21
+ """Create a tree view of dictionary data"""
22
+ lines = []
23
+ if title:
24
+ lines.append(f"📊 {title}:")
25
+
26
+ # Get the maximum length for alignment
27
+ max_key_length = max(len(str(k)) for k in items.keys())
28
+
29
+ # Format each item
30
+ for i, (key, value) in enumerate(items.items()):
31
+ prefix = "└──" if i == len(items) - 1 else "├──"
32
+ if isinstance(value, (int, float)):
33
+ value = f"{value:,}" # Add thousand separators
34
+ lines.append(f"{prefix} {str(key):<{max_key_length}}: {value}")
35
+
36
+ return lines
37
+
38
+ @staticmethod
39
+ def stats(stats: Dict[str, int], title: str = None) -> List[str]:
40
+ """Format statistics with icons"""
41
+ lines = []
42
+ if title:
43
+ lines.append(f"📊 {title}:")
44
+
45
+ # Get the maximum length for alignment
46
+ max_key_length = max(len(str(k)) for k in stats.keys())
47
+
48
+ # Format each stat with an appropriate icon
49
+ icons = {
50
+ "total": "📌",
51
+ "success": "✅",
52
+ "error": "❌",
53
+ "pending": "⏳",
54
+ "processing": "⚙️",
55
+ "finished": "✨",
56
+ "evaluating": "🔄",
57
+ "downloads": "⬇️",
58
+ "files": "📁",
59
+ "cached": "💾",
60
+ "size": "📏",
61
+ "time": "⏱️",
62
+ "rate": "🚀"
63
+ }
64
+
65
+ # Format each item
66
+ for i, (key, value) in enumerate(stats.items()):
67
+ prefix = "└──" if i == len(stats) - 1 else "├──"
68
+ icon = icons.get(key.lower().split('_')[0], "•")
69
+ if isinstance(value, (int, float)):
70
+ value = f"{value:,}" # Add thousand separators
71
+ lines.append(f"{prefix} {icon} {str(key):<{max_key_length}}: {value}")
72
+
73
+ return lines
74
+
75
+ @staticmethod
76
+ def progress_bar(current: int, total: int, width: int = 20) -> str:
77
+ """Create a progress bar"""
78
+ percentage = (current * 100) // total
79
+ filled = "█" * (percentage * width // 100)
80
+ empty = "░" * (width - len(filled))
81
+ return f"{filled}{empty} {percentage:3d}%"
82
+
83
+ @staticmethod
84
+ def error(message: str, error: Optional[Exception] = None) -> str:
85
+ """Format error message"""
86
+ error_msg = f"\n❌ Error: {message}"
87
+ if error:
88
+ error_msg += f"\n └── Details: {str(error)}"
89
+ return error_msg
90
+
91
+ @staticmethod
92
+ def success(message: str) -> str:
93
+ """Format success message"""
94
+ return f"✅ {message}"
95
+
96
+ @staticmethod
97
+ def warning(message: str) -> str:
98
+ """Format warning message"""
99
+ return f"⚠️ {message}"
100
+
101
+ @staticmethod
102
+ def info(message: str) -> str:
103
+ """Format info message"""
104
+ return f"ℹ️ {message}"
backend/app/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI
2
+ from app.config.logging_config import setup_logging
3
+ import logging
4
+
5
+ # Initialize logging configuration
6
+ setup_logging()
7
+ logger = logging.getLogger(__name__)
8
+
9
+ app = FastAPI(title="Open LLM Leaderboard API")
10
+
11
+ @app.on_event("startup")
12
+ async def startup_event():
13
+ logger.info("Starting up the application...")
14
+
15
+ # Import and include routers after app initialization
16
+ from app.api import models, votes
17
+ app.include_router(models.router, prefix="/api", tags=["models"])
18
+ app.include_router(votes.router, prefix="/api", tags=["votes"])
backend/app/services/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from . import hf_service, leaderboard, votes, models
2
+
3
+ __all__ = ["hf_service", "leaderboard", "votes", "models"]
backend/app/services/hf_service.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Optional
2
+ from huggingface_hub import HfApi
3
+ from app.config import HF_TOKEN, API
4
+ from app.core.cache import cache_config
5
+ from app.core.formatting import LogFormatter
6
+ import logging
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ class HuggingFaceService:
11
+ def __init__(self):
12
+ self.api = API
13
+ self.token = HF_TOKEN
14
+ self.cache_dir = cache_config.models_cache
15
+
16
+ async def check_authentication(self) -> bool:
17
+ """Check if the HF token is valid"""
18
+ if not self.token:
19
+ return False
20
+ try:
21
+ logger.info(LogFormatter.info("Checking HF token validity..."))
22
+ self.api.get_token_permission()
23
+ logger.info(LogFormatter.success("HF token is valid"))
24
+ return True
25
+ except Exception as e:
26
+ logger.error(LogFormatter.error("HF token validation failed", e))
27
+ return False
28
+
29
+ async def get_user_info(self) -> Optional[dict]:
30
+ """Get information about the authenticated user"""
31
+ try:
32
+ logger.info(LogFormatter.info("Fetching user information..."))
33
+ info = self.api.get_token_permission()
34
+ logger.info(LogFormatter.success(f"User info retrieved for: {info.get('user', 'Unknown')}"))
35
+ return info
36
+ except Exception as e:
37
+ logger.error(LogFormatter.error("Failed to get user info", e))
38
+ return None
39
+
40
+ def _log_repo_operation(self, operation: str, repo: str, details: str = None):
41
+ """Helper to log repository operations"""
42
+ logger.info(LogFormatter.section(f"HF REPOSITORY OPERATION - {operation.upper()}"))
43
+ stats = {
44
+ "Operation": operation,
45
+ "Repository": repo,
46
+ }
47
+ if details:
48
+ stats["Details"] = details
49
+ for line in LogFormatter.tree(stats):
50
+ logger.info(line)
backend/app/services/leaderboard.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.core.cache import cache_config
2
+ from datetime import datetime
3
+ from typing import List, Dict, Any
4
+ import datasets
5
+ from fastapi import HTTPException
6
+ import logging
7
+ from app.config.base import HF_ORGANIZATION
8
+ from app.core.formatting import LogFormatter
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class LeaderboardService:
13
+ def __init__(self):
14
+ pass
15
+
16
+ async def fetch_raw_data(self) -> List[Dict[str, Any]]:
17
+ """Fetch raw leaderboard data from HuggingFace dataset"""
18
+ try:
19
+ logger.info(LogFormatter.section("FETCHING LEADERBOARD DATA"))
20
+ logger.info(LogFormatter.info(f"Loading dataset from {HF_ORGANIZATION}/contents"))
21
+
22
+ dataset = datasets.load_dataset(
23
+ f"{HF_ORGANIZATION}/contents",
24
+ cache_dir=cache_config.get_cache_path("datasets")
25
+ )["train"]
26
+
27
+ df = dataset.to_pandas()
28
+ data = df.to_dict('records')
29
+
30
+ stats = {
31
+ "Total_Entries": len(data),
32
+ "Dataset_Size": f"{df.memory_usage(deep=True).sum() / 1024 / 1024:.1f}MB"
33
+ }
34
+ for line in LogFormatter.stats(stats, "Dataset Statistics"):
35
+ logger.info(line)
36
+
37
+ return data
38
+
39
+ except Exception as e:
40
+ logger.error(LogFormatter.error("Failed to fetch leaderboard data", e))
41
+ raise HTTPException(status_code=500, detail=str(e))
42
+
43
+ async def get_formatted_data(self) -> List[Dict[str, Any]]:
44
+ """Get formatted leaderboard data"""
45
+ try:
46
+ logger.info(LogFormatter.section("FORMATTING LEADERBOARD DATA"))
47
+
48
+ raw_data = await self.fetch_raw_data()
49
+ formatted_data = []
50
+ type_counts = {}
51
+ error_count = 0
52
+
53
+ # Initialize progress tracking
54
+ total_items = len(raw_data)
55
+ logger.info(LogFormatter.info(f"Processing {total_items:,} entries..."))
56
+
57
+ for i, item in enumerate(raw_data, 1):
58
+ try:
59
+ formatted_item = await self.transform_data(item)
60
+ formatted_data.append(formatted_item)
61
+
62
+ # Count model types
63
+ model_type = formatted_item["model"]["type"]
64
+ type_counts[model_type] = type_counts.get(model_type, 0) + 1
65
+
66
+ except Exception as e:
67
+ error_count += 1
68
+ logger.error(LogFormatter.error(f"Failed to format entry {i}/{total_items}", e))
69
+ continue
70
+
71
+ # Log progress every 10%
72
+ if i % max(1, total_items // 10) == 0:
73
+ progress = (i / total_items) * 100
74
+ logger.info(LogFormatter.info(f"Progress: {LogFormatter.progress_bar(i, total_items)}"))
75
+
76
+ # Log final statistics
77
+ stats = {
78
+ "Total_Processed": total_items,
79
+ "Successful": len(formatted_data),
80
+ "Failed": error_count
81
+ }
82
+ logger.info(LogFormatter.section("PROCESSING SUMMARY"))
83
+ for line in LogFormatter.stats(stats, "Processing Statistics"):
84
+ logger.info(line)
85
+
86
+ # Log model type distribution
87
+ type_stats = {f"Type_{k}": v for k, v in type_counts.items()}
88
+ logger.info(LogFormatter.subsection("MODEL TYPE DISTRIBUTION"))
89
+ for line in LogFormatter.stats(type_stats):
90
+ logger.info(line)
91
+
92
+ return formatted_data
93
+
94
+ except Exception as e:
95
+ logger.error(LogFormatter.error("Failed to format leaderboard data", e))
96
+ raise HTTPException(status_code=500, detail=str(e))
97
+
98
+ async def transform_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
99
+ """Transform raw data into the format expected by the frontend"""
100
+ try:
101
+ # Extract model name for logging
102
+ model_name = data.get("fullname", "Unknown")
103
+ logger.debug(LogFormatter.info(f"Transforming data for model: {model_name}"))
104
+
105
+ # Create unique ID combining model name, precision, sha and chat template status
106
+ unique_id = f"{data.get('fullname', 'Unknown')}_{data.get('Precision', 'Unknown')}_{data.get('Model sha', 'Unknown')}_{str(data.get('Chat Template', False))}"
107
+
108
+ evaluations = {
109
+ "ifeval": {
110
+ "name": "IFEval",
111
+ "value": data.get("IFEval Raw", 0),
112
+ "normalized_score": data.get("IFEval", 0)
113
+ },
114
+ "bbh": {
115
+ "name": "BBH",
116
+ "value": data.get("BBH Raw", 0),
117
+ "normalized_score": data.get("BBH", 0)
118
+ },
119
+ "math": {
120
+ "name": "MATH Level 5",
121
+ "value": data.get("MATH Lvl 5 Raw", 0),
122
+ "normalized_score": data.get("MATH Lvl 5", 0)
123
+ },
124
+ "gpqa": {
125
+ "name": "GPQA",
126
+ "value": data.get("GPQA Raw", 0),
127
+ "normalized_score": data.get("GPQA", 0)
128
+ },
129
+ "musr": {
130
+ "name": "MUSR",
131
+ "value": data.get("MUSR Raw", 0),
132
+ "normalized_score": data.get("MUSR", 0)
133
+ },
134
+ "mmlu_pro": {
135
+ "name": "MMLU-PRO",
136
+ "value": data.get("MMLU-PRO Raw", 0),
137
+ "normalized_score": data.get("MMLU-PRO", 0)
138
+ }
139
+ }
140
+
141
+ features = {
142
+ "is_not_available_on_hub": data.get("Available on the hub", False),
143
+ "is_merged": data.get("Merged", False),
144
+ "is_moe": data.get("MoE", False),
145
+ "is_flagged": data.get("Flagged", False),
146
+ "is_official_provider": data.get("Official Providers", False)
147
+ }
148
+
149
+ metadata = {
150
+ "upload_date": data.get("Upload To Hub Date"),
151
+ "submission_date": data.get("Submission Date"),
152
+ "generation": data.get("Generation"),
153
+ "base_model": data.get("Base Model"),
154
+ "hub_license": data.get("Hub License"),
155
+ "hub_hearts": data.get("Hub ❤️"),
156
+ "params_billions": data.get("#Params (B)"),
157
+ "co2_cost": data.get("CO₂ cost (kg)", 0)
158
+ }
159
+
160
+ # Clean model type by removing emojis if present
161
+ original_type = data.get("Type", "")
162
+ model_type = original_type.lower().strip()
163
+
164
+ # Remove emojis and parentheses
165
+ if "(" in model_type:
166
+ model_type = model_type.split("(")[0].strip()
167
+ model_type = ''.join(c for c in model_type if not c in '🔶🟢🟩💬🤝🌸 ')
168
+
169
+ # Map old model types to new ones
170
+ model_type_mapping = {
171
+ "fine-tuned": "fined-tuned-on-domain-specific-dataset",
172
+ "fine tuned": "fined-tuned-on-domain-specific-dataset",
173
+ "finetuned": "fined-tuned-on-domain-specific-dataset",
174
+ "fine_tuned": "fined-tuned-on-domain-specific-dataset",
175
+ "ft": "fined-tuned-on-domain-specific-dataset",
176
+ "finetuning": "fined-tuned-on-domain-specific-dataset",
177
+ "fine tuning": "fined-tuned-on-domain-specific-dataset",
178
+ "fine-tuning": "fined-tuned-on-domain-specific-dataset"
179
+ }
180
+
181
+ mapped_type = model_type_mapping.get(model_type.lower().strip(), model_type)
182
+
183
+ if mapped_type != model_type:
184
+ logger.debug(LogFormatter.info(f"Model type mapped: {original_type} -> {mapped_type}"))
185
+
186
+ transformed_data = {
187
+ "id": unique_id,
188
+ "model": {
189
+ "name": data.get("fullname"),
190
+ "sha": data.get("Model sha"),
191
+ "precision": data.get("Precision"),
192
+ "type": mapped_type,
193
+ "weight_type": data.get("Weight type"),
194
+ "architecture": data.get("Architecture"),
195
+ "average_score": data.get("Average ⬆️"),
196
+ "has_chat_template": data.get("Chat Template", False)
197
+ },
198
+ "evaluations": evaluations,
199
+ "features": features,
200
+ "metadata": metadata
201
+ }
202
+
203
+ logger.debug(LogFormatter.success(f"Successfully transformed data for {model_name}"))
204
+ return transformed_data
205
+
206
+ except Exception as e:
207
+ logger.error(LogFormatter.error(f"Failed to transform data for {data.get('fullname', 'Unknown')}", e))
208
+ raise
backend/app/services/models.py ADDED
@@ -0,0 +1,668 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timezone, timedelta
2
+ from typing import Dict, Any, Optional, List
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ import logging
7
+ import aiohttp
8
+ import asyncio
9
+ import time
10
+ from huggingface_hub import HfApi, CommitOperationAdd
11
+ from huggingface_hub.utils import build_hf_headers
12
+ from datasets import disable_progress_bar
13
+ import sys
14
+ import contextlib
15
+ from concurrent.futures import ThreadPoolExecutor
16
+ import tempfile
17
+
18
+ from app.config import (
19
+ QUEUE_REPO,
20
+ HF_TOKEN,
21
+ EVAL_REQUESTS_PATH
22
+ )
23
+ from app.config.hf_config import HF_ORGANIZATION
24
+ from app.services.hf_service import HuggingFaceService
25
+ from app.utils.model_validation import ModelValidator
26
+ from app.services.votes import VoteService
27
+ from app.core.cache import cache_config
28
+ from app.core.formatting import LogFormatter
29
+
30
+ # Disable datasets progress bars globally
31
+ disable_progress_bar()
32
+
33
+ logger = logging.getLogger(__name__)
34
+
35
+ # Context manager to temporarily disable stdout and stderr
36
+ @contextlib.contextmanager
37
+ def suppress_output():
38
+ stdout = sys.stdout
39
+ stderr = sys.stderr
40
+ devnull = open(os.devnull, 'w')
41
+ try:
42
+ sys.stdout = devnull
43
+ sys.stderr = devnull
44
+ yield
45
+ finally:
46
+ sys.stdout = stdout
47
+ sys.stderr = stderr
48
+ devnull.close()
49
+
50
+ class ProgressTracker:
51
+ def __init__(self, total: int, desc: str = "Progress", update_frequency: int = 10):
52
+ self.total = total
53
+ self.current = 0
54
+ self.desc = desc
55
+ self.start_time = time.time()
56
+ self.update_frequency = update_frequency # Percentage steps
57
+ self.last_update = -1
58
+
59
+ # Initial log with fancy formatting
60
+ logger.info(LogFormatter.section(desc))
61
+ logger.info(LogFormatter.info(f"Starting processing of {total:,} items..."))
62
+ sys.stdout.flush()
63
+
64
+ def update(self, n: int = 1):
65
+ self.current += n
66
+ current_percentage = (self.current * 100) // self.total
67
+
68
+ # Only update on frequency steps (e.g., 0%, 10%, 20%, etc.)
69
+ if current_percentage >= self.last_update + self.update_frequency or current_percentage == 100:
70
+ elapsed = time.time() - self.start_time
71
+ rate = self.current / elapsed if elapsed > 0 else 0
72
+ remaining = (self.total - self.current) / rate if rate > 0 else 0
73
+
74
+ # Create progress stats
75
+ stats = {
76
+ "Progress": LogFormatter.progress_bar(self.current, self.total),
77
+ "Items": f"{self.current:,}/{self.total:,}",
78
+ "Time": f"⏱️ {elapsed:.1f}s elapsed, {remaining:.1f}s remaining",
79
+ "Rate": f"🚀 {rate:.1f} items/s"
80
+ }
81
+
82
+ # Log progress using tree format
83
+ for line in LogFormatter.tree(stats):
84
+ logger.info(line)
85
+ sys.stdout.flush()
86
+
87
+ self.last_update = (current_percentage // self.update_frequency) * self.update_frequency
88
+
89
+ def close(self):
90
+ elapsed = time.time() - self.start_time
91
+ rate = self.total / elapsed if elapsed > 0 else 0
92
+
93
+ # Final summary with fancy formatting
94
+ logger.info(LogFormatter.section("COMPLETED"))
95
+ stats = {
96
+ "Total": f"{self.total:,} items",
97
+ "Time": f"{elapsed:.1f}s",
98
+ "Rate": f"{rate:.1f} items/s"
99
+ }
100
+ for line in LogFormatter.stats(stats):
101
+ logger.info(line)
102
+ logger.info("="*50)
103
+ sys.stdout.flush()
104
+
105
+ class ModelService(HuggingFaceService):
106
+ _instance: Optional['ModelService'] = None
107
+ _initialized = False
108
+
109
+ def __new__(cls):
110
+ if cls._instance is None:
111
+ logger.info(LogFormatter.info("Creating new ModelService instance"))
112
+ cls._instance = super(ModelService, cls).__new__(cls)
113
+ return cls._instance
114
+
115
+ def __init__(self):
116
+ if not hasattr(self, '_init_done'):
117
+ logger.info(LogFormatter.section("MODEL SERVICE INITIALIZATION"))
118
+ super().__init__()
119
+ self.validator = ModelValidator()
120
+ self.vote_service = VoteService()
121
+ self.eval_requests_path = cache_config.eval_requests_file
122
+ logger.info(LogFormatter.info(f"Using eval requests path: {self.eval_requests_path}"))
123
+
124
+ self.eval_requests_path.parent.mkdir(parents=True, exist_ok=True)
125
+ self.hf_api = HfApi(token=HF_TOKEN)
126
+ self.cached_models = None
127
+ self.last_cache_update = 0
128
+ self.cache_ttl = cache_config.cache_ttl.total_seconds()
129
+ self._init_done = True
130
+ logger.info(LogFormatter.success("Initialization complete"))
131
+
132
+ async def _download_and_process_file(self, file: str, session: aiohttp.ClientSession, progress: ProgressTracker) -> Optional[Dict]:
133
+ """Download and process a file asynchronously"""
134
+ try:
135
+ # Build file URL
136
+ url = f"https://huggingface.co/datasets/{QUEUE_REPO}/resolve/main/{file}"
137
+ headers = build_hf_headers(token=self.token)
138
+
139
+ # Download file
140
+ async with session.get(url, headers=headers) as response:
141
+ if response.status != 200:
142
+ logger.error(LogFormatter.error(f"Failed to download {file}", f"HTTP {response.status}"))
143
+ progress.update()
144
+ return None
145
+
146
+ try:
147
+ # First read content as text
148
+ text_content = await response.text()
149
+ # Then parse JSON
150
+ content = json.loads(text_content)
151
+ except json.JSONDecodeError as e:
152
+ logger.error(LogFormatter.error(f"Failed to decode JSON from {file}", e))
153
+ progress.update()
154
+ return None
155
+
156
+ # Get status and determine target status
157
+ status = content.get("status", "PENDING").upper()
158
+ target_status = None
159
+ status_map = {
160
+ "PENDING": ["PENDING"],
161
+ "EVALUATING": ["RUNNING"],
162
+ "FINISHED": ["FINISHED"]
163
+ }
164
+
165
+ for target, source_statuses in status_map.items():
166
+ if status in source_statuses:
167
+ target_status = target
168
+ break
169
+
170
+ if not target_status:
171
+ progress.update()
172
+ return None
173
+
174
+ # Calculate wait time
175
+ try:
176
+ submit_time = datetime.fromisoformat(content["submitted_time"].replace("Z", "+00:00"))
177
+ if submit_time.tzinfo is None:
178
+ submit_time = submit_time.replace(tzinfo=timezone.utc)
179
+ current_time = datetime.now(timezone.utc)
180
+ wait_time = current_time - submit_time
181
+
182
+ model_info = {
183
+ "name": content["model"],
184
+ "submitter": content.get("sender", "Unknown"),
185
+ "revision": content["revision"],
186
+ "wait_time": f"{wait_time.total_seconds():.1f}s",
187
+ "submission_time": content["submitted_time"],
188
+ "status": target_status,
189
+ "precision": content.get("precision", "Unknown")
190
+ }
191
+
192
+ progress.update()
193
+ return model_info
194
+
195
+ except (ValueError, TypeError) as e:
196
+ logger.error(LogFormatter.error(f"Failed to process {file}", e))
197
+ progress.update()
198
+ return None
199
+
200
+ except Exception as e:
201
+ logger.error(LogFormatter.error(f"Failed to load {file}", e))
202
+ progress.update()
203
+ return None
204
+
205
+ async def _refresh_models_cache(self):
206
+ """Refresh the models cache"""
207
+ try:
208
+ logger.info(LogFormatter.section("CACHE REFRESH"))
209
+ self._log_repo_operation("read", f"{HF_ORGANIZATION}/requests", "Refreshing models cache")
210
+
211
+ # Initialize models dictionary
212
+ models = {
213
+ "finished": [],
214
+ "evaluating": [],
215
+ "pending": []
216
+ }
217
+
218
+ try:
219
+ logger.info(LogFormatter.subsection("DATASET LOADING"))
220
+ logger.info(LogFormatter.info("Loading dataset..."))
221
+
222
+ # Download entire dataset snapshot
223
+ with suppress_output():
224
+ local_dir = self.hf_api.snapshot_download(
225
+ repo_id=QUEUE_REPO,
226
+ repo_type="dataset",
227
+ token=self.token
228
+ )
229
+
230
+ # List JSON files in local directory
231
+ local_path = Path(local_dir)
232
+ json_files = list(local_path.glob("**/*.json"))
233
+ total_files = len(json_files)
234
+
235
+ # Log repository stats
236
+ stats = {
237
+ "Total_Files": total_files,
238
+ "Local_Dir": str(local_path),
239
+ }
240
+ for line in LogFormatter.stats(stats, "Repository Statistics"):
241
+ logger.info(line)
242
+
243
+ if not json_files:
244
+ raise Exception("No JSON files found in repository")
245
+
246
+ # Initialize progress tracker
247
+ progress = ProgressTracker(total_files, "PROCESSING FILES")
248
+
249
+ # Process local files
250
+ model_submissions = {} # Dict to track latest submission for each (model_id, revision, precision)
251
+ for file_path in json_files:
252
+ try:
253
+ with open(file_path, 'r') as f:
254
+ content = json.load(f)
255
+
256
+ # Get status and determine target status
257
+ status = content.get("status", "PENDING").upper()
258
+ target_status = None
259
+ status_map = {
260
+ "PENDING": ["PENDING"],
261
+ "EVALUATING": ["RUNNING"],
262
+ "FINISHED": ["FINISHED"]
263
+ }
264
+
265
+ for target, source_statuses in status_map.items():
266
+ if status in source_statuses:
267
+ target_status = target
268
+ break
269
+
270
+ if not target_status:
271
+ progress.update()
272
+ continue
273
+
274
+ # Calculate wait time
275
+ try:
276
+ submit_time = datetime.fromisoformat(content["submitted_time"].replace("Z", "+00:00"))
277
+ if submit_time.tzinfo is None:
278
+ submit_time = submit_time.replace(tzinfo=timezone.utc)
279
+ current_time = datetime.now(timezone.utc)
280
+ wait_time = current_time - submit_time
281
+
282
+ model_info = {
283
+ "name": content["model"],
284
+ "submitter": content.get("sender", "Unknown"),
285
+ "revision": content["revision"],
286
+ "wait_time": f"{wait_time.total_seconds():.1f}s",
287
+ "submission_time": content["submitted_time"],
288
+ "status": target_status,
289
+ "precision": content.get("precision", "Unknown")
290
+ }
291
+
292
+ # Use (model_id, revision, precision) as key to track latest submission
293
+ key = (content["model"], content["revision"], content.get("precision", "Unknown"))
294
+ if key not in model_submissions or submit_time > datetime.fromisoformat(model_submissions[key]["submission_time"].replace("Z", "+00:00")):
295
+ model_submissions[key] = model_info
296
+
297
+ except (ValueError, TypeError) as e:
298
+ logger.error(LogFormatter.error(f"Failed to process {file_path.name}", e))
299
+
300
+ except Exception as e:
301
+ logger.error(LogFormatter.error(f"Failed to load {file_path.name}", e))
302
+ finally:
303
+ progress.update()
304
+
305
+ # Populate models dict with deduplicated submissions
306
+ for model_info in model_submissions.values():
307
+ models[model_info["status"].lower()].append(model_info)
308
+
309
+ progress.close()
310
+
311
+ # Final summary with fancy formatting
312
+ logger.info(LogFormatter.section("CACHE SUMMARY"))
313
+ stats = {
314
+ "Finished": len(models["finished"]),
315
+ "Evaluating": len(models["evaluating"]),
316
+ "Pending": len(models["pending"])
317
+ }
318
+ for line in LogFormatter.stats(stats, "Models by Status"):
319
+ logger.info(line)
320
+ logger.info("="*50)
321
+
322
+ except Exception as e:
323
+ logger.error(LogFormatter.error("Error processing files", e))
324
+ raise
325
+
326
+ # Update cache
327
+ self.cached_models = models
328
+ self.last_cache_update = time.time()
329
+ logger.info(LogFormatter.success("Cache updated successfully"))
330
+
331
+ return models
332
+
333
+ except Exception as e:
334
+ logger.error(LogFormatter.error("Cache refresh failed", e))
335
+ raise
336
+
337
+ async def initialize(self):
338
+ """Initialize the model service"""
339
+ if self._initialized:
340
+ logger.info(LogFormatter.info("Service already initialized, using cached data"))
341
+ return
342
+
343
+ try:
344
+ logger.info(LogFormatter.section("MODEL SERVICE INITIALIZATION"))
345
+
346
+ # Check if cache already exists
347
+ cache_path = cache_config.get_cache_path("datasets")
348
+ if not cache_path.exists() or not any(cache_path.iterdir()):
349
+ logger.info(LogFormatter.info("No existing cache found, initializing datasets cache..."))
350
+ cache_config.flush_cache("datasets")
351
+ else:
352
+ logger.info(LogFormatter.info("Using existing datasets cache"))
353
+
354
+ # Ensure eval requests directory exists
355
+ self.eval_requests_path.parent.mkdir(parents=True, exist_ok=True)
356
+ logger.info(LogFormatter.info(f"Eval requests directory: {self.eval_requests_path}"))
357
+
358
+ # List existing files
359
+ if self.eval_requests_path.exists():
360
+ files = list(self.eval_requests_path.glob("**/*.json"))
361
+ stats = {
362
+ "Total_Files": len(files),
363
+ "Directory": str(self.eval_requests_path)
364
+ }
365
+ for line in LogFormatter.stats(stats, "Eval Requests"):
366
+ logger.info(line)
367
+
368
+ # Load initial cache
369
+ await self._refresh_models_cache()
370
+
371
+ self._initialized = True
372
+ logger.info(LogFormatter.success("Model service initialization complete"))
373
+
374
+ except Exception as e:
375
+ logger.error(LogFormatter.error("Initialization failed", e))
376
+ raise
377
+
378
+ async def get_models(self) -> Dict[str, List[Dict[str, Any]]]:
379
+ """Get all models with their status"""
380
+ if not self._initialized:
381
+ logger.info(LogFormatter.info("Service not initialized, initializing now..."))
382
+ await self.initialize()
383
+
384
+ current_time = time.time()
385
+ cache_age = current_time - self.last_cache_update
386
+
387
+ # Check if cache needs refresh
388
+ if not self.cached_models:
389
+ logger.info(LogFormatter.info("No cached data available, refreshing cache..."))
390
+ return await self._refresh_models_cache()
391
+ elif cache_age > self.cache_ttl:
392
+ logger.info(LogFormatter.info(f"Cache expired ({cache_age:.1f}s old, TTL: {self.cache_ttl}s)"))
393
+ return await self._refresh_models_cache()
394
+ else:
395
+ logger.info(LogFormatter.info(f"Using cached data ({cache_age:.1f}s old)"))
396
+ return self.cached_models
397
+
398
+ async def submit_model(
399
+ self,
400
+ model_data: Dict[str, Any],
401
+ user_id: str
402
+ ) -> Dict[str, Any]:
403
+ logger.info(LogFormatter.section("MODEL SUBMISSION"))
404
+ self._log_repo_operation("write", f"{HF_ORGANIZATION}/requests", f"Submitting model {model_data['model_id']} by {user_id}")
405
+ stats = {
406
+ "Model": model_data["model_id"],
407
+ "User": user_id,
408
+ "Revision": model_data["revision"],
409
+ "Precision": model_data["precision"],
410
+ "Type": model_data["model_type"]
411
+ }
412
+ for line in LogFormatter.tree(stats, "Submission Details"):
413
+ logger.info(line)
414
+
415
+ # Validate required fields
416
+ required_fields = [
417
+ "model_id", "base_model", "revision", "precision",
418
+ "weight_type", "model_type", "use_chat_template"
419
+ ]
420
+ for field in required_fields:
421
+ if field not in model_data:
422
+ raise ValueError(f"Missing required field: {field}")
423
+
424
+ # Get model info and validate it exists on HuggingFace
425
+ try:
426
+ logger.info(LogFormatter.subsection("MODEL VALIDATION"))
427
+
428
+ # Get the model info to check if it exists
429
+ model_info = self.hf_api.model_info(
430
+ model_data["model_id"],
431
+ revision=model_data["revision"],
432
+ token=self.token
433
+ )
434
+
435
+ if not model_info:
436
+ raise Exception(f"Model {model_data['model_id']} not found on HuggingFace Hub")
437
+
438
+ logger.info(LogFormatter.success("Model exists on HuggingFace Hub"))
439
+
440
+ except Exception as e:
441
+ logger.error(LogFormatter.error("Model validation failed", e))
442
+ raise
443
+
444
+ # Update model revision with commit sha
445
+ model_data["revision"] = model_info.sha
446
+
447
+ # Check if model already exists in the system
448
+ try:
449
+ logger.info(LogFormatter.subsection("CHECKING EXISTING SUBMISSIONS"))
450
+ existing_models = await self.get_models()
451
+
452
+ # Call the official provider status check
453
+ is_valid, error_message = await self.validator.check_official_provider_status(
454
+ model_data["model_id"],
455
+ existing_models
456
+ )
457
+ if not is_valid:
458
+ raise ValueError(error_message)
459
+
460
+ # Check in all statuses (pending, evaluating, finished)
461
+ for status, models in existing_models.items():
462
+ for model in models:
463
+ if model["name"] == model_data["model_id"] and model["revision"] == model_data["revision"]:
464
+ error_msg = f"Model {model_data['model_id']} revision {model_data['revision']} is already in the system with status: {status}"
465
+ logger.error(LogFormatter.error("Submission rejected", error_msg))
466
+ raise ValueError(error_msg)
467
+
468
+ logger.info(LogFormatter.success("No existing submission found"))
469
+ except ValueError:
470
+ raise
471
+ except Exception as e:
472
+ logger.error(LogFormatter.error("Failed to check existing submissions", e))
473
+ raise
474
+
475
+ # Check that model on hub and valid
476
+ valid, error, model_config = await self.validator.is_model_on_hub(
477
+ model_data["model_id"],
478
+ model_data["revision"],
479
+ test_tokenizer=True
480
+ )
481
+ if not valid:
482
+ logger.error(LogFormatter.error("Model on hub validation failed", error))
483
+ raise Exception(error)
484
+ logger.info(LogFormatter.success("Model on hub validation passed"))
485
+
486
+ # Validate model card
487
+ valid, error, model_card = await self.validator.check_model_card(
488
+ model_data["model_id"]
489
+ )
490
+ if not valid:
491
+ logger.error(LogFormatter.error("Model card validation failed", error))
492
+ raise Exception(error)
493
+ logger.info(LogFormatter.success("Model card validation passed"))
494
+
495
+ # Check size limits
496
+ model_size, error = await self.validator.get_model_size(
497
+ model_info,
498
+ model_data["precision"],
499
+ model_data["base_model"],
500
+ revision=model_data["revision"]
501
+ )
502
+ if model_size is None:
503
+ logger.error(LogFormatter.error("Model size validation failed", error))
504
+ raise Exception(error)
505
+ logger.info(LogFormatter.success(f"Model size validation passed: {model_size:.1f}B"))
506
+
507
+ # Size limits based on precision
508
+ if model_data["precision"] in ["float16", "bfloat16"] and model_size > 100:
509
+ error_msg = f"Model too large for {model_data['precision']} (limit: 100B)"
510
+ logger.error(LogFormatter.error("Size limit exceeded", error_msg))
511
+ raise Exception(error_msg)
512
+
513
+ # Chat template validation if requested
514
+ if model_data["use_chat_template"]:
515
+ valid, error = await self.validator.check_chat_template(
516
+ model_data["model_id"],
517
+ model_data["revision"]
518
+ )
519
+ if not valid:
520
+ logger.error(LogFormatter.error("Chat template validation failed", error))
521
+ raise Exception(error)
522
+ logger.info(LogFormatter.success("Chat template validation passed"))
523
+
524
+
525
+ architectures = model_info.config.get("architectures", "")
526
+ if architectures:
527
+ architectures = ";".join(architectures)
528
+
529
+ # Create eval entry
530
+ eval_entry = {
531
+ "model": model_data["model_id"],
532
+ "base_model": model_data["base_model"],
533
+ "revision": model_info.sha,
534
+ "precision": model_data["precision"],
535
+ "params": model_size,
536
+ "architectures": architectures,
537
+ "weight_type": model_data["weight_type"],
538
+ "status": "PENDING",
539
+ "submitted_time": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
540
+ "model_type": model_data["model_type"],
541
+ "job_id": -1,
542
+ "job_start_time": None,
543
+ "use_chat_template": model_data["use_chat_template"],
544
+ "sender": user_id
545
+ }
546
+
547
+ logger.info(LogFormatter.subsection("EVALUATION ENTRY"))
548
+ for line in LogFormatter.tree(eval_entry):
549
+ logger.info(line)
550
+
551
+ # Upload to HF dataset
552
+ try:
553
+ logger.info(LogFormatter.subsection("UPLOADING TO HUGGINGFACE"))
554
+ logger.info(LogFormatter.info(f"Uploading to {HF_ORGANIZATION}/requests..."))
555
+
556
+ # Construct the path in the dataset
557
+ org_or_user = model_data["model_id"].split("/")[0] if "/" in model_data["model_id"] else ""
558
+ model_path = model_data["model_id"].split("/")[-1]
559
+ relative_path = f"{org_or_user}/{model_path}_eval_request_False_{model_data['precision']}_{model_data['weight_type']}.json"
560
+
561
+ # Create a temporary file with the request
562
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as temp_file:
563
+ json.dump(eval_entry, temp_file, indent=2)
564
+ temp_file.flush()
565
+ temp_path = temp_file.name
566
+
567
+ # Upload file directly
568
+ self.hf_api.upload_file(
569
+ path_or_fileobj=temp_path,
570
+ path_in_repo=relative_path,
571
+ repo_id=f"{HF_ORGANIZATION}/requests",
572
+ repo_type="dataset",
573
+ commit_message=f"Add {model_data['model_id']} to eval queue",
574
+ token=self.token
575
+ )
576
+
577
+ # Clean up temp file
578
+ os.unlink(temp_path)
579
+
580
+ logger.info(LogFormatter.success("Upload successful"))
581
+
582
+ except Exception as e:
583
+ logger.error(LogFormatter.error("Upload failed", e))
584
+ raise
585
+
586
+ # Add automatic vote
587
+ try:
588
+ logger.info(LogFormatter.subsection("AUTOMATIC VOTE"))
589
+ logger.info(LogFormatter.info(f"Adding upvote for {model_data['model_id']} by {user_id}"))
590
+ await self.vote_service.add_vote(
591
+ model_data["model_id"],
592
+ user_id,
593
+ "up",
594
+ {
595
+ "precision": model_data["precision"],
596
+ "revision": model_data["revision"]
597
+ }
598
+ )
599
+ logger.info(LogFormatter.success("Vote recorded successfully"))
600
+ except Exception as e:
601
+ logger.error(LogFormatter.error("Failed to record vote", e))
602
+ # Don't raise here as the main submission was successful
603
+
604
+ return {
605
+ "status": "success",
606
+ "message": "The model was submitted successfully, and the vote has been recorded"
607
+ }
608
+
609
+ async def get_model_status(self, model_id: str) -> Dict[str, Any]:
610
+ """Get evaluation status of a model"""
611
+ logger.info(LogFormatter.info(f"Checking status for model: {model_id}"))
612
+ eval_path = self.eval_requests_path
613
+
614
+ for user_folder in eval_path.iterdir():
615
+ if user_folder.is_dir():
616
+ for file in user_folder.glob("*.json"):
617
+ with open(file, "r") as f:
618
+ data = json.load(f)
619
+ if data["model"] == model_id:
620
+ status = {
621
+ "status": data["status"],
622
+ "submitted_time": data["submitted_time"],
623
+ "job_id": data.get("job_id", -1)
624
+ }
625
+ logger.info(LogFormatter.success("Status found"))
626
+ for line in LogFormatter.tree(status, "Model Status"):
627
+ logger.info(line)
628
+ return status
629
+
630
+ logger.warning(LogFormatter.warning(f"No status found for model: {model_id}"))
631
+ return {"status": "not_found"}
632
+
633
+ async def get_organization_submissions(self, organization: str, days: int = 7) -> List[Dict[str, Any]]:
634
+ """Get all submissions from a user in the last n days"""
635
+ try:
636
+ # Get all models
637
+ all_models = await self.get_models()
638
+ current_time = datetime.now(timezone.utc)
639
+ cutoff_time = current_time - timedelta(days=days)
640
+
641
+ # Filter models by submitter and submission time
642
+ user_submissions = []
643
+ for status, models in all_models.items():
644
+ for model in models:
645
+ # Check if model was submitted by the user
646
+ if model["submitter"] == organization:
647
+ # Parse submission time
648
+ submit_time = datetime.fromisoformat(
649
+ model["submission_time"].replace("Z", "+00:00")
650
+ )
651
+ # Check if within time window
652
+ if submit_time > cutoff_time:
653
+ user_submissions.append({
654
+ "name": model["name"],
655
+ "status": status,
656
+ "submission_time": model["submission_time"],
657
+ "precision": model["precision"]
658
+ })
659
+
660
+ return sorted(
661
+ user_submissions,
662
+ key=lambda x: x["submission_time"],
663
+ reverse=True
664
+ )
665
+
666
+ except Exception as e:
667
+ logger.error(LogFormatter.error(f"Failed to get submissions for {organization}", e))
668
+ raise
backend/app/services/rate_limiter.py ADDED
@@ -0,0 +1,72 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ import logging
3
+ from datetime import datetime, timedelta, timezone
4
+ from typing import Tuple, Dict, List
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ class RateLimiter:
9
+ def __init__(self, period_days: int = 7, quota: int = 5):
10
+ self.period_days = period_days
11
+ self.quota = quota
12
+ self.submission_history: Dict[str, List[datetime]] = {}
13
+ self.higher_quota_users = set() # Users with higher quotas
14
+ self.unlimited_users = set() # Users with no quota limits
15
+
16
+ def add_unlimited_user(self, user_id: str):
17
+ """Add a user to the unlimited users list"""
18
+ self.unlimited_users.add(user_id)
19
+
20
+ def add_higher_quota_user(self, user_id: str):
21
+ """Add a user to the higher quota users list"""
22
+ self.higher_quota_users.add(user_id)
23
+
24
+ def record_submission(self, user_id: str):
25
+ """Record a new submission for a user"""
26
+ current_time = datetime.now(timezone.utc)
27
+ if user_id not in self.submission_history:
28
+ self.submission_history[user_id] = []
29
+ self.submission_history[user_id].append(current_time)
30
+
31
+ def clean_old_submissions(self, user_id: str):
32
+ """Remove submissions older than the period"""
33
+ if user_id not in self.submission_history:
34
+ return
35
+
36
+ current_time = datetime.now(timezone.utc)
37
+ cutoff_time = current_time - timedelta(days=self.period_days)
38
+
39
+ self.submission_history[user_id] = [
40
+ time for time in self.submission_history[user_id]
41
+ if time > cutoff_time
42
+ ]
43
+
44
+ async def check_rate_limit(self, user_id: str) -> Tuple[bool, str]:
45
+ """Check if a user has exceeded their rate limit
46
+
47
+ Returns:
48
+ Tuple[bool, str]: (is_allowed, error_message)
49
+ """
50
+ # Unlimited users bypass all checks
51
+ if user_id in self.unlimited_users:
52
+ return True, ""
53
+
54
+ # Clean old submissions
55
+ self.clean_old_submissions(user_id)
56
+
57
+ # Get current submission count
58
+ submission_count = len(self.submission_history.get(user_id, []))
59
+
60
+ # Calculate user's quota
61
+ user_quota = self.quota * 2 if user_id in self.higher_quota_users else self.quota
62
+
63
+ # Check if user has exceeded their quota
64
+ if submission_count >= user_quota:
65
+ error_msg = (
66
+ f"User '{user_id}' has reached the limit of {user_quota} submissions "
67
+ f"in the last {self.period_days} days. Please wait before submitting again."
68
+ )
69
+ return False, error_msg
70
+
71
+ return True, ""
72
+ """
backend/app/services/votes.py ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from datetime import datetime, timezone
2
+ from typing import Dict, Any, List, Set, Tuple, Optional
3
+ import json
4
+ import logging
5
+ import asyncio
6
+ from pathlib import Path
7
+ import aiohttp
8
+ from huggingface_hub import HfApi
9
+ import tempfile
10
+ import os
11
+
12
+ from app.services.hf_service import HuggingFaceService
13
+ from app.config import HF_TOKEN
14
+ from app.config.hf_config import HF_ORGANIZATION
15
+ from app.core.cache import cache_config
16
+ from app.core.formatting import LogFormatter
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ class VoteService(HuggingFaceService):
21
+ _instance: Optional['VoteService'] = None
22
+ _initialized = False
23
+
24
+ def __new__(cls):
25
+ if cls._instance is None:
26
+ cls._instance = super(VoteService, cls).__new__(cls)
27
+ return cls._instance
28
+
29
+ def __init__(self):
30
+ if not hasattr(self, '_init_done'):
31
+ super().__init__()
32
+ self.votes_file = cache_config.votes_file
33
+ self.votes_to_upload: List[Dict[str, Any]] = []
34
+ self.vote_check_set: Set[Tuple[str, str, str, str]] = set()
35
+ self._votes_by_model: Dict[str, List[Dict[str, Any]]] = {}
36
+ self._votes_by_user: Dict[str, List[Dict[str, Any]]] = {}
37
+ self._last_sync = None
38
+ self._sync_interval = 300 # 5 minutes
39
+ self._total_votes = 0
40
+ self._last_vote_timestamp = None
41
+ self._max_retries = 3
42
+ self._retry_delay = 1 # seconds
43
+ self.hf_api = HfApi(token=HF_TOKEN)
44
+ self._init_done = True
45
+
46
+ async def initialize(self):
47
+ """Initialize the vote service"""
48
+ if self._initialized:
49
+ await self._check_for_new_votes()
50
+ return
51
+
52
+ try:
53
+ logger.info(LogFormatter.section("VOTE SERVICE INITIALIZATION"))
54
+
55
+ # Ensure votes directory exists
56
+ self.votes_file.parent.mkdir(parents=True, exist_ok=True)
57
+
58
+ # Load remote votes
59
+ remote_votes = await self._fetch_remote_votes()
60
+ if remote_votes:
61
+ logger.info(LogFormatter.info(f"Loaded {len(remote_votes)} votes from hub"))
62
+
63
+ # Save to local file
64
+ with open(self.votes_file, 'w') as f:
65
+ for vote in remote_votes:
66
+ json.dump(vote, f)
67
+ f.write('\n')
68
+
69
+ # Load into memory
70
+ await self._load_existing_votes()
71
+ else:
72
+ logger.warning(LogFormatter.warning("No votes found on hub"))
73
+
74
+ self._initialized = True
75
+ self._last_sync = datetime.now(timezone.utc)
76
+
77
+ # Final summary
78
+ stats = {
79
+ "Total_Votes": self._total_votes,
80
+ "Last_Sync": self._last_sync.strftime("%Y-%m-%d %H:%M:%S UTC")
81
+ }
82
+ logger.info(LogFormatter.section("INITIALIZATION COMPLETE"))
83
+ for line in LogFormatter.stats(stats):
84
+ logger.info(line)
85
+
86
+ except Exception as e:
87
+ logger.error(LogFormatter.error("Initialization failed", e))
88
+ raise
89
+
90
+ async def _fetch_remote_votes(self) -> List[Dict[str, Any]]:
91
+ """Fetch votes from HF hub"""
92
+ url = f"https://huggingface.co/datasets/{HF_ORGANIZATION}/votes/raw/main/votes_data.jsonl"
93
+ headers = {"Authorization": f"Bearer {self.token}"} if self.token else {}
94
+
95
+ try:
96
+ async with aiohttp.ClientSession() as session:
97
+ async with session.get(url, headers=headers) as response:
98
+ if response.status == 200:
99
+ votes = []
100
+ async for line in response.content:
101
+ if line.strip():
102
+ try:
103
+ vote = json.loads(line.decode())
104
+ votes.append(vote)
105
+ except json.JSONDecodeError:
106
+ continue
107
+ return votes
108
+ else:
109
+ logger.error(f"Failed to get remote votes: HTTP {response.status}")
110
+ return []
111
+ except Exception as e:
112
+ logger.error(f"Error fetching remote votes: {str(e)}")
113
+ return []
114
+
115
+ async def _check_for_new_votes(self):
116
+ """Check for new votes on the hub and sync if needed"""
117
+ try:
118
+ remote_votes = await self._fetch_remote_votes()
119
+ if len(remote_votes) != self._total_votes:
120
+ logger.info(f"Vote count changed: Local ({self._total_votes}) ≠ Remote ({len(remote_votes)})")
121
+ # Save to local file
122
+ with open(self.votes_file, 'w') as f:
123
+ for vote in remote_votes:
124
+ json.dump(vote, f)
125
+ f.write('\n')
126
+
127
+ # Reload into memory
128
+ await self._load_existing_votes()
129
+ else:
130
+ logger.info("Votes are in sync")
131
+
132
+ except Exception as e:
133
+ logger.error(f"Error checking for new votes: {str(e)}")
134
+
135
+ async def _sync_with_hub(self):
136
+ """Sync votes with HuggingFace hub"""
137
+ try:
138
+ logger.info(LogFormatter.section("VOTE SYNC"))
139
+
140
+ # Get current remote votes
141
+ remote_votes = await self._fetch_remote_votes()
142
+ logger.info(LogFormatter.info(f"Loaded {len(remote_votes)} votes from hub"))
143
+
144
+ # If we have pending votes to upload
145
+ if self.votes_to_upload:
146
+ logger.info(LogFormatter.info(f"Adding {len(self.votes_to_upload)} pending votes..."))
147
+
148
+ # Add new votes to remote votes
149
+ remote_votes.extend(self.votes_to_upload)
150
+
151
+ # Create temporary file with all votes
152
+ with tempfile.NamedTemporaryFile(mode='w', suffix='.jsonl', delete=False) as temp_file:
153
+ for vote in remote_votes:
154
+ json.dump(vote, temp_file)
155
+ temp_file.write('\n')
156
+ temp_path = temp_file.name
157
+
158
+ try:
159
+ # Upload JSONL file directly
160
+ self.hf_api.upload_file(
161
+ path_or_fileobj=temp_path,
162
+ path_in_repo="votes_data.jsonl",
163
+ repo_id=f"{HF_ORGANIZATION}/votes",
164
+ repo_type="dataset",
165
+ commit_message=f"Update votes: +{len(self.votes_to_upload)} new votes",
166
+ token=self.token
167
+ )
168
+
169
+ # Clear pending votes only if upload succeeded
170
+ self.votes_to_upload.clear()
171
+ logger.info(LogFormatter.success("Pending votes uploaded successfully"))
172
+
173
+ except Exception as e:
174
+ logger.error(LogFormatter.error("Failed to upload votes to hub", e))
175
+ raise
176
+ finally:
177
+ # Clean up temp file
178
+ os.unlink(temp_path)
179
+
180
+ # Update local state
181
+ with open(self.votes_file, 'w') as f:
182
+ for vote in remote_votes:
183
+ json.dump(vote, f)
184
+ f.write('\n')
185
+
186
+ # Reload votes in memory
187
+ await self._load_existing_votes()
188
+ logger.info(LogFormatter.success("Sync completed successfully"))
189
+
190
+ self._last_sync = datetime.now(timezone.utc)
191
+
192
+ except Exception as e:
193
+ logger.error(LogFormatter.error("Sync failed", e))
194
+ raise
195
+
196
+ async def _load_existing_votes(self):
197
+ """Load existing votes from file"""
198
+ if not self.votes_file.exists():
199
+ logger.warning(LogFormatter.warning("No votes file found"))
200
+ return
201
+
202
+ try:
203
+ logger.info(LogFormatter.section("LOADING VOTES"))
204
+
205
+ # Clear existing data structures
206
+ self.vote_check_set.clear()
207
+ self._votes_by_model.clear()
208
+ self._votes_by_user.clear()
209
+
210
+ vote_count = 0
211
+ latest_timestamp = None
212
+
213
+ with open(self.votes_file, "r") as f:
214
+ for line in f:
215
+ try:
216
+ vote = json.loads(line.strip())
217
+ vote_count += 1
218
+
219
+ # Track latest timestamp
220
+ try:
221
+ vote_timestamp = datetime.fromisoformat(vote["timestamp"].replace("Z", "+00:00"))
222
+ if not latest_timestamp or vote_timestamp > latest_timestamp:
223
+ latest_timestamp = vote_timestamp
224
+ vote["timestamp"] = vote_timestamp.strftime("%Y-%m-%dT%H:%M:%SZ")
225
+ except (KeyError, ValueError) as e:
226
+ logger.warning(LogFormatter.warning(f"Invalid timestamp in vote: {str(e)}"))
227
+ continue
228
+
229
+ if vote_count % 1000 == 0:
230
+ logger.info(LogFormatter.info(f"Processed {vote_count:,} votes..."))
231
+
232
+ self._add_vote_to_memory(vote)
233
+
234
+ except json.JSONDecodeError as e:
235
+ logger.error(LogFormatter.error("Vote parsing failed", e))
236
+ continue
237
+ except Exception as e:
238
+ logger.error(LogFormatter.error("Vote processing failed", e))
239
+ continue
240
+
241
+ self._total_votes = vote_count
242
+ self._last_vote_timestamp = latest_timestamp
243
+
244
+ # Final summary
245
+ stats = {
246
+ "Total_Votes": vote_count,
247
+ "Latest_Vote": latest_timestamp.strftime("%Y-%m-%d %H:%M:%S UTC") if latest_timestamp else "None",
248
+ "Unique_Models": len(self._votes_by_model),
249
+ "Unique_Users": len(self._votes_by_user)
250
+ }
251
+
252
+ logger.info(LogFormatter.section("VOTE SUMMARY"))
253
+ for line in LogFormatter.stats(stats):
254
+ logger.info(line)
255
+
256
+ except Exception as e:
257
+ logger.error(LogFormatter.error("Failed to load votes", e))
258
+ raise
259
+
260
+ def _add_vote_to_memory(self, vote: Dict[str, Any]):
261
+ """Add vote to memory structures"""
262
+ try:
263
+ # Create a unique identifier tuple that includes precision
264
+ check_tuple = (
265
+ vote["model"],
266
+ vote.get("revision", "main"),
267
+ vote["username"],
268
+ vote.get("precision", "unknown")
269
+ )
270
+
271
+ # Skip if we already have this vote
272
+ if check_tuple in self.vote_check_set:
273
+ return
274
+
275
+ self.vote_check_set.add(check_tuple)
276
+
277
+ # Update model votes
278
+ if vote["model"] not in self._votes_by_model:
279
+ self._votes_by_model[vote["model"]] = []
280
+ self._votes_by_model[vote["model"]].append(vote)
281
+
282
+ # Update user votes
283
+ if vote["username"] not in self._votes_by_user:
284
+ self._votes_by_user[vote["username"]] = []
285
+ self._votes_by_user[vote["username"]].append(vote)
286
+
287
+ except KeyError as e:
288
+ logger.error(LogFormatter.error("Malformed vote data, missing key", str(e)))
289
+ except Exception as e:
290
+ logger.error(LogFormatter.error("Error adding vote to memory", str(e)))
291
+
292
+ async def get_user_votes(self, user_id: str) -> List[Dict[str, Any]]:
293
+ """Get all votes from a specific user"""
294
+ logger.info(LogFormatter.info(f"Fetching votes for user: {user_id}"))
295
+
296
+ # Check if we need to refresh votes
297
+ if (datetime.now(timezone.utc) - self._last_sync).total_seconds() > self._sync_interval:
298
+ logger.info(LogFormatter.info("Cache expired, refreshing votes..."))
299
+ await self._check_for_new_votes()
300
+
301
+ votes = self._votes_by_user.get(user_id, [])
302
+ logger.info(LogFormatter.success(f"Found {len(votes):,} votes"))
303
+ return votes
304
+
305
+ async def get_model_votes(self, model_id: str) -> Dict[str, Any]:
306
+ """Get all votes for a specific model"""
307
+ logger.info(LogFormatter.info(f"Fetching votes for model: {model_id}"))
308
+
309
+ # Check if we need to refresh votes
310
+ if (datetime.now(timezone.utc) - self._last_sync).total_seconds() > self._sync_interval:
311
+ logger.info(LogFormatter.info("Cache expired, refreshing votes..."))
312
+ await self._check_for_new_votes()
313
+
314
+ votes = self._votes_by_model.get(model_id, [])
315
+
316
+ # Group votes by revision and precision
317
+ votes_by_config = {}
318
+ for vote in votes:
319
+ revision = vote.get("revision", "main")
320
+ precision = vote.get("precision", "unknown")
321
+ config_key = f"{revision}_{precision}"
322
+ if config_key not in votes_by_config:
323
+ votes_by_config[config_key] = {
324
+ "revision": revision,
325
+ "precision": precision,
326
+ "count": 0
327
+ }
328
+ votes_by_config[config_key]["count"] += 1
329
+
330
+ stats = {
331
+ "Total_Votes": len(votes),
332
+ **{f"Config_{k}": v["count"] for k, v in votes_by_config.items()}
333
+ }
334
+
335
+ logger.info(LogFormatter.section("VOTE STATISTICS"))
336
+ for line in LogFormatter.stats(stats):
337
+ logger.info(line)
338
+
339
+ return {
340
+ "total_votes": len(votes),
341
+ "votes_by_config": votes_by_config,
342
+ "votes": votes
343
+ }
344
+
345
+ async def _get_model_revision(self, model_id: str) -> str:
346
+ """Get current revision of a model with retries"""
347
+ logger.info(f"Getting revision for model: {model_id}")
348
+ for attempt in range(self._max_retries):
349
+ try:
350
+ model_info = await asyncio.to_thread(self.hf_api.model_info, model_id)
351
+ logger.info(f"Successfully got revision {model_info.sha} for model {model_id}")
352
+ return model_info.sha
353
+ except Exception as e:
354
+ logger.error(f"Error getting model revision for {model_id} (attempt {attempt + 1}): {str(e)}")
355
+ if attempt < self._max_retries - 1:
356
+ retry_delay = self._retry_delay * (attempt + 1)
357
+ logger.info(f"Retrying in {retry_delay} seconds...")
358
+ await asyncio.sleep(retry_delay)
359
+ else:
360
+ logger.warning(f"Using 'main' as fallback revision for {model_id} after {self._max_retries} failed attempts")
361
+ return "main"
362
+
363
+ async def add_vote(self, model_id: str, user_id: str, vote_type: str, vote_data: Dict[str, Any] = None) -> Dict[str, Any]:
364
+ """Add a vote for a model"""
365
+ try:
366
+ self._log_repo_operation("add", f"{HF_ORGANIZATION}/votes", f"Adding {vote_type} vote for {model_id} by {user_id}")
367
+ logger.info(LogFormatter.section("NEW VOTE"))
368
+ stats = {
369
+ "Model": model_id,
370
+ "User": user_id,
371
+ "Type": vote_type,
372
+ "Config": vote_data or {}
373
+ }
374
+ for line in LogFormatter.tree(stats, "Vote Details"):
375
+ logger.info(line)
376
+
377
+ # Use provided configuration or fallback to model info
378
+ precision = None
379
+ revision = None
380
+
381
+ if vote_data:
382
+ precision = vote_data.get("precision")
383
+ revision = vote_data.get("revision")
384
+
385
+ # If any info is missing, try to get it from model info
386
+ if not all([precision, revision]):
387
+ try:
388
+ model_info = await asyncio.to_thread(self.hf_api.model_info, model_id)
389
+ model_card_data = model_info.cardData if hasattr(model_info, 'cardData') else {}
390
+
391
+ if not precision:
392
+ precision = model_card_data.get("precision", "unknown")
393
+ if not revision:
394
+ revision = model_info.sha
395
+ except Exception as e:
396
+ logger.warning(LogFormatter.warning(f"Failed to get model info: {str(e)}. Using default values."))
397
+ precision = precision or "unknown"
398
+ revision = revision or "main"
399
+
400
+ # Check if vote already exists with this configuration
401
+ check_tuple = (model_id, revision, user_id, precision)
402
+
403
+ if check_tuple in self.vote_check_set:
404
+ raise ValueError(f"Vote already recorded for this model configuration (precision: {precision}, revision: {revision[:7] if revision else 'unknown'})")
405
+
406
+ vote = {
407
+ "model": model_id,
408
+ "revision": revision,
409
+ "username": user_id,
410
+ "timestamp": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"),
411
+ "vote_type": vote_type,
412
+ "precision": precision
413
+ }
414
+
415
+ # Update local storage
416
+ with open(self.votes_file, "a") as f:
417
+ f.write(json.dumps(vote) + "\n")
418
+
419
+ self._add_vote_to_memory(vote)
420
+ self.votes_to_upload.append(vote)
421
+
422
+ stats = {
423
+ "Status": "Success",
424
+ "Queue_Size": len(self.votes_to_upload),
425
+ "Model_Config": {
426
+ "Precision": precision,
427
+ "Revision": revision[:7] if revision else "unknown"
428
+ }
429
+ }
430
+ for line in LogFormatter.stats(stats):
431
+ logger.info(line)
432
+
433
+ # Force immediate sync
434
+ logger.info(LogFormatter.info("Forcing immediate sync with hub"))
435
+ await self._sync_with_hub()
436
+
437
+ return {"status": "success", "message": "Vote added successfully"}
438
+
439
+ except Exception as e:
440
+ logger.error(LogFormatter.error("Failed to add vote", e))
441
+ raise
backend/app/utils/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from . import model_validation
2
+
3
+ __all__ = ["model_validation"]
backend/app/utils/logging.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from app.core.formatting import LogFormatter
2
+
3
+ __all__ = ['LogFormatter']
backend/app/utils/model_validation.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import logging
3
+ import asyncio
4
+ from typing import Tuple, Optional, Dict, Any
5
+ from datasets import load_dataset
6
+ from huggingface_hub import HfApi, ModelCard, hf_hub_download
7
+ from huggingface_hub import hf_api
8
+ from transformers import AutoConfig, AutoTokenizer
9
+ from app.config.base import HF_TOKEN
10
+ from app.config.hf_config import OFFICIAL_PROVIDERS_REPO
11
+ from app.core.formatting import LogFormatter
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+ class ModelValidator:
16
+ def __init__(self):
17
+ self.token = HF_TOKEN
18
+ self.api = HfApi(token=self.token)
19
+ self.headers = {"Authorization": f"Bearer {self.token}"} if self.token else {}
20
+
21
+ async def check_model_card(self, model_id: str) -> Tuple[bool, str, Optional[Dict[str, Any]]]:
22
+ """Check if model has a valid model card"""
23
+ try:
24
+ logger.info(LogFormatter.info(f"Checking model card for {model_id}"))
25
+
26
+ # Get model card content using ModelCard.load
27
+ try:
28
+ model_card = await asyncio.to_thread(
29
+ ModelCard.load,
30
+ model_id
31
+ )
32
+ logger.info(LogFormatter.success("Model card found"))
33
+ except Exception as e:
34
+ error_msg = "Please add a model card to your model to explain how you trained/fine-tuned it."
35
+ logger.error(LogFormatter.error(error_msg, e))
36
+ return False, error_msg, None
37
+
38
+ # Check license in model card data
39
+ if model_card.data.license is None and not ("license_name" in model_card.data and "license_link" in model_card.data):
40
+ error_msg = "License not found. Please add a license to your model card using the `license` metadata or a `license_name`/`license_link` pair."
41
+ logger.warning(LogFormatter.warning(error_msg))
42
+ return False, error_msg, None
43
+
44
+ # Enforce card content length
45
+ if len(model_card.text) < 200:
46
+ error_msg = "Please add a description to your model card, it is too short."
47
+ logger.warning(LogFormatter.warning(error_msg))
48
+ return False, error_msg, None
49
+
50
+ logger.info(LogFormatter.success("Model card validation passed"))
51
+ return True, "", model_card
52
+
53
+ except Exception as e:
54
+ error_msg = "Failed to validate model card"
55
+ logger.error(LogFormatter.error(error_msg, e))
56
+ return False, str(e), None
57
+
58
+ async def get_safetensors_metadata(self, model_id: str, is_adapter: bool = False, revision: str = "main") -> Optional[Dict]:
59
+ """Get metadata from a safetensors file"""
60
+ try:
61
+ if is_adapter:
62
+ metadata = await asyncio.to_thread(
63
+ hf_api.parse_safetensors_file_metadata,
64
+ model_id,
65
+ "adapter_model.safetensors",
66
+ token=self.token,
67
+ revision=revision,
68
+ )
69
+ else:
70
+ metadata = await asyncio.to_thread(
71
+ hf_api.get_safetensors_metadata,
72
+ repo_id=model_id,
73
+ token=self.token,
74
+ revision=revision,
75
+ )
76
+ return metadata
77
+
78
+ except Exception as e:
79
+ logger.error(f"Failed to get safetensors metadata: {str(e)}")
80
+ return None
81
+
82
+ async def get_model_size(
83
+ self,
84
+ model_info: Any,
85
+ precision: str,
86
+ base_model: str,
87
+ revision: str
88
+ ) -> Tuple[Optional[float], Optional[str]]:
89
+ """Get model size in billions of parameters"""
90
+ try:
91
+ logger.info(LogFormatter.info(f"Checking model size for {model_info.modelId}"))
92
+
93
+ # Check if model is adapter
94
+ is_adapter = any(s.rfilename == "adapter_config.json" for s in model_info.siblings if hasattr(s, 'rfilename'))
95
+
96
+ # Try to get size from safetensors first
97
+ model_size = None
98
+
99
+ if is_adapter and base_model:
100
+ # For adapters, we need both adapter and base model sizes
101
+ adapter_meta = await self.get_safetensors_metadata(model_info.id, is_adapter=True, revision=revision)
102
+ base_meta = await self.get_safetensors_metadata(base_model, revision="main")
103
+
104
+ if adapter_meta and base_meta:
105
+ adapter_size = sum(adapter_meta.parameter_count.values())
106
+ base_size = sum(base_meta.parameter_count.values())
107
+ model_size = adapter_size + base_size
108
+ else:
109
+ # For regular models, just get the model size
110
+ meta = await self.get_safetensors_metadata(model_info.id, revision=revision)
111
+ if meta:
112
+ model_size = sum(meta.parameter_count.values()) # total params
113
+
114
+ if model_size is None:
115
+ # If model size could not be determined, return an error
116
+ return None, "Model size could not be determined"
117
+
118
+ # Adjust size for GPTQ models
119
+ size_factor = 8 if (precision == "GPTQ" or "gptq" in model_info.id.lower()) else 1
120
+ model_size = model_size / 1e9 # Convert to billions, assuming float16
121
+ model_size = round(size_factor * model_size, 3)
122
+
123
+ logger.info(LogFormatter.success(f"Model size: {model_size}B parameters"))
124
+ return model_size, None
125
+
126
+ except Exception as e:
127
+ logger.error(LogFormatter.error(f"Error while determining model size: {e}"))
128
+ return None, str(e)
129
+
130
+
131
+ async def check_chat_template(
132
+ self,
133
+ model_id: str,
134
+ revision: str
135
+ ) -> Tuple[bool, Optional[str]]:
136
+ """Check if model has a valid chat template"""
137
+ try:
138
+ logger.info(LogFormatter.info(f"Checking chat template for {model_id}"))
139
+
140
+ try:
141
+ config_file = await asyncio.to_thread(
142
+ hf_hub_download,
143
+ repo_id=model_id,
144
+ filename="tokenizer_config.json",
145
+ revision=revision,
146
+ repo_type="model"
147
+ )
148
+
149
+ with open(config_file, 'r') as f:
150
+ tokenizer_config = json.load(f)
151
+
152
+ if 'chat_template' not in tokenizer_config:
153
+ error_msg = f"The model {model_id} doesn't have a chat_template in its tokenizer_config.json. Please add a chat_template before submitting or submit without it."
154
+ logger.error(LogFormatter.error(error_msg))
155
+ return False, error_msg
156
+
157
+ logger.info(LogFormatter.success("Valid chat template found"))
158
+ return True, None
159
+
160
+ except Exception as e:
161
+ error_msg = f"Error checking chat_template: {str(e)}"
162
+ logger.error(LogFormatter.error(error_msg))
163
+ return False, error_msg
164
+
165
+ except Exception as e:
166
+ error_msg = "Failed to check chat template"
167
+ logger.error(LogFormatter.error(error_msg, e))
168
+ return False, str(e)
169
+
170
+ async def is_model_on_hub(
171
+ self,
172
+ model_name: str,
173
+ revision: str,
174
+ test_tokenizer: bool = False,
175
+ trust_remote_code: bool = False
176
+ ) -> Tuple[bool, Optional[str], Optional[Any]]:
177
+ """Check if model exists and is properly configured on the Hub"""
178
+ try:
179
+ config = await asyncio.to_thread(
180
+ AutoConfig.from_pretrained,
181
+ model_name,
182
+ revision=revision,
183
+ trust_remote_code=trust_remote_code,
184
+ token=self.token,
185
+ force_download=True
186
+ )
187
+
188
+ if test_tokenizer:
189
+ try:
190
+ await asyncio.to_thread(
191
+ AutoTokenizer.from_pretrained,
192
+ model_name,
193
+ revision=revision,
194
+ trust_remote_code=trust_remote_code,
195
+ token=self.token
196
+ )
197
+ except ValueError as e:
198
+ return False, f"The tokenizer is not available in an official Transformers release: {e}", None
199
+ except Exception:
200
+ return False, "The tokenizer cannot be loaded. Ensure the tokenizer class is part of a stable Transformers release and correctly configured.", None
201
+
202
+ return True, None, config
203
+
204
+ except ValueError:
205
+ return False, "The model requires `trust_remote_code=True` to launch, and for safety reasons, we don't accept such models automatically.", None
206
+ except Exception as e:
207
+ if "You are trying to access a gated repo." in str(e):
208
+ return True, "The model is gated and requires special access permissions.", None
209
+ return False, f"The model was not found or is misconfigured on the Hub. Error: {e.args[0]}", None
210
+
211
+ async def check_official_provider_status(
212
+ self,
213
+ model_id: str,
214
+ existing_models: Dict[str, list]
215
+ ) -> Tuple[bool, Optional[str]]:
216
+ """
217
+ Check if model is from official provider and has finished submission.
218
+
219
+ Args:
220
+ model_id: The model identifier (org/model-name)
221
+ existing_models: Dictionary of models by status from get_models()
222
+
223
+ Returns:
224
+ Tuple[bool, Optional[str]]: (is_valid, error_message)
225
+ """
226
+ try:
227
+ logger.info(LogFormatter.info(f"Checking official provider status for {model_id}"))
228
+
229
+ # Get model organization
230
+ model_org = model_id.split('/')[0] if '/' in model_id else None
231
+
232
+ if not model_org:
233
+ return True, None
234
+
235
+ # Load official providers dataset
236
+ dataset = load_dataset(OFFICIAL_PROVIDERS_REPO)
237
+ official_providers = dataset["train"][0]["CURATED_SET"]
238
+
239
+ # Check if model org is in official providers
240
+ is_official = model_org in official_providers
241
+
242
+ if is_official:
243
+ logger.info(LogFormatter.info(f"Model organization '{model_org}' is an official provider"))
244
+
245
+ # Check for finished submissions
246
+ if "finished" in existing_models:
247
+ for model in existing_models["finished"]:
248
+ if model["name"] == model_id:
249
+ error_msg = (
250
+ f"Model {model_id} is an official provider model "
251
+ f"with a completed evaluation. "
252
+ f"To re-evaluate, please open a discussion."
253
+ )
254
+ logger.error(LogFormatter.error("Validation failed", error_msg))
255
+ return False, error_msg
256
+
257
+ logger.info(LogFormatter.success("No finished submission found for this official provider model"))
258
+ else:
259
+ logger.info(LogFormatter.info(f"Model organization '{model_org}' is not an official provider"))
260
+
261
+ return True, None
262
+
263
+ except Exception as e:
264
+ error_msg = f"Failed to check official provider status: {str(e)}"
265
+ logger.error(LogFormatter.error(error_msg))
266
+ return False, error_msg
backend/pyproject.toml ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [tool.poetry]
2
+ name = "llm-leaderboard-backend"
3
+ version = "0.1.0"
4
+ description = "Backend for the Open LLM Leaderboard"
5
+ authors = ["Your Name <[email protected]>"]
6
+
7
+ [tool.poetry.dependencies]
8
+ python = "^3.12"
9
+ fastapi = "^0.115.6"
10
+ uvicorn = {extras = ["standard"], version = "^0.34.0"}
11
+ numpy = "^2.2.0"
12
+ pandas = "^2.2.3"
13
+ datasets = "^3.3.2"
14
+ pyarrow = "^18.1.0"
15
+ python-multipart = "^0.0.20"
16
+ huggingface-hub = "0.29.1"
17
+ transformers = "4.49.0"
18
+ safetensors = "^0.5.3"
19
+ aiofiles = "^24.1.0"
20
+ fastapi-cache2 = "^0.2.1"
21
+ python-dotenv = "^1.0.1"
22
+
23
+ [tool.poetry.group.dev.dependencies]
24
+ pytest = "^8.3.4"
25
+ black = "^24.10.0"
26
+ isort = "^5.13.2"
27
+ flake8 = "^6.1.0"
28
+
29
+ [build-system]
30
+ requires = ["poetry-core>=1.0.0"]
31
+ build-backend = "poetry.core.masonry.api"
backend/utils/analyze_prod_datasets.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Dict, Any, List
7
+ from huggingface_hub import HfApi
8
+ from dotenv import load_dotenv
9
+ from app.config.hf_config import HF_ORGANIZATION
10
+
11
+ # Get the backend directory path
12
+ BACKEND_DIR = Path(__file__).parent.parent
13
+ ROOT_DIR = BACKEND_DIR.parent
14
+
15
+ # Load environment variables from .env file in root directory
16
+ load_dotenv(ROOT_DIR / ".env")
17
+
18
+ # Configure logging
19
+ logging.basicConfig(
20
+ level=logging.INFO,
21
+ format='%(message)s'
22
+ )
23
+ logger = logging.getLogger(__name__)
24
+
25
+ # Initialize Hugging Face API
26
+ HF_TOKEN = os.getenv("HF_TOKEN")
27
+ if not HF_TOKEN:
28
+ raise ValueError("HF_TOKEN not found in environment variables")
29
+ api = HfApi(token=HF_TOKEN)
30
+
31
+ def analyze_dataset(repo_id: str) -> Dict[str, Any]:
32
+ """Analyze a dataset and return statistics"""
33
+ try:
34
+ # Get dataset info
35
+ dataset_info = api.dataset_info(repo_id=repo_id)
36
+
37
+ # Get file list
38
+ files = api.list_repo_files(repo_id, repo_type="dataset")
39
+
40
+ # Get last commit info
41
+ commits = api.list_repo_commits(repo_id, repo_type="dataset")
42
+ last_commit = next(commits, None)
43
+
44
+ # Count lines in jsonl files
45
+ total_entries = 0
46
+ for file in files:
47
+ if file.endswith('.jsonl'):
48
+ try:
49
+ # Download file content
50
+ content = api.hf_hub_download(
51
+ repo_id=repo_id,
52
+ filename=file,
53
+ repo_type="dataset"
54
+ )
55
+
56
+ # Count lines
57
+ with open(content, 'r') as f:
58
+ for _ in f:
59
+ total_entries += 1
60
+
61
+ except Exception as e:
62
+ logger.error(f"Error processing file {file}: {str(e)}")
63
+ continue
64
+
65
+ # Special handling for requests dataset
66
+ if repo_id == f"{HF_ORGANIZATION}/requests":
67
+ pending_count = 0
68
+ completed_count = 0
69
+
70
+ try:
71
+ content = api.hf_hub_download(
72
+ repo_id=repo_id,
73
+ filename="eval_requests.jsonl",
74
+ repo_type="dataset"
75
+ )
76
+
77
+ with open(content, 'r') as f:
78
+ for line in f:
79
+ try:
80
+ entry = json.loads(line)
81
+ if entry.get("status") == "pending":
82
+ pending_count += 1
83
+ elif entry.get("status") == "completed":
84
+ completed_count += 1
85
+ except json.JSONDecodeError:
86
+ continue
87
+
88
+ except Exception as e:
89
+ logger.error(f"Error analyzing requests: {str(e)}")
90
+
91
+ # Build response
92
+ response = {
93
+ "id": repo_id,
94
+ "last_modified": last_commit.created_at if last_commit else None,
95
+ "total_entries": total_entries,
96
+ "file_count": len(files),
97
+ "size_bytes": dataset_info.size_in_bytes,
98
+ "downloads": dataset_info.downloads,
99
+ }
100
+
101
+ # Add request-specific info if applicable
102
+ if repo_id == f"{HF_ORGANIZATION}/requests":
103
+ response.update({
104
+ "pending_requests": pending_count,
105
+ "completed_requests": completed_count
106
+ })
107
+
108
+ return response
109
+
110
+ except Exception as e:
111
+ logger.error(f"Error analyzing dataset {repo_id}: {str(e)}")
112
+ return {
113
+ "id": repo_id,
114
+ "error": str(e)
115
+ }
116
+
117
+ def main():
118
+ """Main function to analyze all datasets"""
119
+ try:
120
+ # List of datasets to analyze
121
+ datasets = [
122
+ {
123
+ "id": f"{HF_ORGANIZATION}/contents",
124
+ "description": "Aggregated results"
125
+ },
126
+ {
127
+ "id": f"{HF_ORGANIZATION}/requests",
128
+ "description": "Evaluation requests"
129
+ },
130
+ {
131
+ "id": f"{HF_ORGANIZATION}/votes",
132
+ "description": "User votes"
133
+ },
134
+ {
135
+ "id": f"{HF_ORGANIZATION}/official-providers",
136
+ "description": "Highlighted models"
137
+ }
138
+ ]
139
+
140
+ # Analyze each dataset
141
+ results = []
142
+ for dataset in datasets:
143
+ logger.info(f"\nAnalyzing {dataset['description']} ({dataset['id']})...")
144
+ result = analyze_dataset(dataset['id'])
145
+ results.append(result)
146
+
147
+ if 'error' in result:
148
+ logger.error(f"❌ Error: {result['error']}")
149
+ else:
150
+ logger.info(f"✓ {result['total_entries']} entries")
151
+ logger.info(f"✓ {result['file_count']} files")
152
+ logger.info(f"✓ {result['size_bytes'] / 1024:.1f} KB")
153
+ logger.info(f"✓ {result['downloads']} downloads")
154
+
155
+ if 'pending_requests' in result:
156
+ logger.info(f"✓ {result['pending_requests']} pending requests")
157
+ logger.info(f"✓ {result['completed_requests']} completed requests")
158
+
159
+ if result['last_modified']:
160
+ last_modified = datetime.fromisoformat(result['last_modified'].replace('Z', '+00:00'))
161
+ logger.info(f"✓ Last modified: {last_modified.strftime('%Y-%m-%d %H:%M:%S')}")
162
+
163
+ return results
164
+
165
+ except Exception as e:
166
+ logger.error(f"Global error: {str(e)}")
167
+ return []
168
+
169
+ if __name__ == "__main__":
170
+ main()
backend/utils/analyze_prod_models.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from huggingface_hub import HfApi
7
+ from dotenv import load_dotenv
8
+ from app.config.hf_config import HF_ORGANIZATION
9
+
10
+ # Get the backend directory path
11
+ BACKEND_DIR = Path(__file__).parent.parent
12
+ ROOT_DIR = BACKEND_DIR.parent
13
+
14
+ # Load environment variables from .env file in root directory
15
+ load_dotenv(ROOT_DIR / ".env")
16
+
17
+ # Configure logging
18
+ logging.basicConfig(
19
+ level=logging.INFO,
20
+ format='%(message)s'
21
+ )
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Initialize Hugging Face API
25
+ HF_TOKEN = os.getenv("HF_TOKEN")
26
+ if not HF_TOKEN:
27
+ raise ValueError("HF_TOKEN not found in environment variables")
28
+ api = HfApi(token=HF_TOKEN)
29
+
30
+ def count_evaluated_models():
31
+ """Count the number of evaluated models"""
32
+ try:
33
+ # Get dataset info
34
+ dataset_info = api.dataset_info(repo_id=f"{HF_ORGANIZATION}/contents", repo_type="dataset")
35
+
36
+ # Get file list
37
+ files = api.list_repo_files(f"{HF_ORGANIZATION}/contents", repo_type="dataset")
38
+
39
+ # Get last commit info
40
+ commits = api.list_repo_commits(f"{HF_ORGANIZATION}/contents", repo_type="dataset")
41
+ last_commit = next(commits, None)
42
+
43
+ # Count lines in jsonl files
44
+ total_entries = 0
45
+ for file in files:
46
+ if file.endswith('.jsonl'):
47
+ try:
48
+ # Download file content
49
+ content = api.hf_hub_download(
50
+ repo_id=f"{HF_ORGANIZATION}/contents",
51
+ filename=file,
52
+ repo_type="dataset"
53
+ )
54
+
55
+ # Count lines
56
+ with open(content, 'r') as f:
57
+ for _ in f:
58
+ total_entries += 1
59
+
60
+ except Exception as e:
61
+ logger.error(f"Error processing file {file}: {str(e)}")
62
+ continue
63
+
64
+ # Build response
65
+ response = {
66
+ "total_models": total_entries,
67
+ "last_modified": last_commit.created_at if last_commit else None,
68
+ "file_count": len(files),
69
+ "size_bytes": dataset_info.size_in_bytes,
70
+ "downloads": dataset_info.downloads
71
+ }
72
+
73
+ return response
74
+
75
+ except Exception as e:
76
+ logger.error(f"Error counting evaluated models: {str(e)}")
77
+ return {
78
+ "error": str(e)
79
+ }
80
+
81
+ def main():
82
+ """Main function to count evaluated models"""
83
+ try:
84
+ logger.info("\nAnalyzing evaluated models...")
85
+ result = count_evaluated_models()
86
+
87
+ if 'error' in result:
88
+ logger.error(f"❌ Error: {result['error']}")
89
+ else:
90
+ logger.info(f"✓ {result['total_models']} models evaluated")
91
+ logger.info(f"✓ {result['file_count']} files")
92
+ logger.info(f"✓ {result['size_bytes'] / 1024:.1f} KB")
93
+ logger.info(f"✓ {result['downloads']} downloads")
94
+
95
+ if result['last_modified']:
96
+ last_modified = datetime.fromisoformat(result['last_modified'].replace('Z', '+00:00'))
97
+ logger.info(f"✓ Last modified: {last_modified.strftime('%Y-%m-%d %H:%M:%S')}")
98
+
99
+ return result
100
+
101
+ except Exception as e:
102
+ logger.error(f"Global error: {str(e)}")
103
+ return {"error": str(e)}
104
+
105
+ if __name__ == "__main__":
106
+ main()
backend/utils/fix_wrong_model_size.py ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import pytz
4
+ import logging
5
+ import asyncio
6
+ from datetime import datetime
7
+ from pathlib import Path
8
+ import huggingface_hub
9
+ from huggingface_hub.errors import RepositoryNotFoundError, RevisionNotFoundError
10
+ from dotenv import load_dotenv
11
+ from git import Repo
12
+ from datetime import datetime
13
+ from tqdm.auto import tqdm
14
+ from tqdm.contrib.logging import logging_redirect_tqdm
15
+
16
+ from app.config.hf_config import HF_TOKEN, API
17
+
18
+ from app.utils.model_validation import ModelValidator
19
+
20
+ huggingface_hub.logging.set_verbosity_error()
21
+ huggingface_hub.utils.disable_progress_bars()
22
+
23
+ logging.basicConfig(
24
+ level=logging.ERROR,
25
+ format='%(message)s'
26
+ )
27
+ logger = logging.getLogger(__name__)
28
+ load_dotenv()
29
+
30
+ validator = ModelValidator()
31
+
32
+ def get_changed_files(repo_path, start_date, end_date):
33
+ repo = Repo(repo_path)
34
+ start = datetime.strptime(start_date, '%Y-%m-%d')
35
+ end = datetime.strptime(end_date, '%Y-%m-%d')
36
+
37
+ changed_files = set()
38
+ pbar = tqdm(repo.iter_commits(), desc=f"Reading commits from {end_date} to {start_date}")
39
+ for commit in pbar:
40
+ commit_date = datetime.fromtimestamp(commit.committed_date)
41
+ pbar.set_postfix_str(f"Commit date: {commit_date}")
42
+ if start <= commit_date <= end:
43
+ changed_files.update(item.a_path for item in commit.diff(commit.parents[0]))
44
+
45
+ if commit_date < start:
46
+ break
47
+
48
+ return changed_files
49
+
50
+
51
+ def read_json(repo_path, file):
52
+ with open(f"{repo_path}/{file}") as file:
53
+ return json.load(file)
54
+
55
+
56
+ def write_json(repo_path, file, content):
57
+ with open(f"{repo_path}/{file}", "w") as file:
58
+ json.dump(content, file, indent=2)
59
+
60
+
61
+ def main():
62
+ requests_path = "/requests"
63
+ start_date = "2024-12-09"
64
+ end_date = "2025-01-07"
65
+
66
+ changed_files = get_changed_files(requests_path, start_date, end_date)
67
+
68
+ for file in tqdm(changed_files):
69
+ try:
70
+ request_data = read_json(requests_path, file)
71
+ except FileNotFoundError as e:
72
+ tqdm.write(f"File {file} not found")
73
+ continue
74
+
75
+ try:
76
+ model_info = API.model_info(
77
+ repo_id=request_data["model"],
78
+ revision=request_data["revision"],
79
+ token=HF_TOKEN
80
+ )
81
+ except (RepositoryNotFoundError, RevisionNotFoundError) as e:
82
+ tqdm.write(f"Model info for {request_data["model"]} not found")
83
+ continue
84
+
85
+ with logging_redirect_tqdm():
86
+ new_model_size, error = asyncio.run(validator.get_model_size(
87
+ model_info=model_info,
88
+ precision=request_data["precision"],
89
+ base_model=request_data["base_model"],
90
+ revision=request_data["revision"]
91
+ ))
92
+
93
+ if error:
94
+ tqdm.write(f"Error getting model size info for {request_data["model"]}, {error}")
95
+ continue
96
+
97
+ old_model_size = request_data["params"]
98
+ if old_model_size != new_model_size:
99
+ if new_model_size > 100:
100
+ tqdm.write(f"Model: {request_data["model"]}, size is more 100B: {new_model_size}")
101
+
102
+ tqdm.write(f"Model: {request_data["model"]}, old size: {request_data["params"]} new size: {new_model_size}")
103
+ tqdm.write(f"Updating request file {file}")
104
+
105
+ request_data["params"] = new_model_size
106
+ write_json(requests_path, file, content=request_data)
107
+
108
+
109
+ if __name__ == "__main__":
110
+ main()
backend/utils/last_activity.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import logging
4
+ from datetime import datetime
5
+ from pathlib import Path
6
+ from typing import Dict, Any, List, Tuple
7
+ from huggingface_hub import HfApi
8
+ from dotenv import load_dotenv
9
+
10
+ # Get the backend directory path
11
+ BACKEND_DIR = Path(__file__).parent.parent
12
+ ROOT_DIR = BACKEND_DIR.parent
13
+
14
+ # Load environment variables from .env file in root directory
15
+ load_dotenv(ROOT_DIR / ".env")
16
+
17
+ # Configure logging
18
+ logging.basicConfig(
19
+ level=logging.INFO,
20
+ format='%(message)s'
21
+ )
22
+ logger = logging.getLogger(__name__)
23
+
24
+ # Initialize Hugging Face API
25
+ HF_TOKEN = os.getenv("HF_TOKEN")
26
+ if not HF_TOKEN:
27
+ raise ValueError("HF_TOKEN not found in environment variables")
28
+ api = HfApi(token=HF_TOKEN)
29
+
30
+ # Default organization
31
+ HF_ORGANIZATION = os.getenv('HF_ORGANIZATION', 'open-llm-leaderboard')
32
+
33
+ def get_last_votes(limit: int = 5) -> List[Dict]:
34
+ """Get the last votes from the votes dataset"""
35
+ try:
36
+ logger.info("\nFetching last votes...")
37
+
38
+ # Download and read votes file
39
+ logger.info("Downloading votes file...")
40
+ votes_file = api.hf_hub_download(
41
+ repo_id=f"{HF_ORGANIZATION}/votes",
42
+ filename="votes_data.jsonl",
43
+ repo_type="dataset"
44
+ )
45
+
46
+ logger.info("Reading votes file...")
47
+ votes = []
48
+ with open(votes_file, 'r') as f:
49
+ for line in f:
50
+ try:
51
+ vote = json.loads(line)
52
+ votes.append(vote)
53
+ except json.JSONDecodeError:
54
+ continue
55
+
56
+ # Sort by timestamp and get last n votes
57
+ logger.info("Sorting votes...")
58
+ votes.sort(key=lambda x: x.get('timestamp', ''), reverse=True)
59
+ last_votes = votes[:limit]
60
+
61
+ logger.info(f"✓ Found {len(last_votes)} recent votes")
62
+ return last_votes
63
+
64
+ except Exception as e:
65
+ logger.error(f"Error reading votes: {str(e)}")
66
+ return []
67
+
68
+ def get_last_models(limit: int = 5) -> List[Dict]:
69
+ """Get the last models from the requests dataset using commit history"""
70
+ try:
71
+ logger.info("\nFetching last model submissions...")
72
+
73
+ # Get commit history
74
+ logger.info("Getting commit history...")
75
+ commits = list(api.list_repo_commits(
76
+ repo_id=f"{HF_ORGANIZATION}/requests",
77
+ repo_type="dataset"
78
+ ))
79
+ logger.info(f"Found {len(commits)} commits")
80
+
81
+ # Track processed files to avoid duplicates
82
+ processed_files = set()
83
+ models = []
84
+
85
+ # Process commits until we have enough models
86
+ for i, commit in enumerate(commits):
87
+ logger.info(f"Processing commit {i+1}/{len(commits)} ({commit.created_at})")
88
+
89
+ # Look at added/modified files in this commit
90
+ files_to_process = [f for f in (commit.added + commit.modified) if f.endswith('.json')]
91
+ if files_to_process:
92
+ logger.info(f"Found {len(files_to_process)} JSON files in commit")
93
+
94
+ for file in files_to_process:
95
+ if file in processed_files:
96
+ continue
97
+
98
+ processed_files.add(file)
99
+ logger.info(f"Downloading {file}...")
100
+
101
+ try:
102
+ # Download and read the file
103
+ content = api.hf_hub_download(
104
+ repo_id=f"{HF_ORGANIZATION}/requests",
105
+ filename=file,
106
+ repo_type="dataset"
107
+ )
108
+
109
+ with open(content, 'r') as f:
110
+ model_data = json.load(f)
111
+ models.append(model_data)
112
+ logger.info(f"✓ Added model {model_data.get('model', 'Unknown')}")
113
+
114
+ if len(models) >= limit:
115
+ logger.info("Reached desired number of models")
116
+ break
117
+
118
+ except Exception as e:
119
+ logger.error(f"Error reading file {file}: {str(e)}")
120
+ continue
121
+
122
+ if len(models) >= limit:
123
+ break
124
+
125
+ logger.info(f"✓ Found {len(models)} recent model submissions")
126
+ return models
127
+
128
+ except Exception as e:
129
+ logger.error(f"Error reading models: {str(e)}")
130
+ return []
131
+
132
+ def main():
133
+ """Display last activities from the leaderboard"""
134
+ try:
135
+ # Get last votes
136
+ logger.info("\n=== Last Votes ===")
137
+ last_votes = get_last_votes()
138
+ if last_votes:
139
+ for vote in last_votes:
140
+ logger.info(f"\nModel: {vote.get('model')}")
141
+ logger.info(f"User: {vote.get('username')}")
142
+ logger.info(f"Timestamp: {vote.get('timestamp')}")
143
+ else:
144
+ logger.info("No votes found")
145
+
146
+ # Get last model submissions
147
+ logger.info("\n=== Last Model Submissions ===")
148
+ last_models = get_last_models()
149
+ if last_models:
150
+ for model in last_models:
151
+ logger.info(f"\nModel: {model.get('model')}")
152
+ logger.info(f"Submitter: {model.get('sender', 'Unknown')}")
153
+ logger.info(f"Status: {model.get('status', 'Unknown')}")
154
+ logger.info(f"Submission Time: {model.get('submitted_time', 'Unknown')}")
155
+ logger.info(f"Precision: {model.get('precision', 'Unknown')}")
156
+ logger.info(f"Weight Type: {model.get('weight_type', 'Unknown')}")
157
+ else:
158
+ logger.info("No models found")
159
+
160
+ except Exception as e:
161
+ logger.error(f"Global error: {str(e)}")
162
+
163
+ if __name__ == "__main__":
164
+ main()
backend/utils/sync_datasets_locally.py ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import shutil
3
+ import tempfile
4
+ import logging
5
+ from pathlib import Path
6
+ from huggingface_hub import HfApi, snapshot_download, upload_folder, create_repo
7
+ from dotenv import load_dotenv
8
+
9
+ # Configure source and destination usernames
10
+ SOURCE_USERNAME = "open-llm-leaderboard"
11
+ DESTINATION_USERNAME = "tfrere"
12
+
13
+ # Get the backend directory path
14
+ BACKEND_DIR = Path(__file__).parent.parent
15
+ ROOT_DIR = BACKEND_DIR.parent
16
+
17
+ # Load environment variables from .env file in root directory
18
+ load_dotenv(ROOT_DIR / ".env")
19
+
20
+ # Configure logging
21
+ logging.basicConfig(
22
+ level=logging.INFO,
23
+ format='%(message)s'
24
+ )
25
+ logger = logging.getLogger(__name__)
26
+
27
+ # List of dataset names to sync
28
+ DATASET_NAMES = [
29
+ "votes",
30
+ "results",
31
+ "requests",
32
+ "contents",
33
+ "official-providers",
34
+ ]
35
+
36
+ # Build list of datasets with their source and destination paths
37
+ DATASETS = [
38
+ (name, f"{SOURCE_USERNAME}/{name}", f"{DESTINATION_USERNAME}/{name}")
39
+ for name in DATASET_NAMES
40
+ ]
41
+
42
+ # Initialize Hugging Face API
43
+ api = HfApi()
44
+
45
+ def ensure_repo_exists(repo_id, token):
46
+ """Ensure the repository exists, create it if it doesn't"""
47
+ try:
48
+ api.repo_info(repo_id=repo_id, repo_type="dataset")
49
+ logger.info(f"✓ Repository {repo_id} already exists")
50
+ except Exception:
51
+ logger.info(f"Creating repository {repo_id}...")
52
+ create_repo(
53
+ repo_id=repo_id,
54
+ repo_type="dataset",
55
+ token=token,
56
+ private=True
57
+ )
58
+ logger.info(f"✓ Repository {repo_id} created")
59
+
60
+ def process_dataset(dataset_info, token):
61
+ """Process a single dataset"""
62
+ name, source_dataset, destination_dataset = dataset_info
63
+ try:
64
+ logger.info(f"\n📥 Processing dataset: {name}")
65
+
66
+ # Ensure destination repository exists
67
+ ensure_repo_exists(destination_dataset, token)
68
+
69
+ # Create a temporary directory for this dataset
70
+ with tempfile.TemporaryDirectory() as temp_dir:
71
+ try:
72
+ # List files in source dataset
73
+ logger.info(f"Listing files in {source_dataset}...")
74
+ files = api.list_repo_files(source_dataset, repo_type="dataset")
75
+ logger.info(f"Detected structure: {len(files)} files")
76
+
77
+ # Download dataset
78
+ logger.info(f"Downloading from {source_dataset}...")
79
+ local_dir = snapshot_download(
80
+ repo_id=source_dataset,
81
+ repo_type="dataset",
82
+ local_dir=temp_dir,
83
+ token=token
84
+ )
85
+ logger.info(f"✓ Download complete")
86
+
87
+ # Upload to destination while preserving structure
88
+ logger.info(f"📤 Uploading to {destination_dataset}...")
89
+ api.upload_folder(
90
+ folder_path=local_dir,
91
+ repo_id=destination_dataset,
92
+ repo_type="dataset",
93
+ token=token
94
+ )
95
+ logger.info(f"✅ {name} copied successfully!")
96
+ return True
97
+
98
+ except Exception as e:
99
+ logger.error(f"❌ Error processing {name}: {str(e)}")
100
+ return False
101
+
102
+ except Exception as e:
103
+ logger.error(f"❌ Error for {name}: {str(e)}")
104
+ return False
105
+
106
+ def copy_datasets():
107
+ try:
108
+ logger.info("🔑 Checking authentication...")
109
+ # Get token from .env file
110
+ token = os.getenv("HF_TOKEN")
111
+ if not token:
112
+ raise ValueError("HF_TOKEN not found in .env file")
113
+
114
+ # Process datasets sequentially
115
+ results = []
116
+ for dataset_info in DATASETS:
117
+ success = process_dataset(dataset_info, token)
118
+ results.append((dataset_info[0], success))
119
+
120
+ # Print final summary
121
+ logger.info("\n📊 Final summary:")
122
+ for dataset, success in results:
123
+ status = "✅ Success" if success else "❌ Failure"
124
+ logger.info(f"{dataset}: {status}")
125
+
126
+ except Exception as e:
127
+ logger.error(f"❌ Global error: {str(e)}")
128
+
129
+ if __name__ == "__main__":
130
+ copy_datasets()
docker-compose.yml ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ services:
2
+ backend:
3
+ build:
4
+ context: ./backend
5
+ dockerfile: Dockerfile.dev
6
+ args:
7
+ - HF_TOKEN=${HF_TOKEN}
8
+ ports:
9
+ - "${BACKEND_PORT:-8000}:8000"
10
+ volumes:
11
+ - ./backend:/app
12
+ environment:
13
+ - ENVIRONMENT=${ENVIRONMENT:-development}
14
+ - HF_TOKEN=${HF_TOKEN}
15
+ - HF_HOME=${HF_HOME:-/.cache}
16
+ command: uvicorn app.asgi:app --host 0.0.0.0 --port 8000 --reload
17
+
18
+ frontend:
19
+ build:
20
+ context: ./frontend
21
+ dockerfile: Dockerfile.dev
22
+ ports:
23
+ - "${FRONTEND_PORT:-7860}:7860"
24
+ volumes:
25
+ - ./frontend:/app
26
+ - /app/node_modules
27
+ environment:
28
+ - NODE_ENV=${ENVIRONMENT:-development}
29
+ - CHOKIDAR_USEPOLLING=true
30
+ - PORT=${FRONTEND_PORT:-7860}
31
+ command: npm start
32
+ stdin_open: true
33
+ tty: true
frontend/Dockerfile.dev ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18
2
+
3
+ WORKDIR /app
4
+
5
+ # Install required global dependencies
6
+ RUN npm install -g react-scripts
7
+
8
+ # Copy package.json and package-lock.json
9
+ COPY package*.json ./
10
+
11
+ # Install project dependencies
12
+ RUN npm install
13
+
14
+ # Volume will be mounted here, no need for COPY
15
+ CMD ["npm", "start"]
frontend/README.md ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Frontend - Open LLM Leaderboard 🏆
2
+
3
+ React interface for exploring and comparing open-source language models.
4
+
5
+ ## 🏗 Architecture
6
+
7
+ ```mermaid
8
+ flowchart TD
9
+ Client(["User Browser"]) --> Components["React Components"]
10
+
11
+ subgraph Frontend
12
+ Components --> Context["Context Layer<br>• LeaderboardContext<br>• Global State"]
13
+
14
+ API["API Layer<br>• /api/leaderboard/formatted<br>• TanStack Query"] --> |Data Feed| Context
15
+
16
+ Context --> Hooks["Hooks Layer<br>• Data Processing<br>• Filtering<br>• Caching"]
17
+
18
+ Hooks --> Features["Features<br>• Table Management<br>• Search & Filters<br>• Display Options"]
19
+ Features --> Cache["Cache Layer<br>• LocalStorage<br>• URL State"]
20
+ end
21
+
22
+ API --> Backend["Backend Server"]
23
+
24
+ style Backend fill:#f96,stroke:#333,stroke-width:2px
25
+ ```
26
+
27
+ ## ✨ Core Features
28
+
29
+ - 🔍 **Search & Filters**: Real-time filtering, regex search, advanced filters
30
+ - 📊 **Data Visualization**: Interactive table, customizable columns, sorting
31
+ - 🔄 **State Management**: URL sync, client-side caching (5min TTL)
32
+ - 📱 **Responsive Design**: Mobile-friendly, dark/light themes
33
+
34
+ ## 🛠 Tech Stack
35
+
36
+ - React 18 + Material-UI
37
+ - TanStack Query & Table
38
+ - React Router v6
39
+
40
+ ## 📁 Project Structure
41
+
42
+ ```
43
+ src/
44
+ ├── pages/
45
+ │ └── LeaderboardPage/
46
+ │ ├── components/ # UI Components
47
+ │ ├── context/ # Global State
48
+ │ └── hooks/ # Data Processing
49
+ ├── components/ # Shared Components
50
+ └── utils/ # Helper Functions
51
+ ```
52
+
53
+ ## 🚀 Development
54
+
55
+ ```bash
56
+ # Install dependencies
57
+ npm install
58
+
59
+ # Start development server
60
+ npm start
61
+
62
+ # Production build
63
+ npm run build
64
+ ```
65
+
66
+ ## 🔧 Environment Variables
67
+
68
+ ```env
69
+ # API Configuration
70
+ REACT_APP_API_URL=http://localhost:8000
71
+ REACT_APP_CACHE_DURATION=300000 # 5 minutes
72
+ ```
73
+
74
+ ## 🔄 Data Flow
75
+
76
+ 1. API fetches leaderboard data from backend
77
+ 2. Context stores and manages global state
78
+ 3. Hooks handle data processing and filtering
79
+ 4. Components render based on processed data
80
+ 5. Cache maintains user preferences and URL state
frontend/package.json ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "open-llm-leaderboard",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "dependencies": {
6
+ "@emotion/react": "^11.13.3",
7
+ "@emotion/styled": "^11.13.0",
8
+ "@huggingface/hub": "^0.14.0",
9
+ "@mui/icons-material": "^6.1.7",
10
+ "@mui/lab": "^6.0.0-beta.16",
11
+ "@mui/material": "^6.1.6",
12
+ "@mui/x-data-grid": "^7.22.2",
13
+ "@tanstack/react-query": "^5.62.2",
14
+ "@tanstack/react-table": "^8.20.5",
15
+ "@tanstack/react-virtual": "^3.10.9",
16
+ "@testing-library/jest-dom": "^5.17.0",
17
+ "@testing-library/react": "^13.4.0",
18
+ "@testing-library/user-event": "^13.5.0",
19
+ "compression": "^1.7.4",
20
+ "cors": "^2.8.5",
21
+ "express": "^4.18.2",
22
+ "react": "^18.3.1",
23
+ "react-dom": "^18.3.1",
24
+ "react-router-dom": "^6.28.0",
25
+ "react-scripts": "5.0.1",
26
+ "serve-static": "^1.15.0",
27
+ "web-vitals": "^2.1.4"
28
+ },
29
+ "scripts": {
30
+ "start": "react-scripts start",
31
+ "build": "react-scripts build",
32
+ "test": "react-scripts test",
33
+ "eject": "react-scripts eject",
34
+ "serve": "node server.js"
35
+ },
36
+ "eslintConfig": {
37
+ "extends": [
38
+ "react-app",
39
+ "react-app/jest"
40
+ ]
41
+ },
42
+ "browserslist": {
43
+ "production": [
44
+ ">0.2%",
45
+ "not dead",
46
+ "not op_mini all"
47
+ ],
48
+ "development": [
49
+ "last 1 chrome version",
50
+ "last 1 firefox version",
51
+ "last 1 safari version"
52
+ ]
53
+ },
54
+ "proxy": "http://backend:8000"
55
+ }
frontend/public/index.html ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <link rel="icon" href="%PUBLIC_URL%/logo32.png" />
6
+ <meta
7
+ name="viewport"
8
+ content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover"
9
+ />
10
+ <meta
11
+ name="description"
12
+ content="Interactive leaderboard tracking and comparing open-source Large Language Models across multiple benchmarks: IFEval, BBH, MATH, GPQA, MUSR, and MMLU-PRO."
13
+ />
14
+
15
+ <!-- Open Graph / Facebook -->
16
+ <meta property="og:type" content="website" />
17
+ <meta
18
+ property="og:url"
19
+ content="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard"
20
+ />
21
+ <meta
22
+ property="og:title"
23
+ content="Open LLM Leaderboard - Compare Open Source Large Language Models"
24
+ />
25
+ <meta
26
+ property="og:description"
27
+ content="Interactive leaderboard for comparing LLM performance across multiple benchmarks. Features real-time filtering, community voting, and comprehensive model analysis with benchmarks like IFEval, BBH, MATH, GPQA, MUSR, and MMLU-PRO."
28
+ />
29
+ <meta property="og:image" content="%PUBLIC_URL%/og-image.png" />
30
+
31
+ <!-- Twitter -->
32
+ <meta property="twitter:card" content="summary_large_image" />
33
+ <meta
34
+ property="twitter:url"
35
+ content="https://huggingface.co/spaces/HuggingFaceH4/open_llm_leaderboard"
36
+ />
37
+ <meta
38
+ property="twitter:title"
39
+ content="Open LLM Leaderboard - Compare Open Source Large Language Models"
40
+ />
41
+ <meta
42
+ property="twitter:description"
43
+ content="Interactive leaderboard for comparing LLM performance across multiple benchmarks. Features real-time filtering, community voting, and comprehensive model analysis with benchmarks like IFEval, BBH, MATH, GPQA, MUSR, and MMLU-PRO."
44
+ />
45
+ <meta property="twitter:image" content="%PUBLIC_URL%/og-image.png" />
46
+ <!--
47
+ Notice the use of %PUBLIC_URL% in the tags above.
48
+ It will be replaced with the URL of the `public` folder during the build.
49
+ Only files inside the `public` folder can be referenced from the HTML.
50
+
51
+ Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
52
+ work correctly both with client-side routing and a non-root public URL.
53
+ Learn how to configure a non-root public URL by running `npm run build`.
54
+ -->
55
+ <title>
56
+ Open LLM Leaderboard - Compare Open Source Large Language Models
57
+ </title>
58
+ <link
59
+ href="https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;600;700&display=swap"
60
+ rel="stylesheet"
61
+ />
62
+ <style>
63
+ html,
64
+ body {
65
+ position: fixed;
66
+ width: 100%;
67
+ height: 100%;
68
+ overflow: hidden;
69
+ -webkit-overflow-scrolling: touch;
70
+ }
71
+ #root {
72
+ position: absolute;
73
+ top: 0;
74
+ left: 0;
75
+ right: 0;
76
+ bottom: 0;
77
+ overflow-y: auto;
78
+ -webkit-overflow-scrolling: touch;
79
+ }
80
+ </style>
81
+ </head>
82
+ <body>
83
+ <noscript>You need to enable JavaScript to run this app.</noscript>
84
+ <div id="root"></div>
85
+ <!--
86
+ This HTML file is a template.
87
+ If you open it directly in the browser, you will see an empty page.
88
+
89
+ You can add webfonts, meta tags, or analytics to this file.
90
+ The build step will place the bundled scripts into the <body> tag.
91
+
92
+ To begin the development, run `npm start` or `yarn start`.
93
+ To create a production bundle, use `npm run build` or `yarn build`.
94
+ -->
95
+ </body>
96
+ </html>
frontend/public/logo256.png ADDED
frontend/public/logo32.png ADDED
frontend/public/og-image.jpg ADDED
frontend/public/robots.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # https://www.robotstxt.org/robotstxt.html
2
+ User-agent: *
3
+ Disallow:
frontend/server.js ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ const express = require("express");
2
+ const cors = require("cors");
3
+ const compression = require("compression");
4
+ const path = require("path");
5
+ const serveStatic = require("serve-static");
6
+ const { createProxyMiddleware } = require("http-proxy-middleware");
7
+
8
+ const app = express();
9
+ const port = process.env.PORT || 7860;
10
+ const apiPort = process.env.INTERNAL_API_PORT || 7861;
11
+
12
+ // Enable CORS for all routes
13
+ app.use(cors());
14
+
15
+ // Enable GZIP compression
16
+ app.use(compression());
17
+
18
+ // Proxy all API requests to the Python backend
19
+ app.use(
20
+ "/api",
21
+ createProxyMiddleware({
22
+ target: `http://127.0.0.1:${apiPort}`,
23
+ changeOrigin: true,
24
+ onError: (err, req, res) => {
25
+ console.error("Proxy Error:", err);
26
+ res.status(500).json({ error: "Proxy Error", details: err.message });
27
+ },
28
+ })
29
+ );
30
+
31
+ // Serve static files from the build directory
32
+ app.use(
33
+ express.static(path.join(__dirname, "build"), {
34
+ // Don't cache HTML files
35
+ setHeaders: (res, path) => {
36
+ if (path.endsWith(".html")) {
37
+ res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
38
+ res.setHeader("Pragma", "no-cache");
39
+ res.setHeader("Expires", "0");
40
+ } else {
41
+ // Cache other static resources for 1 year
42
+ res.setHeader("Cache-Control", "public, max-age=31536000");
43
+ }
44
+ },
45
+ })
46
+ );
47
+
48
+ // Middleware to preserve URL parameters
49
+ app.use((req, res, next) => {
50
+ // Don't interfere with API requests
51
+ if (req.url.startsWith("/api")) {
52
+ return next();
53
+ }
54
+
55
+ // Preserve original URL parameters
56
+ req.originalUrl = req.url;
57
+ next();
58
+ });
59
+
60
+ // Handle all other routes by serving index.html
61
+ app.get("*", (req, res) => {
62
+ // Don't interfere with API requests
63
+ if (req.url.startsWith("/api")) {
64
+ return next();
65
+ }
66
+
67
+ // Headers for client-side routing
68
+ res.set({
69
+ "Cache-Control": "no-cache, no-store, must-revalidate",
70
+ Pragma: "no-cache",
71
+ Expires: "0",
72
+ });
73
+
74
+ // Send index.html for all other routes
75
+ res.sendFile(path.join(__dirname, "build", "index.html"));
76
+ });
77
+
78
+ app.listen(port, "0.0.0.0", () => {
79
+ console.log(
80
+ `Frontend server is running on port ${port} in ${
81
+ process.env.NODE_ENV || "development"
82
+ } mode`
83
+ );
84
+ console.log(`API proxy target: http://127.0.0.1:${apiPort}`);
85
+ });
frontend/src/App.js ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useEffect } from "react";
2
+ import {
3
+ HashRouter as Router,
4
+ Routes,
5
+ Route,
6
+ useSearchParams,
7
+ useLocation,
8
+ } from "react-router-dom";
9
+ import { ThemeProvider } from "@mui/material/styles";
10
+ import { Box, CssBaseline } from "@mui/material";
11
+ import Navigation from "./components/Navigation/Navigation";
12
+ import LeaderboardPage from "./pages/LeaderboardPage/LeaderboardPage";
13
+ import QuotePage from "./pages/QuotePage/QuotePage";
14
+ import Footer from "./components/Footer/Footer";
15
+ import getTheme from "./config/theme";
16
+ import { useThemeMode } from "./hooks/useThemeMode";
17
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
18
+ import LeaderboardProvider from "./pages/LeaderboardPage/components/Leaderboard/context/LeaderboardContext";
19
+
20
+ const queryClient = new QueryClient({
21
+ defaultOptions: {
22
+ queries: {
23
+ retry: 1,
24
+ refetchOnWindowFocus: false,
25
+ },
26
+ },
27
+ });
28
+
29
+ function UrlHandler() {
30
+ const location = useLocation();
31
+ const [searchParams] = useSearchParams();
32
+
33
+ // Synchroniser l'URL avec la page parente HF
34
+ useEffect(() => {
35
+ // Vérifier si nous sommes dans un iframe HF Space
36
+ const isHFSpace = window.location !== window.parent.location;
37
+ if (!isHFSpace) return;
38
+
39
+ // Sync query and hash from this embedded app to the parent page URL
40
+ const queryString = window.location.search;
41
+ const hash = window.location.hash;
42
+
43
+ // HF Spaces' special message type to update the query string and the hash in the parent page URL
44
+ window.parent.postMessage(
45
+ {
46
+ queryString,
47
+ hash,
48
+ },
49
+ "https://huggingface.co"
50
+ );
51
+ }, [location, searchParams]);
52
+
53
+ // Read the updated hash reactively
54
+ useEffect(() => {
55
+ const handleHashChange = (event) => {
56
+ console.log("hash change event", event);
57
+ };
58
+
59
+ window.addEventListener("hashchange", handleHashChange);
60
+ return () => window.removeEventListener("hashchange", handleHashChange);
61
+ }, []);
62
+
63
+ return null;
64
+ }
65
+
66
+ function App() {
67
+ const { mode, toggleTheme } = useThemeMode();
68
+ const theme = getTheme(mode);
69
+
70
+ return (
71
+ <div
72
+ className="App"
73
+ style={{
74
+ height: "100%",
75
+ width: "100%",
76
+ WebkitOverflowScrolling: "touch",
77
+ overflow: "auto",
78
+ }}
79
+ >
80
+ <QueryClientProvider client={queryClient}>
81
+ <ThemeProvider theme={theme}>
82
+ <CssBaseline />
83
+ <Router>
84
+ <LeaderboardProvider>
85
+ <UrlHandler />
86
+ <Box
87
+ sx={{
88
+ minHeight: "100vh",
89
+ display: "flex",
90
+ flexDirection: "column",
91
+ bgcolor: "background.default",
92
+ color: "text.primary",
93
+ }}
94
+ >
95
+ <Navigation onToggleTheme={toggleTheme} mode={mode} />
96
+ <Box
97
+ sx={{
98
+ flex: 1,
99
+ display: "flex",
100
+ flexDirection: "column",
101
+ width: "100%",
102
+ px: 4,
103
+ pb: 4,
104
+ }}
105
+ >
106
+ <Routes>
107
+ <Route path="/" element={<LeaderboardPage />} />
108
+ <Route path="/quote" element={<QuotePage />} />
109
+ </Routes>
110
+ </Box>
111
+ <Footer />
112
+ </Box>
113
+ </LeaderboardProvider>
114
+ </Router>
115
+ </ThemeProvider>
116
+ </QueryClientProvider>
117
+ </div>
118
+ );
119
+ }
120
+
121
+ export default App;
frontend/src/components/Footer/Footer.js ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import { Box, Typography, Link } from "@mui/material";
3
+
4
+ const Footer = () => {
5
+ return (
6
+ <Box
7
+ component="footer"
8
+ sx={{
9
+ width: "100%",
10
+ py: 4,
11
+ textAlign: "center",
12
+ }}
13
+ >
14
+ <Typography variant="body2" color="text.secondary" sx={{ mx: 4 }}>
15
+ © 2024 Hugging Face - Open LLM Leaderboard - Made with 🤗 by the HF team
16
+ -{" "}
17
+ <Link
18
+ href="https://huggingface.co"
19
+ target="_blank"
20
+ rel="noopener noreferrer"
21
+ color="inherit"
22
+ >
23
+ huggingface.co
24
+ </Link>
25
+ </Typography>
26
+ </Box>
27
+ );
28
+ };
29
+
30
+ export default Footer;
frontend/src/components/Logo/HFLogo.js ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+
3
+ const HFLogo = () => (
4
+ <svg width="100%" viewBox="0 0 236 220" version="1.1" xmlns="http://www.w3.org/2000/svg" >
5
+ <title>hg-logo</title>
6
+ <g id="hg-logo" stroke="none" strokeWidth="1" fill="none">
7
+ <g id="Group" transform="translate(-1.000000, 0.000000)">
8
+ <path d="M236.188357,161.726225 C235.570415,159.393906 234.569281,157.181253 233.22638,155.176863 C233.514062,154.120588 233.732701,153.048205 233.879994,151.965466 C234.832798,145.089325 232.449638,138.794251 227.956041,133.922501 C225.522249,131.262254 222.913547,129.506398 220.150646,128.428262 C221.964195,120.669591 222.882477,112.729122 222.88708,104.761037 C222.88708,101.122758 222.681099,97.5581193 222.335881,94.046409 C222.155216,92.2928551 221.937728,90.5427531 221.683417,88.7984042 C220.891716,83.6516545 219.717972,78.5709507 218.171392,73.5986359 C217.1576,70.3316637 215.985007,67.1160095 214.658216,63.9632945 C212.668606,59.2945148 210.345284,54.7746261 207.706662,50.4388375 C205.974815,47.5549087 204.089921,44.7659066 202.058884,42.0841428 C201.063504,40.7298561 200.026697,39.4075568 198.947313,38.1190859 C195.750588,34.2338824 192.277687,30.5855928 188.552777,27.2030978 C187.316894,26.0660493 186.045339,24.9682371 184.739261,23.9111571 C183.453897,22.8390039 182.139764,21.8011393 180.799165,20.798714 C178.100706,18.7906417 175.311338,16.9066068 172.44142,15.1525926 C156.583223,5.52185376 137.986291,0 118.109749,0 C60.2385495,0 13.3336831,46.9018135 13.3336831,104.76564 C13.3321871,112.833829 14.2670394,120.874403 16.1195981,128.726274 C13.6340233,129.805561 11.2508635,131.486626 9.04261448,133.920199 C4.55016831,138.788498 2.16585774,145.06171 3.11981211,151.9367 C3.26365324,153.029795 3.48229176,154.111383 3.77227548,155.174561 C2.4301802,157.180102 1.42939115,159.393906 0.810298929,161.726225 C-0.570575919,166.97423 -0.116037948,171.706754 1.63882384,175.85246 C-0.267934182,181.273058 0.208467641,187.044598 2.69289164,192.062477 C4.49953623,195.727221 7.08522438,198.561213 10.2715931,201.096041 C14.0609438,204.107229 18.8042489,206.667372 24.5268244,209.121657 C31.3529491,212.032741 39.6842274,214.767779 43.4735781,215.771124 C53.2616793,218.305953 62.6470253,219.912227 72.1599872,219.989319 C85.7109724,220.115888 97.3816663,216.928654 105.738261,208.774168 C109.842911,209.276992 113.974028,209.528979 118.109749,209.527828 C122.479067,209.518623 126.843782,209.242473 131.179729,208.70398 C139.51561,216.910244 151.231182,220.126243 164.8328,219.996223 C174.343575,219.921432 183.728921,218.315158 193.491706,215.776877 C197.306373,214.773532 205.63535,212.038494 212.464927,209.128561 C218.187502,206.668523 222.929657,204.109531 226.745474,201.101795 C229.907678,198.568116 232.491064,195.732974 234.29886,192.068231 C236.8086,187.050351 237.260836,181.278811 235.378244,175.858213 C237.116995,171.712507 237.568081,166.969627 236.188357,161.726225 Z M226.477354,175.501519 C228.400223,179.150153 228.523351,183.272846 226.826025,187.112485 C224.252995,192.932351 217.861846,197.515294 205.448932,202.436521 C197.730992,205.498336 190.662064,207.4544 190.599924,207.47281 C180.390656,210.1204 171.157207,211.464332 163.164243,211.464332 C149.928557,211.464332 140.080618,207.813396 133.834461,200.601272 C123.271919,202.399701 112.486136,202.460684 101.904031,200.781921 C95.6509699,207.874379 85.857115,211.464332 72.7330503,211.464332 C64.7390507,211.464332 55.5067517,210.1204 45.2974836,207.47281 C45.2341935,207.4544 38.1698679,205.498336 30.4484761,202.436521 C18.0355619,197.515294 11.6432621,192.934652 9.07138271,187.112485 C7.37405737,183.272846 7.49718538,179.150153 9.4200536,175.501519 C9.59841661,175.163235 9.7882869,174.831854 9.99196594,174.513131 C8.83939573,172.78259 8.06645104,170.826526 7.72364885,168.77611 C7.38096175,166.725695 7.47635718,164.624652 8.00350616,162.613358 C8.76759024,159.711479 10.3463905,157.297466 12.489048,155.563473 C11.4573043,153.86745 10.7801003,151.980424 10.4982867,150.015155 C9.88149595,145.74173 11.2991941,141.474059 14.4913165,137.995716 C16.9757405,135.288294 20.4889162,133.798233 24.3795311,133.798233 L24.4830967,133.798233 C21.5502336,124.39877 20.0630314,114.608094 20.0723523,104.762188 C20.0723523,51.0601755 63.612487,7.52279222 117.324951,7.52279222 C171.038681,7.52279222 214.577665,51.0567236 214.577665,104.762188 C214.58457,114.634558 213.087471,124.450548 210.137002,133.871873 C210.606499,133.825848 211.066791,133.801685 211.517877,133.801685 C215.407341,133.801685 218.921668,135.290595 221.406092,137.998018 C224.595912,141.474059 226.017063,145.745182 225.399121,150.017456 C225.117193,151.982725 224.440564,153.870902 223.40836,155.566925 C225.551018,157.299767 227.129818,159.71378 227.893902,162.61681 C228.419785,164.628104 228.515296,166.727996 228.172378,168.779562 C227.829461,170.829977 227.057322,172.784891 225.905442,174.516583 C226.109121,174.831854 226.301293,175.163235 226.477354,175.501519 Z" id="Shape" fill="#FFFFFF" fillRule="nonzero"></path>
9
+ <path d="M226.52977,174.037682 C227.682419,172.305523 228.455074,170.350082 228.798221,168.299114 C229.141367,166.246994 229.045793,164.146536 228.519558,162.134699 C227.754964,159.232038 226.175108,156.817373 224.031019,155.082913 C225.062761,153.386432 225.740993,151.498897 226.02311,149.533098 C226.640313,145.258521 225.221668,140.989698 222.027412,137.510418 C219.541328,134.802265 216.024653,133.311802 212.13259,133.311802 C211.680051,133.311802 211.220603,133.334821 210.750792,133.382009 C213.699779,123.958143 215.195575,114.139506 215.186363,104.265624 C215.186363,50.5501622 171.617132,7 117.873265,7 C64.1293973,7 20.5555606,50.5455585 20.5555606,104.265624 C20.5462334,114.114185 22.0344295,123.907502 24.9692525,133.3095 L24.8656177,133.3095 C20.9735543,133.3095 17.4580309,134.799963 14.9719466,137.508116 C11.7799941,140.985094 10.3590456,145.256219 10.9762485,149.530796 C11.2580201,151.496595 11.9356766,153.384131 12.9683401,155.080611 C10.8242508,156.815072 9.24439546,159.229736 8.48095227,162.133548 C7.95379648,164.145385 7.85868274,166.246994 8.20205945,168.299114 C8.54543616,170.350082 9.31935798,172.306674 10.4730439,174.037682 C10.2669257,174.356491 10.07808,174.687961 9.90074934,175.026336 C7.97774764,178.675955 7.85338585,182.79976 9.55184544,186.640434 C12.1254435,192.460719 18.522015,197.0472 30.9432242,201.968603 C38.6663215,205.031245 45.7399738,206.987836 45.8021547,207.006251 C56.0182452,209.654556 65.2567139,211 73.2550191,211 C86.3890056,211 96.1882538,207.409079 102.446646,200.313557 C113.035821,201.992773 123.828812,201.931773 134.398413,200.13286 C140.647592,207.346928 150.503264,211 163.747794,211 C171.7461,211 180.98572,209.654556 191.20181,207.006251 C191.263991,206.987836 198.334189,205.031245 206.060741,201.968603 C218.48195,197.0472 224.878521,192.460719 227.45212,186.640434 C229.150579,182.79976 229.027369,178.675955 227.103216,175.026336 C226.927036,174.684508 226.733585,174.354189 226.52977,174.037682 Z M97.9684697,189.207022 C97.4295686,190.149639 96.8526681,191.069237 96.2377682,191.963514 C94.6199135,194.33099 92.4919451,196.139111 90.0231334,197.484555 C85.30084,200.056898 79.3257167,200.95693 73.2538676,200.95693 C63.6641921,200.95693 53.832702,198.713755 48.3216324,197.284293 C48.0510304,197.214085 14.5435894,187.75454 18.7857081,179.70259 C19.4996369,178.349089 20.6753163,177.808149 22.1538398,177.808149 C28.1266601,177.808149 39.0026741,186.697981 43.677756,186.697981 C44.7210132,186.697981 45.4578568,186.252568 45.7583978,185.169537 C47.7504894,178.027978 15.4820603,175.026336 18.1995956,164.686325 C18.6797703,162.856336 19.9798115,162.113982 21.8095419,162.113982 C29.7053639,162.112831 47.4292214,175.993123 51.1358936,175.993123 C51.4203136,175.993123 51.6241287,175.910255 51.7346726,175.735313 L51.7830355,175.655898 C53.5217975,172.784312 52.5246002,170.696514 40.6042927,163.399578 L39.4597036,162.703262 C26.3441411,154.767556 17.1367629,149.993472 22.3737759,144.296338 C22.9760094,143.638002 23.8292694,143.346815 24.8667692,143.346815 C26.0977205,143.346815 27.5866075,143.758851 29.2263407,144.448261 C36.1537528,147.368187 45.7549433,155.331515 49.7656109,158.80504 C50.9481994,159.833977 51.6448557,160.462389 51.6448557,160.462389 C51.6448557,160.462389 56.722962,165.740582 59.7940072,165.740582 C60.501027,165.740582 61.099806,165.463207 61.5062848,164.773796 C63.684919,161.104611 41.282525,144.137509 40.0193317,137.137514 C39.1637686,132.393355 40.6204136,129.991351 43.3160705,129.991351 C44.5965363,129.991351 46.1602706,130.535744 47.8863662,131.630284 C53.240832,135.027848 63.5789812,152.784493 67.3639552,159.691261 C68.6329061,162.006945 70.7988738,162.985241 72.750663,162.985241 C76.6231508,162.985241 79.6504392,159.137661 73.1053244,154.248484 C63.2680768,146.890548 66.7202678,134.865566 71.4149253,134.125514 C71.6152859,134.094439 71.8179496,134.078326 72.0194617,134.077175 C76.2892164,134.077175 78.1730672,141.431658 78.1730672,141.431658 C78.1730672,141.431658 83.6921972,155.286628 93.1747834,164.756532 C101.779928,173.352875 102.980941,180.408114 97.9684697,189.207022 Z M128.631711,190.829842 L128.140021,190.88854 L127.300579,190.985218 C126.859555,191.030105 126.418531,191.07384 125.975205,191.115274 L125.542241,191.154406 L125.148429,191.187783 L124.58765,191.23267 C124.381531,191.247632 124.175413,191.263745 123.969295,191.276405 L123.352092,191.317839 L123.216215,191.325896 L122.730283,191.354669 L122.524165,191.365027 L121.948416,191.393801 L121.279396,191.421423 L120.671405,191.44214 L120.266077,191.453649 L120.061111,191.453649 C119.934446,191.453649 119.808933,191.460555 119.682268,191.461706 L119.480756,191.461706 C119.354091,191.461706 119.228578,191.461706 119.101913,191.468612 L118.587193,191.474366 L117.866356,191.474366 C117.30097,191.474366 116.737888,191.468612 116.174805,191.458253 L115.718812,191.447895 C115.589844,191.447895 115.459725,191.440989 115.330757,191.437536 L114.848279,191.422574 L114.247197,191.399555 L113.707145,191.376537 L113.566662,191.370782 L113.051942,191.34316 C112.909157,191.335103 112.766371,191.328197 112.624737,191.31899 L112.291954,191.299424 C111.87396,191.272952 111.455966,191.243028 111.037972,191.210802 L110.600403,191.176274 L110.047684,191.129085 L109.401694,191.069237 C109.041275,191.03586 108.680856,190.99903 108.320437,190.958747 L108.303164,190.958747 C113.56551,179.224952 110.904399,168.266887 100.270314,157.646048 C93.2968422,150.685185 88.6563052,140.403871 87.6948043,138.146885 C85.7464697,131.4657 80.58891,124.038709 72.0252192,124.038709 C71.300927,124.03986 70.5789377,124.097406 69.8638574,124.210198 C66.1111254,124.799478 62.831659,126.958634 60.491815,130.206576 C57.9642769,127.063369 55.5058286,124.564687 53.2834374,123.152488 C49.9325781,121.030161 46.5897794,119.952885 43.33104,119.952885 C39.2662519,119.952885 35.6309727,121.621743 33.097677,124.648705 L33.0343446,124.725818 L32.8915589,124.12618 L32.8858014,124.100859 C32.4040146,122.040683 31.9992631,119.962092 31.6721225,117.871992 C31.6721225,117.85703 31.6721225,117.843219 31.6652135,117.829408 L31.5938206,117.356373 C31.5552454,117.100865 31.5175914,116.844206 31.4809738,116.588698 C31.4510349,116.375775 31.4210959,116.162852 31.39346,115.95108 C31.365824,115.739307 31.3358851,115.526384 31.3105521,115.313461 C31.2829161,115.099387 31.2575832,114.886464 31.2322502,114.67354 C31.2079536,114.470976 31.1845782,114.268411 31.1620089,114.065846 L31.155215,114.014054 C31.0513499,113.079494 30.9623391,112.143782 30.8879523,111.20692 L30.8476499,110.664829 C30.8361349,110.516359 30.8257714,110.366737 30.8165594,110.215964 C30.8165594,110.181436 30.8119535,110.145757 30.8096505,110.113531 L30.7717662,109.512742 C30.7579482,109.295215 30.7474696,109.077688 30.7359546,108.860161 C30.7244396,108.643785 30.7129246,108.426258 30.7037126,108.20758 L30.6806827,107.637867 L30.6737737,107.465226 L30.6565012,106.938098 L30.6439499,106.491534 C30.6439499,106.313139 30.6357743,106.133593 30.6323198,105.955198 L30.6231078,105.39239 C30.6208048,105.204788 30.6231078,105.018336 30.6185018,104.830733 C30.6127443,104.64198 30.6185018,104.454377 30.6185018,104.265624 C30.6185018,56.0965241 69.6899812,17.0441057 117.887083,17.0441057 C166.084184,17.0441057 205.154512,56.0942222 205.154512,104.265624 L205.154512,105.39239 C205.154512,105.579993 205.149906,105.768746 205.1453,105.955198 C205.1453,106.111725 205.139542,106.2648 205.134936,106.421327 C205.134936,106.560591 205.129179,106.698703 205.123421,106.833362 C205.123421,107.009456 205.113058,107.184398 205.1073,107.360491 L205.1073,107.375453 C205.092331,107.757564 205.07621,108.139675 205.060089,108.521786 L205.05318,108.648389 L205.023241,109.219253 L204.995605,109.671571 C204.931121,110.743093 204.847062,111.814615 204.744579,112.883834 L204.744579,112.898797 C204.726155,113.09906 204.705428,113.300473 204.683549,113.500736 C204.632883,113.966865 204.581066,114.432995 204.529248,114.899124 L204.470522,115.367555 L204.397978,115.917702 C204.372645,116.119116 204.345009,116.319379 204.316221,116.519642 C204.285131,116.744075 204.251737,116.967356 204.219495,117.190638 L204.138891,117.717767 L204.044468,118.316254 C204.012226,118.515366 203.979984,118.713327 203.941984,118.912439 C203.902833,119.1104 203.872894,119.308361 203.83835,119.507473 C203.76926,119.903395 203.697867,120.298166 203.620716,120.692937 C203.467567,121.479026 203.307509,122.262814 203.138239,123.045451 C203.095633,123.239959 203.051876,123.435618 203.006968,123.630126 C200.550823,121.244235 197.298992,119.944828 193.710924,119.944828 C190.45564,119.944828 187.109386,121.020954 183.759679,123.144431 C181.536136,124.555479 179.079991,127.055313 176.550149,130.19852 C174.206851,126.949427 170.927385,124.791421 167.179258,124.200991 C166.464178,124.08935 165.741037,124.031803 165.016745,124.030652 C156.450751,124.030652 151.296646,131.457644 149.34716,138.138829 C148.381053,140.395815 143.740516,150.675978 136.758984,157.648349 C126.13296,168.234661 123.451121,179.144386 128.631711,190.829842 Z M218.724916,167.341535 L218.690371,167.443968 C218.66619,167.509571 218.642008,167.575175 218.615524,167.639627 C218.57407,167.737457 218.530313,167.832984 218.484253,167.928512 C218.372558,168.156398 218.245893,168.377377 218.106562,168.58915 C217.914261,168.875733 217.703537,169.149656 217.474389,169.407465 C217.42142,169.467314 217.370754,169.526012 217.310876,169.584709 C217.230272,169.673332 217.143909,169.759652 217.058698,169.845972 C215.507631,171.382472 213.144757,172.727916 210.473281,173.964022 C210.170437,174.100983 209.864139,174.237945 209.553234,174.374906 L209.244633,174.511868 C209.038515,174.602792 208.833548,174.693716 208.617066,174.782338 C208.406342,174.872111 208.194467,174.960733 207.982591,175.048204 L207.340055,175.31407 C205.83735,175.932123 204.297797,176.520251 202.795092,177.102625 L202.153708,177.351227 L201.518081,177.59983 C201.096633,177.764414 200.67979,177.928997 200.268706,178.093581 L199.65726,178.339882 L199.05733,178.586182 L198.761395,178.709332 C198.56564,178.7922 198.372188,178.872765 198.18104,178.955633 C193.76159,180.850074 190.583456,182.777892 191.251325,185.170688 C191.269749,185.238594 191.290476,185.303046 191.313506,185.365197 C191.373383,185.542441 191.45514,185.710477 191.556472,185.867005 C191.61635,185.961382 191.685439,186.050004 191.76259,186.130569 C192.547911,186.945432 193.97692,186.816527 195.779015,186.169701 C195.925255,186.116758 196.07495,186.060362 196.225796,186.000513 C196.329431,185.961382 196.431914,185.919948 196.535549,185.878514 L196.691001,185.812911 C197.103238,185.637968 197.539655,185.44346 197.986437,185.230537 C198.098132,185.178745 198.210979,185.128104 198.323826,185.068255 C200.526641,183.99213 203.02424,182.540799 205.502264,181.220675 C205.882259,181.014658 206.263404,180.817847 206.642247,180.622188 C207.261753,180.301077 207.887017,179.991475 208.518038,179.695685 C210.86479,178.593088 213.069909,177.810451 214.844368,177.810451 C215.675749,177.810451 216.411556,177.98079 217.023002,178.372108 L217.125485,178.440013 C217.435238,178.658691 217.704689,178.926859 217.924625,179.23531 C217.975291,179.307819 218.02826,179.38263 218.073168,179.459743 C218.121531,179.539157 218.167591,179.620874 218.211348,179.703741 C219.087638,181.365693 218.354134,183.088645 216.638402,184.777068 C214.990608,186.397586 212.427373,187.987029 209.512932,189.459077 C209.297602,189.568416 209.079969,189.677755 208.861184,189.783641 C200.189252,194.039803 188.835482,197.245161 188.676575,197.285443 C185.650438,198.069231 181.323109,199.099319 176.448818,199.869295 L175.726828,199.980936 L175.609375,199.9982 C174.503937,200.163935 173.395045,200.310104 172.283849,200.436707 L172.181366,200.447065 C170.160487,200.677253 168.08779,200.844138 166.011639,200.914346 L165.980549,200.914346 C165.234378,200.941968 164.489359,200.954628 163.743188,200.954628 L162.884171,200.954628 C161.746491,200.938515 160.609962,200.887874 159.475737,200.800403 C159.449252,200.800403 159.421616,200.800403 159.39398,200.794648 C158.988653,200.763573 158.582174,200.725592 158.177998,200.680705 C157.482494,200.605895 156.78814,200.51382 156.09609,200.405632 C155.826639,200.360745 155.538765,200.313557 155.261254,200.265217 C155.134589,200.242199 155.009076,200.21918 154.883563,200.193859 L154.853624,200.188105 C154.454054,200.112143 154.056787,200.028125 153.660672,199.937201 C153.431524,199.885408 153.201224,199.833616 152.974379,199.772617 L152.838502,199.736938 C152.725655,199.709315 152.615111,199.679391 152.504568,199.649467 L152.443538,199.633353 L152.087725,199.53092 C151.958758,199.49409 151.830941,199.456109 151.701974,199.418128 L151.655914,199.404317 L151.320828,199.301884 C151.194163,199.262752 151.06865,199.221318 150.943136,199.181036 C150.839501,199.146507 150.737018,199.110828 150.633383,199.077451 L150.406538,198.998037 C150.187754,198.922075 149.971272,198.841509 149.75479,198.758642 L149.549824,198.679227 L149.380554,198.611322 C149.05468,198.480115 148.729957,198.343154 148.408689,198.199287 L148.194511,198.096854 L148.158814,198.08074 C148.082815,198.045061 148.007968,198.009382 147.931969,197.977156 C147.783426,197.905798 147.636034,197.832138 147.489794,197.757327 L147.446037,197.73546 L147.234161,197.623819 C146.857621,197.42816 146.48799,197.218689 146.12642,196.995408 L145.928362,196.874559 C145.825879,196.812409 145.732608,196.749107 145.63473,196.685806 L145.376795,196.514316 C145.283524,196.452166 145.190252,196.388864 145.099284,196.323261 L144.933468,196.205865 C144.75844,196.080413 144.586867,195.952659 144.417597,195.820302 C144.32778,195.751245 144.239115,195.679887 144.150449,195.614284 C143.953543,195.456606 143.761243,195.294324 143.571246,195.127438 L143.563185,195.120532 C143.377794,194.960552 143.197009,194.794817 143.02083,194.62563 L143.011618,194.61412 C142.919498,194.526649 142.829681,194.439178 142.739864,194.348254 C142.651199,194.25733 142.561382,194.167557 142.47502,194.076633 C142.387506,193.984558 142.299992,193.891332 142.214781,193.795805 C142.131873,193.706032 142.051269,193.615108 141.970664,193.523033 L141.943028,193.491958 C141.527337,193.009715 141.138131,192.504454 140.777712,191.978476 L140.634926,191.767855 C140.540503,191.628592 140.447232,191.488178 140.355112,191.347763 L140.083358,190.929973 C139.949785,190.726258 139.819665,190.52024 139.693001,190.313072 C139.602032,190.165752 139.512215,190.018432 139.42355,189.869961 C139.348703,189.743358 139.273855,189.617906 139.202462,189.493605 C139.16216,189.4257 139.124161,189.356644 139.086161,189.287587 C139.048162,189.216229 139.003253,189.14372 138.962951,189.071211 L138.898467,188.957269 C138.876589,188.919288 138.86968,188.907778 138.858165,188.882458 C138.779863,188.745496 138.703864,188.606233 138.630168,188.464668 C138.596775,188.403668 138.563381,188.341518 138.527685,188.280518 L138.42405,188.082557 L138.321566,187.885747 L138.123509,187.486372 C138.091267,187.420769 138.060176,187.355165 138.029086,187.289562 C137.961148,187.147997 137.896664,187.008734 137.83218,186.86947 C137.806847,186.813074 137.780362,186.756679 137.757332,186.700283 C137.668667,186.508076 137.584608,186.313568 137.505154,186.116758 C137.451034,185.986702 137.40152,185.857797 137.348551,185.728892 C137.200007,185.349083 137.060676,184.965822 136.93286,184.577956 C136.89486,184.462863 136.856861,184.34892 136.823468,184.233826 C136.74056,183.972564 136.665712,183.708999 136.598925,183.443133 C136.517169,183.133531 136.444625,182.820477 136.383595,182.506271 C136.344444,182.32097 136.309899,182.134518 136.281112,181.946916 C136.250022,181.761615 136.223537,181.576314 136.201659,181.392165 C136.154447,181.025016 136.121054,180.656716 136.101478,180.287266 C136.101478,180.226266 136.095721,180.164116 136.094569,180.103116 C136.088812,179.981117 136.085357,179.859118 136.084206,179.737118 C136.019722,174.820319 138.510412,170.091121 143.833788,164.772645 C153.315222,155.303892 158.835504,141.447771 158.835504,141.447771 C158.835504,141.447771 158.984047,140.866548 159.2938,140.030968 C159.397435,139.753592 159.505676,139.477368 159.619674,139.204596 C159.749793,138.896145 159.889124,138.591147 160.038819,138.291903 L160.082576,138.204432 C160.274876,137.822321 160.483297,137.450569 160.710143,137.088024 C160.76196,137.004006 160.812626,136.919987 160.872504,136.83712 C161.040622,136.586216 161.219105,136.343368 161.406799,136.107426 C161.551888,135.926729 161.706188,135.751787 161.86855,135.586052 C162.418966,135.019792 163.046532,134.557115 163.757006,134.309664 L163.846823,134.280891 C163.906701,134.261325 163.965427,134.24291 164.026457,134.225646 C164.09785,134.207231 164.169243,134.189967 164.240636,134.175004 L164.272877,134.16925 C164.423724,134.139325 164.57457,134.118608 164.727719,134.10825 L164.741537,134.10825 C164.820991,134.10825 164.900444,134.100194 164.9822,134.100194 C165.186015,134.101344 165.388679,134.117458 165.590191,134.148533 C166.444603,134.283192 167.258712,134.791906 167.958823,135.576845 L168.053245,135.687335 C168.281242,135.954352 168.488512,136.239784 168.672751,136.539027 C168.810931,136.761158 168.94105,136.994798 169.064261,137.239948 C169.113775,137.343532 169.160987,137.437909 169.208198,137.539191 C169.231228,137.590983 169.254258,137.641624 169.276136,137.694567 C169.322196,137.797001 169.364802,137.900585 169.407407,138.008773 C169.64807,138.625675 169.834613,139.262143 169.965884,139.911271 C170.105215,140.612191 170.18582,141.32347 170.20885,142.038201 C170.21691,142.352407 170.21691,142.670065 170.20885,142.992327 C170.203092,143.114326 170.197335,143.237477 170.188123,143.360627 C170.0937,144.72909 169.795462,146.075685 169.302621,147.356677 C169.253106,147.48328 169.198986,147.612185 169.14832,147.74109 C168.900748,148.332671 168.612873,148.905838 168.288151,149.458287 C168.10276,149.773644 167.902399,150.086698 167.688221,150.397451 L167.55695,150.583903 C167.049139,151.292879 166.482602,151.958121 165.863096,152.572721 C165.252802,153.183868 164.598751,153.748977 163.906701,154.265748 C163.212348,154.777914 162.561751,155.346477 161.960669,155.96453 C160.229968,157.780707 159.826944,159.381659 160.21615,160.595897 C160.26221,160.732859 160.31633,160.867518 160.378511,160.997574 C160.522448,161.279553 160.708991,161.538514 160.932382,161.764098 L160.993411,161.825097 C161.056743,161.886097 161.12353,161.944795 161.191469,162.00119 L161.260559,162.057586 C161.428677,162.186491 161.606008,162.301585 161.791399,162.401716 C161.844368,162.43049 161.893883,162.459263 161.954912,162.486886 C162.209393,162.613489 162.475389,162.714771 162.749446,162.791884 L162.825444,162.81145 L162.927928,162.839072 L163.017745,162.86094 L163.113319,162.881657 L163.208893,162.901223 L163.299862,162.916185 C163.3678,162.928845 163.43689,162.939203 163.504828,162.948411 L163.571615,162.957618 L163.690219,162.970279 L163.763915,162.977184 L163.885974,162.986392 L163.956215,162.986392 L164.08288,162.992147 L164.48245,162.992147 L164.596448,162.986392 L164.727719,162.978335 L164.887777,162.963373 L165.037472,162.944958 C165.070866,162.944958 165.105411,162.935751 165.139955,162.929996 C165.667342,162.852883 166.18091,162.704412 166.666842,162.488036 C166.770477,162.444301 166.87296,162.395962 166.967383,162.34532 C167.170046,162.244038 167.366953,162.130095 167.555798,162.004643 C167.819491,161.832003 168.068215,161.637495 168.301969,161.425722 L168.46433,161.271497 C168.489663,161.246176 168.516148,161.222007 168.540329,161.195535 C168.699236,161.036706 168.848931,160.868669 168.988262,160.692576 C169.234682,160.381823 169.453467,160.051504 169.643464,159.703922 C171.896946,155.60889 174.258668,151.573708 176.726329,147.604129 L177.064869,147.062038 L177.406864,146.518796 C177.578437,146.244873 177.75001,145.973252 177.921584,145.705084 L178.09546,145.434614 L178.442061,144.894825 C178.907266,144.17549 179.373623,143.464211 179.843434,142.759838 L180.194641,142.235011 C180.899357,141.192263 181.602923,140.177137 182.298428,139.211501 L182.64618,138.735014 C183.402714,137.689964 184.19149,136.669083 185.010205,135.672372 L185.336079,135.283356 C185.389048,135.217753 185.438563,135.154451 185.497289,135.09115 L185.816254,134.721699 C185.869223,134.660699 185.919889,134.600851 185.975161,134.542153 L186.283762,134.193419 L186.439214,134.025383 C186.588909,133.863101 186.740907,133.703121 186.895208,133.544291 C186.998842,133.441858 187.094417,133.338274 187.194597,133.242746 C187.778407,132.64541 188.422094,132.110225 189.116447,131.646397 L189.276506,131.543964 L189.438867,131.44038 C189.542502,131.375927 189.647288,131.313777 189.754377,131.255079 C192.476519,129.711674 194.731152,129.597731 196.027738,130.893685 C196.814212,131.679775 197.248326,132.981483 197.225296,134.791906 C197.225296,134.872472 197.225296,134.951886 197.218387,135.034754 L197.218387,135.124527 C197.218387,135.206243 197.211478,135.290262 197.205721,135.37428 C197.205721,135.476713 197.193054,135.580298 197.182691,135.682731 C197.172327,135.786315 197.167722,135.864579 197.157358,135.955503 C197.157358,135.981974 197.151601,136.008446 197.148146,136.034917 C197.140086,136.115483 197.129722,136.197199 197.119359,136.277765 C197.119359,136.303086 197.119359,136.327255 197.108995,136.352576 C197.09748,136.461915 197.081359,136.571254 197.061784,136.679442 C197.049117,136.779573 197.032996,136.879705 197.013421,136.979836 C197.00536,137.037383 196.993845,137.093779 196.984633,137.151326 C196.954694,137.314759 196.915543,137.477041 196.868332,137.63587 C196.779667,137.932811 196.671426,138.223998 196.54361,138.507128 C196.488338,138.630279 196.429611,138.753429 196.369733,138.874277 C196.245372,139.12403 196.104889,139.379538 195.950588,139.644253 C195.873438,139.774309 195.793984,139.908969 195.708774,140.043628 L195.579806,140.248495 C195.448535,140.454513 195.311507,140.662832 195.168721,140.873453 C195.025935,141.084075 194.872786,141.2993 194.708122,141.516827 C194.495095,141.806863 194.270552,142.102653 194.033344,142.401897 L193.85371,142.628631 C193.3459,143.260495 192.825422,143.882001 192.29343,144.493148 L191.870831,144.970787 L191.6555,145.212483 C191.361868,145.5405 191.067085,145.866215 190.769998,146.189628 L190.542002,146.435928 C190.391156,146.599361 190.2334,146.765096 190.082554,146.92968 C189.928253,147.096566 189.77165,147.262301 189.615046,147.428035 L189.140629,147.927542 L188.660454,148.428199 C188.500396,148.595085 188.339186,148.764272 188.176825,148.931158 L187.199203,149.938227 C182.529879,154.724971 177.609528,159.495602 175.944461,162.469621 C175.833918,162.66413 175.733737,162.863242 175.642769,163.066957 C175.406712,163.607897 175.307683,164.070574 175.37447,164.444628 C175.396348,164.567778 175.440105,164.686325 175.503438,164.793362 C175.597861,164.955644 175.711859,165.105266 175.84313,165.238774 C175.904159,165.298623 175.967491,165.355019 176.036581,165.40566 C176.378576,165.649658 176.793115,165.77511 177.213412,165.76245 L177.344683,165.76245 L177.479408,165.753243 L177.615285,165.73713 L177.72698,165.721016 C177.74195,165.719865 177.756919,165.716413 177.771889,165.71296 L177.874372,165.693394 L177.902008,165.686488 L178.014855,165.662319 L178.055157,165.65196 L178.176065,165.620885 L178.320002,165.5783 C178.414425,165.549527 178.508848,165.517301 178.605573,165.481622 C178.813994,165.407962 179.01781,165.322792 179.217019,165.227265 C179.267685,165.203095 179.319502,165.180076 179.369017,165.153605 L179.522166,165.077643 C179.722526,164.976361 179.921736,164.867022 180.122096,164.748475 C180.282154,164.654099 180.441061,164.55742 180.599968,164.454987 L180.754269,164.352553 C180.804935,164.320327 180.856752,164.28695 180.907418,164.248969 L181.060567,164.146536 L181.141172,164.09014 L181.36226,163.935914 C181.568378,163.793198 181.760678,163.647029 181.956433,163.498559 L181.973705,163.485898 L182.282307,163.246503 C182.704906,162.912732 183.106779,162.576659 183.469501,162.260151 L183.711316,162.047228 L183.734346,162.026511 L183.859859,161.91487 C184.156945,161.649004 184.42179,161.400402 184.642877,161.194384 L184.732694,161.106913 L184.950327,160.900895 L185.080447,160.774292 L185.125355,160.728255 L185.138022,160.715595 C185.248565,160.605105 185.248565,160.605105 185.359109,160.493464 L185.368321,160.484256 L185.409775,160.447426 C185.422442,160.433615 185.439714,160.419804 185.45929,160.40254 L185.475411,160.387578 L185.519167,160.348446 L185.752922,160.142428 L185.884192,160.023882 C185.948676,159.968637 186.018918,159.905335 186.090311,159.83743 L186.248066,159.699318 C186.275702,159.676299 186.303338,159.649828 186.332125,159.624507 L186.496789,159.48064 L186.739755,159.268868 L186.867572,159.157227 C187.368473,158.721022 187.978767,158.195044 188.677727,157.602312 L188.96445,157.360615 L189.438867,156.963542 L189.922496,156.558413 C190.55582,156.033586 191.237507,155.475382 191.958344,154.89531 L192.43161,154.515501 C192.834634,154.195541 193.249174,153.867524 193.668319,153.538356 C193.836438,153.405998 194.008011,153.273641 194.183039,153.142434 C194.612547,152.810964 195.044359,152.480646 195.477322,152.154931 C196.465308,151.409124 197.484383,150.662166 198.514974,149.933624 L198.958301,149.626324 C199.266902,149.41225 199.575504,149.199326 199.885257,148.991007 L200.165071,148.801102 C200.718941,148.428199 201.277418,148.063352 201.8405,147.706562 L202.120315,147.530469 L202.397826,147.357828 C202.675337,147.186339 202.952848,147.016 203.226904,146.851416 L203.502112,146.687983 L204.05368,146.366872 C204.234465,146.262137 204.414099,146.159704 204.593732,146.058421 L204.701973,145.997422 L205.13033,145.763782 C205.307661,145.667103 205.486143,145.573877 205.662322,145.482953 L205.927167,145.347143 L206.1828,145.217087 C206.767761,144.923598 207.363085,144.651977 207.968773,144.403375 C208.051681,144.368847 208.133437,144.33547 208.215194,144.299791 C208.377555,144.231885 208.538764,144.169735 208.698823,144.11449 C208.858881,144.060396 209.016636,144.004 209.172088,143.948755 C209.313723,143.901567 209.450751,143.857831 209.584325,143.816397 L209.629233,143.802586 C209.700626,143.780718 209.772019,143.760002 209.843412,143.741587 L209.864139,143.735832 C210.009227,143.694398 210.154316,143.656417 210.300556,143.621889 L210.312071,143.621889 C210.527402,143.570097 210.736974,143.526362 210.941941,143.491833 C211.012182,143.480324 211.081272,143.468815 211.148059,143.459607 C211.347268,143.429683 211.547629,143.408966 211.747989,143.395155 C211.874654,143.385947 212.002471,143.382494 212.129135,143.382494 L212.225861,143.382494 C212.354829,143.382494 212.479191,143.390551 212.602401,143.403211 C212.658824,143.403211 212.715248,143.41357 212.77052,143.421626 L212.79355,143.421626 C212.848822,143.427381 212.905245,143.436588 212.959365,143.448098 C213.013486,143.459607 213.068758,143.467664 213.121727,143.480324 L213.140151,143.480324 C213.19312,143.491833 213.242634,143.506796 213.297906,143.521758 C213.737778,143.640304 214.141954,143.863586 214.47704,144.172037 L214.506979,144.201961 L214.565705,144.259508 L214.62328,144.320508 C215.085031,144.806203 215.467328,145.362105 215.755203,145.967497 L215.800111,146.069931 C215.897988,146.300118 215.975139,146.538362 216.029259,146.78236 C216.178954,147.462564 216.138651,148.17154 215.911806,148.829876 C215.813929,149.116459 215.694173,149.394986 215.552539,149.662003 L215.458116,149.832341 C215.098849,150.434281 214.675097,150.994787 214.19262,151.503501 L214.088985,151.61284 C213.86214,151.853385 213.626083,152.087025 213.383117,152.311458 C213.305966,152.383967 213.226513,152.456476 213.145908,152.528985 L213.023849,152.639475 C212.77052,152.863907 212.511433,153.082585 212.248891,153.296659 C212.202831,153.333489 212.15562,153.370319 212.110711,153.409451 C211.927623,153.556771 211.738777,153.706393 211.543023,153.856014 C210.868245,154.371634 210.180801,154.868838 209.48069,155.348779 C209.068454,155.630758 208.642399,155.915039 208.202527,156.203924 C207.38266,156.741412 206.557036,157.26739 205.723352,157.783009 C205.482689,157.93148 205.240874,158.082252 204.994454,158.231874 C202.729457,159.624507 200.21804,161.113819 197.539655,162.733186 L196.846454,163.154429 C196.087616,163.617105 195.371385,164.059064 194.697758,164.479156 L194.358066,164.69323 L193.71553,165.105266 C193.461049,165.266397 193.213477,165.425226 192.972814,165.581753 C192.790878,165.697998 192.613547,165.813091 192.439671,165.927034 L192.096524,166.152618 C191.929557,166.261956 191.763741,166.371295 191.597926,166.482936 L191.436716,166.58537 C191.266294,166.699312 191.100478,166.810953 190.939269,166.921443 L190.675576,167.100989 L190.365823,167.316214 L190.080251,167.515326 C189.628864,167.831834 189.183234,168.155247 188.741059,168.484414 L188.586758,168.60181 C188.412882,168.733017 188.244763,168.864223 188.082402,168.991977 L187.870526,169.161165 C187.513562,169.447748 187.189991,169.722822 186.897511,169.985235 L186.754725,170.116442 C186.672969,170.191253 186.592364,170.266064 186.516365,170.339724 C186.462244,170.390365 186.41273,170.442157 186.35861,170.492798 L186.283762,170.566458 C186.119098,170.731042 185.95904,170.90023 185.803588,171.074021 L185.727589,171.161492 C185.558318,171.357151 185.410927,171.544754 185.280807,171.727753 L185.223232,171.81062 C185.073538,172.029298 184.941115,172.260636 184.828269,172.501182 L184.789118,172.5852 L184.764936,172.643898 L184.745361,172.694539 L184.718876,172.764746 C184.710816,172.788916 184.694695,172.831501 184.685483,172.868331 C184.638272,173.005292 184.603727,173.146857 184.580697,173.290724 L184.569182,173.367837 L184.55997,173.438044 L184.554212,173.505949 L184.554212,173.867343 C184.554212,173.897268 184.554212,173.928343 184.561121,173.959418 C184.562273,173.977833 184.564576,173.996248 184.566879,174.014663 C184.566879,174.044587 184.574939,174.074512 184.578394,174.105587 L184.600272,174.23219 L184.600272,174.237945 C184.608333,174.278228 184.616393,174.31851 184.626757,174.358793 C184.63712,174.399076 184.647483,174.443962 184.66015,174.486547 C184.696998,174.616603 184.741906,174.745508 184.793724,174.87096 C184.793724,174.880167 184.801784,174.890526 184.805239,174.899733 C184.819057,174.930808 184.831723,174.963035 184.846693,174.99411 C184.903116,175.125317 184.966448,175.253071 185.035538,175.378523 L185.108083,175.508578 L185.184082,175.639785 L185.263535,175.770992 C185.277353,175.79286 185.292322,175.812426 185.309595,175.831992 L185.336079,175.859614 L185.363715,175.884935 L185.394806,175.909104 C185.415533,175.924066 185.437411,175.936727 185.460441,175.949387 C185.483471,175.959745 185.508804,175.971255 185.534137,175.980462 C185.561773,175.98967 185.589409,175.997726 185.617045,176.004632 C186.281459,176.154254 187.647136,175.604106 189.460745,174.646527 L189.786619,174.473887 L190.338187,174.173492 L190.608789,174.023871 C190.799937,173.920286 190.996844,173.805193 191.196053,173.69125 L191.561078,173.485232 C193.959648,172.10526 196.825727,170.331667 199.78738,168.600659 C200.063739,168.438377 200.342402,168.277246 200.621064,168.116115 L201.180692,167.759325 C201.460506,167.598193 201.739169,167.439364 202.018983,167.282837 C203.209632,166.606086 204.416402,165.955807 205.635838,165.333151 L206.1828,165.055775 C206.546673,164.876229 206.907092,164.701287 207.262905,164.530948 C207.952652,164.204082 208.649308,163.894481 209.352874,163.600992 L209.738625,163.444465 L209.785837,163.42605 C211.828594,162.616941 213.681355,162.116284 215.180605,162.116284 C215.505328,162.113982 215.828898,162.140454 216.149015,162.195699 L216.158227,162.195699 C216.261862,162.214114 216.355133,162.23483 216.449556,162.259 L216.466828,162.259 C216.605008,162.292377 216.740885,162.334962 216.872156,162.387905 C216.976942,162.425886 217.078274,162.470772 217.176151,162.523715 C217.60566,162.741242 217.972988,163.062354 218.247044,163.458276 C218.270074,163.491653 218.291953,163.526181 218.31268,163.56186 C218.414012,163.716085 218.501525,163.878367 218.572918,164.048706 C218.615524,164.146536 218.654675,164.247818 218.691523,164.357157 C218.728371,164.467647 218.760612,164.573533 218.794006,164.687476 C219.038123,165.559885 219.013942,166.482936 218.724916,167.341535 Z" id="Shape" fill="#FF9D00" fillRule="nonzero"></path>
10
+ <path d="M205.169417,104.827718 L205.169417,104.263487 C205.169417,56.0689579 166.09852,17 117.882512,17 C69.6688088,17 30.5830519,56.0712609 30.5830519,104.263487 L30.5805699,104.452331 C30.5805699,104.577844 30.5805699,104.702205 30.5805699,104.827718 C30.5874292,105.015411 30.5893875,105.203104 30.5876596,105.390797 L30.5945712,105.805334 L30.596875,105.955028 L30.6026346,106.159993 C30.6049385,106.270536 30.6083942,106.379928 30.6083942,106.490471 L30.6199134,106.938402 L30.6383442,107.464633 L30.6441038,107.637357 L30.6671422,108.182012 L30.6671422,108.207345 L30.6993961,108.84412 L30.6993961,108.860241 C30.7109153,109.077873 30.7224345,109.295505 30.7362576,109.511985 C30.7477768,109.713496 30.7604479,109.913856 30.774271,110.114215 L30.7777268,110.149912 C30.7878637,110.321484 30.7993829,110.494208 30.8122844,110.66578 L30.8157402,110.710688 C30.8272594,110.876503 30.8399306,111.043469 30.8537536,111.208132 L30.8560575,111.240374 L30.8790959,111.528247 C30.9480961,112.358472 31.0287306,113.188698 31.1209996,114.01662 L31.1267592,114.068437 L31.1958745,114.676425 L31.2753572,115.316654 C31.3286912,115.742706 31.3855961,116.167607 31.4458417,116.592507 C31.4817816,116.84929 31.5191039,117.104921 31.5575781,117.360552 L31.5633377,117.391642 C31.6493863,117.969691 31.7422312,118.54774 31.8421029,119.123486 C32.1285859,120.7943 32.4650624,122.457054 32.8511867,124.108293 L32.8569463,124.134777 L32.8938079,124.290228 C32.9283655,124.437619 32.9629232,124.58501 32.9997847,124.733552 L33.0631404,124.656403 C35.5973693,121.62798 39.2328357,119.958317 43.300273,119.958317 C46.5602128,119.958317 49.9053949,121.034962 53.2563365,123.159464 C55.4795464,124.571192 57.9377483,127.072232 60.4673695,130.216955 C62.8080754,126.966295 66.0886346,124.807248 69.8415971,124.217684 C70.5580927,124.104838 71.2814999,124.047264 72.004907,124.046112 C80.5717524,124.046112 85.7312119,131.477839 87.6802643,138.161096 C88.6421193,140.420322 93.2843658,150.704294 100.283445,157.661606 C110.921447,168.286419 113.583539,179.250921 108.319254,190.990378 L108.336533,190.990378 C108.695933,191.029529 109.056484,191.066377 109.418188,191.09977 C109.633597,191.120497 109.849007,191.141224 110.064416,191.159647 L110.140443,191.166556 C110.298257,191.181526 110.457222,191.195344 110.616187,191.206859 L111.053918,191.242555 C111.470913,191.274797 111.889061,191.303584 112.309513,191.33122 L112.641266,191.350795 L112.905057,191.365764 L113.069781,191.374976 L113.583539,191.402612 L113.724073,191.40837 L114.265477,191.431399 L114.865628,191.454429 L115.349435,191.468247 L115.43007,191.471702 C115.531439,191.475156 115.635112,191.479762 115.737633,191.479762 L115.847066,191.482065 C116.5267,191.498186 117.205182,191.506246 117.884816,191.506246 L118.605919,191.506246 L119.121981,191.500489 C119.248692,191.49358 119.374252,191.49358 119.500963,191.49358 L119.701397,191.49358 L119.875338,191.490126 C119.943301,191.487823 120.012416,191.48552 120.08038,191.48552 L120.286574,191.48552 L120.690899,191.474005 L121.299114,191.453278 L121.969532,191.424491 C122.161903,191.41643 122.353122,191.407218 122.545493,191.396855 L122.751687,191.38534 L123.056947,191.368067 L123.237799,191.357704 L123.372574,191.349644 L123.991156,191.30819 C124.19735,191.295523 124.403544,191.279403 124.609738,191.263282 L125.169572,191.219525 L125.564681,191.186132 L125.996652,191.146981 C126.440142,191.105527 126.88248,191.061771 127.322515,191.015711 C127.603584,190.984621 127.882349,190.95353 128.162266,190.920137 L128.654137,190.861411 C123.471639,179.170317 126.154466,168.255328 136.749846,157.671969 C143.732798,150.697385 148.375045,140.41111 149.342659,138.153035 C151.292864,131.468627 156.447716,124.038052 165.016865,124.038052 C165.741424,124.039203 166.464831,124.095626 167.180175,124.209624 C170.929681,124.799188 174.211508,126.959386 176.555669,130.208895 C179.086443,127.064171 181.543493,124.563132 183.766702,123.151403 C187.117644,121.026901 190.46513,119.950256 193.721614,119.950256 C197.311004,119.950256 200.565184,121.251442 203.022234,123.637333 C203.157009,123.036254 203.287176,122.435175 203.411584,121.831794 C203.48761,121.455256 203.562485,121.077567 203.636208,120.698726 C203.726058,120.233523 203.811301,119.766018 203.889631,119.298512 C203.910366,119.171848 203.932252,119.045184 203.956443,118.917369 C203.994456,118.71816 204.027862,118.521255 204.058964,118.320896 L204.071635,118.244897 L204.153421,117.723272 L204.234056,117.194737 L204.235208,117.18898 C204.267462,116.967893 204.300867,116.745655 204.330817,116.523418 C204.355008,116.3553 204.378046,116.187182 204.399933,116.017913 L204.413756,115.921187 L204.485175,115.370774 L204.543923,114.902117 C204.579633,114.593518 204.61419,114.284918 204.647596,113.975167 C204.662571,113.851957 204.675242,113.727596 204.687913,113.603235 L204.699433,113.503055 C204.721319,113.296938 204.740902,113.091973 204.759333,112.885856 L204.805409,112.406835 C204.901019,111.34516 204.978198,110.283485 205.038098,109.219506 L205.038098,109.212597 L205.0692,108.648367 L205.074959,108.521703 L205.097998,107.986259 C205.107213,107.784748 205.115276,107.580934 205.122188,107.374817 L205.122188,107.359847 L205.127948,107.211305 C205.133707,107.084641 205.138315,106.957977 205.138315,106.832464 L205.140619,106.780647 C205.145226,106.663195 205.149834,106.541137 205.149834,106.42023 L205.15329,106.316596 C205.156746,106.19569 205.160201,106.075934 205.160201,105.955028 L205.162505,105.852545 C205.165961,105.698245 205.169417,105.545097 205.169417,105.390797 L205.169417,104.827718 Z M96.2309827,192.003691 C103.143668,181.868262 102.654101,174.261508 93.1680216,164.784733 C83.6819423,155.307957 78.1607792,141.448604 78.1607792,141.448604 C78.1607792,141.448604 76.0999904,133.397375 71.4001479,134.137784 C66.7003053,134.877042 63.2527174,146.906674 73.0923216,154.270463 C82.932041,161.634252 71.1352058,166.627119 67.3476856,159.717018 C63.5591287,152.805766 53.219475,135.041705 47.8572772,131.643653 C42.4962313,128.245602 38.7259899,130.150169 39.9896486,137.153541 C40.6174462,140.629894 46.4553879,146.562378 51.9097395,152.105659 C57.4435737,157.730695 62.5822986,162.95271 61.4845168,164.800854 C59.3039281,168.471809 51.6240628,160.488518 51.6240628,160.488518 C51.6240628,160.488518 27.576535,138.613632 22.3410486,144.314667 C17.5156465,149.568923 24.9570639,154.035559 36.4256013,160.919175 C37.4012794,161.505285 38.4057556,162.108666 39.4344221,162.730472 C52.5548159,170.671158 53.575419,172.765721 51.7139127,175.769962 C51.0262152,176.88 46.6339358,174.244236 41.2314207,171.001636 C32.0218028,165.474476 19.877087,158.186686 18.1630268,164.709886 C16.6781991,170.355649 25.6125077,173.81473 33.7151284,176.952544 C40.4653925,179.56643 46.6396954,181.956927 45.7319807,185.20183 C44.7908603,188.565337 39.6889969,185.760303 34.1113896,182.69273 C27.8506925,179.248619 20.9909958,175.475181 18.7470514,179.733396 C14.50337,187.782323 48.0173944,197.257947 48.2927038,197.327036 C59.1207724,200.134373 86.6193439,206.082978 96.2309827,192.003691 Z M140.768903,192.003691 C133.855066,181.868262 134.345784,174.261508 143.831864,164.784733 C153.317943,155.307957 158.840258,141.448604 158.840258,141.448604 C158.840258,141.448604 160.899895,133.397375 165.599737,134.137784 C170.29958,134.877042 173.748435,146.906674 163.907564,154.270463 C154.066692,161.634252 165.86468,166.627119 169.653352,159.717018 C173.440872,152.805766 183.775918,135.041705 189.136964,131.643653 C194.49801,128.245602 198.269403,130.150169 197.005744,137.153541 C196.377947,140.629894 190.540005,146.56353 185.084501,152.10681 C179.551819,157.730695 174.413094,162.95271 175.510876,164.800854 C177.690313,168.471809 185.37709,160.483912 185.37709,160.483912 C185.37709,160.483912 209.423465,138.609026 214.658952,144.310061 C219.484354,149.564317 212.041785,154.032104 200.573247,160.916873 C199.597569,161.50183 198.594245,162.10406 197.565578,162.727017 C184.444033,170.666552 183.424581,172.762266 185.286088,175.765356 C185.973785,176.875394 190.366065,174.23963 195.767428,170.998181 C204.978198,165.471022 217.122913,158.18208 218.836974,164.70528 C220.321801,170.351043 211.387493,173.811275 203.28372,176.94909 C196.533456,179.562976 190.360305,181.952321 191.266868,185.198376 C192.207988,188.561882 197.307548,185.755697 202.882851,182.688124 C209.143548,179.244013 216.004397,175.469423 218.249493,179.72879 C222.493175,187.783474 188.974543,197.248735 188.702689,197.318976 C177.87462,200.134373 150.375934,206.082978 140.768903,192.003691 Z" id="Shape" fill="#FFD21E"></path>
11
+ <path d="M146.614758,80.6109193 C147.976342,81.0911 148.989747,82.5534685 149.953577,83.9434653 C151.256362,85.8239816 152.46922,87.5723908 154.330012,86.5867567 C156.5263,85.424214 158.325988,83.6390445 159.503106,81.456405 C160.680224,79.2743398 161.180586,76.793253 160.941934,74.3272149 C160.764386,72.4833439 160.178709,70.7015059 159.226408,69.1104765 C158.274107,67.5193323 156.979392,66.1585139 155.436803,65.1258957 C153.89306,64.0933923 152.138336,63.414936 150.300602,63.139349 C148.461715,62.8637621 146.584783,62.9980518 144.804694,63.532339 C142.423941,64.2467514 140.30951,65.6481209 138.727721,67.5595388 C137.147086,69.4708418 136.169421,71.806381 135.91924,74.2709257 C135.670212,76.7360448 136.160198,79.2193143 137.326939,81.4070084 C138.189314,83.0233104 140.10314,82.2616841 142.123033,81.456405 C143.707127,80.825737 145.355784,80.1686476 146.614758,80.6109193 Z M87.3830077,80.6120681 C86.0214245,81.0922488 85.0068663,82.5546173 84.043036,83.9446141 C82.7402511,85.8239816 81.5273929,87.5735396 79.666601,86.5879055 C78.0260143,85.7182959 76.5998684,84.4960178 75.4896189,83.0106741 C74.3793695,81.5253304 73.6138392,79.8136815 73.2472148,77.9977254 C72.8805903,76.1821139 72.922095,74.3082604 73.3694229,72.5107992 C73.8179037,70.7133381 74.6595259,69.036956 75.8343381,67.6023875 C77.0114562,66.168623 78.4917888,65.0125133 80.1704214,64.2172284 C81.849054,63.4219434 83.684482,63.0074717 85.5429681,63.0033066 C87.4014542,62.9993155 89.238035,63.4058608 90.9201264,64.1936788 C92.6022177,64.9816117 94.0883149,66.1312884 95.2711974,67.5598834 C96.8529859,69.4711864 97.830651,71.8071852 98.0808318,74.2723042 C98.3298598,76.7374233 97.8398743,79.2208077 96.6719795,81.4081571 C95.8072992,83.0244592 93.8934736,82.2616841 91.8735805,81.456405 C90.2906392,80.825737 88.6419821,80.1686476 87.3830077,80.6120681 Z M137.451453,134.317638 C146.839575,126.947209 150.28792,114.91627 150.28792,107.504485 C150.28792,101.645821 146.331143,103.489577 139.998225,106.613049 L139.641977,106.789958 C133.827867,109.658406 126.089555,113.476876 117.594936,113.476876 C109.099164,113.476876 101.360852,109.657257 95.549048,106.788809 C89.0109124,103.561949 84.9100221,101.537838 84.9100221,107.505634 C84.9100221,115.150616 88.5785722,127.705389 98.649215,135 C99.9773639,132.311907 101.847379,129.925937 104.14397,127.991429 C106.439408,126.056922 109.111846,124.616379 111.992961,123.758257 C112.998296,123.45958 114.033606,125.183866 115.094281,126.948357 C116.116909,128.651965 117.161443,130.393482 118.225576,130.393482 C119.357731,130.393482 120.471439,128.677238 121.55863,127.0012 C122.694244,125.250494 123.801034,123.54344 124.872085,123.884621 C130.263078,125.608906 134.772098,129.348112 137.451453,134.317638 Z" id="Shape" fill="#32343D"></path>
12
+ <path d="M137,134.589063 C132.085163,138.426668 125.552521,141 117.046296,141 C109.053457,141 102.801266,138.72867 98,135.271151 C99.3361959,132.585286 101.217068,130.200275 103.524411,128.267693 C105.831753,126.333962 108.518052,124.892851 111.41295,124.033925 C113.398122,123.447146 115.500342,130.666483 117.67673,130.666483 C120.00725,130.666483 122.252013,123.493078 124.358868,124.160238 C129.775503,125.884979 134.306748,129.622682 137,134.589063 Z" id="Path" fill="#FF323D" fillRule="nonzero"></path>
13
+ <path d="M64.7091655,90.6371141 C63.8188025,91.2251504 62.8188988,91.6348571 61.7668713,91.8425321 C60.7147288,92.0502071 59.6311738,92.0524645 58.5782258,91.8493041 C57.5252778,91.6450151 56.5236482,91.2398231 55.6307538,90.6563014 C54.7378594,90.0716511 53.971305,89.3210865 53.3749298,88.4452396 C52.7785546,87.5705215 52.3642101,86.5874511 52.1555996,85.555848 C51.9469891,84.5231163 51.9481398,83.4599105 52.1590515,82.4271787 C52.3699633,81.3955756 52.786609,80.4147626 53.3848253,79.5400444 C53.9831565,78.6662292 54.7514369,77.9166804 55.645597,77.3345131 C57.4440428,76.1637452 59.642335,75.7399301 61.7587018,76.1560702 C63.8750686,76.5722103 65.7370296,77.7943327 66.9366838,79.5547171 C68.1356477,81.3154401 68.5751911,83.4700685 68.1575098,85.5468187 C67.7398285,87.6246976 66.4994417,89.455398 64.7091655,90.6371141 Z M181.39746,90.6371141 C180.506867,91.2251504 179.506963,91.6348571 178.455281,91.8425321 C177.403599,92.0502071 176.319699,92.0524645 175.266866,91.8493041 C174.214033,91.6450151 173.211828,91.2398231 172.318933,90.6563014 C171.426039,90.0716511 170.659715,89.3210865 170.063685,88.4452396 C169.466504,87.5705215 169.052275,86.5874511 168.844009,85.555848 C168.635744,84.5231163 168.636895,83.4599105 168.847461,82.4271787 C169.058028,81.3955756 169.474559,80.4147626 170.07289,79.5400444 C170.671221,78.6662292 171.439847,77.9166804 172.333892,77.3345131 C174.132338,76.1637452 176.331205,75.7399301 178.447227,76.1560702 C180.563248,76.5722103 182.424979,77.7943327 183.625094,79.5547171 C184.824057,81.3154401 185.263601,83.4700685 184.84592,85.5468187 C184.428238,87.6246976 183.187852,89.455398 181.39746,90.6371141 Z" id="Shape" fill="#FFAD03"></path>
14
+ </g>
15
+ </g>
16
+ </svg>
17
+ );
18
+
19
+ export default HFLogo;
frontend/src/components/Logo/Logo.js ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from "react";
2
+ import { useNavigate, useSearchParams, useLocation } from "react-router-dom";
3
+ import { Box } from "@mui/material";
4
+ import HFLogo from "./HFLogo";
5
+ import { useLeaderboard } from "../../pages/LeaderboardPage/components/Leaderboard/context/LeaderboardContext";
6
+
7
+ const Logo = ({ height = "40px" }) => {
8
+ const navigate = useNavigate();
9
+ const [searchParams, setSearchParams] = useSearchParams();
10
+ const location = useLocation();
11
+ const { actions } = useLeaderboard();
12
+
13
+ const handleReset = () => {
14
+ // Reset all leaderboard state first
15
+ actions.resetAll();
16
+
17
+ // Then clean URL in one go
18
+ if (
19
+ location.pathname !== "/" ||
20
+ searchParams.toString() !== "" ||
21
+ location.hash !== ""
22
+ ) {
23
+ window.history.replaceState(null, "", "/");
24
+ navigate("/", { replace: true, state: { skipUrlSync: true } });
25
+ setSearchParams({}, { replace: true, state: { skipUrlSync: true } });
26
+ }
27
+ };
28
+
29
+ return (
30
+ <Box
31
+ onClick={handleReset}
32
+ sx={{
33
+ height,
34
+ display: "flex",
35
+ alignItems: "center",
36
+ justifyContent: "center",
37
+ cursor: "pointer",
38
+ transition: "opacity 0.2s ease",
39
+ "&:hover": {
40
+ opacity: 0.8,
41
+ },
42
+ }}
43
+ >
44
+ <Box
45
+ sx={{
46
+ height: "100%",
47
+ aspectRatio: "95/88", // Ratio du SVG original (width/height)
48
+ }}
49
+ >
50
+ <HFLogo />
51
+ </Box>
52
+ </Box>
53
+ );
54
+ };
55
+
56
+ export default Logo;