Spaces:
Running
Running
COLE CI commited on
Commit ·
3906683
0
Parent(s):
deploy to HF Space
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -0
- .gitignore +66 -0
- .idea/.gitignore +8 -0
- CITATION.cff +40 -0
- CODE_OF_CONDUCT.md +35 -0
- Dockerfile +55 -0
- LICENSE +21 -0
- README.md +105 -0
- SECURITY.md +15 -0
- docs/architecture.md +172 -0
- frontend/README.md +38 -0
- frontend/cole.pdf +3 -0
- frontend/eslint.config.mjs +14 -0
- frontend/jsconfig.json +7 -0
- frontend/next.config.mjs +4 -0
- frontend/package-lock.json +0 -0
- frontend/package.json +27 -0
- frontend/postcss.config.mjs +5 -0
- frontend/src/app/FAQ/page.js +53 -0
- frontend/src/app/benchmarks/page.js +132 -0
- frontend/src/app/components/BigBlueButton.js +17 -0
- frontend/src/app/components/ClientHeader.js +17 -0
- frontend/src/app/components/CodeBlock.js +10 -0
- frontend/src/app/components/ErrorMessage.js +14 -0
- frontend/src/app/components/LanguageSwitcher.js +24 -0
- frontend/src/app/components/Modal.js +30 -0
- frontend/src/app/components/ModalManager.js +26 -0
- frontend/src/app/components/SubmitForm.js +177 -0
- frontend/src/app/components/taskbar.js +117 -0
- frontend/src/app/contact/page.js +32 -0
- frontend/src/app/en/translation.json +162 -0
- frontend/src/app/fr/translation.json +162 -0
- frontend/src/app/globals.css +28 -0
- frontend/src/app/guide/page.js +76 -0
- frontend/src/app/i18n.js +28 -0
- frontend/src/app/layout.js +49 -0
- frontend/src/app/leaderboard/page.js +344 -0
- frontend/src/app/leaderboard/util.js +47 -0
- frontend/src/app/page.js +74 -0
- frontend/src/app/papers/page.js +47 -0
- frontend/src/app/resources/ResourcesPaths.js +2 -0
- frontend/src/app/results/[id]/page.js +129 -0
- frontend/src/app/results/page.js +31 -0
- nginx.conf +34 -0
- pytest.ini +2 -0
- src/__init__.py +6 -0
- src/backend/__init__.py +0 -0
- src/backend/evaluation.py +36 -0
- src/backend/results/leaderboard.json +0 -0
- src/backend/submission_api.py +250 -0
.gitattributes
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.jsonl filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.pdf filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.idea/COLE.iml
|
| 2 |
+
.idea/inspectionProfiles/profiles_settings.xml
|
| 3 |
+
.idea/misc.xml
|
| 4 |
+
.idea/modules.xml
|
| 5 |
+
.idea/vcs.xml
|
| 6 |
+
.idea/workspace.xml
|
| 7 |
+
.idea/*
|
| 8 |
+
/Benchmarks
|
| 9 |
+
/__pycache__
|
| 10 |
+
src/config.py
|
| 11 |
+
/Loaded_Models
|
| 12 |
+
/results
|
| 13 |
+
/hf_data
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
COLE_nlu_benchmark_site/node_modules
|
| 17 |
+
COLE_nlu_benchmark_site/.pnp
|
| 18 |
+
COLE_nlu_benchmark_site/.pnp.*
|
| 19 |
+
COLE_nlu_benchmark_site/.yarn/*
|
| 20 |
+
!.yarn/patches
|
| 21 |
+
!.yarn/plugins
|
| 22 |
+
!.yarn/releases
|
| 23 |
+
!.yarn/versions
|
| 24 |
+
|
| 25 |
+
# testing
|
| 26 |
+
/coverage
|
| 27 |
+
|
| 28 |
+
# next.js
|
| 29 |
+
COLE_nlu_benchmark_site/.next/
|
| 30 |
+
COLE_nlu_benchmark_site/out/
|
| 31 |
+
|
| 32 |
+
# production
|
| 33 |
+
COLE_nlu_benchmark_site/build
|
| 34 |
+
|
| 35 |
+
# misc
|
| 36 |
+
.DS_Store
|
| 37 |
+
*.pem
|
| 38 |
+
|
| 39 |
+
# debug
|
| 40 |
+
npm-debug.log*
|
| 41 |
+
yarn-debug.log*
|
| 42 |
+
yarn-error.log*
|
| 43 |
+
.pnpm-debug.log*
|
| 44 |
+
|
| 45 |
+
# env files (can opt-in for committing if needed)
|
| 46 |
+
.env*
|
| 47 |
+
|
| 48 |
+
# vercel
|
| 49 |
+
.vercel
|
| 50 |
+
|
| 51 |
+
# typescript
|
| 52 |
+
*.tsbuildinfo
|
| 53 |
+
next-env.d.ts
|
| 54 |
+
/src/results
|
| 55 |
+
/src/__pycache__
|
| 56 |
+
/src/backend/__pycache__
|
| 57 |
+
/archives/Models/__pycache__
|
| 58 |
+
*.pyc
|
| 59 |
+
/Benchmarks_data
|
| 60 |
+
/src/light_eval_custom/results
|
| 61 |
+
/offline_evaluation/results
|
| 62 |
+
/frontend/node_modules
|
| 63 |
+
/.idea/*
|
| 64 |
+
/frontend/.next
|
| 65 |
+
/src/backend/results
|
| 66 |
+
/node_modules
|
.idea/.gitignore
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Default ignored files
|
| 2 |
+
/shelf/
|
| 3 |
+
/workspace.xml
|
| 4 |
+
# Editor-based HTTP Client requests
|
| 5 |
+
/httpRequests/
|
| 6 |
+
# Datasource local storage ignored files
|
| 7 |
+
/dataSources/
|
| 8 |
+
/dataSources.local.xml
|
CITATION.cff
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
cff-version: 1.2.0
|
| 2 |
+
title: "COLE: a Comprehensive Benchmark for French Language Understanding Evaluation"
|
| 3 |
+
message: "If you use COLE in your research, please cite our paper."
|
| 4 |
+
type: software
|
| 5 |
+
authors:
|
| 6 |
+
- given-names: David
|
| 7 |
+
family-names: Beauchemin
|
| 8 |
+
affiliation: Université Laval
|
| 9 |
+
- given-names: Yan
|
| 10 |
+
family-names: Tremblay
|
| 11 |
+
affiliation: Université Laval
|
| 12 |
+
- given-names: Mohamed Amine
|
| 13 |
+
family-names: Youssef
|
| 14 |
+
affiliation: Université Laval
|
| 15 |
+
- given-names: Richard
|
| 16 |
+
family-names: Khoury
|
| 17 |
+
affiliation: Université Laval
|
| 18 |
+
repository-code: "https://github.com/GRAAL-Research/COLE"
|
| 19 |
+
url: "https://colebenchmark.org"
|
| 20 |
+
license: MIT
|
| 21 |
+
version: 1.0.0
|
| 22 |
+
date-released: "2025-10-07"
|
| 23 |
+
preferred-citation:
|
| 24 |
+
type: article
|
| 25 |
+
title: "COLE: a Comprehensive Benchmark for French Language Understanding Evaluation"
|
| 26 |
+
authors:
|
| 27 |
+
- given-names: David
|
| 28 |
+
family-names: Beauchemin
|
| 29 |
+
- given-names: Yan
|
| 30 |
+
family-names: Tremblay
|
| 31 |
+
- given-names: Mohamed Amine
|
| 32 |
+
family-names: Youssef
|
| 33 |
+
- given-names: Richard
|
| 34 |
+
family-names: Khoury
|
| 35 |
+
year: 2025
|
| 36 |
+
url: "https://arxiv.org/abs/2510.05046"
|
| 37 |
+
identifiers:
|
| 38 |
+
- type: other
|
| 39 |
+
value: "arXiv:2510.05046"
|
| 40 |
+
description: arXiv preprint
|
CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributor Covenant Code of Conduct
|
| 2 |
+
|
| 3 |
+
## Our Pledge
|
| 4 |
+
|
| 5 |
+
We as members, contributors, and leaders pledge to make participation in our
|
| 6 |
+
community a harassment-free experience for everyone, regardless of age, body
|
| 7 |
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
| 8 |
+
identity and expression, level of experience, education, socio-economic status,
|
| 9 |
+
nationality, personal appearance, race, religion, or sexual identity
|
| 10 |
+
and orientation.
|
| 11 |
+
|
| 12 |
+
## Our Standards
|
| 13 |
+
|
| 14 |
+
Examples of behavior that contributes to a positive environment:
|
| 15 |
+
|
| 16 |
+
* Using welcoming and inclusive language
|
| 17 |
+
* Being respectful of differing viewpoints and experiences
|
| 18 |
+
* Gracefully accepting constructive criticism
|
| 19 |
+
* Focusing on what is best for the community
|
| 20 |
+
|
| 21 |
+
Examples of unacceptable behavior:
|
| 22 |
+
|
| 23 |
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
| 24 |
+
* Public or private harassment
|
| 25 |
+
* Publishing others' private information without explicit permission
|
| 26 |
+
* Other conduct which could reasonably be considered inappropriate
|
| 27 |
+
|
| 28 |
+
## Enforcement
|
| 29 |
+
|
| 30 |
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
| 31 |
+
reported to the project team at david.beauchemin@ift.ulaval.ca.
|
| 32 |
+
|
| 33 |
+
## Attribution
|
| 34 |
+
|
| 35 |
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/), version 2.1.
|
Dockerfile
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Stage 1: Build frontend
|
| 2 |
+
FROM node:20-slim AS frontend-build
|
| 3 |
+
WORKDIR /app/frontend
|
| 4 |
+
COPY frontend/package*.json ./
|
| 5 |
+
RUN npm ci
|
| 6 |
+
COPY frontend/ ./
|
| 7 |
+
RUN npm run build
|
| 8 |
+
|
| 9 |
+
# Stage 2: Final image with backend + built frontend
|
| 10 |
+
FROM python:3.12-slim
|
| 11 |
+
|
| 12 |
+
WORKDIR /app
|
| 13 |
+
|
| 14 |
+
# Install system dependencies (nginx, curl, Node.js runtime for Next.js)
|
| 15 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 16 |
+
nginx \
|
| 17 |
+
curl \
|
| 18 |
+
netcat-openbsd \
|
| 19 |
+
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
|
| 20 |
+
&& apt-get install -y --no-install-recommends nodejs \
|
| 21 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 22 |
+
|
| 23 |
+
# Install Python dependencies
|
| 24 |
+
COPY src/docker_requirements.txt /app/src/
|
| 25 |
+
RUN pip install --no-cache-dir --upgrade pip wheel \
|
| 26 |
+
&& pip install --no-cache-dir --prefer-binary pyarrow pandas numpy scipy fsspec aiohttp tqdm \
|
| 27 |
+
&& pip install --no-cache-dir --prefer-binary -r /app/src/docker_requirements.txt
|
| 28 |
+
|
| 29 |
+
# Copy backend source
|
| 30 |
+
COPY src/ /app/src/
|
| 31 |
+
|
| 32 |
+
# Copy built frontend from stage 1
|
| 33 |
+
COPY --from=frontend-build /app/frontend /app/frontend
|
| 34 |
+
|
| 35 |
+
# Copy config files
|
| 36 |
+
COPY nginx.conf /etc/nginx/nginx.conf
|
| 37 |
+
COPY start.sh /start.sh
|
| 38 |
+
RUN chmod +x /start.sh
|
| 39 |
+
|
| 40 |
+
# Create non-root user and set permissions
|
| 41 |
+
RUN useradd -m -u 1000 user \
|
| 42 |
+
&& mkdir -p /app/.cache /var/lib/nginx /var/log/nginx /app/logs /run \
|
| 43 |
+
&& touch /run/nginx.pid \
|
| 44 |
+
&& chown -R user:user /app /var/lib/nginx /var/log/nginx /run
|
| 45 |
+
|
| 46 |
+
ENV HF_HOME=/app/.cache \
|
| 47 |
+
HF_DATASETS_CACHE=/app/.cache
|
| 48 |
+
|
| 49 |
+
USER user
|
| 50 |
+
EXPOSE 7860
|
| 51 |
+
|
| 52 |
+
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
|
| 53 |
+
CMD curl -f http://localhost:7860/ || exit 1
|
| 54 |
+
|
| 55 |
+
CMD ["sh", "/start.sh"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 GRAAL Research Group, Université Laval
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: COLE !
|
| 3 |
+
emoji: 🐳
|
| 4 |
+
colorFrom: purple
|
| 5 |
+
colorTo: gray
|
| 6 |
+
sdk: docker
|
| 7 |
+
app_port: 7860
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
# COLE: Comprehensive Benchmark for Quebec French Language Understanding Evaluation
|
| 11 |
+
|
| 12 |
+
[](https://colebenchmark.org/)
|
| 13 |
+
[](https://arxiv.org/abs/2510.05046)
|
| 14 |
+
[](https://huggingface.co/datasets/graalul/COLE-public)
|
| 15 |
+
|
| 16 |
+
**COLE** is a comprehensive benchmark for evaluating Quebec French Natural Language Understanding (NLU). It includes 23 diverse tasks covering sentiment analysis, paraphrase detection, natural language inference, question answering, grammatical judgment, word sense disambiguation, and more — with a particular focus on linguistic phenomena relevant to the French language.
|
| 17 |
+
|
| 18 |
+
We benchmark 94 large language models (LLMs), providing an extensive analysis of the current state of Quebec French NLU. Our results highlight a significant performance gap between closed- and open-weight models and identify key challenging frontiers such as zero-shot extractive question answering, fine-grained word sense disambiguation, and understanding of regional language variations.
|
| 19 |
+
|
| 20 |
+
## Links
|
| 21 |
+
|
| 22 |
+
- **Leaderboard**: [colebenchmark.org](https://colebenchmark.org/)
|
| 23 |
+
- **Paper**: [COLE: a Comprehensive Benchmark for Quebec French Language Understanding Evaluation (arXiv:2510.05046)](https://arxiv.org/abs/2510.05046)
|
| 24 |
+
- **Dataset**: [HuggingFace — graalul/COLE-public](https://huggingface.co/datasets/graalul/COLE-public)
|
| 25 |
+
|
| 26 |
+
## Tasks
|
| 27 |
+
|
| 28 |
+
COLE consists of 23 tasks grouped by NLU capability:
|
| 29 |
+
|
| 30 |
+
### Sentiment Analysis
|
| 31 |
+
| Task | Description | Test size |
|
| 32 |
+
|------|-------------|-----------|
|
| 33 |
+
| **Allocine** | Sentiment classification of French movie reviews (positive/negative) | 20,000 |
|
| 34 |
+
| **MMS-fr** | Sentiment analysis with 3 classes (positive, neutral, negative) | 63,190 |
|
| 35 |
+
|
| 36 |
+
### Natural Language Inference (NLI)
|
| 37 |
+
| Task | Description | Test size |
|
| 38 |
+
|------|-------------|-----------|
|
| 39 |
+
| **FraCaS** | NLI involving quantifiers, plurality, anaphora, and ellipsis | 346 |
|
| 40 |
+
| **GQNLI-fr** | NLI with quantifier logic (e.g., most, at least, more than half) | 30 |
|
| 41 |
+
| **LingNLI** | NLI corpus constructed with a linguist in the loop | 4,893 |
|
| 42 |
+
| **MNLI-nineeleven-Fr-MT** | French machine-translated MNLI using 9/11 context | 2,000 |
|
| 43 |
+
| **RTE3-Fr** | French version of RTE3 for textual entailment | 3,121 |
|
| 44 |
+
| **SICK-fr** | Sentence pair relatedness and entailment | 4,906 |
|
| 45 |
+
| **XNLI-fr** | Cross-lingual NLI in French | 5,010 |
|
| 46 |
+
|
| 47 |
+
### Question Answering
|
| 48 |
+
| Task | Description | Test size |
|
| 49 |
+
|------|-------------|-----------|
|
| 50 |
+
| **FQuAD** | Extractive QA on high-quality French Wikipedia articles | 400 |
|
| 51 |
+
| **Fr-BoolQ** | Boolean question answering in French | 178 |
|
| 52 |
+
| **PIAF** | French extractive QA pairs | 384 |
|
| 53 |
+
|
| 54 |
+
### Paraphrase Detection
|
| 55 |
+
| Task | Description | Test size |
|
| 56 |
+
|------|-------------|-----------|
|
| 57 |
+
| **PAWS-X** | Paraphrase identification from sentence pairs | 2,000 |
|
| 58 |
+
| **QFrBLiMP** | Semantic equivalence detection between sentence pairs | 2,290 |
|
| 59 |
+
|
| 60 |
+
### Grammatical Judgment
|
| 61 |
+
| Task | Description | Test size |
|
| 62 |
+
|------|-------------|-----------|
|
| 63 |
+
| **DACCORD** | Semantic plausibility of French sentences (binary) | 1,034 |
|
| 64 |
+
| **MultiBLiMP-Fr** | Grammatical correctness from minimal pairs | 77 |
|
| 65 |
+
| **QFrCoLA** | Sentence acceptability in French (grammar, syntax) | 7,546 |
|
| 66 |
+
|
| 67 |
+
### Semantic Similarity
|
| 68 |
+
| Task | Description | Test size |
|
| 69 |
+
|------|-------------|-----------|
|
| 70 |
+
| **STS22** | Document-level similarity of multilingual news articles | 72 |
|
| 71 |
+
|
| 72 |
+
### Word Sense Disambiguation
|
| 73 |
+
| Task | Description | Test size |
|
| 74 |
+
|------|-------------|-----------|
|
| 75 |
+
| **WSD-Fr** | Disambiguating verb meanings in context | 3,121 |
|
| 76 |
+
|
| 77 |
+
### Quebec French
|
| 78 |
+
| Task | Description | Test size |
|
| 79 |
+
|------|-------------|-----------|
|
| 80 |
+
| **QFrCoRE** | Matching Quebec French expressions to standard definitions | 4,633 |
|
| 81 |
+
| **QFrCoRT** | Matching Quebec French terms to standard definitions | 201 |
|
| 82 |
+
|
| 83 |
+
### Coreference / Pronoun Resolution
|
| 84 |
+
| Task | Description | Test size |
|
| 85 |
+
|------|-------------|-----------|
|
| 86 |
+
| **Wino-X-LM** | Pronoun resolution with ambiguous referents | 2,793 |
|
| 87 |
+
| **Wino-X-MT** | Translation-based pronoun resolution with gendered pronouns | 2,988 |
|
| 88 |
+
|
| 89 |
+
## Language
|
| 90 |
+
|
| 91 |
+
All data in COLE is in **French**.
|
| 92 |
+
|
| 93 |
+
## Citation
|
| 94 |
+
|
| 95 |
+
If you use COLE in your research, please cite our paper:
|
| 96 |
+
|
| 97 |
+
```bibtex
|
| 98 |
+
@article{beauchemin2025cole,
|
| 99 |
+
title={COLE: a Comprehensive Benchmark for Quebec French Language Understanding Evaluation},
|
| 100 |
+
author={Beauchemin, David and Tremblay, Yan and Youssef, Mohamed Amine and Khoury, Richard},
|
| 101 |
+
journal={arXiv preprint arXiv:2510.05046},
|
| 102 |
+
year={2025},
|
| 103 |
+
url={https://arxiv.org/abs/2510.05046}
|
| 104 |
+
}
|
| 105 |
+
```
|
SECURITY.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Security Policy
|
| 2 |
+
|
| 3 |
+
## Supported Versions
|
| 4 |
+
|
| 5 |
+
| Version | Supported |
|
| 6 |
+
| ------- | ------------------ |
|
| 7 |
+
| 1.0.x | :white_check_mark: |
|
| 8 |
+
|
| 9 |
+
## Reporting a Vulnerability
|
| 10 |
+
|
| 11 |
+
If you discover a security vulnerability, please report it responsibly by emailing **david.beauchemin@ift.ulaval.ca**.
|
| 12 |
+
|
| 13 |
+
Please do **not** open a public GitHub issue for security vulnerabilities.
|
| 14 |
+
|
| 15 |
+
We will acknowledge your report within 48 hours and provide a timeline for a fix.
|
docs/architecture.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# COLE Architecture
|
| 2 |
+
|
| 3 |
+
## System Overview
|
| 4 |
+
|
| 5 |
+
COLE runs as a single Docker container with three services behind an nginx reverse proxy, deployed on HuggingFace Spaces.
|
| 6 |
+
|
| 7 |
+
```mermaid
|
| 8 |
+
graph LR
|
| 9 |
+
User([User]) -->|:7860| Nginx
|
| 10 |
+
subgraph Docker Container
|
| 11 |
+
Nginx -->|/api/*| FastAPI[FastAPI :8000]
|
| 12 |
+
Nginx -->|/*| NextJS[Next.js :8001]
|
| 13 |
+
FastAPI -->|reads| HF[(HuggingFace\ngraalul/COLE)]
|
| 14 |
+
FastAPI -->|writes| Results[(results/*.json)]
|
| 15 |
+
end
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
## Backend (FastAPI)
|
| 19 |
+
|
| 20 |
+
### API Endpoints
|
| 21 |
+
|
| 22 |
+
```mermaid
|
| 23 |
+
graph TD
|
| 24 |
+
subgraph API
|
| 25 |
+
POST[POST /submit] -->|ZIP upload| Validate[Validate format]
|
| 26 |
+
Validate -->|OK| Evaluate[Evaluate predictions]
|
| 27 |
+
Evaluate -->|Save| JSON[results/uuid.json]
|
| 28 |
+
GET_LB[GET /leaderboard] -->|Read all| JSON
|
| 29 |
+
GET_H[GET /health] -->|200| OK[status: healthy]
|
| 30 |
+
end
|
| 31 |
+
```
|
| 32 |
+
|
| 33 |
+
| Endpoint | Method | Description |
|
| 34 |
+
|----------|--------|-------------|
|
| 35 |
+
| `/submit` | POST | Upload predictions ZIP, evaluate, save results |
|
| 36 |
+
| `/leaderboard` | GET | Return all submissions with metrics |
|
| 37 |
+
| `/health` | GET | Health check |
|
| 38 |
+
|
| 39 |
+
### Security
|
| 40 |
+
|
| 41 |
+
- **Rate limiting**: 5 submissions/minute per IP (slowapi)
|
| 42 |
+
- **ZIP validation**: Max 50MB compressed, 200MB decompressed
|
| 43 |
+
- **Input validation**: Email (max 320 chars, must contain @), display name (max 200 chars)
|
| 44 |
+
- **CORS**: Open origins (proxied through nginx)
|
| 45 |
+
|
| 46 |
+
### Key Modules
|
| 47 |
+
|
| 48 |
+
```mermaid
|
| 49 |
+
graph TD
|
| 50 |
+
API[submission_api.py] --> VT[validation_tools.py]
|
| 51 |
+
API --> ST[submit_tools.py]
|
| 52 |
+
API --> EV[evaluation.py]
|
| 53 |
+
VT --> TN[task_names.py]
|
| 54 |
+
EV --> TF[task_factory.py]
|
| 55 |
+
TF --> T[task.py]
|
| 56 |
+
T --> MF[metric_factory.py]
|
| 57 |
+
T --> DS[dataset.py]
|
| 58 |
+
MF --> MW[metrics_wrapper.py]
|
| 59 |
+
MF --> FQ[fquad_metric.py]
|
| 60 |
+
DS --> HF[(HuggingFace)]
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
## Frontend (Next.js)
|
| 64 |
+
|
| 65 |
+
### Pages
|
| 66 |
+
|
| 67 |
+
```mermaid
|
| 68 |
+
graph LR
|
| 69 |
+
subgraph Pages
|
| 70 |
+
Home[/ Home]
|
| 71 |
+
Guide[/guide]
|
| 72 |
+
FAQ[/FAQ]
|
| 73 |
+
Contact[/contact]
|
| 74 |
+
Papers[/papers]
|
| 75 |
+
Benchmarks[/benchmarks]
|
| 76 |
+
Leaderboard[/leaderboard]
|
| 77 |
+
Results[/results/id]
|
| 78 |
+
end
|
| 79 |
+
subgraph Features
|
| 80 |
+
i18n[EN/FR i18n]
|
| 81 |
+
Responsive[Mobile responsive]
|
| 82 |
+
Pagination[Leaderboard pagination]
|
| 83 |
+
Submit[ZIP submission modal]
|
| 84 |
+
end
|
| 85 |
+
```
|
| 86 |
+
|
| 87 |
+
| Page | Description |
|
| 88 |
+
|------|-------------|
|
| 89 |
+
| `/` | What is COLE, links to paper and GLUE/SuperGLUE |
|
| 90 |
+
| `/guide` | How to train, test, and format submissions |
|
| 91 |
+
| `/FAQ` | 6 questions with code formatting support |
|
| 92 |
+
| `/benchmarks` | 23 tasks organized by 9 NLU categories |
|
| 93 |
+
| `/leaderboard` | Sortable table, 25/page, loading skeleton, error states |
|
| 94 |
+
| `/papers` | Embedded arxiv PDF viewer |
|
| 95 |
+
| `/results/[id]` | Per-submission detailed results |
|
| 96 |
+
| `/contact` | Email contact |
|
| 97 |
+
|
| 98 |
+
### i18n
|
| 99 |
+
|
| 100 |
+
Full English and French translations in `frontend/src/app/en/translation.json` and `fr/translation.json`. Language switcher in the header persists selection to localStorage.
|
| 101 |
+
|
| 102 |
+
## Evaluation Pipeline
|
| 103 |
+
|
| 104 |
+
### Task Flow
|
| 105 |
+
|
| 106 |
+
```mermaid
|
| 107 |
+
graph TD
|
| 108 |
+
Submit[User submits ZIP] --> Unzip[Extract predictions.json]
|
| 109 |
+
Unzip --> Validate[Validate task names & format]
|
| 110 |
+
Validate --> Factory[task_factory creates Task objects]
|
| 111 |
+
Factory --> Compute[Task.compute per task]
|
| 112 |
+
Compute --> Dataset[Load ground truths from HF]
|
| 113 |
+
Compute --> Metric[metric_factory selects metric]
|
| 114 |
+
Metric --> Score[Compute score]
|
| 115 |
+
Score --> Save[Save results JSON]
|
| 116 |
+
```
|
| 117 |
+
|
| 118 |
+
### Tasks (30 total)
|
| 119 |
+
|
| 120 |
+
Grouped by capability:
|
| 121 |
+
|
| 122 |
+
| Category | Tasks |
|
| 123 |
+
|----------|-------|
|
| 124 |
+
| Sentiment | allocine, mms |
|
| 125 |
+
| NLI | fracas, gqnli, lingnli, mnli-nineeleven-fr-mt, rte3-french, sickfr, xnli, daccord |
|
| 126 |
+
| QA | fquad, french_boolq, piaf |
|
| 127 |
+
| Paraphrase | paws_x, qfrblimp |
|
| 128 |
+
| Grammar | multiblimp, qfrcola |
|
| 129 |
+
| Similarity | sts22 |
|
| 130 |
+
| WSD | wsd |
|
| 131 |
+
| Quebec French | qfrcore, qfrcort |
|
| 132 |
+
| Coreference | wino_x_lm, wino_x_mt |
|
| 133 |
+
| Other | frcoe, timeline, lqle, qccp, qccy, qccr, piqafr, piqaqfr |
|
| 134 |
+
|
| 135 |
+
### Metrics
|
| 136 |
+
|
| 137 |
+
| Metric | Implementation | Used by |
|
| 138 |
+
|--------|---------------|---------|
|
| 139 |
+
| Accuracy | HuggingFace `evaluate` | Most classification tasks |
|
| 140 |
+
| Pearson | HuggingFace `evaluate` | sickfr, sts22 |
|
| 141 |
+
| FQuAD | Custom (F1 + Exact Match) | fquad, piaf |
|
| 142 |
+
| ExactMatch | Custom string comparison | wsd |
|
| 143 |
+
| F1 | HuggingFace `evaluate` | Classification variants |
|
| 144 |
+
|
| 145 |
+
## CI/CD Pipeline
|
| 146 |
+
|
| 147 |
+
```mermaid
|
| 148 |
+
graph TD
|
| 149 |
+
Push[git push to main] --> F[Formatting\nblack --check]
|
| 150 |
+
Push --> L[Linting\npylint src/ tests/]
|
| 151 |
+
Push --> T[Tests\npytest]
|
| 152 |
+
Push --> FB[Frontend Build\nnpm ci + lint + build]
|
| 153 |
+
Push --> HF[HF Sync\nDeploy to Space]
|
| 154 |
+
|
| 155 |
+
F -->|Python 3.12| Pass
|
| 156 |
+
L -->|Python 3.10-3.12| Pass
|
| 157 |
+
T -->|Python 3.12\nHF_TOKEN required| Pass
|
| 158 |
+
FB -->|Node 20| Pass
|
| 159 |
+
HF -->|Orphan branch\nLFS for .jsonl/.pdf| Space[davebulaval/cole]
|
| 160 |
+
```
|
| 161 |
+
|
| 162 |
+
## Deployment
|
| 163 |
+
|
| 164 |
+
The HF Space deployment uses an orphan branch strategy to handle large `.jsonl` files in git history:
|
| 165 |
+
|
| 166 |
+
1. Checkout main with LFS
|
| 167 |
+
2. Create fresh orphan branch
|
| 168 |
+
3. Track `.jsonl` and `.pdf` with Git LFS
|
| 169 |
+
4. Remove CI/test files not needed in production
|
| 170 |
+
5. Force push to `davebulaval/cole` Space
|
| 171 |
+
|
| 172 |
+
The Space builds the Docker image and runs the container with nginx on port 7860.
|
frontend/README.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
|
| 2 |
+
|
| 3 |
+
## Getting Started
|
| 4 |
+
|
| 5 |
+
First, run the development server:
|
| 6 |
+
|
| 7 |
+
```bash
|
| 8 |
+
npm install
|
| 9 |
+
|
| 10 |
+
npm run dev
|
| 11 |
+
# or
|
| 12 |
+
yarn dev
|
| 13 |
+
# or
|
| 14 |
+
pnpm dev
|
| 15 |
+
# or
|
| 16 |
+
bun dev
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
| 20 |
+
|
| 21 |
+
You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file.
|
| 22 |
+
|
| 23 |
+
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
| 24 |
+
|
| 25 |
+
## Learn More
|
| 26 |
+
|
| 27 |
+
To learn more about Next.js, take a look at the following resources:
|
| 28 |
+
|
| 29 |
+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
| 30 |
+
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
| 31 |
+
|
| 32 |
+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
| 33 |
+
|
| 34 |
+
## Deploy on Vercel
|
| 35 |
+
|
| 36 |
+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
| 37 |
+
|
| 38 |
+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
|
frontend/cole.pdf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1a5a63b843b58da52d57907935421416d7cd72cb351220577e24bbed4056c2b0
|
| 3 |
+
size 463232
|
frontend/eslint.config.mjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { dirname } from "path";
|
| 2 |
+
import { fileURLToPath } from "url";
|
| 3 |
+
import { FlatCompat } from "@eslint/eslintrc";
|
| 4 |
+
|
| 5 |
+
const __filename = fileURLToPath(import.meta.url);
|
| 6 |
+
const __dirname = dirname(__filename);
|
| 7 |
+
|
| 8 |
+
const compat = new FlatCompat({
|
| 9 |
+
baseDirectory: __dirname,
|
| 10 |
+
});
|
| 11 |
+
|
| 12 |
+
const eslintConfig = [...compat.extends("next/core-web-vitals")];
|
| 13 |
+
|
| 14 |
+
export default eslintConfig;
|
frontend/jsconfig.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"compilerOptions": {
|
| 3 |
+
"paths": {
|
| 4 |
+
"@/*": ["./src/*"]
|
| 5 |
+
}
|
| 6 |
+
}
|
| 7 |
+
}
|
frontend/next.config.mjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/** @type {import('next').NextConfig} */
|
| 2 |
+
const nextConfig = {};
|
| 3 |
+
|
| 4 |
+
export default nextConfig;
|
frontend/package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "cole_nlu_benchmark",
|
| 3 |
+
"version": "0.1.0",
|
| 4 |
+
"private": true,
|
| 5 |
+
"scripts": {
|
| 6 |
+
"dev": "next dev --turbopack",
|
| 7 |
+
"build": "next build",
|
| 8 |
+
"start": "next start",
|
| 9 |
+
"lint": "next lint"
|
| 10 |
+
},
|
| 11 |
+
"dependencies": {
|
| 12 |
+
"i18next": "^25.10.3",
|
| 13 |
+
"i18next-browser-languagedetector": "^8.2.1",
|
| 14 |
+
"lucide-react": "^0.523.0",
|
| 15 |
+
"next": "15.3.3",
|
| 16 |
+
"react": "^19.2.4",
|
| 17 |
+
"react-dom": "^19.2.4",
|
| 18 |
+
"react-i18next": "^13.0.0"
|
| 19 |
+
},
|
| 20 |
+
"devDependencies": {
|
| 21 |
+
"@eslint/eslintrc": "^3",
|
| 22 |
+
"@tailwindcss/postcss": "^4",
|
| 23 |
+
"eslint": "^9",
|
| 24 |
+
"eslint-config-next": "^15.3.3",
|
| 25 |
+
"tailwindcss": "^4"
|
| 26 |
+
}
|
| 27 |
+
}
|
frontend/postcss.config.mjs
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const config = {
|
| 2 |
+
plugins: ["@tailwindcss/postcss"],
|
| 3 |
+
};
|
| 4 |
+
|
| 5 |
+
export default config;
|
frontend/src/app/FAQ/page.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import '../i18n';
|
| 4 |
+
import { useState } from 'react';
|
| 5 |
+
import { useTranslation } from 'react-i18next';
|
| 6 |
+
|
| 7 |
+
export default function FAQ() {
|
| 8 |
+
const { t } = useTranslation();
|
| 9 |
+
const faqs = t('faqs', { returnObjects: true });
|
| 10 |
+
const [openIndex, setOpenIndex] = useState(null);
|
| 11 |
+
|
| 12 |
+
const toggle = (index) => {
|
| 13 |
+
setOpenIndex(openIndex === index ? null : index);
|
| 14 |
+
};
|
| 15 |
+
|
| 16 |
+
return (
|
| 17 |
+
<div className="max-w-5xl mx-auto px-6 py-3">
|
| 18 |
+
<h2 className="text-3xl font-bold text-center text-blue-700 border-b pb-4 mb-10">
|
| 19 |
+
{t('faq_title')}
|
| 20 |
+
</h2>
|
| 21 |
+
|
| 22 |
+
<div className="space-y-4">
|
| 23 |
+
{faqs.map((faq, i) => (
|
| 24 |
+
<div
|
| 25 |
+
key={i}
|
| 26 |
+
className="p-6 bg-white border border-gray-200 rounded-lg shadow-sm transition"
|
| 27 |
+
>
|
| 28 |
+
<button
|
| 29 |
+
className="w-full text-left text-xl font-semibold text-gray-800 flex justify-between items-center"
|
| 30 |
+
onClick={() => toggle(i)}
|
| 31 |
+
>
|
| 32 |
+
<span>{`${i + 1}. ${faq.question}`}</span>
|
| 33 |
+
<span className="text-2xl text-gray-500">
|
| 34 |
+
{openIndex === i ? '▴' : '▾'}
|
| 35 |
+
</span>
|
| 36 |
+
</button>
|
| 37 |
+
{openIndex === i && (
|
| 38 |
+
<p className="mt-4 text-gray-600 text-sm">
|
| 39 |
+
{faq.answer.split(/(<code>.*?<\/code>)/g).map((part, j) =>
|
| 40 |
+
part.startsWith('<code>') ? (
|
| 41 |
+
<code key={j}>{part.replace(/<\/?code>/g, '')}</code>
|
| 42 |
+
) : (
|
| 43 |
+
part
|
| 44 |
+
)
|
| 45 |
+
)}
|
| 46 |
+
</p>
|
| 47 |
+
)}
|
| 48 |
+
</div>
|
| 49 |
+
))}
|
| 50 |
+
</div>
|
| 51 |
+
</div>
|
| 52 |
+
);
|
| 53 |
+
}
|
frontend/src/app/benchmarks/page.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import '../i18n';
|
| 4 |
+
import { useTranslation } from 'react-i18next';
|
| 5 |
+
import Link from 'next/link';
|
| 6 |
+
|
| 7 |
+
const categories = [
|
| 8 |
+
{
|
| 9 |
+
key: 'benchmarks_category_sentiment',
|
| 10 |
+
benchmarks: [
|
| 11 |
+
{ titleKey: 'benchmark_alloCine_title', descKey: 'benchmark_alloCine_description', link: 'https://huggingface.co/datasets/CATIE-AQ/allocine_fr_prompt_sentiment_analysis', metrics: 'Accuracy' },
|
| 12 |
+
{ titleKey: 'benchmark_mms_title', descKey: 'benchmark_mms_description', link: 'https://huggingface.co/datasets/Brand24/mms', metrics: 'Accuracy' },
|
| 13 |
+
],
|
| 14 |
+
},
|
| 15 |
+
{
|
| 16 |
+
key: 'benchmarks_category_nli',
|
| 17 |
+
benchmarks: [
|
| 18 |
+
{ titleKey: 'benchmark_fracas_title', descKey: 'benchmark_fracas_description', link: 'https://huggingface.co/datasets/maximoss/fracas', metrics: 'Accuracy' },
|
| 19 |
+
{ titleKey: 'benchmark_gqnli_title', descKey: 'benchmark_gqnli_description', link: 'https://huggingface.co/datasets/maximoss/gqnli-fr', metrics: 'Accuracy' },
|
| 20 |
+
{ titleKey: 'benchmark_lingnli_title', descKey: 'benchmark_lingnli_description', link: 'https://huggingface.co/datasets/maximoss/lingnli-multi-mt', metrics: 'Accuracy' },
|
| 21 |
+
{ titleKey: 'benchmark_mnli_nineeleven_fr_mt_title', descKey: 'benchmark_mnli_nineeleven_fr_mt_description', link: 'https://huggingface.co/datasets/maximoss/mnli-nineeleven-fr-mt', metrics: 'Accuracy' },
|
| 22 |
+
{ titleKey: 'benchmark_rte3_french_title', descKey: 'benchmark_rte3_french_description', link: 'https://huggingface.co/datasets/maximoss/rte3-french', metrics: 'Accuracy' },
|
| 23 |
+
{ titleKey: 'benchmark_sickfr_title', descKey: 'benchmark_sickfr_description', link: 'https://huggingface.co/datasets/Lajavaness/SICK-fr', metrics: 'Pearson' },
|
| 24 |
+
{ titleKey: 'benchmark_xnli_title', descKey: 'benchmark_xnli_description', link: 'https://github.com/facebookresearch/XNLI', metrics: 'Accuracy' },
|
| 25 |
+
{ titleKey: 'benchmark_daccord_title', descKey: 'benchmark_daccord_description', link: 'https://huggingface.co/datasets/maximoss/daccord-contradictions', metrics: 'Accuracy' },
|
| 26 |
+
],
|
| 27 |
+
},
|
| 28 |
+
{
|
| 29 |
+
key: 'benchmarks_category_qa',
|
| 30 |
+
benchmarks: [
|
| 31 |
+
{ titleKey: 'benchmark_fquad_title', descKey: 'benchmark_fquad_description', link: 'https://arxiv.org/pdf/2002.06071', metrics: 'F1 Score, Exact Match Ratio' },
|
| 32 |
+
{ titleKey: 'benchmark_french_boolq_title', descKey: 'benchmark_french_boolq_description', link: 'https://huggingface.co/datasets/manu/french_boolq', metrics: 'Accuracy' },
|
| 33 |
+
{ titleKey: 'benchmark_piaf_title', descKey: 'benchmark_piaf_description', link: 'https://aclanthology.org/2020.lrec-1.673/', metrics: 'F1 Score, Exact Match Ratio' },
|
| 34 |
+
],
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
key: 'benchmarks_category_paraphrase',
|
| 38 |
+
benchmarks: [
|
| 39 |
+
{ titleKey: 'benchmark_paws_title', descKey: 'benchmark_paws_description', link: 'https://huggingface.co/datasets/google-research-datasets/paws-x', metrics: 'Accuracy' },
|
| 40 |
+
{ titleKey: 'benchmark_qfrblimp_title', descKey: 'benchmark_qfrblimp_description', link: 'https://github.com/davebulaval/FrBLiMP', metrics: 'Accuracy' },
|
| 41 |
+
],
|
| 42 |
+
},
|
| 43 |
+
{
|
| 44 |
+
key: 'benchmarks_category_grammar',
|
| 45 |
+
benchmarks: [
|
| 46 |
+
{ titleKey: 'benchmark_multiblimp_title', descKey: 'benchmark_multiblimp_description', link: 'https://huggingface.co/datasets/jumelet/multiblimp', metrics: 'Accuracy' },
|
| 47 |
+
{ titleKey: 'benchmark_qfrcola_title', descKey: 'benchmark_qfrcola_description', link: 'https://github.com/davebulaval/qfrcola', metrics: 'Accuracy' },
|
| 48 |
+
],
|
| 49 |
+
},
|
| 50 |
+
{
|
| 51 |
+
key: 'benchmarks_category_similarity',
|
| 52 |
+
benchmarks: [
|
| 53 |
+
{ titleKey: 'benchmark_sts22_title', descKey: 'benchmark_sts22_description', link: 'https://huggingface.co/datasets/mteb/sts22-crosslingual-sts/viewer/fr', metrics: 'Pearson' },
|
| 54 |
+
],
|
| 55 |
+
},
|
| 56 |
+
{
|
| 57 |
+
key: 'benchmarks_category_wsd',
|
| 58 |
+
benchmarks: [
|
| 59 |
+
{ titleKey: 'benchmark_wsd_title', descKey: 'benchmark_wsd_description', link: 'https://huggingface.co/datasets/GETALP/flue', metrics: 'Exact Match Ratio' },
|
| 60 |
+
],
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
key: 'benchmarks_category_quebec',
|
| 64 |
+
benchmarks: [
|
| 65 |
+
{ titleKey: 'benchmark_qfrcore_title', descKey: 'benchmark_qfrcore_description', link: '', metrics: 'Accuracy' },
|
| 66 |
+
{ titleKey: 'benchmark_qfrcort_title', descKey: 'benchmark_qfrcort_description', link: '', metrics: 'Accuracy' },
|
| 67 |
+
],
|
| 68 |
+
},
|
| 69 |
+
{
|
| 70 |
+
key: 'benchmarks_category_coreference',
|
| 71 |
+
benchmarks: [
|
| 72 |
+
{ titleKey: 'benchmark_wino_x_lm_title', descKey: 'benchmark_wino_x_lm_description', link: 'https://huggingface.co/datasets/demelin/wino_x/viewer/lm_en_fr?views%5B%5D=lm_en_fr', metrics: 'Accuracy' },
|
| 73 |
+
{ titleKey: 'benchmark_wino_x_mt_title', descKey: 'benchmark_wino_x_mt_description', link: 'https://huggingface.co/datasets/demelin/wino_x/viewer/mt_en_fr', metrics: 'Accuracy' },
|
| 74 |
+
],
|
| 75 |
+
},
|
| 76 |
+
];
|
| 77 |
+
|
| 78 |
+
export default function Benchmarks() {
|
| 79 |
+
const { t } = useTranslation();
|
| 80 |
+
|
| 81 |
+
return (
|
| 82 |
+
<div>
|
| 83 |
+
<div className="max-w-5xl mx-auto px-2 py-3">
|
| 84 |
+
<p className="text-1.5xl text-left text-gray-800">
|
| 85 |
+
{t('benchmarksIntro')}
|
| 86 |
+
</p>
|
| 87 |
+
</div>
|
| 88 |
+
<div className="space-y-10">
|
| 89 |
+
{categories.map((cat) => (
|
| 90 |
+
<div key={cat.key}>
|
| 91 |
+
<h2 className="text-xl font-bold text-blue-700 mb-4 border-l-4 border-blue-600 pl-3">
|
| 92 |
+
{t(cat.key)}
|
| 93 |
+
</h2>
|
| 94 |
+
<div className="space-y-4">
|
| 95 |
+
{cat.benchmarks.map((b) => (
|
| 96 |
+
<Benchmark
|
| 97 |
+
key={b.titleKey}
|
| 98 |
+
title={t(b.titleKey)}
|
| 99 |
+
link={b.link}
|
| 100 |
+
description={t(b.descKey)}
|
| 101 |
+
metrics={b.metrics}
|
| 102 |
+
/>
|
| 103 |
+
))}
|
| 104 |
+
</div>
|
| 105 |
+
</div>
|
| 106 |
+
))}
|
| 107 |
+
</div>
|
| 108 |
+
</div>
|
| 109 |
+
);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
function Benchmark({ title, description, metrics, link }) {
|
| 113 |
+
const { t } = useTranslation();
|
| 114 |
+
|
| 115 |
+
return (
|
| 116 |
+
<div className="p-6 bg-white border border-gray-200 rounded-lg shadow-sm">
|
| 117 |
+
<h3 className="text-xl font-semibold text-blue-700 mb-2 border-b-2 border-blue-500 inline-block">
|
| 118 |
+
{link ? (
|
| 119 |
+
<Link href={link} className="hover:underline">
|
| 120 |
+
{title}
|
| 121 |
+
</Link>
|
| 122 |
+
) : (
|
| 123 |
+
title
|
| 124 |
+
)}
|
| 125 |
+
</h3>
|
| 126 |
+
<p className="text-gray-700 mb-2">{description}</p>
|
| 127 |
+
<p className="text-sm text-gray-500">
|
| 128 |
+
<span className="font-medium">{t('metrics')}</span> {metrics}
|
| 129 |
+
</p>
|
| 130 |
+
</div>
|
| 131 |
+
);
|
| 132 |
+
}
|
frontend/src/app/components/BigBlueButton.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
|
| 3 |
+
export default function BigBlueButton({ children, onClick, disabled }) {
|
| 4 |
+
return (
|
| 5 |
+
<button
|
| 6 |
+
onClick={onClick}
|
| 7 |
+
disabled={disabled}
|
| 8 |
+
className={`px-4 py-2 text-white text-base font-medium rounded-md shadow-sm focus:outline-none focus:ring-2 ${
|
| 9 |
+
disabled
|
| 10 |
+
? "bg-gray-400 cursor-not-allowed"
|
| 11 |
+
: "bg-blue-500 hover:bg-blue-600 focus:ring-blue-300"
|
| 12 |
+
}`}
|
| 13 |
+
>
|
| 14 |
+
{children}
|
| 15 |
+
</button>
|
| 16 |
+
);
|
| 17 |
+
}
|
frontend/src/app/components/ClientHeader.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import '../i18n';
|
| 4 |
+
import { useTranslation } from 'react-i18next';
|
| 5 |
+
import Taskbar from './taskbar';
|
| 6 |
+
import { LanguageSwitcher } from './LanguageSwitcher';
|
| 7 |
+
|
| 8 |
+
export default function ClientHeader() {
|
| 9 |
+
useTranslation();
|
| 10 |
+
|
| 11 |
+
return (
|
| 12 |
+
<header className="flex items-center justify-between px-4 py-3 shadow">
|
| 13 |
+
<Taskbar />
|
| 14 |
+
<LanguageSwitcher />
|
| 15 |
+
</header>
|
| 16 |
+
);
|
| 17 |
+
}
|
frontend/src/app/components/CodeBlock.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export default function CodeBlock({children}){
|
| 2 |
+
return (
|
| 3 |
+
<pre className="bg-gray-100 p-4 rounded-md overflow-x-auto text-sm text-gray-800 mt-4">
|
| 4 |
+
<code className="font-mono">
|
| 5 |
+
{children}
|
| 6 |
+
</code>
|
| 7 |
+
</pre>
|
| 8 |
+
|
| 9 |
+
);
|
| 10 |
+
};
|
frontend/src/app/components/ErrorMessage.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
export default function ErrorMessage({children,condition}){
|
| 2 |
+
return(
|
| 3 |
+
<div className="pt-2">
|
| 4 |
+
<div className="pt-2 space-y-2">
|
| 5 |
+
{condition && (
|
| 6 |
+
<div className="text-red-600 text-sm font-medium">
|
| 7 |
+
{children}
|
| 8 |
+
</div>
|
| 9 |
+
)}
|
| 10 |
+
</div>
|
| 11 |
+
</div>
|
| 12 |
+
);
|
| 13 |
+
|
| 14 |
+
}
|
frontend/src/app/components/LanguageSwitcher.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import {useTranslation} from 'react-i18next';
|
| 4 |
+
|
| 5 |
+
export function LanguageSwitcher() {
|
| 6 |
+
const {i18n} = useTranslation();
|
| 7 |
+
const currentLang = i18n.language?.startsWith('fr') ? 'fr' : 'en';
|
| 8 |
+
|
| 9 |
+
const btnStyle = (lng) =>
|
| 10 |
+
lng === currentLang
|
| 11 |
+
? 'px-2 py-1 rounded-lg border-2 border-blue-600 bg-blue-600 text-white font-semibold'
|
| 12 |
+
: 'px-2 py-1 rounded-lg border border-gray-300 text-gray-500 hover:border-gray-400';
|
| 13 |
+
|
| 14 |
+
return (
|
| 15 |
+
<div className="flex space-x-1">
|
| 16 |
+
<button onClick={() => i18n.changeLanguage('en')} className={btnStyle('en')}>
|
| 17 |
+
EN
|
| 18 |
+
</button>
|
| 19 |
+
<button onClick={() => i18n.changeLanguage('fr')} className={btnStyle('fr')}>
|
| 20 |
+
FR
|
| 21 |
+
</button>
|
| 22 |
+
</div>
|
| 23 |
+
);
|
| 24 |
+
}
|
frontend/src/app/components/Modal.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"use client";
|
| 2 |
+
import BigBlueButton from "./BigBlueButton";
|
| 3 |
+
import { useTranslation } from "react-i18next";
|
| 4 |
+
|
| 5 |
+
export default function Modal({ children, onClose }) {
|
| 6 |
+
const { t } = useTranslation();
|
| 7 |
+
|
| 8 |
+
return (
|
| 9 |
+
<div
|
| 10 |
+
className="fixed inset-0 bg-gray-600 bg-opacity-25 overflow-y-auto h-full w-full flex items-center justify-center z-50"
|
| 11 |
+
style={{ backgroundColor: 'rgba(75, 85, 99, 0.55)' }}
|
| 12 |
+
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
| 13 |
+
onKeyDown={(e) => { if (e.key === 'Escape') onClose(); }}
|
| 14 |
+
role="dialog"
|
| 15 |
+
aria-modal="true"
|
| 16 |
+
tabIndex={-1}
|
| 17 |
+
>
|
| 18 |
+
<div className="p-8 border w-96 shadow-lg rounded-md bg-white">
|
| 19 |
+
<div className="text-center text-black">
|
| 20 |
+
{children}
|
| 21 |
+
<div className="flex justify-center mt-4">
|
| 22 |
+
<BigBlueButton onClick={onClose}>
|
| 23 |
+
{t('close')}
|
| 24 |
+
</BigBlueButton>
|
| 25 |
+
</div>
|
| 26 |
+
</div>
|
| 27 |
+
</div>
|
| 28 |
+
</div>
|
| 29 |
+
);
|
| 30 |
+
}
|
frontend/src/app/components/ModalManager.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useSearchParams, useRouter } from 'next/navigation';
|
| 4 |
+
import Modal from './Modal';
|
| 5 |
+
import SubmitForm from './SubmitForm';
|
| 6 |
+
|
| 7 |
+
export default function ModalManager() {
|
| 8 |
+
const searchParams = useSearchParams();
|
| 9 |
+
const submitModal = searchParams.get("show") === "submit";
|
| 10 |
+
const router = useRouter();
|
| 11 |
+
|
| 12 |
+
const handleClose = () => {
|
| 13 |
+
const newUrl = window.location.pathname;
|
| 14 |
+
router.push(newUrl);
|
| 15 |
+
};
|
| 16 |
+
|
| 17 |
+
return (
|
| 18 |
+
<>
|
| 19 |
+
{submitModal && (
|
| 20 |
+
<Modal onClose={handleClose}>
|
| 21 |
+
<SubmitForm />
|
| 22 |
+
</Modal>
|
| 23 |
+
)}
|
| 24 |
+
</>
|
| 25 |
+
);
|
| 26 |
+
}
|
frontend/src/app/components/SubmitForm.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import { useState } from "react";
|
| 4 |
+
import { useRouter } from "next/navigation";
|
| 5 |
+
import ErrorMessage from "./ErrorMessage";
|
| 6 |
+
import { BACKEND_ADDRESS } from "@/app/resources/ResourcesPaths";
|
| 7 |
+
import { Trans } from 'react-i18next';
|
| 8 |
+
import BigBlueButton from "./BigBlueButton";
|
| 9 |
+
import { useTranslation } from 'react-i18next';
|
| 10 |
+
|
| 11 |
+
export default function SubmitForm() {
|
| 12 |
+
const { t } = useTranslation();
|
| 13 |
+
const router = useRouter();
|
| 14 |
+
|
| 15 |
+
const [requiredVisible, setRequiredVisible] = useState(false);
|
| 16 |
+
const [email, setEmail] = useState('');
|
| 17 |
+
const [displayName, setDisplayName] = useState('');
|
| 18 |
+
const [file, setFile] = useState(null);
|
| 19 |
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
| 20 |
+
const [submitStatus, setSubmitStatus] = useState(null); // 'success' | 'error'
|
| 21 |
+
const [errorMessage, setErrorMessage] = useState('');
|
| 22 |
+
const [submissionId, setSubmissionId] = useState(null);
|
| 23 |
+
|
| 24 |
+
const handleFileChange = (e) => {
|
| 25 |
+
setFile(e.target.files[0]);
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
const submitResults = async () => {
|
| 29 |
+
if (!email || !displayName || !file) {
|
| 30 |
+
setRequiredVisible(true);
|
| 31 |
+
return;
|
| 32 |
+
}
|
| 33 |
+
if (!file.name.toLowerCase().endsWith('.zip')) {
|
| 34 |
+
alert(t('submit_zipAlert'));
|
| 35 |
+
return;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
setRequiredVisible(false);
|
| 39 |
+
setIsSubmitting(true);
|
| 40 |
+
|
| 41 |
+
const formData = new FormData();
|
| 42 |
+
formData.append('email', email);
|
| 43 |
+
formData.append('display_name', displayName);
|
| 44 |
+
formData.append('predictions_zip', file);
|
| 45 |
+
|
| 46 |
+
try {
|
| 47 |
+
const res = await fetch(`${BACKEND_ADDRESS}/submit`, {
|
| 48 |
+
method: "POST",
|
| 49 |
+
body: formData,
|
| 50 |
+
});
|
| 51 |
+
if (!res.ok) {
|
| 52 |
+
const err = await res.json().catch(() => null);
|
| 53 |
+
throw new Error(err?.detail || `HTTP ${res.status}`);
|
| 54 |
+
}
|
| 55 |
+
const json = await res.json();
|
| 56 |
+
const id = json.submission_id;
|
| 57 |
+
setSubmissionId(id);
|
| 58 |
+
localStorage.setItem('last_result_file', `${id}.json`);
|
| 59 |
+
localStorage.setItem('just_submitted', 'true');
|
| 60 |
+
setSubmitStatus('success');
|
| 61 |
+
} catch (err) {
|
| 62 |
+
setErrorMessage(err.message);
|
| 63 |
+
setSubmitStatus('error');
|
| 64 |
+
} finally {
|
| 65 |
+
setIsSubmitting(false);
|
| 66 |
+
}
|
| 67 |
+
};
|
| 68 |
+
|
| 69 |
+
const renderModal = () => {
|
| 70 |
+
if (submitStatus === 'success') {
|
| 71 |
+
return (
|
| 72 |
+
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
|
| 73 |
+
<div className="bg-white p-6 rounded-2xl shadow-lg max-w-sm text-center">
|
| 74 |
+
<h3 className="text-xl font-semibold text-green-600">
|
| 75 |
+
{t('submit_successTitle')}
|
| 76 |
+
</h3>
|
| 77 |
+
<p className="mt-2">{t('submit_successMessage')}</p>
|
| 78 |
+
<BigBlueButton
|
| 79 |
+
className="mt-4 px-4 py-2 rounded-full shadow hover:shadow-md"
|
| 80 |
+
onClick={() => router.push(`/results/${submissionId}`)}
|
| 81 |
+
>
|
| 82 |
+
{t('submit_checkResults')}
|
| 83 |
+
</BigBlueButton>
|
| 84 |
+
</div>
|
| 85 |
+
</div>
|
| 86 |
+
);
|
| 87 |
+
}
|
| 88 |
+
if (submitStatus === 'error') {
|
| 89 |
+
return (
|
| 90 |
+
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50">
|
| 91 |
+
<div className="bg-white p-6 rounded-2xl shadow-lg max-w-sm text-center">
|
| 92 |
+
<h3 className="text-xl font-semibold text-red-600">
|
| 93 |
+
{t('submit_errorTitle')}
|
| 94 |
+
</h3>
|
| 95 |
+
<p className="mt-2">
|
| 96 |
+
<Trans i18nKey="submit_errorMessage" values={{ errorMessage }}>
|
| 97 |
+
Submission error: {{ errorMessage }}
|
| 98 |
+
</Trans>
|
| 99 |
+
</p>
|
| 100 |
+
<button
|
| 101 |
+
className="mt-4 px-4 py-2 rounded-full shadow hover:shadow-md"
|
| 102 |
+
onClick={() => setSubmitStatus(null)}
|
| 103 |
+
>
|
| 104 |
+
{t('submit_closeButton')}
|
| 105 |
+
</button>
|
| 106 |
+
</div>
|
| 107 |
+
</div>
|
| 108 |
+
);
|
| 109 |
+
}
|
| 110 |
+
return null;
|
| 111 |
+
};
|
| 112 |
+
|
| 113 |
+
return (
|
| 114 |
+
<div className="relative">
|
| 115 |
+
<div className="space-y-6 bg-white rounded-xl shadow-md p-6 w-full max-w-xl mx-auto border border-gray-200">
|
| 116 |
+
<h2 className="text-2xl font-semibold text-gray-800 text-center">
|
| 117 |
+
{t('submit_formTitle')}
|
| 118 |
+
</h2>
|
| 119 |
+
|
| 120 |
+
<div className="space-y-2">
|
| 121 |
+
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
|
| 122 |
+
{t('submit_labelEmail')}
|
| 123 |
+
</label>
|
| 124 |
+
<input
|
| 125 |
+
id="email"
|
| 126 |
+
type="email"
|
| 127 |
+
placeholder={t('submit_placeholderEmail')}
|
| 128 |
+
value={email}
|
| 129 |
+
onChange={(e) => setEmail(e.target.value)}
|
| 130 |
+
className="border border-gray-300 p-3 rounded-md w-full focus:ring-2 focus:ring-blue-500"
|
| 131 |
+
/>
|
| 132 |
+
</div>
|
| 133 |
+
|
| 134 |
+
<div className="space-y-2">
|
| 135 |
+
<label htmlFor="displayname" className="block text-sm font-medium text-gray-700">
|
| 136 |
+
{t('submit_labelDisplayName')}
|
| 137 |
+
</label>
|
| 138 |
+
<input
|
| 139 |
+
id="displayname"
|
| 140 |
+
type="text"
|
| 141 |
+
placeholder={t('submit_placeholderDisplayName')}
|
| 142 |
+
value={displayName}
|
| 143 |
+
onChange={(e) => setDisplayName(e.target.value)}
|
| 144 |
+
className="border border-gray-300 p-3 rounded-md w-full focus:ring-2 focus:ring-blue-500"
|
| 145 |
+
/>
|
| 146 |
+
</div>
|
| 147 |
+
|
| 148 |
+
<div className="space-y-2">
|
| 149 |
+
<label htmlFor="zipfile" className="block text-sm font-medium text-gray-700">
|
| 150 |
+
{t('submit_labelZip')}
|
| 151 |
+
</label>
|
| 152 |
+
<input
|
| 153 |
+
id="zipfile"
|
| 154 |
+
type="file"
|
| 155 |
+
accept=".zip"
|
| 156 |
+
onChange={handleFileChange}
|
| 157 |
+
className="border border-gray-300 p-3 rounded-md w-full focus:ring-2 focus:ring-blue-500"
|
| 158 |
+
/>
|
| 159 |
+
</div>
|
| 160 |
+
|
| 161 |
+
<ErrorMessage condition={requiredVisible}>
|
| 162 |
+
⚠️ Email, display name & ZIP are required.
|
| 163 |
+
</ErrorMessage>
|
| 164 |
+
|
| 165 |
+
<button
|
| 166 |
+
onClick={submitResults}
|
| 167 |
+
disabled={isSubmitting}
|
| 168 |
+
className="w-full bg-blue-600 text-white py-3 rounded-xl hover:bg-blue-700 mt-4"
|
| 169 |
+
>
|
| 170 |
+
{isSubmitting ? t('submit_submitting') : t('submit_button')}
|
| 171 |
+
</button>
|
| 172 |
+
|
| 173 |
+
{renderModal()}
|
| 174 |
+
</div>
|
| 175 |
+
</div>
|
| 176 |
+
);
|
| 177 |
+
}
|
frontend/src/app/components/taskbar.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import '../i18n';
|
| 4 |
+
import Link from 'next/link';
|
| 5 |
+
import { usePathname } from 'next/navigation';
|
| 6 |
+
import { FileText, Menu, X } from 'lucide-react';
|
| 7 |
+
import { useTranslation } from 'react-i18next';
|
| 8 |
+
import { useState } from 'react';
|
| 9 |
+
|
| 10 |
+
export default function Taskbar() {
|
| 11 |
+
const { t } = useTranslation();
|
| 12 |
+
const pathname = usePathname();
|
| 13 |
+
const [menuOpen, setMenuOpen] = useState(false);
|
| 14 |
+
|
| 15 |
+
const linkStyle = (path) =>
|
| 16 |
+
(pathname === path || (path !== '/' && pathname.startsWith(path)))
|
| 17 |
+
? 'text-blue-600 font-semibold border-b-2 border-blue-600 pb-1'
|
| 18 |
+
: 'text-gray-700 hover:text-blue-500';
|
| 19 |
+
|
| 20 |
+
const mobileLink = (path) =>
|
| 21 |
+
(pathname === path || (path !== '/' && pathname.startsWith(path)))
|
| 22 |
+
? 'block text-blue-600 font-semibold py-2'
|
| 23 |
+
: 'block text-gray-700 hover:text-blue-500 py-2';
|
| 24 |
+
|
| 25 |
+
const links = (
|
| 26 |
+
<>
|
| 27 |
+
<Link href="/guide" className={linkStyle('/guide')} onClick={() => setMenuOpen(false)}>
|
| 28 |
+
{t('nav_guide')}
|
| 29 |
+
</Link>
|
| 30 |
+
<Link href="/FAQ" className={linkStyle('/FAQ')} onClick={() => setMenuOpen(false)}>
|
| 31 |
+
{t('nav_faq')}
|
| 32 |
+
</Link>
|
| 33 |
+
<Link href="/contact" className={linkStyle('/contact')} onClick={() => setMenuOpen(false)}>
|
| 34 |
+
{t('nav_contact')}
|
| 35 |
+
</Link>
|
| 36 |
+
<Link href={`${pathname}?show=submit`} className={linkStyle('/submit')} onClick={() => setMenuOpen(false)}>
|
| 37 |
+
{t('nav_submit')}
|
| 38 |
+
</Link>
|
| 39 |
+
<Link href="/results" className={linkStyle('/results')} onClick={() => setMenuOpen(false)}>
|
| 40 |
+
{t('nav_results')}
|
| 41 |
+
</Link>
|
| 42 |
+
<Link href="/benchmarks" className={linkStyle('/benchmarks')} onClick={() => setMenuOpen(false)}>
|
| 43 |
+
{t('nav_tasks')}
|
| 44 |
+
</Link>
|
| 45 |
+
<Link href="/leaderboard" className={linkStyle('/leaderboard')} onClick={() => setMenuOpen(false)}>
|
| 46 |
+
{t('nav_leaderboard')}
|
| 47 |
+
</Link>
|
| 48 |
+
<Link href="https://huggingface.co/datasets/graalul/COLE-public" target="_blank" rel="noopener noreferrer" className={linkStyle('/hf')} onClick={() => setMenuOpen(false)}>
|
| 49 |
+
{t('nav_datasets')}
|
| 50 |
+
</Link>
|
| 51 |
+
</>
|
| 52 |
+
);
|
| 53 |
+
|
| 54 |
+
const mobileLinks = (
|
| 55 |
+
<>
|
| 56 |
+
<Link href="/guide" className={mobileLink('/guide')} onClick={() => setMenuOpen(false)}>
|
| 57 |
+
{t('nav_guide')}
|
| 58 |
+
</Link>
|
| 59 |
+
<Link href="/FAQ" className={mobileLink('/FAQ')} onClick={() => setMenuOpen(false)}>
|
| 60 |
+
{t('nav_faq')}
|
| 61 |
+
</Link>
|
| 62 |
+
<Link href="/contact" className={mobileLink('/contact')} onClick={() => setMenuOpen(false)}>
|
| 63 |
+
{t('nav_contact')}
|
| 64 |
+
</Link>
|
| 65 |
+
<Link href={`${pathname}?show=submit`} className={mobileLink('/submit')} onClick={() => setMenuOpen(false)}>
|
| 66 |
+
{t('nav_submit')}
|
| 67 |
+
</Link>
|
| 68 |
+
<Link href="/results" className={mobileLink('/results')} onClick={() => setMenuOpen(false)}>
|
| 69 |
+
{t('nav_results')}
|
| 70 |
+
</Link>
|
| 71 |
+
<Link href="/benchmarks" className={mobileLink('/benchmarks')} onClick={() => setMenuOpen(false)}>
|
| 72 |
+
{t('nav_tasks')}
|
| 73 |
+
</Link>
|
| 74 |
+
<Link href="/leaderboard" className={mobileLink('/leaderboard')} onClick={() => setMenuOpen(false)}>
|
| 75 |
+
{t('nav_leaderboard')}
|
| 76 |
+
</Link>
|
| 77 |
+
<Link href="https://huggingface.co/datasets/graalul/COLE-public" className={mobileLink('/hf')} onClick={() => setMenuOpen(false)}>
|
| 78 |
+
{t('nav_datasets')}
|
| 79 |
+
</Link>
|
| 80 |
+
</>
|
| 81 |
+
);
|
| 82 |
+
|
| 83 |
+
return (
|
| 84 |
+
<nav className="w-full py-4 mx-auto max-w-7xl px-4">
|
| 85 |
+
<div className="flex justify-between items-center">
|
| 86 |
+
<div className="flex items-center">
|
| 87 |
+
<Link href="/">
|
| 88 |
+
<span className="text-xl font-bold text-blue-600">{t('nav_home')}</span>
|
| 89 |
+
</Link>
|
| 90 |
+
<Link href="/papers" className="ml-2">
|
| 91 |
+
<FileText className="w-6 h-6 text-blue-600 hover:text-blue-500" />
|
| 92 |
+
</Link>
|
| 93 |
+
</div>
|
| 94 |
+
|
| 95 |
+
{/* Desktop nav */}
|
| 96 |
+
<div className="hidden md:flex space-x-6">
|
| 97 |
+
{links}
|
| 98 |
+
</div>
|
| 99 |
+
|
| 100 |
+
{/* Mobile hamburger */}
|
| 101 |
+
<button
|
| 102 |
+
className="md:hidden text-gray-700"
|
| 103 |
+
onClick={() => setMenuOpen(!menuOpen)}
|
| 104 |
+
>
|
| 105 |
+
{menuOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
|
| 106 |
+
</button>
|
| 107 |
+
</div>
|
| 108 |
+
|
| 109 |
+
{/* Mobile menu */}
|
| 110 |
+
{menuOpen && (
|
| 111 |
+
<div className="md:hidden mt-4 border-t border-gray-200 pt-4 space-y-1">
|
| 112 |
+
{mobileLinks}
|
| 113 |
+
</div>
|
| 114 |
+
)}
|
| 115 |
+
</nav>
|
| 116 |
+
);
|
| 117 |
+
}
|
frontend/src/app/contact/page.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import '../i18n';
|
| 4 |
+
import { useTranslation } from 'react-i18next';
|
| 5 |
+
|
| 6 |
+
export default function Contact() {
|
| 7 |
+
const { t } = useTranslation();
|
| 8 |
+
|
| 9 |
+
return (
|
| 10 |
+
<div className="max-w-5xl mx-auto px-6 py-3">
|
| 11 |
+
<h2 className="text-3xl font-bold text-center text-blue-700 border-b pb-4 mb-10">
|
| 12 |
+
{t('contact_title')}
|
| 13 |
+
</h2>
|
| 14 |
+
|
| 15 |
+
<p className="text-gray-700 mb-4 leading-relaxed">
|
| 16 |
+
{t('contact_paragraph')}
|
| 17 |
+
</p>
|
| 18 |
+
|
| 19 |
+
<div className="bg-gray-50 p-4 rounded-md border border-dashed border-blue-400">
|
| 20 |
+
<p className="text-sm text-gray-500 mb-2">
|
| 21 |
+
{t('contact_email_label')}
|
| 22 |
+
</p>
|
| 23 |
+
<a
|
| 24 |
+
href="mailto:david.beauchemin@ift.ulaval.ca"
|
| 25 |
+
className="text-blue-600 font-mono text-lg hover:underline"
|
| 26 |
+
>
|
| 27 |
+
david.beauchemin@ift.ulaval.ca
|
| 28 |
+
</a>
|
| 29 |
+
</div>
|
| 30 |
+
</div>
|
| 31 |
+
);
|
| 32 |
+
}
|
frontend/src/app/en/translation.json
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"siteTitle": "COLE",
|
| 3 |
+
"welcome": "Welcome to COLE!",
|
| 4 |
+
"upload": "Upload",
|
| 5 |
+
"submit": "Submit",
|
| 6 |
+
"results": "Results",
|
| 7 |
+
"contact": "Contact",
|
| 8 |
+
"contactUs": "Contact us",
|
| 9 |
+
"guide": "Guide",
|
| 10 |
+
"faq": "FAQ",
|
| 11 |
+
"submitResults": "Submit your results",
|
| 12 |
+
"ourTasks": "Our tasks",
|
| 13 |
+
"ourDatasets": "Our datasets",
|
| 14 |
+
"leaderboard": "COLE Leaderboard",
|
| 15 |
+
"errorOccurred": "An error occurred",
|
| 16 |
+
"close": "Close",
|
| 17 |
+
"details": "Details",
|
| 18 |
+
"benchmarksIntro": "COLE is constituted of 23 tasks, each of them aims to test one or more facets of language understanding in machine learning. Below are each of the tasks in more detail.",
|
| 19 |
+
"metrics": "Metric(s) :",
|
| 20 |
+
"benchmark_alloCine_title": "Allo-ciné.ca",
|
| 21 |
+
"benchmark_alloCine_description": "Allo-ciné tests language understanding in sentiment classification by feeding movie reviews which can be either positive and negative. The task consists in giving the correct sentiment for each review.",
|
| 22 |
+
"benchmark_lingnli_title": "LingNLI",
|
| 23 |
+
"benchmark_lingnli_description": "LingNLI is a Natural Language Inference corpus collected by putting a linguist 'in the loop' to dynamically introduce novel constraints during data collection, aiming to mitigate the systematic gaps and biases often found in crowdsourced datasets.",
|
| 24 |
+
"benchmark_daccord_title": "DACCORD",
|
| 25 |
+
"benchmark_daccord_description":"Predict whether the two sentences are compatible (0) or contradict each other (1).",
|
| 26 |
+
"benchmark_fquad_title": "FQuAD - French Question Answering Dataset",
|
| 27 |
+
"benchmark_fquad_description": "FQuAD is question/answer pairs built on high-quality Wikipedia articles. The goal in this task is to accurately predict if the answer to the question can be found in the provided article.",
|
| 28 |
+
"benchmark_french_boolq_title": "French BoolQ",
|
| 29 |
+
"benchmark_french_boolq_description": "Answer whether the context allows answering 'yes' to the question (1) or only 'no' or doesn't answer (0).",
|
| 30 |
+
"benchmark_fracas_title": "FraCaS",
|
| 31 |
+
"benchmark_fracas_description": "Natural language inference task : predict the relation between two sentences (implication, neutral, contradiction).",
|
| 32 |
+
"benchmark_gqnli_title": "GQNLI-Fr - The Generalized Quantifier NLI Challenge Dataset",
|
| 33 |
+
"benchmark_gqnli_description": "The dataset consists of carefully constructed premise-hypothesis pairs. Each hypothesis logically follows from the premise, contradicts it, or is neutral.",
|
| 34 |
+
"benchmark_mms_title": "MMS - Massive Multilingual Sentiment Corpora",
|
| 35 |
+
"benchmark_mms_description": "A massive multilingual sentiment analysis corpus in 27 languages.",
|
| 36 |
+
"benchmark_mnli_nineeleven_fr_mt_title": "MNLI-NineEleven-FR-MT",
|
| 37 |
+
"benchmark_mnli_nineeleven_fr_mt_description": "Predict the relation between two sentences (entailment, neutral, contradiction).",
|
| 38 |
+
"benchmark_paws_title": "PAWS: Paraphrase Adversaries from Word Scrambling",
|
| 39 |
+
"benchmark_paws_description": "This task aims to test paraphrase identification by giving two sentences and having the model define if these sentences are equivalent in meaning or not.",
|
| 40 |
+
"benchmark_piaf_title": "PIAF - The French-Language Dataset of Questions-Answers",
|
| 41 |
+
"benchmark_piaf_description": "This task consists of pairs of questions and text answers with information of where in the answer is the truly relevant information.",
|
| 42 |
+
"benchmark_qfrblimp_title": "QFrBLiMP - a Quebec-French Linguistic minimal pairs",
|
| 43 |
+
"benchmark_qfrblimp_description": "This task gives the model sentence pairs. The goal is to determine if the sentences are semantically equivalent, even with slightly different syntax and words.",
|
| 44 |
+
"benchmark_qfrcola_title": "QFrCoLA - a Quebec-French Corpus of Linguistic Acceptability Judgments",
|
| 45 |
+
"benchmark_qfrcola_description": "QFrCoLA is a French dataset sourced from multiple linguistic sites such as académie-française.fr and vitrinelinguistique.com. It aims to test models’ ability to determine grammatical correctness. The answer is a binary label indicating if the sentence is correct or not.",
|
| 46 |
+
"benchmark_qfrcore_title": "QFRCoRE: Quebec-French Corpus of Regional Expressions",
|
| 47 |
+
"benchmark_qfrcore_description": "Match the Quebec expression with its definition from a list.",
|
| 48 |
+
"benchmark_qfrcort_title": "QFRCoRT: Quebec-French Corpus of Regional Terms",
|
| 49 |
+
"benchmark_qfrcort_description": "Match the Quebec term with its definition from a list.",
|
| 50 |
+
"benchmark_rte3_french_title": "RTE3-French",
|
| 51 |
+
"benchmark_rte3_french_description": "Predict the relation between two sentences (entailment, neutral, contradiction).",
|
| 52 |
+
"benchmark_sickfr_title": "Sick-FR - French Sentences Involving Compositional Knowledge",
|
| 53 |
+
"benchmark_sickfr_description": "This task also has pairs of sentences annotated on two dimensions: relatedness (scored 1 to 5) and entailment (choices: entails, contradicts, neutral).",
|
| 54 |
+
"benchmark_sts22_title": "Sts22-Crosslingual - Multilingual News Article Similarity",
|
| 55 |
+
"benchmark_sts22_description": "This task evaluates whether pairs of news articles, written in different languages, cover the same story. It focuses on document-level similarity, where systems rate article pairs on a 4-point scale from most to least similar.",
|
| 56 |
+
"benchmark_wino_x_lm_title": "WiNo-X LM - Pronoun Resolution ",
|
| 57 |
+
"benchmark_wino_x_lm_description": "Predict the correct referent (1 or 2) of a pronoun in a sentence by choosing between two candidates.",
|
| 58 |
+
"benchmark_wino_x_mt_title": "WiNo-X MT - Pronoun Resolution ",
|
| 59 |
+
"benchmark_wino_x_mt_description": "Choose which of two French translations uses the correct pronoun (il/elle) based on the intended referent in the original English sentence.",
|
| 60 |
+
"benchmark_xnli_title": "XNLI - The Cross-Lingual NLI Corpus",
|
| 61 |
+
"benchmark_xnli_description": "This task consists of pairs of sentences where the goal is to determine the relation between the two: entailment, neutral, or contradiction.",
|
| 62 |
+
"benchmark_wsd_title": "WSD-Fr : Word Sense Disambiguation",
|
| 63 |
+
"benchmark_wsd_description": "WSD-Fr is a word sense disambiguation task where the model must identify the correct meaning of an ambiguous verb in context, as part of the FLUE benchmark.",
|
| 64 |
+
"benchmark_multiblimp_title": "MultiBLiMP-Fr - Multilingual Linguistic Minimal Pairs",
|
| 65 |
+
"benchmark_multiblimp_description": "A grammaticality judgment task using the French subset of the Multilingual Benchmark of Linguistic Minimal Pairs . Each instance is a minimal pair—one grammatical and one ungrammatical—differing by a single targeted feature. The model must select the grammatically correct sentence. This task probes fine-grained knowledge of French syntax, morphology, and agreement.",
|
| 66 |
+
"home_whatIsColleTitle": "What is COLE?",
|
| 67 |
+
"home_paragraph1": "COLE is a multidisciplinary French Natural Language Understanding benchmark ( <1>NLU</1> ). It takes inspiration from its predecessors <3>GLUE</3> and <5>SuperGLUE</5> to build a benchmark capable of evaluating models in the French language on multiple topics of language understanding. See <7>our paper</7> for more information.",
|
| 68 |
+
"home_paragraph2": "The COLE benchmark is built with multiple goals in mind. First, it aims to provide a solid and complete French alternative for benchmarking models on NLU tasks. Second, it provides the user with multiple datasets, all usable through HuggingFace’s libraries, to train or fine-tune models on specific tasks.",
|
| 69 |
+
"home_paragraph3": "We have made the choice to hide test labels to discourage cheating or overfitting on test data. To get results on your test data, you may send us your results as explained in <1>our guide</1>.",
|
| 70 |
+
"guide_title": "Using the COLE Benchmark",
|
| 71 |
+
"guide_section1_title": "Training and Testing",
|
| 72 |
+
"guide_section1_para1": "The COLE benchmark can be used to train and/or test models on multiple tasks. To train or fine-tune a model, you can fetch the train, validation and test data splits from our <0>Hugging Face public repository</0>. We recommend using Hugging Face’s libraries to simplify the process.",
|
| 73 |
+
"guide_section1_para2": "To test a model, you also need to fetch the data in the same way. Once done, your model should infer predictions for each line in the test split. Our repository includes benchmark evaluation scripts for each dataset. You only need to plug in your model's inference method using the HuggingFace Model interface. Our inference scripts are available on our <0>GitHub Repository</0>.",
|
| 74 |
+
"guide_section1_para3": "If you prefer to run inference separately, please ensure that the predictions are formatted correctly before submitting them for evaluation (see our \"Formatting the Dataset\" section).",
|
| 75 |
+
"guide_section2_title": "Formatting the Dataset",
|
| 76 |
+
"guide_section2_para1": "Before submitting your results, make sure your output is properly formatted so that our systems can process it. The expected format is a nested JSON dictionary as shown below. Once formatted, compress your JSON file into a ZIP archive (.zip) and upload it via the submission form.",
|
| 77 |
+
"faq_title": "Frequently Asked Questions",
|
| 78 |
+
"faqs": [
|
| 79 |
+
{
|
| 80 |
+
"question": "How can I evaluate my model?",
|
| 81 |
+
"answer": "Format your model predictions as a JSON file following the format described in the Guide, compress it into a ZIP archive, and upload it via the submission form on the website. The system will automatically evaluate your predictions against the hidden test labels and display the results."
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"question": "Is COLE multilingual?",
|
| 85 |
+
"answer": "No, COLE is available only in French. The benchmark is specifically designed to evaluate NLU models in the French language."
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"question": "What format should my predictions be in?",
|
| 89 |
+
"answer": "Your predictions should be a JSON file containing your model name, model URL, and a list of tasks with predictions arrays. Each task prediction array must match the order and size of the corresponding test split. See the Guide page for the exact format. The JSON file must then be compressed into a ZIP archive before submission."
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"question": "Where can I find the test data?",
|
| 93 |
+
"answer": "The test data (without labels) is available on our HuggingFace repository at <code>graalul/COLE-public</code>. You can load any task using the datasets library: <code>load_dataset('graalul/COLE-public', 'task_name')</code>."
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"question": "Why are the test labels hidden?",
|
| 97 |
+
"answer": "To ensure fair evaluation and prevent overfitting on test data, we do not release test labels. Models are evaluated server-side against the hidden ground truth when you submit your predictions."
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"question": "Can I evaluate on only a subset of tasks?",
|
| 101 |
+
"answer": "Yes, you can submit predictions for any subset of the 23 tasks. The leaderboard will show your scores on the tasks you submitted and N/A for the rest."
|
| 102 |
+
}
|
| 103 |
+
],
|
| 104 |
+
"contact_title": "Contact us",
|
| 105 |
+
"contact_paragraph": "If you have any questions, feedback, or suggestions regarding the COLE benchmark, feel free to reach out to us. We are happy to help — please note that response times may vary.",
|
| 106 |
+
"contact_email_label": "Email us at:",
|
| 107 |
+
|
| 108 |
+
"submit_formTitle": "Submit Your Results",
|
| 109 |
+
"submit_labelEmail": "Your Email",
|
| 110 |
+
"submit_placeholderEmail": "you@example.com",
|
| 111 |
+
"submit_labelDisplayName": "Display Name",
|
| 112 |
+
"submit_placeholderDisplayName": "Leaderboard Name",
|
| 113 |
+
"submit_labelFile": "Predictions ZIP",
|
| 114 |
+
"submit_labelZip" : "Select your results file",
|
| 115 |
+
"submit_requiredError": "⚠️ Email, display name & ZIP are required.",
|
| 116 |
+
"submit_zipAlert": "Please upload a ZIP (.zip) file.",
|
| 117 |
+
"submit_button": "Submit Your Results",
|
| 118 |
+
"submit_submitting": "Submitting...",
|
| 119 |
+
"submit_successTitle": "Success",
|
| 120 |
+
"submit_successMessage": "Your submission has been successfully sent!",
|
| 121 |
+
"submit_checkResults": "Check the results",
|
| 122 |
+
"submit_errorTitle": "Error ⚠️",
|
| 123 |
+
"submit_errorMessage": "Submission error: {{errorMessage}}",
|
| 124 |
+
"submit_closeButton": "Close",
|
| 125 |
+
"results_default_title": "No Results Yet",
|
| 126 |
+
"results_default_message": "Please submit a ZIP file to generate benchmark results.",
|
| 127 |
+
"results_loading": "⏳ Loading results...",
|
| 128 |
+
"results_page_title": "📊 Results for {{displayName}}",
|
| 129 |
+
"results_download": "Download JSON",
|
| 130 |
+
"results_no_results": "⚠️ No benchmark results found.",
|
| 131 |
+
"results_benchmark_label": "🧪 Benchmark: {{name}}",
|
| 132 |
+
"leaderboard_title": "Leaderboard",
|
| 133 |
+
"leaderboard_modelHeader": "Model Name",
|
| 134 |
+
"leaderboard_overallHeader": "Overall",
|
| 135 |
+
"leaderboard_avgScoreLabel": "(avg score)",
|
| 136 |
+
"leaderboard_notSpecified": "N/A",
|
| 137 |
+
"leaderboard_notSpecifiedTooltip": "This model was not evaluated on this task",
|
| 138 |
+
"leaderboard_modalTitle": "Results for {{name}}",
|
| 139 |
+
"leaderboard_closeButton": "Close",
|
| 140 |
+
"nav_home": "COLE",
|
| 141 |
+
"nav_guide": "Guide",
|
| 142 |
+
"nav_faq": "FAQ",
|
| 143 |
+
"nav_contact": "Contact us",
|
| 144 |
+
"nav_submit": "Submit your results",
|
| 145 |
+
"nav_tasks": "Our tasks",
|
| 146 |
+
"nav_results": "Results",
|
| 147 |
+
"nav_leaderboard": "COLE Leaderboard",
|
| 148 |
+
"nav_datasets": "Our datasets",
|
| 149 |
+
"papers_title": "Our papers",
|
| 150 |
+
"papers_arxiv_label": "COLE: a Comprehensive Benchmark for French Language Understanding Evaluation",
|
| 151 |
+
"papers_arxiv_authors": "David Beauchemin, Yan Tremblay, Mohamed Amine Youssef, Richard Khoury (arXiv:2510.05046, ICLR 2025 Workshop)",
|
| 152 |
+
"leaderboard_errorMessage": "Failed to load leaderboard data. Please try again later.",
|
| 153 |
+
"benchmarks_category_sentiment": "Sentiment Analysis",
|
| 154 |
+
"benchmarks_category_nli": "Natural Language Inference",
|
| 155 |
+
"benchmarks_category_qa": "Question Answering",
|
| 156 |
+
"benchmarks_category_paraphrase": "Paraphrase Detection",
|
| 157 |
+
"benchmarks_category_grammar": "Grammatical Judgment",
|
| 158 |
+
"benchmarks_category_similarity": "Semantic Similarity",
|
| 159 |
+
"benchmarks_category_wsd": "Word Sense Disambiguation",
|
| 160 |
+
"benchmarks_category_quebec": "Quebec French",
|
| 161 |
+
"benchmarks_category_coreference": "Coreference / Pronoun Resolution"
|
| 162 |
+
}
|
frontend/src/app/fr/translation.json
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"siteTitle": "COLE",
|
| 3 |
+
"welcome": "Bienvenue sur COLE !",
|
| 4 |
+
"upload": "Téléverser",
|
| 5 |
+
"submit": "Soumettre",
|
| 6 |
+
"results": "Résultats",
|
| 7 |
+
"contact": "Contact",
|
| 8 |
+
"contactUs": "Nous contacter",
|
| 9 |
+
"guide": "Guide",
|
| 10 |
+
"faq": "FAQ",
|
| 11 |
+
"submitResults": "Soumettre vos résultats",
|
| 12 |
+
"ourTasks": "Nos tâches",
|
| 13 |
+
"ourDatasets": "Nos jeux de données",
|
| 14 |
+
"leaderboard": "Classement COLE",
|
| 15 |
+
"errorOccurred": "Une erreur est survenue",
|
| 16 |
+
"close": "Fermer",
|
| 17 |
+
"details": "Détails",
|
| 18 |
+
"benchmarksIntro": "COLE est constitué de 23 tâches, chacune visant à tester une ou plusieurs facettes de la compréhension du langage en apprentissage automatique. Ci-dessous, chaque tâche est décrite en détail.",
|
| 19 |
+
"metrics": "Métrique(s) :",
|
| 20 |
+
"benchmark_alloCine_title": "Allo-ciné.ca",
|
| 21 |
+
"benchmark_alloCine_description": "Allo-ciné teste la compréhension du langage dans la classification des sentiments en fournissant des critiques de films pouvant être positives ou négatives. La tâche consiste à donner le sentiment correct pour chaque critique.",
|
| 22 |
+
"benchmark_lingnli_title": "LingNLI",
|
| 23 |
+
"benchmark_lingnli_description": "LingNLI est un corpus d'inférence en langage naturel collecté en faisant appel à un linguiste afin d'introduire de manière dynamique de nouvelles contraintes pendant la collecte des données, dans le but d'atténuer les lacunes et les biais systématiques souvent présents dans les ensembles de données issus du crowdsourcing.",
|
| 24 |
+
"benchmark_daccord_title": "DACCORD",
|
| 25 |
+
"benchmark_daccord_description": " Prédisez si les deux phrases sont compatibles (0) ou se contredisent (1). ",
|
| 26 |
+
"benchmark_fquad_title": "FQuAD - Corpus de questions-réponses français",
|
| 27 |
+
"benchmark_fquad_description": "FQuAD est un ensemble de paires question/réponse construit à partir d’articles Wikipédia de haute qualité. L’objectif est de prédire correctement si la réponse à la question se trouve réellement dans l’article fourni.",
|
| 28 |
+
"benchmark_french_boolq_title": "French BoolQ",
|
| 29 |
+
"benchmark_french_boolq_description": " Répondez si le contexte permet de répondre « oui » à la question (1) ou « non »/ne répond pas (0).",
|
| 30 |
+
"benchmark_fracas_title": "FraCaS",
|
| 31 |
+
"benchmark_fracas_description": "Tâche d'inférence en langage naturel : prédire la relation entre deux phrases (implication, neutralité, contradiction).",
|
| 32 |
+
"benchmark_gqnli_title": "GQNLI-Fr - Jeu de données Generalized Quantifier NLI Challenge",
|
| 33 |
+
"benchmark_gqnli_description": "Le jeu se compose de paires prémisse-hypothèse soigneusement construites. Chaque hypothèse découle logiquement de la prémisse, la contredit ou est neutre.",
|
| 34 |
+
"benchmark_mms_title": "MMS - Massive Multilingual Sentiment Corpora",
|
| 35 |
+
"benchmark_mms_description": "Un corpus multilingue massif d'analyse des sentiments en 27 langues.",
|
| 36 |
+
"benchmark_mnli_nineeleven_fr_mt_title": "MNLI-NineEleven-FR-MT",
|
| 37 |
+
"benchmark_mnli_nineeleven_fr_mt_description": "Prédisez la relation entre deux phrases (implication, neutre, contradiction).",
|
| 38 |
+
"benchmark_multiblimp_title": "MultiBLiMP-Fr - Paires minimales linguistiques en français",
|
| 39 |
+
"benchmark_multiblimp_description": "Une tâche de jugement de grammaticalité utilisant le sous-ensemble français du Multilingual Benchmark of Linguistic Minimal Pairs. Chaque instance est une paire minimale — l’une grammaticale et l’autre agrammaticale — ne différant que par une seule caractéristique ciblée. Le modèle doit sélectionner la phrase grammaticalement correcte. Cette tâche évalue les connaissances fines de la syntaxe, de la morphologie et des accords en français.",
|
| 40 |
+
"benchmark_paws_title": "PAWS : Paraphrase Adversaries from Word Scrambling",
|
| 41 |
+
"benchmark_paws_description": "Cette tâche vise à tester l’identification de paraphrases en donnant deux phrases et en demandant au modèle de définir si ces phrases sont équivalentes en sens ou non.",
|
| 42 |
+
"benchmark_piaf_title": "PIAF - Jeu de questions-réponses en français",
|
| 43 |
+
"benchmark_piaf_description": "Cette tâche consiste en paires de questions et de réponses textuelles avec l’indication de l’emplacement de l’information réellement pertinente dans la réponse.",
|
| 44 |
+
"benchmark_qfrblimp_title": "QFrBLiMP - Paires minimales linguistiques québécoises",
|
| 45 |
+
"benchmark_qfrblimp_description": "Cette tâche présente au modèle des paires de phrases. Le but est de déterminer si les phrases sont sémantiquement équivalentes, même avec une syntaxe et des mots légèrement différents.",
|
| 46 |
+
"benchmark_qfrcola_title": "QFrCoLA - Corpus québécois de jugements d’acceptabilité linguistique",
|
| 47 |
+
"benchmark_qfrcola_description": "QFrCoLA est un jeu de données français issu de plusieurs sites linguistiques tels qu’académie-française.fr et vitrinelinguistique.com. Il vise à tester la capacité des modèles à déterminer la correction grammaticale. La réponse est un label binaire indiquant si la phrase est correcte ou non.",
|
| 48 |
+
"benchmark_qfrcore_title": "QFRCoRE: Quebec-French Corpus of Regional Expressions",
|
| 49 |
+
"benchmark_qfrcore_description": "Associez l'expression québécoise à sa définition parmi une liste proposée.",
|
| 50 |
+
"benchmark_qfrcort_title": "QFRCoRT: Quebec-French Corpus of Regional Terms",
|
| 51 |
+
"benchmark_qfrcort_description": "Associez le terme québécois à sa définition parmi une liste proposée.",
|
| 52 |
+
"benchmark_rte3_french_title": "RTE3-Français",
|
| 53 |
+
"benchmark_rte3_french_description": "Prédisez la relation entre deux phrases (implication, neutre, contradiction).",
|
| 54 |
+
"benchmark_sickfr_title": "Sick-FR - Phrases françaises impliquant des connaissances compositionnelles",
|
| 55 |
+
"benchmark_sickfr_description": "Cette tâche propose des paires de phrases annotées selon deux dimensions : la similarité (1 à 5) et l’inférence (implique, contredit ou neutre).",
|
| 56 |
+
"benchmark_sts22_title": "Sts22-Crosslingual - Similarité d’articles d’actualités multilingues",
|
| 57 |
+
"benchmark_sts22_description": "Cette tâche évalue si des paires d’articles d’actualités, écrits dans différentes langues, couvrent la même histoire. Elle se concentre sur la similarité au niveau du document, où les systèmes notent les paires sur une échelle de 4 points, du plus similaire au moins similaire.",
|
| 58 |
+
"benchmark_wino_x_lm_title": "WiNo-X LM - Résolution de pronom ",
|
| 59 |
+
"benchmark_wino_x_lm_description": "Prédire le bon référent (1 ou 2) d’un pronom dans une phrase en choisissant parmi deux candidats.",
|
| 60 |
+
"benchmark_wino_x_mt_title": "WiNo-X MT - Résolution de pronom ",
|
| 61 |
+
"benchmark_wino_x_mt_description": " Choisir laquelle de deux traductions françaises utilise le bon pronom (il/elle) selon le référent correct de la phrase anglaise.",
|
| 62 |
+
"benchmark_xnli_title": "XNLI - Corpus NLI multilingue",
|
| 63 |
+
"benchmark_xnli_description": "Cette tâche consiste en paires de phrases où l’objectif est de déterminer la relation entre les deux : implication, neutre ou contradiction.",
|
| 64 |
+
"benchmark_wsd_title": "WSD-Fr : Désambiguïsation lexicale",
|
| 65 |
+
"benchmark_wsd_description": "WSD-Fr est une tâche de désambiguïsation lexicale dans laquelle le modèle doit identifier le sens correct d’un verbe ambigu en contexte, dans le cadre du benchmark FLUE.",
|
| 66 |
+
"home_whatIsColleTitle": "Qu’est-ce que COLE ?",
|
| 67 |
+
"home_paragraph1": "COLE est un benchmark multidisciplinaire de compréhension du langage naturel en français ( <1>NLU</1> ). Il s’inspire de ses prédécesseurs <3>GLUE</3> et <5>SuperGLUE</5> pour construire un benchmark capable d’évaluer les modèles en langue française sur plusieurs facettes de la compréhension du langage. Consultez <7>notre article</7> pour plus d’informations.",
|
| 68 |
+
"home_paragraph2": "Le benchmark COLE poursuit plusieurs objectifs : d’abord fournir une alternative solide et complète en français pour évaluer les modèles sur des tâches NLU, puis offrir à l’utilisateur plusieurs jeux de données, tous utilisables via les bibliothèques HuggingFace, pour entraîner ou affiner des modèles sur des tâches spécifiques.",
|
| 69 |
+
"home_paragraph3": "Nous avons choisi de masquer les étiquettes de test pour décourager la triche ou le sur-apprentissage sur les données de test. Pour obtenir des résultats sur vos données de test, vous pouvez nous envoyer vos résultats comme expliqué dans <1>notre guide</1>.",
|
| 70 |
+
"guide_title": "Utilisation du benchmark COLE",
|
| 71 |
+
"guide_section1_title": "Entraînement et tests",
|
| 72 |
+
"guide_section1_para1": "Le benchmark COLE peut être utilisé pour entraîner et/ou tester des modèles sur plusieurs tâches. Pour entraîner ou affiner un modèle, vous pouvez récupérer les jeux de données train, validation et test depuis notre <0>dépôt public Hugging Face</0>. Nous recommandons d’utiliser les bibliothèques Hugging Face pour simplifier le processus.",
|
| 73 |
+
"guide_section1_para2": "Pour tester un modèle, vous devez également récupérer les données de la même façon. Une fois fait, votre modèle doit inférer les prédictions pour chaque ligne de la partition de test. Notre dépôt inclut des scripts d’évaluation pour chaque dataset. Il vous suffit de connecter la méthode d’inférence de votre modèle via l’interface HuggingFace. Nos scripts d’inférence sont disponibles sur notre <0>dépôt GitHub</0>.",
|
| 74 |
+
"guide_section1_para3": "Si vous préférez lancer l’inférence séparément, assurez-vous que les prédictions sont correctement formatées avant de les soumettre pour évaluation (voir notre section « Formatting the Dataset »).",
|
| 75 |
+
"guide_section2_title": "Formatage du jeu de données",
|
| 76 |
+
"guide_section2_para1": "Avant de soumettre vos résultats, assurez-vous que votre sortie est correctement formatée afin que nos systèmes puissent la traiter. Le format attendu est un dictionnaire JSON imbriqué comme ci-dessous. Une fois formaté, compressez votre fichier JSON dans une archive ZIP (.zip) et soumettez-le via le formulaire de soumission.",
|
| 77 |
+
"faq_title": "Foire aux questions",
|
| 78 |
+
"faqs": [
|
| 79 |
+
{
|
| 80 |
+
"question": "Comment évaluer mon modèle ?",
|
| 81 |
+
"answer": "Formatez les prédictions de votre modèle dans un fichier JSON selon le format décrit dans le Guide, compressez-le dans une archive ZIP, et soumettez-le via le formulaire sur le site. Le système évaluera automatiquement vos prédictions par rapport aux labels de test cachés et affichera les résultats."
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"question": "COLE est-il multilingue ?",
|
| 85 |
+
"answer": "Non, COLE est disponible uniquement en français. Le benchmark est spécifiquement conçu pour évaluer les modèles en compréhension de la langue française (NLU)."
|
| 86 |
+
},
|
| 87 |
+
{
|
| 88 |
+
"question": "Quel format doivent avoir mes prédictions ?",
|
| 89 |
+
"answer": "Vos prédictions doivent être un fichier JSON contenant le nom du modèle, l’URL du modèle, et une liste de tâches avec des tableaux de prédictions. Chaque tableau de prédictions doit correspondre à l’ordre et à la taille du split de test correspondant. Consultez la page Guide pour le format exact. Le fichier JSON doit ensuite être compressé en archive ZIP avant la soumission."
|
| 90 |
+
},
|
| 91 |
+
{
|
| 92 |
+
"question": "Où puis-je trouver les données de test ?",
|
| 93 |
+
"answer": "Les données de test (sans labels) sont disponibles sur notre dépôt HuggingFace : <code>graalul/COLE-public</code>. Vous pouvez charger n’importe quelle tâche avec la bibliothèque datasets : <code>load_dataset(‘graalul/COLE-public’, ‘nom_tache’)</code>."
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"question": "Pourquoi les labels de test sont-ils cachés ?",
|
| 97 |
+
"answer": "Pour garantir une évaluation équitable et empêcher le sur-apprentissage sur les données de test, nous ne publions pas les labels de test. Les modèles sont évalués côté serveur par rapport à la vérité terrain cachée lors de la soumission de vos prédictions."
|
| 98 |
+
},
|
| 99 |
+
{
|
| 100 |
+
"question": "Puis-je évaluer sur un sous-ensemble de tâches ?",
|
| 101 |
+
"answer": "Oui, vous pouvez soumettre des prédictions pour n’importe quel sous-ensemble des 23 tâches. Le classement affichera vos scores sur les tâches soumises et N/A pour les autres."
|
| 102 |
+
}
|
| 103 |
+
],
|
| 104 |
+
"contact_title": "Nous contacter",
|
| 105 |
+
"contact_paragraph": "Si vous avez des questions, des commentaires ou des suggestions concernant le benchmark COLE, n’hésitez pas à nous contacter. Nous serons ravis de vous aider — veuillez noter que les délais de réponse peuvent varier.",
|
| 106 |
+
"contact_email_label": "Envoyez-nous un email à :",
|
| 107 |
+
"submit_formTitle": "Soumettre vos résultats",
|
| 108 |
+
"submit_labelEmail": "Votre email",
|
| 109 |
+
"submit_placeholderEmail": "vous@exemple.com",
|
| 110 |
+
"submit_labelDisplayName": "Nom affiché",
|
| 111 |
+
"submit_placeholderDisplayName": "Nom au classement",
|
| 112 |
+
"submit_labelFile": "Fichier ZIP de prédictions",
|
| 113 |
+
"submit_labelZip": "Sélectionnez votre fichier de résultats",
|
| 114 |
+
"submit_requiredError": "⚠️ Email, nom affiché et ZIP sont requis.",
|
| 115 |
+
"submit_zipAlert": "Veuillez téléverser un fichier ZIP (.zip).",
|
| 116 |
+
"submit_button": "Soumettre vos résultats",
|
| 117 |
+
"submit_submitting": "Envoi en cours...",
|
| 118 |
+
"submit_successTitle": "Succès",
|
| 119 |
+
"submit_successMessage": "Votre soumission a été envoyée avec succès !",
|
| 120 |
+
"submit_checkResults": "Voir les résultats",
|
| 121 |
+
"submit_errorTitle": "Erreur ⚠️",
|
| 122 |
+
"submit_errorMessage": "Erreur de soumission : {{errorMessage}}",
|
| 123 |
+
"submit_closeButton": "Fermer",
|
| 124 |
+
"results_default_title": "Pas encore de résultats",
|
| 125 |
+
"results_default_message": "Veuillez soumettre un fichier ZIP pour générer les résultats du benchmark.",
|
| 126 |
+
"results_loading": "⏳ Chargement des résultats...",
|
| 127 |
+
"results_page_title": "📊 Résultats pour {{displayName}}",
|
| 128 |
+
"results_download": "Télécharger le JSON",
|
| 129 |
+
"results_no_results": "⚠️ Aucun résultat de benchmark trouvé.",
|
| 130 |
+
"results_benchmark_label": "🧪 Benchmark : {{name}}",
|
| 131 |
+
"leaderboard_title": "Classement",
|
| 132 |
+
"leaderboard_modelHeader": "Nom du modèle",
|
| 133 |
+
"leaderboard_overallHeader": "Global",
|
| 134 |
+
"leaderboard_avgScoreLabel": "(score moyen)",
|
| 135 |
+
"leaderboard_notSpecified": "N/A",
|
| 136 |
+
"leaderboard_notSpecifiedTooltip": "Ce modèle n'a pas été évalué sur cette tâche",
|
| 137 |
+
"leaderboard_modalTitle": "Résultats pour {{name}}",
|
| 138 |
+
"leaderboard_closeButton": "Fermer",
|
| 139 |
+
"nav_home": "COLE",
|
| 140 |
+
"nav_guide": "Guide",
|
| 141 |
+
"nav_faq": "FAQ",
|
| 142 |
+
"nav_contact": "Nous contacter",
|
| 143 |
+
"nav_submit": "Soumettre vos résultats",
|
| 144 |
+
"nav_tasks": "Nos tâches",
|
| 145 |
+
"nav_results": "Résultats",
|
| 146 |
+
"nav_leaderboard": "Classement COLE",
|
| 147 |
+
"nav_datasets": "Nos données",
|
| 148 |
+
"papers_title": "Nos articles",
|
| 149 |
+
"papers_arxiv_label": "COLE: a Comprehensive Benchmark for French Language Understanding Evaluation",
|
| 150 |
+
"papers_arxiv_authors": "David Beauchemin, Yan Tremblay, Mohamed Amine Youssef, Richard Khoury (arXiv:2510.05046, ICLR 2025 Workshop)",
|
| 151 |
+
"leaderboard_errorMessage": "Impossible de charger les données du classement. Veuillez réessayer plus tard.",
|
| 152 |
+
"benchmarks_category_sentiment": "Analyse de sentiments",
|
| 153 |
+
"benchmarks_category_nli": "Inférence en langage naturel",
|
| 154 |
+
"benchmarks_category_qa": "Réponse aux questions",
|
| 155 |
+
"benchmarks_category_paraphrase": "Détection de paraphrases",
|
| 156 |
+
"benchmarks_category_grammar": "Jugement grammatical",
|
| 157 |
+
"benchmarks_category_similarity": "Similarité sémantique",
|
| 158 |
+
"benchmarks_category_wsd": "Désambiguïsation lexicale",
|
| 159 |
+
"benchmarks_category_quebec": "Français québécois",
|
| 160 |
+
"benchmarks_category_coreference": "Coréférence / Résolution de pronoms"
|
| 161 |
+
}
|
| 162 |
+
|
frontend/src/app/globals.css
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
@import "tailwindcss";
|
| 2 |
+
|
| 3 |
+
:root {
|
| 4 |
+
--background: #ffffff;
|
| 5 |
+
--foreground: #6526ae;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
@theme inline {
|
| 9 |
+
--color-background: var(--background);
|
| 10 |
+
--color-foreground: var(--foreground);
|
| 11 |
+
--font-sans: var(--font-geist-sans);
|
| 12 |
+
--font-mono: var(--font-geist-mono);
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
body {
|
| 17 |
+
background: var(--background);
|
| 18 |
+
color: var(--foreground);
|
| 19 |
+
font-family: Arial, Helvetica, sans-serif;
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
code {
|
| 23 |
+
background-color: #f3f4f6;
|
| 24 |
+
padding: 0.15em 0.4em;
|
| 25 |
+
border-radius: 0.25em;
|
| 26 |
+
font-size: 0.875em;
|
| 27 |
+
font-family: var(--font-mono), monospace;
|
| 28 |
+
}
|
frontend/src/app/guide/page.js
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import '../i18n';
|
| 4 |
+
import {useTranslation, Trans} from 'react-i18next';
|
| 5 |
+
import Link from 'next/link';
|
| 6 |
+
import CodeBlock from '../components/CodeBlock';
|
| 7 |
+
|
| 8 |
+
export default function Guide() {
|
| 9 |
+
const {t} = useTranslation();
|
| 10 |
+
|
| 11 |
+
return (
|
| 12 |
+
<div className="max-w-5xl mx-auto px-6 py-3">
|
| 13 |
+
<h2 className="text-3xl font-bold text-center text-blue-700 border-b pb-4 mb-10">
|
| 14 |
+
{t('guide_title')}
|
| 15 |
+
</h2>
|
| 16 |
+
|
| 17 |
+
<div className="space-y-8">
|
| 18 |
+
{/* SECTION TRAINING & TESTING */}
|
| 19 |
+
<div className="p-6 bg-white border border-gray-200 rounded-lg shadow-sm hover:shadow transition">
|
| 20 |
+
<h3 className="text-2xl font-semibold text-gray-900 mb-4 border-l-4 border-blue-600 pl-4">
|
| 21 |
+
{t('guide_section1_title')}
|
| 22 |
+
</h3>
|
| 23 |
+
|
| 24 |
+
<p className="text-gray-700">
|
| 25 |
+
<Trans i18nKey="guide_section1_para1" components={[
|
| 26 |
+
<a key="hf-link"
|
| 27 |
+
href="https://huggingface.co/datasets/graalul/COLE-public"
|
| 28 |
+
target="_blank"
|
| 29 |
+
rel="noopener noreferrer"
|
| 30 |
+
className="text-blue-600 underline hover:text-blue-800"
|
| 31 |
+
/>
|
| 32 |
+
]}>
|
| 33 |
+
</Trans>
|
| 34 |
+
</p>
|
| 35 |
+
|
| 36 |
+
<p className="text-gray-700 mt-4">
|
| 37 |
+
<Trans i18nKey="guide_section1_para2" components={[<a key="github-ref"
|
| 38 |
+
href="https://github.com/GRAAL-Research/COLE"
|
| 39 |
+
target="_blank"
|
| 40 |
+
rel="noopener noreferrer"
|
| 41 |
+
className="text-blue-600 underline hover:text-blue-800">
|
| 42 |
+
GitHub Repository.
|
| 43 |
+
</a>]}> </Trans>
|
| 44 |
+
|
| 45 |
+
</p>
|
| 46 |
+
|
| 47 |
+
<p className="text-gray-700 mt-4">
|
| 48 |
+
<Trans i18nKey="guide_section1_para3">
|
| 49 |
+
</Trans>
|
| 50 |
+
</p>
|
| 51 |
+
|
| 52 |
+
{/* SECTION FORMATTING */}
|
| 53 |
+
<h3 className="text-2xl font-semibold text-gray-900 mb-4 border-l-4 border-blue-600 pl-4">
|
| 54 |
+
{t('guide_section2_title')}
|
| 55 |
+
</h3>
|
| 56 |
+
<p className="text-gray-700 mb-4">
|
| 57 |
+
{t('guide_section2_para1')}
|
| 58 |
+
</p>
|
| 59 |
+
|
| 60 |
+
<CodeBlock>{`{
|
| 61 |
+
"model_name": "a_model_name",
|
| 62 |
+
"model_url": "a_model_url",
|
| 63 |
+
"tasks": [
|
| 64 |
+
{
|
| 65 |
+
"qfrcola": { "predictions": [1,1,1,1,1] }
|
| 66 |
+
},
|
| 67 |
+
{
|
| 68 |
+
"allocine": { "predictions": [1,1,1,1,1] }
|
| 69 |
+
}
|
| 70 |
+
]
|
| 71 |
+
}`}</CodeBlock>
|
| 72 |
+
</div>
|
| 73 |
+
</div>
|
| 74 |
+
</div>
|
| 75 |
+
);
|
| 76 |
+
}
|
frontend/src/app/i18n.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import i18n from 'i18next';
|
| 2 |
+
import { initReactI18next } from 'react-i18next';
|
| 3 |
+
import LanguageDetector from 'i18next-browser-languagedetector';
|
| 4 |
+
|
| 5 |
+
import en from "./en/translation.json";
|
| 6 |
+
import fr from './fr/translation.json';
|
| 7 |
+
|
| 8 |
+
i18n
|
| 9 |
+
.use(LanguageDetector)
|
| 10 |
+
.use(initReactI18next)
|
| 11 |
+
.init({
|
| 12 |
+
resources: {
|
| 13 |
+
en: { translation: en },
|
| 14 |
+
fr: { translation: fr },
|
| 15 |
+
},
|
| 16 |
+
lng: 'en',
|
| 17 |
+
fallbackLng: 'en',
|
| 18 |
+
interpolation: {
|
| 19 |
+
escapeValue: false,
|
| 20 |
+
},
|
| 21 |
+
detection: {
|
| 22 |
+
order: ['localStorage', 'navigator'],
|
| 23 |
+
caches: ['localStorage'],
|
| 24 |
+
},
|
| 25 |
+
});
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
export default i18n;
|
frontend/src/app/layout.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Geist, Geist_Mono } from "next/font/google";
|
| 2 |
+
import "./globals.css";
|
| 3 |
+
|
| 4 |
+
import ClientHeader from "./components/ClientHeader";
|
| 5 |
+
import ModalManager from "./components/ModalManager";
|
| 6 |
+
import {Suspense} from "react";
|
| 7 |
+
|
| 8 |
+
const geistSans = Geist({
|
| 9 |
+
variable: "--font-geist-sans",
|
| 10 |
+
subsets: ["latin"],
|
| 11 |
+
});
|
| 12 |
+
|
| 13 |
+
const geistMono = Geist_Mono({
|
| 14 |
+
variable: "--font-geist-mono",
|
| 15 |
+
subsets: ["latin"],
|
| 16 |
+
});
|
| 17 |
+
|
| 18 |
+
export const metadata = {
|
| 19 |
+
title: "COLE - Comprehensive Benchmark for French Language Understanding",
|
| 20 |
+
description: "COLE is a benchmark of 23 tasks for evaluating French Natural Language Understanding (NLU) in large language models.",
|
| 21 |
+
openGraph: {
|
| 22 |
+
title: "COLE - French NLU Benchmark",
|
| 23 |
+
description: "Evaluate LLMs on 23 French NLU tasks: sentiment analysis, NLI, QA, and more.",
|
| 24 |
+
url: "https://colebenchmark.org",
|
| 25 |
+
siteName: "COLE Benchmark",
|
| 26 |
+
type: "website",
|
| 27 |
+
},
|
| 28 |
+
twitter: {
|
| 29 |
+
card: "summary",
|
| 30 |
+
title: "COLE - French NLU Benchmark",
|
| 31 |
+
description: "Evaluate LLMs on 23 French NLU tasks: sentiment analysis, NLI, QA, and more.",
|
| 32 |
+
},
|
| 33 |
+
};
|
| 34 |
+
|
| 35 |
+
export default function RootLayout({ children }) {
|
| 36 |
+
return (
|
| 37 |
+
<html lang="en">
|
| 38 |
+
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
|
| 39 |
+
<ClientHeader />
|
| 40 |
+
<main className="w-full flex justify-center px-4 pt-8">
|
| 41 |
+
<div className="w-full max-w-7xl">{children}</div>
|
| 42 |
+
</main>
|
| 43 |
+
<Suspense fallback={null}>
|
| 44 |
+
<ModalManager/>
|
| 45 |
+
</Suspense>
|
| 46 |
+
</body>
|
| 47 |
+
</html>
|
| 48 |
+
);
|
| 49 |
+
}
|
frontend/src/app/leaderboard/page.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import React, { useEffect, useState } from "react";
|
| 4 |
+
import {
|
| 5 |
+
normalizeBenchmarkName,
|
| 6 |
+
computeAverageScore,
|
| 7 |
+
} from "./util";
|
| 8 |
+
import { useTranslation } from "react-i18next";
|
| 9 |
+
import { BACKEND_ADDRESS } from "@/app/resources/ResourcesPaths";
|
| 10 |
+
|
| 11 |
+
const allowedMetrics = [
|
| 12 |
+
'acc',
|
| 13 |
+
'accuracy',
|
| 14 |
+
'f1',
|
| 15 |
+
'pearson',
|
| 16 |
+
'pearsonr',
|
| 17 |
+
'spearman',
|
| 18 |
+
'fquad',
|
| 19 |
+
'exact_match',
|
| 20 |
+
];
|
| 21 |
+
|
| 22 |
+
const PAGE_SIZE = 25;
|
| 23 |
+
|
| 24 |
+
export default function LeaderboardPage() {
|
| 25 |
+
const { t } = useTranslation();
|
| 26 |
+
const [entries, setEntries] = useState([]);
|
| 27 |
+
const [benchmarks, setBenchmarks] = useState([]);
|
| 28 |
+
const [sortCol, setSortCol] = useState('overall');
|
| 29 |
+
const [sortOrder, setSortOrder] = useState('desc');
|
| 30 |
+
const [selectedEntry, setSelectedEntry] = useState(null);
|
| 31 |
+
const [error, setError] = useState(false);
|
| 32 |
+
const [loading, setLoading] = useState(true);
|
| 33 |
+
const [currentPage, setCurrentPage] = useState(1);
|
| 34 |
+
|
| 35 |
+
const headerLabels = {
|
| 36 |
+
model: t('leaderboard_modelHeader'),
|
| 37 |
+
overall: t('leaderboard_overallHeader'),
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
useEffect(() => {
|
| 41 |
+
fetch(`${BACKEND_ADDRESS}/leaderboard`)
|
| 42 |
+
.then((res) => {
|
| 43 |
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
| 44 |
+
return res.json();
|
| 45 |
+
})
|
| 46 |
+
.then((data) => {
|
| 47 |
+
const withOverall = data.map((e) => ({
|
| 48 |
+
...e,
|
| 49 |
+
averageScore: computeAverageScore(e),
|
| 50 |
+
}));
|
| 51 |
+
setEntries(withOverall);
|
| 52 |
+
|
| 53 |
+
const allBench = new Set();
|
| 54 |
+
withOverall.forEach((entry) => {
|
| 55 |
+
Object.keys(entry.results || {}).forEach((raw) => {
|
| 56 |
+
allBench.add(normalizeBenchmarkName(raw));
|
| 57 |
+
});
|
| 58 |
+
});
|
| 59 |
+
setBenchmarks(Array.from(allBench));
|
| 60 |
+
})
|
| 61 |
+
.catch(() => setError(true))
|
| 62 |
+
.finally(() => setLoading(false));
|
| 63 |
+
}, []);
|
| 64 |
+
|
| 65 |
+
const getCellValue = (entry, col) => {
|
| 66 |
+
if (col === 'model') return entry.display_name;
|
| 67 |
+
if (col === 'overall') return entry.averageScore ?? null;
|
| 68 |
+
|
| 69 |
+
const pair = Object.entries(entry.results || {}).find(
|
| 70 |
+
([rawName]) => normalizeBenchmarkName(rawName) === col
|
| 71 |
+
);
|
| 72 |
+
if (!pair) return null;
|
| 73 |
+
|
| 74 |
+
const rawValues = [];
|
| 75 |
+
Object.values(pair[1]).forEach((metricGroup) => {
|
| 76 |
+
if (metricGroup && typeof metricGroup === 'object') {
|
| 77 |
+
Object.entries(metricGroup).forEach(([metricName, metricValue]) => {
|
| 78 |
+
if (
|
| 79 |
+
!metricName.includes('_warning') &&
|
| 80 |
+
typeof metricValue === 'number' &&
|
| 81 |
+
allowedMetrics.includes(metricName.toLowerCase())
|
| 82 |
+
) {
|
| 83 |
+
rawValues.push(metricValue);
|
| 84 |
+
}
|
| 85 |
+
});
|
| 86 |
+
}
|
| 87 |
+
});
|
| 88 |
+
|
| 89 |
+
if (rawValues.length === 0) return null;
|
| 90 |
+
const normalized = rawValues.map((v) => v > 1 ? v / 100 : v);
|
| 91 |
+
const avg = normalized.reduce((a, b) => a + b, 0) / normalized.length;
|
| 92 |
+
return avg;
|
| 93 |
+
};
|
| 94 |
+
|
| 95 |
+
const sorted = [...entries].sort((a, b) => {
|
| 96 |
+
const va = getCellValue(a, sortCol);
|
| 97 |
+
const vb = getCellValue(b, sortCol);
|
| 98 |
+
if (sortCol === 'model') {
|
| 99 |
+
if (va == null) return 1;
|
| 100 |
+
if (vb == null) return -1;
|
| 101 |
+
return sortOrder === 'asc'
|
| 102 |
+
? va.localeCompare(vb)
|
| 103 |
+
: vb.localeCompare(va);
|
| 104 |
+
}
|
| 105 |
+
const na = va ?? -Infinity;
|
| 106 |
+
const nb = vb ?? -Infinity;
|
| 107 |
+
return sortOrder === 'asc' ? na - nb : nb - na;
|
| 108 |
+
});
|
| 109 |
+
|
| 110 |
+
const totalPages = Math.max(1, Math.ceil(sorted.length / PAGE_SIZE));
|
| 111 |
+
const paginatedEntries = sorted.slice(
|
| 112 |
+
(currentPage - 1) * PAGE_SIZE,
|
| 113 |
+
currentPage * PAGE_SIZE
|
| 114 |
+
);
|
| 115 |
+
|
| 116 |
+
const handleSort = (col) => {
|
| 117 |
+
if (sortCol === col) {
|
| 118 |
+
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
|
| 119 |
+
} else {
|
| 120 |
+
setSortCol(col);
|
| 121 |
+
setSortOrder('desc');
|
| 122 |
+
}
|
| 123 |
+
setCurrentPage(1);
|
| 124 |
+
};
|
| 125 |
+
|
| 126 |
+
const renderHeader = (col) => {
|
| 127 |
+
const baseLabel = headerLabels[col] ?? col;
|
| 128 |
+
const arrow = sortCol === col ? (sortOrder === 'asc' ? ' ▲' : ' ▼') : '';
|
| 129 |
+
|
| 130 |
+
if (col === 'overall') {
|
| 131 |
+
return (
|
| 132 |
+
<div>
|
| 133 |
+
<div onClick={() => handleSort(col)} className="cursor-pointer">
|
| 134 |
+
{baseLabel}
|
| 135 |
+
{arrow}
|
| 136 |
+
</div>
|
| 137 |
+
<div className="text-xs text-blue-100 text-center">
|
| 138 |
+
{t('leaderboard_avgScoreLabel')}
|
| 139 |
+
</div>
|
| 140 |
+
</div>
|
| 141 |
+
);
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
if (col === 'model') {
|
| 145 |
+
return (
|
| 146 |
+
<div onClick={() => handleSort(col)} className="cursor-pointer">
|
| 147 |
+
{baseLabel}
|
| 148 |
+
{arrow}
|
| 149 |
+
</div>
|
| 150 |
+
);
|
| 151 |
+
}
|
| 152 |
+
|
| 153 |
+
let metricText = '';
|
| 154 |
+
const sample = entries[0];
|
| 155 |
+
if (sample && sample.results) {
|
| 156 |
+
const p = Object.entries(sample.results).find(
|
| 157 |
+
([raw]) => normalizeBenchmarkName(raw) === col
|
| 158 |
+
);
|
| 159 |
+
if (p) {
|
| 160 |
+
const grp = Object.values(p[1])[0];
|
| 161 |
+
if (grp) {
|
| 162 |
+
const metrics = Object.keys(grp)
|
| 163 |
+
.filter((m) => allowedMetrics.includes(m.toLowerCase()));
|
| 164 |
+
if (metrics.length > 0) {
|
| 165 |
+
metricText = ` (${metrics.join(', ')})`;
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
return (
|
| 172 |
+
<div onClick={() => handleSort(col)} className="cursor-pointer">
|
| 173 |
+
{baseLabel}
|
| 174 |
+
{arrow}
|
| 175 |
+
{metricText}
|
| 176 |
+
</div>
|
| 177 |
+
);
|
| 178 |
+
};
|
| 179 |
+
|
| 180 |
+
if (loading) {
|
| 181 |
+
return (
|
| 182 |
+
<div className="space-y-8">
|
| 183 |
+
<h3 className="text-2xl font-semibold text-gray-900 mb-4 border-l-4 border-blue-600 pl-4">
|
| 184 |
+
{t('leaderboard_title')}
|
| 185 |
+
</h3>
|
| 186 |
+
<div className="overflow-auto">
|
| 187 |
+
<div className="animate-pulse space-y-3">
|
| 188 |
+
<div className="h-10 bg-blue-100 rounded w-full" />
|
| 189 |
+
{[...Array(8)].map((_, i) => (
|
| 190 |
+
<div key={i} className="h-8 bg-gray-100 rounded w-full" />
|
| 191 |
+
))}
|
| 192 |
+
</div>
|
| 193 |
+
</div>
|
| 194 |
+
</div>
|
| 195 |
+
);
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
if (error) {
|
| 199 |
+
return (
|
| 200 |
+
<div className="space-y-8">
|
| 201 |
+
<h3 className="text-2xl font-semibold text-gray-900 mb-4 border-l-4 border-blue-600 pl-4">
|
| 202 |
+
{t('leaderboard_title')}
|
| 203 |
+
</h3>
|
| 204 |
+
<div className="text-center py-12">
|
| 205 |
+
<p className="text-red-600 text-lg">{t('leaderboard_errorMessage')}</p>
|
| 206 |
+
</div>
|
| 207 |
+
</div>
|
| 208 |
+
);
|
| 209 |
+
}
|
| 210 |
+
|
| 211 |
+
return (
|
| 212 |
+
<div className="space-y-8">
|
| 213 |
+
<h3 className="text-2xl font-semibold text-gray-900 mb-4 border-l-4 border-blue-600 pl-4">
|
| 214 |
+
{t('leaderboard_title')}</h3>
|
| 215 |
+
<div className="overflow-auto">
|
| 216 |
+
<table className="min-w-full border-collapse">
|
| 217 |
+
<thead>
|
| 218 |
+
<tr>
|
| 219 |
+
{['model', 'overall', ...benchmarks].map((b) => (
|
| 220 |
+
<th
|
| 221 |
+
key={b}
|
| 222 |
+
className="border border-gray-300 px-2 py-1 bg-blue-600 text-left text-sm font-semibold text-white"
|
| 223 |
+
>
|
| 224 |
+
{renderHeader(b)}
|
| 225 |
+
</th>
|
| 226 |
+
))}
|
| 227 |
+
</tr>
|
| 228 |
+
</thead>
|
| 229 |
+
<tbody>
|
| 230 |
+
{paginatedEntries.map((entry) => (
|
| 231 |
+
<tr
|
| 232 |
+
key={entry.submission_id}
|
| 233 |
+
className="bg-white hover:bg-gray-50 cursor-pointer"
|
| 234 |
+
onClick={() => setSelectedEntry(entry)}
|
| 235 |
+
>
|
| 236 |
+
<td className="border border-gray-300 px-2 py-1 font-medium text-blue-600">
|
| 237 |
+
{entry.display_name}
|
| 238 |
+
</td>
|
| 239 |
+
<td className="border border-gray-300 px-2 py-1 text-center text-black font-bold">
|
| 240 |
+
{entry.averageScore == null
|
| 241 |
+
? t('leaderboard_notSpecified')
|
| 242 |
+
: (entry.averageScore * 100).toFixed(1) + '%'}
|
| 243 |
+
</td>
|
| 244 |
+
{benchmarks.map((b) => {
|
| 245 |
+
const val = getCellValue(entry, b);
|
| 246 |
+
return (
|
| 247 |
+
<td
|
| 248 |
+
key={b}
|
| 249 |
+
className="border border-gray-200 px-2 py-1 text-center text-gray-800"
|
| 250 |
+
title={val == null ? t('leaderboard_notSpecifiedTooltip') : undefined}
|
| 251 |
+
>
|
| 252 |
+
{val == null
|
| 253 |
+
? <span className="text-gray-400 italic">{t('leaderboard_notSpecified')}</span>
|
| 254 |
+
: (val * 100).toFixed(1) + '%'}
|
| 255 |
+
</td>
|
| 256 |
+
);
|
| 257 |
+
})}
|
| 258 |
+
</tr>
|
| 259 |
+
))}
|
| 260 |
+
</tbody>
|
| 261 |
+
</table>
|
| 262 |
+
</div>
|
| 263 |
+
|
| 264 |
+
{totalPages > 1 && (
|
| 265 |
+
<div className="flex justify-center items-center gap-4 py-4">
|
| 266 |
+
<button
|
| 267 |
+
onClick={() => setCurrentPage((p) => Math.max(1, p - 1))}
|
| 268 |
+
disabled={currentPage === 1}
|
| 269 |
+
className="px-3 py-1 rounded bg-blue-600 text-white disabled:opacity-40 hover:bg-blue-700 transition"
|
| 270 |
+
>
|
| 271 |
+
«
|
| 272 |
+
</button>
|
| 273 |
+
<span className="text-gray-700 text-sm">
|
| 274 |
+
{currentPage} / {totalPages}
|
| 275 |
+
</span>
|
| 276 |
+
<button
|
| 277 |
+
onClick={() => setCurrentPage((p) => Math.min(totalPages, p + 1))}
|
| 278 |
+
disabled={currentPage === totalPages}
|
| 279 |
+
className="px-3 py-1 rounded bg-blue-600 text-white disabled:opacity-40 hover:bg-blue-700 transition"
|
| 280 |
+
>
|
| 281 |
+
»
|
| 282 |
+
</button>
|
| 283 |
+
</div>
|
| 284 |
+
)}
|
| 285 |
+
|
| 286 |
+
{selectedEntry && (
|
| 287 |
+
<div
|
| 288 |
+
className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"
|
| 289 |
+
onClick={(e) => { if (e.target === e.currentTarget) setSelectedEntry(null); }}
|
| 290 |
+
onKeyDown={(e) => { if (e.key === 'Escape') setSelectedEntry(null); }}
|
| 291 |
+
role="dialog"
|
| 292 |
+
aria-modal="true"
|
| 293 |
+
tabIndex={-1}
|
| 294 |
+
>
|
| 295 |
+
<div className="bg-white p-6 rounded-2xl shadow-lg max-w-2xl w-full mx-4 max-h-[80vh] overflow-y-auto">
|
| 296 |
+
<h3 className="text-xl font-semibold text-gray-800 mb-4">
|
| 297 |
+
{t('leaderboard_modalTitle', {
|
| 298 |
+
name: selectedEntry.display_name,
|
| 299 |
+
})}
|
| 300 |
+
</h3>
|
| 301 |
+
{Object.entries(selectedEntry.results || {}).map(
|
| 302 |
+
([taskKey, metricsObj]) => {
|
| 303 |
+
const prettyName = taskKey.split('|')[1] || taskKey;
|
| 304 |
+
const [metricType, values] = Object.entries(metricsObj)[0];
|
| 305 |
+
return (
|
| 306 |
+
<div key={taskKey} className="mb-4">
|
| 307 |
+
<h4 className="font-medium text-blue-700">
|
| 308 |
+
{prettyName}
|
| 309 |
+
</h4>
|
| 310 |
+
<ul className="list-disc list-inside text-gray-700">
|
| 311 |
+
{Object.entries(values)
|
| 312 |
+
.filter(([k]) => !k.endsWith('_warning'))
|
| 313 |
+
.map(([metricKey, value]) => (
|
| 314 |
+
<li key={metricKey}>
|
| 315 |
+
<strong>{metricKey.replace(/_/g, ' ')}</strong>:{' '}
|
| 316 |
+
{typeof value === 'number'
|
| 317 |
+
? (value > 1
|
| 318 |
+
? value.toFixed(1) + '%'
|
| 319 |
+
: (value * 100).toFixed(1) + '%')
|
| 320 |
+
: value}
|
| 321 |
+
</li>
|
| 322 |
+
))}
|
| 323 |
+
</ul>
|
| 324 |
+
{values[`${metricType}_warning`] && (
|
| 325 |
+
<p className="text-sm text-yellow-700 mt-2">
|
| 326 |
+
⚠️ {values[`${metricType}_warning`]}
|
| 327 |
+
</p>
|
| 328 |
+
)}
|
| 329 |
+
</div>
|
| 330 |
+
);
|
| 331 |
+
}
|
| 332 |
+
)}
|
| 333 |
+
<button
|
| 334 |
+
className="mt-4 px-4 py-2 bg-gray-200 rounded-full hover:bg-gray-300"
|
| 335 |
+
onClick={() => setSelectedEntry(null)}
|
| 336 |
+
>
|
| 337 |
+
{t('leaderboard_closeButton')}
|
| 338 |
+
</button>
|
| 339 |
+
</div>
|
| 340 |
+
</div>
|
| 341 |
+
)}
|
| 342 |
+
</div>
|
| 343 |
+
);
|
| 344 |
+
}
|
frontend/src/app/leaderboard/util.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
export const normalizeBenchmarkName = (name) => {
|
| 3 |
+
const parts = name.toLowerCase().split("|");
|
| 4 |
+
if (parts.length >= 2) return parts[1].replace(/-/g, "_");
|
| 5 |
+
return name.toLowerCase();
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
export const computeAverageScore = (entry) => {
|
| 9 |
+
const allowedMetrics = [
|
| 10 |
+
"acc",
|
| 11 |
+
"accuracy",
|
| 12 |
+
"f1",
|
| 13 |
+
"exact_match",
|
| 14 |
+
"fquad",
|
| 15 |
+
"pearson",
|
| 16 |
+
"pearsonr",
|
| 17 |
+
"spearman",
|
| 18 |
+
];
|
| 19 |
+
|
| 20 |
+
const perTaskAverages = [];
|
| 21 |
+
|
| 22 |
+
Object.values(entry.results || {}).forEach((taskData) => {
|
| 23 |
+
if (taskData && typeof taskData === "object") {
|
| 24 |
+
Object.values(taskData).forEach((metricGroup) => {
|
| 25 |
+
if (metricGroup && typeof metricGroup === "object") {
|
| 26 |
+
const taskMetrics = Object.entries(metricGroup)
|
| 27 |
+
.filter(([metric]) => allowedMetrics.includes(metric.toLowerCase()))
|
| 28 |
+
.map(([, value]) =>
|
| 29 |
+
typeof value === "number" ? value : null
|
| 30 |
+
)
|
| 31 |
+
.filter((v) => v !== null);
|
| 32 |
+
|
| 33 |
+
if (taskMetrics.length > 0) {
|
| 34 |
+
const normalized = taskMetrics.map((v) => v > 1 ? v / 100 : v);
|
| 35 |
+
const taskAvg = normalized.reduce((a, b) => a + b, 0) / normalized.length;
|
| 36 |
+
perTaskAverages.push(taskAvg);
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
});
|
| 40 |
+
}
|
| 41 |
+
});
|
| 42 |
+
|
| 43 |
+
if (perTaskAverages.length === 0) return null;
|
| 44 |
+
|
| 45 |
+
return perTaskAverages.reduce((a, b) => a + b, 0) / perTaskAverages.length;
|
| 46 |
+
};
|
| 47 |
+
|
frontend/src/app/page.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client'
|
| 2 |
+
|
| 3 |
+
import Link from "next/link";
|
| 4 |
+
import { Trans } from 'react-i18next';
|
| 5 |
+
import { useTranslation } from 'react-i18next';
|
| 6 |
+
|
| 7 |
+
export default function Home() {
|
| 8 |
+
const { t } = useTranslation();
|
| 9 |
+
|
| 10 |
+
return (
|
| 11 |
+
<div className="max-w-5xl mx-auto px-6 py-3">
|
| 12 |
+
<h2 className="text-3xl font-bold text-center text-blue-700 border-b pb-4 mb-10">
|
| 13 |
+
{t('home_whatIsColleTitle')}
|
| 14 |
+
</h2>
|
| 15 |
+
|
| 16 |
+
<p className="text-gray-700 mb-4 leading-relaxed space-y-4">
|
| 17 |
+
<Trans i18nKey="home_paragraph1">
|
| 18 |
+
COLE is a multidisciplinary French Natural Language Understanding benchmark (
|
| 19 |
+
<a
|
| 20 |
+
href="https://en.wikipedia.org/wiki/Natural_language_understanding"
|
| 21 |
+
target="_blank"
|
| 22 |
+
rel="noopener noreferrer"
|
| 23 |
+
className="text-blue-600 underline hover:text-blue-800"
|
| 24 |
+
>
|
| 25 |
+
NLU
|
| 26 |
+
</a>
|
| 27 |
+
). It takes inspiration from its predecessors
|
| 28 |
+
<a
|
| 29 |
+
href="https://gluebenchmark.com/"
|
| 30 |
+
target="_blank"
|
| 31 |
+
rel="noopener noreferrer"
|
| 32 |
+
className="text-blue-600 underline hover:text-blue-800"
|
| 33 |
+
>
|
| 34 |
+
GLUE
|
| 35 |
+
</a>
|
| 36 |
+
and
|
| 37 |
+
<a
|
| 38 |
+
href="https://super.gluebenchmark.com/"
|
| 39 |
+
target="_blank"
|
| 40 |
+
rel="noopener noreferrer"
|
| 41 |
+
className="text-blue-600 underline hover:text-blue-800"
|
| 42 |
+
>
|
| 43 |
+
SuperGLUE
|
| 44 |
+
</a>
|
| 45 |
+
to build a benchmark capable of evaluating models in the French language on multiple topics of language understanding. See
|
| 46 |
+
<Link
|
| 47 |
+
href="https://arxiv.org/abs/2510.05046"
|
| 48 |
+
className="text-blue-600 underline hover:text-blue-800"
|
| 49 |
+
>
|
| 50 |
+
our paper
|
| 51 |
+
</Link>
|
| 52 |
+
for more information.
|
| 53 |
+
</Trans>
|
| 54 |
+
</p>
|
| 55 |
+
|
| 56 |
+
<p className="text-gray-700 leading-relaxed">
|
| 57 |
+
{t('home_paragraph2')}
|
| 58 |
+
</p>
|
| 59 |
+
|
| 60 |
+
<p className="text-gray-700 leading-relaxed mt-4">
|
| 61 |
+
<Trans i18nKey="home_paragraph3">
|
| 62 |
+
We have made the choice to hide test labels to discourage cheating or overfitting on test data. To get results on your test data, you may send us your results as explained in
|
| 63 |
+
<Link
|
| 64 |
+
href="/guide"
|
| 65 |
+
className="text-blue-600 underline hover:text-blue-800"
|
| 66 |
+
>
|
| 67 |
+
our guide
|
| 68 |
+
</Link>
|
| 69 |
+
.
|
| 70 |
+
</Trans>
|
| 71 |
+
</p>
|
| 72 |
+
</div>
|
| 73 |
+
);
|
| 74 |
+
}
|
frontend/src/app/papers/page.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import React, { useState } from 'react';
|
| 4 |
+
import '../i18n';
|
| 5 |
+
import { useTranslation } from 'react-i18next';
|
| 6 |
+
|
| 7 |
+
export default function PapersPage() {
|
| 8 |
+
const [loaded, setLoaded] = useState(false);
|
| 9 |
+
const { t } = useTranslation();
|
| 10 |
+
|
| 11 |
+
return (
|
| 12 |
+
<div className="relative h-screen">
|
| 13 |
+
{!loaded && (
|
| 14 |
+
<div className="absolute inset-0 flex items-center justify-center bg-white z-10">
|
| 15 |
+
<div className="animate-spin h-12 w-12 border-4 border-blue-600 border-t-transparent rounded-full" />
|
| 16 |
+
</div>
|
| 17 |
+
)}
|
| 18 |
+
<div className="max-w-5xl mx-auto px-6 py-3">
|
| 19 |
+
<h2 className="text-3xl font-bold text-center text-blue-700 border-b pb-4 mb-6">
|
| 20 |
+
{t('papers_title')}
|
| 21 |
+
</h2>
|
| 22 |
+
<div className="mb-6 space-y-2">
|
| 23 |
+
<p className="text-gray-700">
|
| 24 |
+
<a
|
| 25 |
+
href="https://arxiv.org/abs/2510.05046"
|
| 26 |
+
target="_blank"
|
| 27 |
+
rel="noopener noreferrer"
|
| 28 |
+
className="text-blue-600 underline hover:text-blue-800 font-medium"
|
| 29 |
+
>
|
| 30 |
+
{t('papers_arxiv_label')}
|
| 31 |
+
</a>
|
| 32 |
+
{' '}— {t('papers_arxiv_authors')}
|
| 33 |
+
</p>
|
| 34 |
+
</div>
|
| 35 |
+
</div>
|
| 36 |
+
|
| 37 |
+
<iframe
|
| 38 |
+
onLoad={() => setLoaded(true)}
|
| 39 |
+
src="https://arxiv.org/pdf/2510.05046"
|
| 40 |
+
title="Document COLE"
|
| 41 |
+
width="100%"
|
| 42 |
+
height="100%"
|
| 43 |
+
className="border-none"
|
| 44 |
+
/>
|
| 45 |
+
</div>
|
| 46 |
+
);
|
| 47 |
+
}
|
frontend/src/app/resources/ResourcesPaths.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
const BACKEND_ADDRESS = "/api"
|
| 2 |
+
export {BACKEND_ADDRESS}
|
frontend/src/app/results/[id]/page.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import '../../i18n';
|
| 4 |
+
import { useTranslation } from 'react-i18next';
|
| 5 |
+
import React, { useEffect, useState } from 'react';
|
| 6 |
+
import { useParams } from 'next/navigation';
|
| 7 |
+
import { BACKEND_ADDRESS } from '@/app/resources/ResourcesPaths';
|
| 8 |
+
|
| 9 |
+
export default function ResultsPage() {
|
| 10 |
+
const { t } = useTranslation();
|
| 11 |
+
const { id: submissionId } = useParams();
|
| 12 |
+
const [data, setData] = useState(null);
|
| 13 |
+
|
| 14 |
+
// Noms de métriques fixes en anglais
|
| 15 |
+
const metricLabel = {
|
| 16 |
+
accuracy: 'Accuracy',
|
| 17 |
+
exact_match: 'Exact Match',
|
| 18 |
+
f1: 'F1 Score',
|
| 19 |
+
pearsonr: 'Pearson Correlation',
|
| 20 |
+
};
|
| 21 |
+
const getReadableMetricName = (metricKey) =>
|
| 22 |
+
metricLabel[metricKey] ||
|
| 23 |
+
metricKey.replace(/_/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
|
| 24 |
+
|
| 25 |
+
useEffect(() => {
|
| 26 |
+
fetch(`${BACKEND_ADDRESS}/results/${submissionId}.json`)
|
| 27 |
+
.then((res) => {
|
| 28 |
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
| 29 |
+
return res.json();
|
| 30 |
+
})
|
| 31 |
+
.then(setData)
|
| 32 |
+
.catch(() => setData({ error: true }));
|
| 33 |
+
}, [submissionId]);
|
| 34 |
+
|
| 35 |
+
const handleDownload = async () => {
|
| 36 |
+
if (!data) return;
|
| 37 |
+
try {
|
| 38 |
+
const res = await fetch(`${BACKEND_ADDRESS}/results/${submissionId}.json`);
|
| 39 |
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
| 40 |
+
const blob = await res.blob();
|
| 41 |
+
const url = URL.createObjectURL(blob);
|
| 42 |
+
const link = document.createElement('a');
|
| 43 |
+
link.href = url;
|
| 44 |
+
link.download = `${submissionId}.json`;
|
| 45 |
+
document.body.appendChild(link);
|
| 46 |
+
link.click();
|
| 47 |
+
document.body.removeChild(link);
|
| 48 |
+
URL.revokeObjectURL(url);
|
| 49 |
+
} catch {
|
| 50 |
+
console.error('Download failed');
|
| 51 |
+
}
|
| 52 |
+
};
|
| 53 |
+
|
| 54 |
+
if (!data) {
|
| 55 |
+
return (
|
| 56 |
+
<main className="max-w-5xl mx-auto px-6 py-6 text-center">
|
| 57 |
+
<p className="text-gray-600">{t('results_loading')}</p>
|
| 58 |
+
</main>
|
| 59 |
+
);
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
const tasksArray = data.tasks || [];
|
| 63 |
+
const displayName = data.display_name || data.config_general?.display_name;
|
| 64 |
+
|
| 65 |
+
return (
|
| 66 |
+
<main className="max-w-5xl mx-auto px-6 py-6">
|
| 67 |
+
<h2 className="text-2xl font-bold text-center mb-4">
|
| 68 |
+
{t('results_page_title', { displayName })}
|
| 69 |
+
</h2>
|
| 70 |
+
|
| 71 |
+
<div className="flex justify-center mb-6">
|
| 72 |
+
<button
|
| 73 |
+
onClick={handleDownload}
|
| 74 |
+
className="px-4 py-2 bg-blue-600 text-white rounded-lg shadow hover:bg-blue-700 transition"
|
| 75 |
+
>
|
| 76 |
+
{t('results_download')}
|
| 77 |
+
</button>
|
| 78 |
+
</div>
|
| 79 |
+
|
| 80 |
+
{tasksArray.length === 0 ? (
|
| 81 |
+
<p className="text-blue-700 text-center">
|
| 82 |
+
{t('results_no_results')}
|
| 83 |
+
</p>
|
| 84 |
+
) : (
|
| 85 |
+
<div className="space-y-6">
|
| 86 |
+
{tasksArray.map((taskObj) => {
|
| 87 |
+
const [taskName, metricsObj] = Object.entries(taskObj)[0];
|
| 88 |
+
const [metricType, metricValues] = Object.entries(metricsObj)[0];
|
| 89 |
+
const prettyName = taskName.split('|')[1] || taskName;
|
| 90 |
+
const warningKey = `${metricType}_warning`;
|
| 91 |
+
|
| 92 |
+
return (
|
| 93 |
+
<div
|
| 94 |
+
key={taskName}
|
| 95 |
+
className="p-5 border border-purple-400 rounded-xl shadow-md bg-white"
|
| 96 |
+
>
|
| 97 |
+
<h3 className="text-xl font-semibold text-blue-700 mb-3">
|
| 98 |
+
{t('results_benchmark_label', { name: prettyName })}
|
| 99 |
+
</h3>
|
| 100 |
+
<ul className="list-disc ml-6 text-gray-700">
|
| 101 |
+
{Object.entries(metricValues)
|
| 102 |
+
.filter(([k]) => !k.endsWith('_warning'))
|
| 103 |
+
.map(([metricKey, value]) => (
|
| 104 |
+
<li key={metricKey}>
|
| 105 |
+
<strong>{getReadableMetricName(metricKey)}</strong>:{' '}
|
| 106 |
+
{typeof value === 'number' ? (
|
| 107 |
+
(metricKey === 'exact_match' || metricKey === 'f1'
|
| 108 |
+
? value
|
| 109 |
+
: value * 100
|
| 110 |
+
).toFixed(1) + '%'
|
| 111 |
+
) : (
|
| 112 |
+
value
|
| 113 |
+
)}
|
| 114 |
+
</li>
|
| 115 |
+
))}
|
| 116 |
+
</ul>
|
| 117 |
+
{metricValues[warningKey] && (
|
| 118 |
+
<p className="text-sm text-yellow-700 mt-2">
|
| 119 |
+
⚠️ {metricValues[warningKey]}
|
| 120 |
+
</p>
|
| 121 |
+
)}
|
| 122 |
+
</div>
|
| 123 |
+
);
|
| 124 |
+
})}
|
| 125 |
+
</div>
|
| 126 |
+
)}
|
| 127 |
+
</main>
|
| 128 |
+
);
|
| 129 |
+
}
|
frontend/src/app/results/page.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
'use client';
|
| 2 |
+
|
| 3 |
+
import '../i18n'
|
| 4 |
+
import { useEffect } from 'react';
|
| 5 |
+
import { useRouter } from 'next/navigation';
|
| 6 |
+
import { useTranslation } from 'react-i18next';
|
| 7 |
+
|
| 8 |
+
export default function ResultsDefaultPage() {
|
| 9 |
+
const router = useRouter();
|
| 10 |
+
const { t } = useTranslation();
|
| 11 |
+
|
| 12 |
+
useEffect(() => {
|
| 13 |
+
const justSubmitted = localStorage.getItem('just_submitted');
|
| 14 |
+
const savedFile = localStorage.getItem('last_result_file');
|
| 15 |
+
|
| 16 |
+
if (justSubmitted && savedFile) {
|
| 17 |
+
const id = savedFile.replace('.json', '');
|
| 18 |
+
localStorage.removeItem('just_submitted');
|
| 19 |
+
router.push(`/results/${id}`);
|
| 20 |
+
}
|
| 21 |
+
}, [router]);
|
| 22 |
+
|
| 23 |
+
return (
|
| 24 |
+
<main className="max-w-2xl mx-auto px-6 py-12 text-center">
|
| 25 |
+
<h1 className="text-3xl font-bold text-blue-700 mb-4">
|
| 26 |
+
{t('results_default_title')}
|
| 27 |
+
</h1>
|
| 28 |
+
<p className="text-gray-700">{t('results_default_message')}</p>
|
| 29 |
+
</main>
|
| 30 |
+
);
|
| 31 |
+
}
|
nginx.conf
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
worker_processes 1;
|
| 2 |
+
events { worker_connections 1024; }
|
| 3 |
+
pid /tmp/nginx.pid;
|
| 4 |
+
http {
|
| 5 |
+
server {
|
| 6 |
+
listen 7860;
|
| 7 |
+
|
| 8 |
+
# Security headers (X-Frame-Options omitted: HF Spaces embeds via iframe)
|
| 9 |
+
add_header X-Content-Type-Options "nosniff" always;
|
| 10 |
+
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
| 11 |
+
|
| 12 |
+
location /api/ {
|
| 13 |
+
proxy_pass http://127.0.0.1:8000/;
|
| 14 |
+
proxy_http_version 1.1;
|
| 15 |
+
proxy_set_header Host $host;
|
| 16 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 17 |
+
proxy_connect_timeout 600s;
|
| 18 |
+
proxy_send_timeout 600s;
|
| 19 |
+
proxy_read_timeout 600s;
|
| 20 |
+
send_timeout 600s;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
location / {
|
| 24 |
+
proxy_pass http://127.0.0.1:8001;
|
| 25 |
+
proxy_http_version 1.1;
|
| 26 |
+
proxy_set_header Host $host;
|
| 27 |
+
proxy_set_header X-Real-IP $remote_addr;
|
| 28 |
+
proxy_connect_timeout 600s;
|
| 29 |
+
proxy_send_timeout 600s;
|
| 30 |
+
proxy_read_timeout 600s;
|
| 31 |
+
send_timeout 600s;
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
}
|
pytest.ini
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[pytest]
|
| 2 |
+
testpaths = tests
|
src/__init__.py
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
REPO_ID = "COLE-Graal/COLEGraal"
|
| 2 |
+
cole = "COLE-final"
|
| 3 |
+
boreal = "COLE-final-boreal"
|
| 4 |
+
complete = "COLE-finale-complete"
|
| 5 |
+
comparison = "Fr-comparison"
|
| 6 |
+
NA_VALUE = -1
|
src/backend/__init__.py
ADDED
|
File without changes
|
src/backend/evaluation.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import copy
|
| 2 |
+
import operator
|
| 3 |
+
from functools import reduce
|
| 4 |
+
from typing import List, Dict
|
| 5 |
+
|
| 6 |
+
from src.task.task_factory import Task
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def compute_tasks_ratings(tasks: List[Task], submission: Dict) -> Dict:
|
| 10 |
+
"""
|
| 11 |
+
Method to compute the tasks ratings.
|
| 12 |
+
:param tasks: list of tasks
|
| 13 |
+
:param submission: submission dictionary
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
# We merge the tasks dictionary for simpler handling.
|
| 17 |
+
submission_copy = copy.deepcopy(submission)
|
| 18 |
+
submission_response = reduce(operator.ior, submission_copy.get("tasks"), {})
|
| 19 |
+
|
| 20 |
+
for task in tasks:
|
| 21 |
+
task_name = task.task_name
|
| 22 |
+
|
| 23 |
+
# We remove the prediction since we do not keep it in the response.
|
| 24 |
+
predictions = submission_response.get(task_name).pop("predictions")
|
| 25 |
+
|
| 26 |
+
ratings, warning = task.compute(predictions=predictions)
|
| 27 |
+
ratings.update({f"{task.metric_name}_warning": warning})
|
| 28 |
+
submission_response.get(task_name).update({f"{task.metric_name}": ratings})
|
| 29 |
+
|
| 30 |
+
# Final submission response where we unwrap the merge tasks dictionary into a list of dictionary.
|
| 31 |
+
submission_response = {
|
| 32 |
+
"model_name": submission.get("model_name"),
|
| 33 |
+
"model_url": submission.get("model_url"),
|
| 34 |
+
"tasks": [{key: value} for key, value in submission_response.items()],
|
| 35 |
+
}
|
| 36 |
+
return submission_response
|
src/backend/results/leaderboard.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
src/backend/submission_api.py
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import glob
|
| 2 |
+
import json
|
| 3 |
+
import logging
|
| 4 |
+
import os
|
| 5 |
+
import sys
|
| 6 |
+
import uuid
|
| 7 |
+
from contextlib import asynccontextmanager
|
| 8 |
+
from datetime import datetime
|
| 9 |
+
from functools import lru_cache
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from typing import Dict, List, Any, Union
|
| 12 |
+
|
| 13 |
+
import huggingface_hub
|
| 14 |
+
from fastapi import FastAPI, UploadFile, Form, File, HTTPException
|
| 15 |
+
from fastapi.responses import JSONResponse
|
| 16 |
+
from fastapi.staticfiles import StaticFiles
|
| 17 |
+
|
| 18 |
+
from slowapi import Limiter
|
| 19 |
+
from slowapi.util import get_remote_address
|
| 20 |
+
from slowapi.errors import RateLimitExceeded
|
| 21 |
+
from starlette.middleware.cors import CORSMiddleware
|
| 22 |
+
from starlette.requests import Request
|
| 23 |
+
|
| 24 |
+
from src.backend.evaluation import compute_tasks_ratings
|
| 25 |
+
from src.backend.submit_tools import unzip_predictions_from_zip
|
| 26 |
+
from src.dataset.datasets_data import preload_all_datasets
|
| 27 |
+
from src.backend.validation_tools import (
|
| 28 |
+
validate_submission_tasks_name,
|
| 29 |
+
validate_submission_json,
|
| 30 |
+
validate_submission_template,
|
| 31 |
+
)
|
| 32 |
+
from src.task.task import Task
|
| 33 |
+
from src.task.task_factory import (
|
| 34 |
+
tasks_factory,
|
| 35 |
+
)
|
| 36 |
+
|
| 37 |
+
MAX_ZIP_SIZE_MB = 50
|
| 38 |
+
|
| 39 |
+
BASE_DIR = Path(__file__).resolve().parents[2]
|
| 40 |
+
SRC_DIR = BASE_DIR / "src"
|
| 41 |
+
sys.path.insert(0, str(SRC_DIR))
|
| 42 |
+
|
| 43 |
+
RESULTS_DIR = BASE_DIR / "src" / "backend" / "results"
|
| 44 |
+
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
|
| 45 |
+
FRONTEND_DIR = BASE_DIR / "frontend"
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
@asynccontextmanager
|
| 49 |
+
async def lifespan(application: FastAPI = None): # pylint: disable=unused-argument
|
| 50 |
+
"""Called before the backend comes online, is used to load datasets in memory."""
|
| 51 |
+
# Load the ML model
|
| 52 |
+
try:
|
| 53 |
+
token = os.environ.get("HF_TOKEN")
|
| 54 |
+
huggingface_hub.login(token=token)
|
| 55 |
+
preload_all_datasets()
|
| 56 |
+
except Exception as e:
|
| 57 |
+
error_message = f"The datasets could not be loaded : {e}"
|
| 58 |
+
logging.critical(error_message)
|
| 59 |
+
|
| 60 |
+
yield
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
limiter = Limiter(key_func=get_remote_address)
|
| 64 |
+
app = FastAPI(lifespan=lifespan)
|
| 65 |
+
app.state.limiter = limiter
|
| 66 |
+
app.add_exception_handler(
|
| 67 |
+
RateLimitExceeded,
|
| 68 |
+
lambda req, exc: JSONResponse(
|
| 69 |
+
status_code=429,
|
| 70 |
+
content={"detail": "Too many submissions. Please try again later."},
|
| 71 |
+
),
|
| 72 |
+
)
|
| 73 |
+
app.mount("/results", StaticFiles(directory=str(RESULTS_DIR)), name="results")
|
| 74 |
+
front_end_info_message = f"The Front-end directory is: {FRONTEND_DIR}"
|
| 75 |
+
logging.info(front_end_info_message)
|
| 76 |
+
|
| 77 |
+
app.add_middleware(
|
| 78 |
+
CORSMiddleware,
|
| 79 |
+
allow_origins=["*"],
|
| 80 |
+
allow_methods=["*"],
|
| 81 |
+
allow_headers=["*"],
|
| 82 |
+
)
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
@app.post("/submit")
|
| 86 |
+
@limiter.limit("5/minute")
|
| 87 |
+
async def submit(
|
| 88 |
+
request: Request, # pylint: disable=unused-argument # required by slowapi limiter
|
| 89 |
+
email: str = Form(...),
|
| 90 |
+
predictions_zip: UploadFile = File(...),
|
| 91 |
+
display_name: str = Form(...),
|
| 92 |
+
):
|
| 93 |
+
"""Route for making submissions with user generated results.
|
| 94 |
+
:param request : The incoming request (used for rate limiting)
|
| 95 |
+
:param email : The email of the user's submission
|
| 96 |
+
:param predictions_zip : The zip file of the user's predictions'
|
| 97 |
+
:param display_name : The display name associated with the user's submission'
|
| 98 |
+
"""
|
| 99 |
+
logging.info("Starting submission")
|
| 100 |
+
if len(display_name) > 200:
|
| 101 |
+
raise HTTPException(
|
| 102 |
+
status_code=400, detail="Display name must be under 200 characters."
|
| 103 |
+
)
|
| 104 |
+
if len(email) > 320 or "@" not in email:
|
| 105 |
+
raise HTTPException(status_code=400, detail="Invalid email address.")
|
| 106 |
+
info_message = f"Submission from {email!r} as {display_name!r}."
|
| 107 |
+
logging.info(info_message)
|
| 108 |
+
zip_bytes = await predictions_zip.read()
|
| 109 |
+
if len(zip_bytes) > MAX_ZIP_SIZE_MB * 1024 * 1024:
|
| 110 |
+
raise HTTPException(
|
| 111 |
+
status_code=413, detail=f"ZIP file exceeds {MAX_ZIP_SIZE_MB}MB limit."
|
| 112 |
+
)
|
| 113 |
+
submission_json = unzip_predictions_from_zip(zip_bytes)
|
| 114 |
+
|
| 115 |
+
validate_submission_template(submission_json)
|
| 116 |
+
validate_submission_tasks_name(submission_json)
|
| 117 |
+
validate_submission_json(submission_json)
|
| 118 |
+
|
| 119 |
+
tasks: List[Task] = tasks_factory(submission_json)
|
| 120 |
+
logging.info("Computation started")
|
| 121 |
+
start = datetime.now()
|
| 122 |
+
submission_response = compute_tasks_ratings(tasks=tasks, submission=submission_json)
|
| 123 |
+
computation_time = datetime.now() - start
|
| 124 |
+
info_message = f"Computation ended in {computation_time}"
|
| 125 |
+
logging.info(info_message)
|
| 126 |
+
submission_id = str(uuid.uuid4())
|
| 127 |
+
submission_response.update(
|
| 128 |
+
{
|
| 129 |
+
"display_name": display_name,
|
| 130 |
+
"email": email,
|
| 131 |
+
"submission_id": submission_id,
|
| 132 |
+
}
|
| 133 |
+
)
|
| 134 |
+
|
| 135 |
+
out_path = RESULTS_DIR / f"{submission_id}.json"
|
| 136 |
+
with open(out_path, "w", encoding="utf-8") as f:
|
| 137 |
+
json.dump(submission_response, f, ensure_ascii=False, indent=2)
|
| 138 |
+
|
| 139 |
+
get_leaderboard_entries.cache_clear()
|
| 140 |
+
|
| 141 |
+
return JSONResponse(content=submission_response)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
@lru_cache(maxsize=1)
|
| 145 |
+
def get_leaderboard_entries() -> List[Dict[str, Any]]:
|
| 146 |
+
"""Returns all entries currently in the leaderboard.
|
| 147 |
+
Supporte aussi les fichiers JSON qui contiennent une LISTE d'entrées
|
| 148 |
+
et normalise les métriques 'plates' en groupes imbriqués pour le front.
|
| 149 |
+
"""
|
| 150 |
+
|
| 151 |
+
def _wrap_flat_metrics(task_payload: Dict[str, Any]) -> Dict[str, Any]:
|
| 152 |
+
"""
|
| 153 |
+
Si task_payload est 'plat' (ex: {"accuracy": 94.2}),
|
| 154 |
+
on le transforme en {"<group>": {...}} pour que le front puisse l'agréger.
|
| 155 |
+
Règles de nommage du groupe :
|
| 156 |
+
- présence de exact_match/f1 -> "fquad"
|
| 157 |
+
- sinon présence de acc/accuracy -> "accuracy"
|
| 158 |
+
- sinon présence de pearson/pearsonr/spearman -> "correlation"
|
| 159 |
+
- sinon -> "metrics"
|
| 160 |
+
Les valeurs >1 sont laissées telles quelles (le front normalise déjà % -> [0,1]).
|
| 161 |
+
"""
|
| 162 |
+
if not isinstance(task_payload, dict):
|
| 163 |
+
return task_payload
|
| 164 |
+
|
| 165 |
+
# si c'est déjà "imbriqué" (une valeur est un dict), on ne touche pas
|
| 166 |
+
if any(isinstance(v, dict) for v in task_payload.values()):
|
| 167 |
+
return task_payload
|
| 168 |
+
|
| 169 |
+
keys = set(k.lower() for k in task_payload.keys())
|
| 170 |
+
if {"exact_match", "f1"} & keys:
|
| 171 |
+
group = "fquad"
|
| 172 |
+
elif {"accuracy", "acc"} & keys:
|
| 173 |
+
group = "accuracy"
|
| 174 |
+
elif {"pearson", "pearsonr", "spearman"} & keys:
|
| 175 |
+
group = "correlation"
|
| 176 |
+
else:
|
| 177 |
+
group = "metrics"
|
| 178 |
+
|
| 179 |
+
# Rien de spécial pour les warnings ici : le front les considère optionnels
|
| 180 |
+
# et s'attend à "<group>_warning" dans l'objet interne si on veut en fournir.
|
| 181 |
+
return {group: task_payload}
|
| 182 |
+
|
| 183 |
+
entries: List[Dict[str, Any]] = []
|
| 184 |
+
|
| 185 |
+
for filepath in glob.glob(str(RESULTS_DIR / "*.json")):
|
| 186 |
+
try:
|
| 187 |
+
with open(filepath, encoding="utf-8") as f:
|
| 188 |
+
data = json.load(f)
|
| 189 |
+
|
| 190 |
+
# Fonction interne qui traite UNE entrée (dict) au bon format minimal
|
| 191 |
+
def process_entry(entry: Dict[str, Any]) -> Union[Dict[str, Any], None]:
|
| 192 |
+
if not isinstance(entry, dict):
|
| 193 |
+
return None
|
| 194 |
+
if "model_name" not in entry or "tasks" not in entry:
|
| 195 |
+
return None
|
| 196 |
+
|
| 197 |
+
# Re-construire "results" comme le front s'y attend
|
| 198 |
+
results = {}
|
| 199 |
+
for task_obj in entry.get("tasks", []):
|
| 200 |
+
if not isinstance(task_obj, dict) or len(task_obj) != 1:
|
| 201 |
+
continue
|
| 202 |
+
task_name, payload = list(task_obj.items())[0]
|
| 203 |
+
normalized = _wrap_flat_metrics(payload)
|
| 204 |
+
results[task_name] = normalized
|
| 205 |
+
|
| 206 |
+
if not results:
|
| 207 |
+
return None
|
| 208 |
+
|
| 209 |
+
return {
|
| 210 |
+
"submission_id": entry.get("submission_id") or str(uuid.uuid4()),
|
| 211 |
+
"display_name": entry.get("display_name")
|
| 212 |
+
or entry.get("model_name")
|
| 213 |
+
or "Unnamed Model",
|
| 214 |
+
"email": entry.get("email", "N/A"),
|
| 215 |
+
"results": results,
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
# Le fichier peut contenir UNE entrée (dict) ou PLUSIEURS (list)
|
| 219 |
+
if isinstance(data, list):
|
| 220 |
+
for item in data:
|
| 221 |
+
processed = process_entry(item)
|
| 222 |
+
if processed:
|
| 223 |
+
entries.append(processed)
|
| 224 |
+
else:
|
| 225 |
+
processed = process_entry(data)
|
| 226 |
+
if processed:
|
| 227 |
+
entries.append(processed)
|
| 228 |
+
|
| 229 |
+
except Exception as e:
|
| 230 |
+
logging_message = f"Error processing file '{filepath}': {e}"
|
| 231 |
+
logging.error(logging_message)
|
| 232 |
+
continue
|
| 233 |
+
|
| 234 |
+
return entries
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
@app.get("/leaderboard")
|
| 238 |
+
async def leaderboard() -> List[Dict[str, Any]]:
|
| 239 |
+
|
| 240 |
+
return get_leaderboard_entries()
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
@app.get("/health")
|
| 244 |
+
async def health_check():
|
| 245 |
+
return {"status": "healthy", "message": "API is running."}
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
@app.get("/")
|
| 249 |
+
async def home():
|
| 250 |
+
return {"status": "working"}
|