Spaces:
Running
Running
Commit ·
b372d3c
1
Parent(s): 23bf45f
add english version
Browse files- application_neo4j/app.py +62 -12
- application_neo4j/app_algorithms.py +23 -22
- application_neo4j/static/js/script.js +9 -6
- application_neo4j/static/js/script_dataset.js +39 -32
- application_neo4j/static/js/script_expert.js +16 -10
- application_neo4j/static/js/utils.js +113 -45
- application_neo4j/templates/expert.html +56 -48
- application_neo4j/templates/index.html +56 -46
- application_neo4j/templates/search.html +94 -86
- application_neo4j/templates/search_dataset.html +50 -42
application_neo4j/app.py
CHANGED
|
@@ -1,15 +1,17 @@
|
|
| 1 |
# --- Import et initialisation Flask ---
|
| 2 |
import os
|
| 3 |
-
from flask import Flask, request, render_template, jsonify
|
| 4 |
from neo4j import GraphDatabase, basic_auth
|
| 5 |
from graphdatascience import GraphDataScience
|
| 6 |
import app_algorithms as algo
|
|
|
|
| 7 |
import pandas as pd
|
| 8 |
from typing import Dict
|
| 9 |
from collections import defaultdict
|
| 10 |
import argparse
|
| 11 |
|
| 12 |
app = Flask(__name__, static_url_path="/static/") # Application Flask
|
|
|
|
| 13 |
|
| 14 |
# --- Connexion à Neo4j et GDS ---
|
| 15 |
NEO4J_URI = "bolt://localhost:7687"
|
|
@@ -99,6 +101,54 @@ def ensure_graph_projected(gds: GraphDataScience, graph_name):
|
|
| 99 |
""")
|
| 100 |
print(f"Graphe '{reverse_graph_name}' projeté.")
|
| 101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
@app.route("/") # Page d'accueil
|
| 103 |
def home():
|
| 104 |
return render_template("index.html")
|
|
@@ -177,7 +227,7 @@ def findnode():
|
|
| 177 |
search_info = {"name": name, "depth": request.form.get("depth", 3), "unlimited": is_unlimited}
|
| 178 |
|
| 179 |
if not name:
|
| 180 |
-
message = "
|
| 181 |
return render_template("search.html", message=message, search=search_info, graph_data=graph_data, highlights=highlights)
|
| 182 |
|
| 183 |
try:
|
|
@@ -189,28 +239,28 @@ def findnode():
|
|
| 189 |
# Appeler la fonction GDS BFS
|
| 190 |
gds_result = algo.run_gds_bfs(gds, natural_graph_name,reverse_graph_name, name, depth,False)
|
| 191 |
if not gds_result :
|
| 192 |
-
message =
|
| 193 |
return render_template("search.html", message=message, search=search_info, graph_data=graph_data,highlights=highlights)
|
| 194 |
|
| 195 |
process_gds_bfs_results(gds_result, graph_data, name)
|
| 196 |
if gds_result["source_label"] == "Model" :
|
| 197 |
-
highlights = algo.get_genealogy_highlights(gds, name)
|
| 198 |
return render_template("search.html", message=message, search=search_info, graph_data=graph_data,highlights=highlights)
|
| 199 |
|
| 200 |
if gds_result["source_label"] == "Dataset" :
|
| 201 |
return render_template("search_dataset.html", message=message, search=search_info, graph_data=graph_data)
|
| 202 |
|
| 203 |
if not graph_data["nodes"] and not graph_data["edges"]:
|
| 204 |
-
message =
|
| 205 |
return render_template("search.html", message=message, search=search_info, graph_data=graph_data,highlights=highlights)
|
| 206 |
|
| 207 |
except Exception as e:
|
| 208 |
# Gérer le cas où le nœud source n'existe pas du tout
|
| 209 |
if "Failed to find a node" in str(e):
|
| 210 |
-
message =
|
| 211 |
else:
|
| 212 |
print(f"GDS BFS Error: {e}")
|
| 213 |
-
message =
|
| 214 |
return render_template("search.html", message=message, search=search_info, graph_data=graph_data,highlights=highlights)
|
| 215 |
|
| 216 |
|
|
@@ -250,7 +300,7 @@ def findnode_expert():
|
|
| 250 |
search_info = {"name": name, "depth": request.form.get("depth", 3), "unlimited": is_unlimited,"filters": current_filters }
|
| 251 |
|
| 252 |
if not name:
|
| 253 |
-
message = "
|
| 254 |
return render_template("expert.html", message=message, search=search_info, graph_data=graph_data)
|
| 255 |
|
| 256 |
try:
|
|
@@ -262,22 +312,22 @@ def findnode_expert():
|
|
| 262 |
# Appeler la fonction GDS BFS
|
| 263 |
gds_result = algo.run_gds_bfs(gds, natural_graph_name,reverse_graph_name, name, depth,True)
|
| 264 |
if not gds_result :
|
| 265 |
-
message =
|
| 266 |
return render_template("expert.html", message=message, search=search_info, graph_data=graph_data)
|
| 267 |
|
| 268 |
|
| 269 |
process_gds_bfs_results(gds_result, graph_data, name)
|
| 270 |
|
| 271 |
if not graph_data["nodes"] and not graph_data["edges"]:
|
| 272 |
-
message =
|
| 273 |
|
| 274 |
except Exception as e:
|
| 275 |
# Gérer le cas où le nœud source n'existe pas du tout
|
| 276 |
if "Failed to find a node" in str(e):
|
| 277 |
-
message =
|
| 278 |
else:
|
| 279 |
print(f"GDS BFS Error: {e}")
|
| 280 |
-
message =
|
| 281 |
return render_template("expert.html", message=message, search=search_info, graph_data=graph_data)
|
| 282 |
|
| 283 |
|
|
|
|
| 1 |
# --- Import et initialisation Flask ---
|
| 2 |
import os
|
| 3 |
+
from flask import Flask, request, render_template, jsonify, session, redirect, url_for
|
| 4 |
from neo4j import GraphDatabase, basic_auth
|
| 5 |
from graphdatascience import GraphDataScience
|
| 6 |
import app_algorithms as algo
|
| 7 |
+
from translations import t
|
| 8 |
import pandas as pd
|
| 9 |
from typing import Dict
|
| 10 |
from collections import defaultdict
|
| 11 |
import argparse
|
| 12 |
|
| 13 |
app = Flask(__name__, static_url_path="/static/") # Application Flask
|
| 14 |
+
app.secret_key = os.urandom(24)
|
| 15 |
|
| 16 |
# --- Connexion à Neo4j et GDS ---
|
| 17 |
NEO4J_URI = "bolt://localhost:7687"
|
|
|
|
| 101 |
""")
|
| 102 |
print(f"Graphe '{reverse_graph_name}' projeté.")
|
| 103 |
|
| 104 |
+
@app.before_request
|
| 105 |
+
def set_language():
|
| 106 |
+
"""Determine current language from session or URL query param."""
|
| 107 |
+
if "lang" not in session:
|
| 108 |
+
session["lang"] = "fr"
|
| 109 |
+
lang = request.args.get("lang")
|
| 110 |
+
if lang in ("fr", "en"):
|
| 111 |
+
session["lang"] = lang
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
@app.route("/set_language")
|
| 115 |
+
def set_language_route():
|
| 116 |
+
"""Endpoint used by the language selector in the navbar."""
|
| 117 |
+
lang = request.args.get("lang", "fr")
|
| 118 |
+
if lang in ("fr", "en"):
|
| 119 |
+
session["lang"] = lang
|
| 120 |
+
referrer = request.headers.get("Referer", "/")
|
| 121 |
+
if referrer and "//" in referrer:
|
| 122 |
+
from urllib.parse import urlparse, parse_qs, urlencode
|
| 123 |
+
parsed = urlparse(referrer)
|
| 124 |
+
qs = parse_qs(parsed.query, keep_blank_values=True)
|
| 125 |
+
qs["lang"] = [lang]
|
| 126 |
+
return redirect(parsed.path + ("?" + urlencode(qs, doseq=True) if qs else ""))
|
| 127 |
+
return redirect("/")
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
@app.context_processor
|
| 131 |
+
def inject_i18n():
|
| 132 |
+
"""Inject t() and current_lang into all Jinja2 templates."""
|
| 133 |
+
def _t(key, **kwargs):
|
| 134 |
+
return t(key, session.get("lang", "fr"), **kwargs)
|
| 135 |
+
# Dictionnaire JS pour les clés utilisées côté client
|
| 136 |
+
js_i18n_keys = [
|
| 137 |
+
"js.node_info.name", "js.node_info.type", "js.node_info.followers",
|
| 138 |
+
"js.node_info.downloads", "js.node_info.created", "js.node_info.task",
|
| 139 |
+
"js.node_info.dataset", "js.node_info.undefined", "js.node_info.unknown_date",
|
| 140 |
+
"js.node_info.see_on_hf",
|
| 141 |
+
"js.unknown", "js.unknown_f", "js.na",
|
| 142 |
+
]
|
| 143 |
+
lang = session.get("lang", "fr")
|
| 144 |
+
js_i18n_data = {k: t(k, lang) for k in js_i18n_keys}
|
| 145 |
+
return {
|
| 146 |
+
"t": _t,
|
| 147 |
+
"current_lang": lang,
|
| 148 |
+
"js_i18n_data": js_i18n_data,
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
|
| 152 |
@app.route("/") # Page d'accueil
|
| 153 |
def home():
|
| 154 |
return render_template("index.html")
|
|
|
|
| 227 |
search_info = {"name": name, "depth": request.form.get("depth", 3), "unlimited": is_unlimited}
|
| 228 |
|
| 229 |
if not name:
|
| 230 |
+
message = t("error.empty_name", session.get("lang", "fr"))
|
| 231 |
return render_template("search.html", message=message, search=search_info, graph_data=graph_data, highlights=highlights)
|
| 232 |
|
| 233 |
try:
|
|
|
|
| 239 |
# Appeler la fonction GDS BFS
|
| 240 |
gds_result = algo.run_gds_bfs(gds, natural_graph_name,reverse_graph_name, name, depth,False)
|
| 241 |
if not gds_result :
|
| 242 |
+
message = t("error.model_not_found", session.get("lang", "fr"), name=name)
|
| 243 |
return render_template("search.html", message=message, search=search_info, graph_data=graph_data,highlights=highlights)
|
| 244 |
|
| 245 |
process_gds_bfs_results(gds_result, graph_data, name)
|
| 246 |
if gds_result["source_label"] == "Model" :
|
| 247 |
+
highlights = algo.get_genealogy_highlights(gds, name, lang=session.get("lang", "fr"))
|
| 248 |
return render_template("search.html", message=message, search=search_info, graph_data=graph_data,highlights=highlights)
|
| 249 |
|
| 250 |
if gds_result["source_label"] == "Dataset" :
|
| 251 |
return render_template("search_dataset.html", message=message, search=search_info, graph_data=graph_data)
|
| 252 |
|
| 253 |
if not graph_data["nodes"] and not graph_data["edges"]:
|
| 254 |
+
message = t("error.no_neighbors", session.get("lang", "fr"), name=name)
|
| 255 |
return render_template("search.html", message=message, search=search_info, graph_data=graph_data,highlights=highlights)
|
| 256 |
|
| 257 |
except Exception as e:
|
| 258 |
# Gérer le cas où le nœud source n'existe pas du tout
|
| 259 |
if "Failed to find a node" in str(e):
|
| 260 |
+
message = t("error.node_not_found", session.get("lang", "fr"), name=name)
|
| 261 |
else:
|
| 262 |
print(f"GDS BFS Error: {e}")
|
| 263 |
+
message = t("error.gds", session.get("lang", "fr"), error=str(e))
|
| 264 |
return render_template("search.html", message=message, search=search_info, graph_data=graph_data,highlights=highlights)
|
| 265 |
|
| 266 |
|
|
|
|
| 300 |
search_info = {"name": name, "depth": request.form.get("depth", 3), "unlimited": is_unlimited,"filters": current_filters }
|
| 301 |
|
| 302 |
if not name:
|
| 303 |
+
message = t("error.empty_name", session.get("lang", "fr"))
|
| 304 |
return render_template("expert.html", message=message, search=search_info, graph_data=graph_data)
|
| 305 |
|
| 306 |
try:
|
|
|
|
| 312 |
# Appeler la fonction GDS BFS
|
| 313 |
gds_result = algo.run_gds_bfs(gds, natural_graph_name,reverse_graph_name, name, depth,True)
|
| 314 |
if not gds_result :
|
| 315 |
+
message = t("error.node_not_found_expert", session.get("lang", "fr"), name=name)
|
| 316 |
return render_template("expert.html", message=message, search=search_info, graph_data=graph_data)
|
| 317 |
|
| 318 |
|
| 319 |
process_gds_bfs_results(gds_result, graph_data, name)
|
| 320 |
|
| 321 |
if not graph_data["nodes"] and not graph_data["edges"]:
|
| 322 |
+
message = t("error.no_neighbors", session.get("lang", "fr"), name=name)
|
| 323 |
|
| 324 |
except Exception as e:
|
| 325 |
# Gérer le cas où le nœud source n'existe pas du tout
|
| 326 |
if "Failed to find a node" in str(e):
|
| 327 |
+
message = t("error.node_not_found", session.get("lang", "fr"), name=name)
|
| 328 |
else:
|
| 329 |
print(f"GDS BFS Error: {e}")
|
| 330 |
+
message = t("error.gds", session.get("lang", "fr"), error=str(e))
|
| 331 |
return render_template("expert.html", message=message, search=search_info, graph_data=graph_data)
|
| 332 |
|
| 333 |
|
application_neo4j/app_algorithms.py
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
from graphdatascience import GraphDataScience
|
| 3 |
from typing import Dict, List, Any
|
| 4 |
import pandas as pd
|
|
|
|
| 5 |
|
| 6 |
def run_gds_bfs(gds: GraphDataScience, natural_graph_name: str, reverse_graph_name: str, source_name: str, max_depth: int = None, expert = False) -> Dict[str, Any]:
|
| 7 |
"""
|
|
@@ -78,7 +79,7 @@ def run_gds_bfs(gds: GraphDataScience, natural_graph_name: str, reverse_graph_na
|
|
| 78 |
|
| 79 |
|
| 80 |
|
| 81 |
-
def get_genealogy_highlights(gds: "GraphDataScience", model_name: str, num_highlights: int = 2) -> Dict:
|
| 82 |
"""
|
| 83 |
Trouve les modèles clés dans l'ascendance et la descendance (1er/2e plus cités/téléchargés).
|
| 84 |
|
|
@@ -100,50 +101,50 @@ def get_genealogy_highlights(gds: "GraphDataScience", model_name: str, num_highl
|
|
| 100 |
# Les classes CSS sont directement des classes Bootstrap 5 pour simplifier le rendu dans le template HTML.
|
| 101 |
badges_info = {
|
| 102 |
'desc_cited_1': {
|
| 103 |
-
'text': '
|
| 104 |
'class': 'bg-success',
|
| 105 |
-
'title':
|
| 106 |
},
|
| 107 |
'desc_cited_2': {
|
| 108 |
-
'text': '
|
| 109 |
'class': 'bg-success bg-opacity-75',
|
| 110 |
-
'title':
|
| 111 |
},
|
| 112 |
'desc_downloaded_1': {
|
| 113 |
-
'text': '
|
| 114 |
'class': 'beta',
|
| 115 |
-
'title':
|
| 116 |
},
|
| 117 |
'desc_downloaded_2': {
|
| 118 |
-
'text': '
|
| 119 |
'class': 'alpha',
|
| 120 |
-
'title':
|
| 121 |
},
|
| 122 |
|
| 123 |
'asc_foundation': {
|
| 124 |
-
'text': '
|
| 125 |
'class': 'bg-warning text-dark',
|
| 126 |
-
'title':
|
| 127 |
},
|
| 128 |
'asc_cited_1': {
|
| 129 |
-
'text': '
|
| 130 |
'class': 'bg-success',
|
| 131 |
-
'title':
|
| 132 |
},
|
| 133 |
'asc_cited_2': {
|
| 134 |
-
'text': '
|
| 135 |
'class': 'bg-success bg-opacity-75',
|
| 136 |
-
'title':
|
| 137 |
},
|
| 138 |
'asc_downloaded_1': {
|
| 139 |
-
'text': '
|
| 140 |
'class': 'beta',
|
| 141 |
-
'title':
|
| 142 |
},
|
| 143 |
'asc_downloaded_2': {
|
| 144 |
-
'text': '
|
| 145 |
'class': 'alpha',
|
| 146 |
-
'title':
|
| 147 |
},
|
| 148 |
}
|
| 149 |
|
|
@@ -272,13 +273,13 @@ def create_node_data(node_props, label):
|
|
| 272 |
"followers": node_props.get("followers", 1)
|
| 273 |
}
|
| 274 |
elif label == "Model":
|
| 275 |
-
licens_ =str(node_props.get("license", "
|
| 276 |
if licens_ =="\'other\'" or pd.isna(licens_) or licens_ =="nan":
|
| 277 |
-
licens_ = "
|
| 278 |
|
| 279 |
tache = node_props.get("task", "")
|
| 280 |
if tache =="unknown":
|
| 281 |
-
tache = "
|
| 282 |
return {
|
| 283 |
**base_data,
|
| 284 |
"label": "Modèle",
|
|
|
|
| 2 |
from graphdatascience import GraphDataScience
|
| 3 |
from typing import Dict, List, Any
|
| 4 |
import pandas as pd
|
| 5 |
+
from translations import t
|
| 6 |
|
| 7 |
def run_gds_bfs(gds: GraphDataScience, natural_graph_name: str, reverse_graph_name: str, source_name: str, max_depth: int = None, expert = False) -> Dict[str, Any]:
|
| 8 |
"""
|
|
|
|
| 79 |
|
| 80 |
|
| 81 |
|
| 82 |
+
def get_genealogy_highlights(gds: "GraphDataScience", model_name: str, num_highlights: int = 2, lang: str = "fr") -> Dict:
|
| 83 |
"""
|
| 84 |
Trouve les modèles clés dans l'ascendance et la descendance (1er/2e plus cités/téléchargés).
|
| 85 |
|
|
|
|
| 101 |
# Les classes CSS sont directement des classes Bootstrap 5 pour simplifier le rendu dans le template HTML.
|
| 102 |
badges_info = {
|
| 103 |
'desc_cited_1': {
|
| 104 |
+
'text': t('badge.desc_cited_1.text', lang),
|
| 105 |
'class': 'bg-success',
|
| 106 |
+
'title': t('badge.desc_cited_1.title', lang)
|
| 107 |
},
|
| 108 |
'desc_cited_2': {
|
| 109 |
+
'text': t('badge.desc_cited_2.text', lang),
|
| 110 |
'class': 'bg-success bg-opacity-75',
|
| 111 |
+
'title': t('badge.desc_cited_2.title', lang)
|
| 112 |
},
|
| 113 |
'desc_downloaded_1': {
|
| 114 |
+
'text': t('badge.desc_downloaded_1.text', lang),
|
| 115 |
'class': 'beta',
|
| 116 |
+
'title': t('badge.desc_downloaded_1.title', lang)
|
| 117 |
},
|
| 118 |
'desc_downloaded_2': {
|
| 119 |
+
'text': t('badge.desc_downloaded_2.text', lang),
|
| 120 |
'class': 'alpha',
|
| 121 |
+
'title': t('badge.desc_downloaded_2.title', lang)
|
| 122 |
},
|
| 123 |
|
| 124 |
'asc_foundation': {
|
| 125 |
+
'text': t('badge.asc_foundation.text', lang),
|
| 126 |
'class': 'bg-warning text-dark',
|
| 127 |
+
'title': t('badge.asc_foundation.title', lang)
|
| 128 |
},
|
| 129 |
'asc_cited_1': {
|
| 130 |
+
'text': t('badge.asc_cited_1.text', lang),
|
| 131 |
'class': 'bg-success',
|
| 132 |
+
'title': t('badge.asc_cited_1.title', lang)
|
| 133 |
},
|
| 134 |
'asc_cited_2': {
|
| 135 |
+
'text': t('badge.asc_cited_2.text', lang),
|
| 136 |
'class': 'bg-success bg-opacity-75',
|
| 137 |
+
'title': t('badge.asc_cited_2.title', lang)
|
| 138 |
},
|
| 139 |
'asc_downloaded_1': {
|
| 140 |
+
'text': t('badge.asc_downloaded_1.text', lang),
|
| 141 |
'class': 'beta',
|
| 142 |
+
'title': t('badge.asc_downloaded_1.title', lang)
|
| 143 |
},
|
| 144 |
'asc_downloaded_2': {
|
| 145 |
+
'text': t('badge.asc_downloaded_2.text', lang),
|
| 146 |
'class': 'alpha',
|
| 147 |
+
'title': t('badge.asc_downloaded_2.title', lang)
|
| 148 |
},
|
| 149 |
}
|
| 150 |
|
|
|
|
| 273 |
"followers": node_props.get("followers", 1)
|
| 274 |
}
|
| 275 |
elif label == "Model":
|
| 276 |
+
licens_ =str(node_props.get("license", t("node.unknown", "fr"))).strip("[]")
|
| 277 |
if licens_ =="\'other\'" or pd.isna(licens_) or licens_ =="nan":
|
| 278 |
+
licens_ = t("node.other", "fr")
|
| 279 |
|
| 280 |
tache = node_props.get("task", "")
|
| 281 |
if tache =="unknown":
|
| 282 |
+
tache = t("node.unknown", "fr")
|
| 283 |
return {
|
| 284 |
**base_data,
|
| 285 |
"label": "Modèle",
|
application_neo4j/static/js/script.js
CHANGED
|
@@ -187,8 +187,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 187 |
return;
|
| 188 |
}
|
| 189 |
|
|
|
|
|
|
|
|
|
|
| 190 |
const common_dt_options = {
|
| 191 |
-
"language": { "url":
|
| 192 |
"pageLength": 10,
|
| 193 |
"responsive": true,"scrollX": true , "scrollY":true,
|
| 194 |
"columnDefs": [
|
|
@@ -227,12 +230,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 227 |
node.id || "N/A",
|
| 228 |
node.author || "Inconnu",
|
| 229 |
// La conversion en string via la condition est parfaite ici
|
| 230 |
-
String(node.downloads ?? "Inconnu"),
|
| 231 |
-
node.task || "Inconnue",
|
| 232 |
String(node.likes ?? "0"),
|
| 233 |
-
String(node.createdAt ?? "Inconnue"),
|
| 234 |
-
node.dataset || "Inconnu",
|
| 235 |
-
node.license || "Inconnue",
|
| 236 |
distance > 0 ? `+${distance}` : String(distance ?? 0),
|
| 237 |
String(node.ascendantsCount ?? "0"),
|
| 238 |
String(node.descendantsCount ?? "0"),
|
|
|
|
| 187 |
return;
|
| 188 |
}
|
| 189 |
|
| 190 |
+
const datatablesLangUrl = (window.__I18N_LANG === 'en')
|
| 191 |
+
? "https://cdn.datatables.net/plug-ins/1.13.4/i18n/en.json"
|
| 192 |
+
: "https://cdn.datatables.net/plug-ins/1.13.4/i18n/fr-FR.json";
|
| 193 |
const common_dt_options = {
|
| 194 |
+
"language": { "url": datatablesLangUrl },
|
| 195 |
"pageLength": 10,
|
| 196 |
"responsive": true,"scrollX": true , "scrollY":true,
|
| 197 |
"columnDefs": [
|
|
|
|
| 230 |
node.id || "N/A",
|
| 231 |
node.author || "Inconnu",
|
| 232 |
// La conversion en string via la condition est parfaite ici
|
| 233 |
+
String(node.downloads ?? (window.__I18N['js.unknown'] || "Inconnu")),
|
| 234 |
+
node.task || (window.__I18N['js.unknown_f'] || "Inconnue"),
|
| 235 |
String(node.likes ?? "0"),
|
| 236 |
+
String(node.createdAt ?? (window.__I18N['js.unknown_f'] || "Inconnue")),
|
| 237 |
+
node.dataset || (window.__I18N['js.unknown'] || "Inconnu"),
|
| 238 |
+
node.license || (window.__I18N['js.unknown_f'] || "Inconnue"),
|
| 239 |
distance > 0 ? `+${distance}` : String(distance ?? 0),
|
| 240 |
String(node.ascendantsCount ?? "0"),
|
| 241 |
String(node.descendantsCount ?? "0"),
|
application_neo4j/static/js/script_dataset.js
CHANGED
|
@@ -1,39 +1,43 @@
|
|
| 1 |
const edgeInfos_dataset = {
|
| 2 |
"Fait partie de cette organisation": {
|
| 3 |
-
color: "#a05195",
|
| 4 |
-
|
|
|
|
| 5 |
},
|
| 6 |
"A publié": {
|
| 7 |
-
color: "#003f5c",
|
| 8 |
-
|
|
|
|
| 9 |
},
|
| 10 |
"A été utilisé dans ce modèle": {
|
| 11 |
-
color: "#f50f0f",
|
| 12 |
-
|
| 13 |
-
|
| 14 |
},
|
| 15 |
"A généré": {
|
| 16 |
-
color: "#8f7340",
|
| 17 |
-
|
| 18 |
-
|
| 19 |
},
|
| 20 |
"finetune": {
|
| 21 |
-
color: "#d9c00d",
|
| 22 |
-
|
| 23 |
-
|
| 24 |
},
|
| 25 |
"adapter": {
|
| 26 |
-
color: "#9af17c",
|
| 27 |
-
|
| 28 |
-
|
| 29 |
},
|
| 30 |
"quantized": {
|
| 31 |
-
color: "#1bc3c6",
|
| 32 |
-
|
|
|
|
| 33 |
},
|
| 34 |
"merge": {
|
| 35 |
-
color: "#e407e4",
|
| 36 |
-
|
|
|
|
| 37 |
}
|
| 38 |
};
|
| 39 |
|
|
@@ -42,7 +46,7 @@ const edgeInfos_dataset = {
|
|
| 42 |
const legendContainer = document.getElementById("legend-edges");
|
| 43 |
legendContainer.innerHTML = ""; // reset
|
| 44 |
|
| 45 |
-
Object.entries(edgeInfos_dataset).forEach(([relation, {color,
|
| 46 |
const li = document.createElement("li");
|
| 47 |
li.className = "list-group-item d-flex align-items-center";
|
| 48 |
|
|
@@ -50,15 +54,15 @@ const edgeInfos_dataset = {
|
|
| 50 |
li.setAttribute("data-bs-toggle", "tooltip");
|
| 51 |
li.setAttribute("data-bs-placement", "top");
|
| 52 |
li.setAttribute("data-bs-html", "true");
|
| 53 |
-
li.setAttribute("title",
|
| 54 |
-
|
| 55 |
const span = document.createElement("span");
|
| 56 |
span.className = "legend-color edge me-2";
|
| 57 |
span.style.backgroundColor = color;
|
| 58 |
-
|
| 59 |
li.appendChild(span);
|
| 60 |
-
li.appendChild(document.createTextNode(
|
| 61 |
-
|
| 62 |
legendContainer.appendChild(li);
|
| 63 |
});
|
| 64 |
|
|
@@ -199,8 +203,11 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 199 |
return;
|
| 200 |
}
|
| 201 |
|
|
|
|
|
|
|
|
|
|
| 202 |
const common_dt_options = {
|
| 203 |
-
"language": { "url":
|
| 204 |
"pageLength": 10,
|
| 205 |
"responsive": true,"scrollX": true , "scrollY":true,
|
| 206 |
"columnDefs": [
|
|
@@ -236,12 +243,12 @@ document.addEventListener("DOMContentLoaded", () => {
|
|
| 236 |
node.id || "N/A",
|
| 237 |
node.author || "Inconnu",
|
| 238 |
// La conversion en string via la condition est parfaite ici
|
| 239 |
-
String(node.downloads ?? "Inconnu"),
|
| 240 |
-
node.task || "Inconnue",
|
| 241 |
String(node.likes ?? "0"),
|
| 242 |
-
String(node.createdAt ?? "Inconnue"),
|
| 243 |
-
node.dataset || "Inconnu",
|
| 244 |
-
node.license || "Inconnue",
|
| 245 |
String(node.ascendantsCount ?? "0"),
|
| 246 |
String(node.descendantsCount ?? "0"),
|
| 247 |
String(node.citationCount ?? "0")
|
|
|
|
| 1 |
const edgeInfos_dataset = {
|
| 2 |
"Fait partie de cette organisation": {
|
| 3 |
+
color: "#a05195",
|
| 4 |
+
tooltip_fr: "Cette personne est membre de cette organisation",
|
| 5 |
+
tooltip_en: "This person is a member of this organization"
|
| 6 |
},
|
| 7 |
"A publié": {
|
| 8 |
+
color: "#003f5c",
|
| 9 |
+
tooltip_fr: "Un auteur (personne ou organisation) a publié un modèle",
|
| 10 |
+
tooltip_en: "An author (person or organization) published a model"
|
| 11 |
},
|
| 12 |
"A été utilisé dans ce modèle": {
|
| 13 |
+
color: "#f50f0f",
|
| 14 |
+
tooltip_fr: "Ce dataset a servi pour l'entraînement de ce modèle",
|
| 15 |
+
tooltip_en: "This dataset was used for training this model"
|
| 16 |
},
|
| 17 |
"A généré": {
|
| 18 |
+
color: "#8f7340",
|
| 19 |
+
tooltip_fr: "Le modèle source a été téléchargé et modifié afin de créer le modèle cible (type de transformation inconnu)",
|
| 20 |
+
tooltip_en: "The source model was downloaded and modified to create the target model (transformation type unknown)"
|
| 21 |
},
|
| 22 |
"finetune": {
|
| 23 |
+
color: "#d9c00d",
|
| 24 |
+
tooltip_fr: "Ajustement : le modèle source est ré-entraîné sur un jeu de données spécifique afin de pouvoir être performant pour une tâche précise.",
|
| 25 |
+
tooltip_en: "Fine-tuning: the source model is retrained on a specific dataset to perform well on a precise task."
|
| 26 |
},
|
| 27 |
"adapter": {
|
| 28 |
+
color: "#9af17c",
|
| 29 |
+
tooltip_fr: "Adaptation : méthode d'ajustement qui peut être utilisée avec peu de ressources de calcul.",
|
| 30 |
+
tooltip_en: "Adaptation: a fine-tuning method that can be used with limited computing resources."
|
| 31 |
},
|
| 32 |
"quantized": {
|
| 33 |
+
color: "#1bc3c6",
|
| 34 |
+
tooltip_fr: "Quantisation : la précision des poids du modèle source est réduite afin de diminuer son empreinte en mémoire.",
|
| 35 |
+
tooltip_en: "Quantization: the precision of the source model's weights is reduced to decrease its memory footprint."
|
| 36 |
},
|
| 37 |
"merge": {
|
| 38 |
+
color: "#e407e4",
|
| 39 |
+
tooltip_fr: "Fusion : méthode visant à mélanger des couches de différents modèles pour améliorer leur performance.",
|
| 40 |
+
tooltip_en: "Merge: a method aiming to mix layers of different models to improve their performance."
|
| 41 |
}
|
| 42 |
};
|
| 43 |
|
|
|
|
| 46 |
const legendContainer = document.getElementById("legend-edges");
|
| 47 |
legendContainer.innerHTML = ""; // reset
|
| 48 |
|
| 49 |
+
Object.entries(edgeInfos_dataset).forEach(([relation, {color, tooltip_fr, tooltip_en}]) => {
|
| 50 |
const li = document.createElement("li");
|
| 51 |
li.className = "list-group-item d-flex align-items-center";
|
| 52 |
|
|
|
|
| 54 |
li.setAttribute("data-bs-toggle", "tooltip");
|
| 55 |
li.setAttribute("data-bs-placement", "top");
|
| 56 |
li.setAttribute("data-bs-html", "true");
|
| 57 |
+
li.setAttribute("title", I18N_LANG === 'en' ? (tooltip_en || tooltip_fr) : (tooltip_fr || tooltip_en || ""));
|
| 58 |
+
|
| 59 |
const span = document.createElement("span");
|
| 60 |
span.className = "legend-color edge me-2";
|
| 61 |
span.style.backgroundColor = color;
|
| 62 |
+
|
| 63 |
li.appendChild(span);
|
| 64 |
+
li.appendChild(document.createTextNode(edgeDisplayName(relation)));
|
| 65 |
+
|
| 66 |
legendContainer.appendChild(li);
|
| 67 |
});
|
| 68 |
|
|
|
|
| 203 |
return;
|
| 204 |
}
|
| 205 |
|
| 206 |
+
const datatablesLangUrl = (window.__I18N_LANG === 'en')
|
| 207 |
+
? "https://cdn.datatables.net/plug-ins/1.13.4/i18n/en.json"
|
| 208 |
+
: "https://cdn.datatables.net/plug-ins/1.13.4/i18n/fr-FR.json";
|
| 209 |
const common_dt_options = {
|
| 210 |
+
"language": { "url": datatablesLangUrl },
|
| 211 |
"pageLength": 10,
|
| 212 |
"responsive": true,"scrollX": true , "scrollY":true,
|
| 213 |
"columnDefs": [
|
|
|
|
| 243 |
node.id || "N/A",
|
| 244 |
node.author || "Inconnu",
|
| 245 |
// La conversion en string via la condition est parfaite ici
|
| 246 |
+
String(node.downloads ?? (window.__I18N['js.unknown'] || "Inconnu")),
|
| 247 |
+
node.task || (window.__I18N['js.unknown_f'] || "Inconnue"),
|
| 248 |
String(node.likes ?? "0"),
|
| 249 |
+
String(node.createdAt ?? (window.__I18N['js.unknown_f'] || "Inconnue")),
|
| 250 |
+
node.dataset || (window.__I18N['js.unknown'] || "Inconnu"),
|
| 251 |
+
node.license || (window.__I18N['js.unknown_f'] || "Inconnue"),
|
| 252 |
String(node.ascendantsCount ?? "0"),
|
| 253 |
String(node.descendantsCount ?? "0"),
|
| 254 |
String(node.citationCount ?? "0")
|
application_neo4j/static/js/script_expert.js
CHANGED
|
@@ -6,7 +6,7 @@ function buildEdgeFilters() {
|
|
| 6 |
const container = document.getElementById('edge-filters-container');
|
| 7 |
if (!container) return;
|
| 8 |
container.innerHTML = '';
|
| 9 |
-
Object.entries(edgeInfos).forEach(([key, {
|
| 10 |
const div = document.createElement('div');
|
| 11 |
div.className = 'form-check form-switch';
|
| 12 |
const input = document.createElement('input');
|
|
@@ -18,7 +18,7 @@ function buildEdgeFilters() {
|
|
| 18 |
const label = document.createElement('label');
|
| 19 |
label.className = 'form-check-label';
|
| 20 |
label.htmlFor = input.id;
|
| 21 |
-
label.textContent =
|
| 22 |
div.appendChild(input);
|
| 23 |
div.appendChild(label);
|
| 24 |
container.appendChild(div);
|
|
@@ -34,16 +34,19 @@ function populateGraphModelsTable(graphData, table) {
|
|
| 34 |
|
| 35 |
|
| 36 |
// Construction de la ligne avec les nouvelles données
|
|
|
|
|
|
|
|
|
|
| 37 |
const rowData = [
|
| 38 |
-
node.id ||
|
| 39 |
-
node.author ||
|
| 40 |
// La conversion en string via la condition est parfaite ici
|
| 41 |
-
String(node.downloads ??
|
| 42 |
-
node.task ||
|
| 43 |
String(node.likes ?? "0"),
|
| 44 |
-
String(node.createdAt ??
|
| 45 |
-
node.dataset ||
|
| 46 |
-
node.license ||
|
| 47 |
distance > 0 ? `+${distance}` : String(distance ?? 0),
|
| 48 |
String(node.ascendantsCount ?? "0"),
|
| 49 |
String(node.descendantsCount ?? "0"),
|
|
@@ -204,8 +207,11 @@ function initializeSigmaGraph(graphData) {
|
|
| 204 |
buildEdgeLegend();
|
| 205 |
buildEdgeFilters();
|
| 206 |
|
|
|
|
|
|
|
|
|
|
| 207 |
const graphModelsTable = $('#graph-models-table').DataTable({
|
| 208 |
-
language: { "url":
|
| 209 |
pageLength: 5, responsive: true, scrollX: true
|
| 210 |
});
|
| 211 |
|
|
|
|
| 6 |
const container = document.getElementById('edge-filters-container');
|
| 7 |
if (!container) return;
|
| 8 |
container.innerHTML = '';
|
| 9 |
+
Object.entries(edgeInfos).forEach(([key, { color }]) => {
|
| 10 |
const div = document.createElement('div');
|
| 11 |
div.className = 'form-check form-switch';
|
| 12 |
const input = document.createElement('input');
|
|
|
|
| 18 |
const label = document.createElement('label');
|
| 19 |
label.className = 'form-check-label';
|
| 20 |
label.htmlFor = input.id;
|
| 21 |
+
label.textContent = edgeDisplayName(key);
|
| 22 |
div.appendChild(input);
|
| 23 |
div.appendChild(label);
|
| 24 |
container.appendChild(div);
|
|
|
|
| 34 |
|
| 35 |
|
| 36 |
// Construction de la ligne avec les nouvelles données
|
| 37 |
+
const unknown = window.__I18N['js.unknown'] || "Inconnu";
|
| 38 |
+
const unknownF = window.__I18N['js.unknown_f'] || "Inconnue";
|
| 39 |
+
const na = window.__I18N['js.na'] || "N/A";
|
| 40 |
const rowData = [
|
| 41 |
+
node.id || na,
|
| 42 |
+
node.author || unknown,
|
| 43 |
// La conversion en string via la condition est parfaite ici
|
| 44 |
+
String(node.downloads ?? unknown),
|
| 45 |
+
node.task || unknownF,
|
| 46 |
String(node.likes ?? "0"),
|
| 47 |
+
String(node.createdAt ?? unknownF),
|
| 48 |
+
node.dataset || unknown,
|
| 49 |
+
node.license || unknownF,
|
| 50 |
distance > 0 ? `+${distance}` : String(distance ?? 0),
|
| 51 |
String(node.ascendantsCount ?? "0"),
|
| 52 |
String(node.descendantsCount ?? "0"),
|
|
|
|
| 207 |
buildEdgeLegend();
|
| 208 |
buildEdgeFilters();
|
| 209 |
|
| 210 |
+
const datatablesLangUrl = (window.__I18N_LANG === 'en')
|
| 211 |
+
? "https://cdn.datatables.net/plug-ins/1.13.4/i18n/en.json"
|
| 212 |
+
: "https://cdn.datatables.net/plug-ins/1.13.4/i18n/fr-FR.json";
|
| 213 |
const graphModelsTable = $('#graph-models-table').DataTable({
|
| 214 |
+
language: { "url": datatablesLangUrl },
|
| 215 |
pageLength: 5, responsive: true, scrollX: true
|
| 216 |
});
|
| 217 |
|
application_neo4j/static/js/utils.js
CHANGED
|
@@ -1,45 +1,99 @@
|
|
| 1 |
// --- FONCTIONS UTILITAIRES GLOBALES ---
|
| 2 |
// Ces fonctions sont utilisées à plusieurs endroits et sont donc définies en premier.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
// Mapping relations → {couleur, tooltip}
|
| 4 |
const edgeInfos = {
|
| 5 |
"Fait partie de cette organisation": {
|
| 6 |
-
color: "#a05195",
|
| 7 |
-
|
|
|
|
| 8 |
},
|
| 9 |
"A publié": {
|
| 10 |
-
color: "#003f5c",
|
| 11 |
-
|
|
|
|
| 12 |
},
|
| 13 |
"A généré": {
|
| 14 |
-
color: "#f50f0f",
|
| 15 |
-
|
| 16 |
-
|
| 17 |
},
|
| 18 |
"finetune": {
|
| 19 |
-
color: "#cd6700",
|
| 20 |
-
|
| 21 |
-
|
| 22 |
},
|
| 23 |
"adapter": {
|
| 24 |
-
color: "#238a00",
|
| 25 |
-
|
| 26 |
-
|
| 27 |
},
|
| 28 |
"quantized": {
|
| 29 |
-
color: "#009194",
|
| 30 |
-
|
|
|
|
| 31 |
},
|
| 32 |
"merge": {
|
| 33 |
-
color: "#c29bc2",
|
| 34 |
-
|
|
|
|
| 35 |
},
|
| 36 |
"other": {
|
| 37 |
-
color: "#8f7340",
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
,
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
| 43 |
}
|
| 44 |
};
|
| 45 |
|
|
@@ -49,8 +103,8 @@ const edgeInfos = {
|
|
| 49 |
function buildEdgeLegend() {
|
| 50 |
const legendContainer = document.getElementById("legend-edges");
|
| 51 |
legendContainer.innerHTML = ""; // reset
|
| 52 |
-
|
| 53 |
-
Object.entries(edgeInfos).forEach(([relation, {color,
|
| 54 |
if (relation == "unknown") return;
|
| 55 |
const li = document.createElement("li");
|
| 56 |
li.className = "list-group-item d-flex align-items-center";
|
|
@@ -59,18 +113,19 @@ const edgeInfos = {
|
|
| 59 |
li.setAttribute("data-bs-toggle", "tooltip");
|
| 60 |
li.setAttribute("data-bs-placement", "top");
|
| 61 |
li.setAttribute("data-bs-html", "true");
|
| 62 |
-
|
| 63 |
-
|
|
|
|
| 64 |
const span = document.createElement("span");
|
| 65 |
span.className = "legend-color edge me-2";
|
| 66 |
span.style.backgroundColor = color;
|
| 67 |
-
|
| 68 |
li.appendChild(span);
|
| 69 |
-
li.appendChild(document.createTextNode(
|
| 70 |
-
|
| 71 |
legendContainer.appendChild(li);
|
| 72 |
});
|
| 73 |
-
|
| 74 |
// nécessaire pour activer les tooltips Bootstrap dynamiques
|
| 75 |
const tooltipTriggerList = [].slice.call(legendContainer.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
| 76 |
tooltipTriggerList.map(el => new bootstrap.Tooltip(el));
|
|
@@ -97,19 +152,30 @@ function showNodeInfo(attr) {
|
|
| 97 |
infoCard.dataset.nodeId = attr.id;
|
| 98 |
|
| 99 |
// --- ÉTAPE 2: Construction des détails ---
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
// ... (Le reste de la construction de infosHtml ne change pas) ...
|
| 103 |
if (["personne", "organisation", "Author"].includes(attr.dataCat)) {
|
| 104 |
-
if (attr.followers) infosHtml += `<p><strong>
|
| 105 |
} else if (["Modèle", "Model"].includes(attr.dataCat)) {
|
| 106 |
-
if (attr.downloads) infosHtml += `<p><strong>
|
| 107 |
-
if (attr.createdAt) infosHtml += `<p><strong>
|
| 108 |
-
if (attr.task) infosHtml += `<p><strong>
|
| 109 |
-
if (attr.dataset) infosHtml += `<p><strong>
|
| 110 |
} else if (attr.dataCat === "Dataset") {
|
| 111 |
-
if (attr.downloads) infosHtml += `<p><strong>
|
| 112 |
-
if (attr.createdAt_dataset) infosHtml += `<p><strong>
|
| 113 |
}
|
| 114 |
|
| 115 |
// --- ÉTAPE 3: Création du lien ---
|
|
@@ -126,7 +192,8 @@ function showNodeInfo(attr) {
|
|
| 126 |
link.target = '_blank';
|
| 127 |
link.rel = 'noopener noreferrer';
|
| 128 |
link.className = 'stretched-link';
|
| 129 |
-
|
|
|
|
| 130 |
cardBody.appendChild(link);
|
| 131 |
}
|
| 132 |
|
|
@@ -143,17 +210,18 @@ function showNodeInfo(attr) {
|
|
| 143 |
|
| 144 |
|
| 145 |
/**
|
| 146 |
-
* Formate une chaîne de date ISO
|
| 147 |
* @param {string} dateString - La date à formater.
|
| 148 |
-
* @returns {string} La date formatée ou "Date inconnue".
|
| 149 |
*/
|
| 150 |
function formatDateFr(dateString) {
|
| 151 |
-
if (!dateString || dateString === "inconnue") return "Date inconnue";
|
| 152 |
try {
|
|
|
|
| 153 |
const options = { year: 'numeric', month: 'long', day: 'numeric' };
|
| 154 |
-
return new Date(dateString).toLocaleDateString(
|
| 155 |
} catch (e) {
|
| 156 |
-
return "Date inconnue";
|
| 157 |
}
|
| 158 |
}
|
| 159 |
|
|
|
|
| 1 |
// --- FONCTIONS UTILITAIRES GLOBALES ---
|
| 2 |
// Ces fonctions sont utilisées à plusieurs endroits et sont donc définies en premier.
|
| 3 |
+
|
| 4 |
+
// ── I18N runtime (peut être surchargé par le template) ──
|
| 5 |
+
const I18N = window.__I18N_DATA || {};
|
| 6 |
+
const I18N_LANG = window.__I18N_LANG || 'fr';
|
| 7 |
+
|
| 8 |
+
function _(key) {
|
| 9 |
+
return I18N[key] !== undefined ? I18N[key] : key;
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
// ── Edge labels translations (Neo4j relation name → display name) ──
|
| 13 |
+
const EDGE_LABEL_MAP = {
|
| 14 |
+
fr: {
|
| 15 |
+
"POSTED": "A publié",
|
| 16 |
+
"IS_IN": "Fait partie de cette organisation",
|
| 17 |
+
"USED_IN": "A été utilisé dans ce modèle",
|
| 18 |
+
"A publié": "A publié",
|
| 19 |
+
"Fait partie de cette organisation": "Fait partie de cette organisation",
|
| 20 |
+
"A été utilisé dans ce modèle": "A été utilisé dans ce modèle",
|
| 21 |
+
"A généré": "A généré",
|
| 22 |
+
"finetune": "Finetune",
|
| 23 |
+
"adapter": "Adapter",
|
| 24 |
+
"quantized": "Quantized",
|
| 25 |
+
"merge": "Merge",
|
| 26 |
+
"other": "Autre",
|
| 27 |
+
"unknown": "Autre",
|
| 28 |
+
},
|
| 29 |
+
en: {
|
| 30 |
+
"POSTED": "Published",
|
| 31 |
+
"IS_IN": "Member of this organization",
|
| 32 |
+
"USED_IN": "Used in this model",
|
| 33 |
+
"A publié": "Published",
|
| 34 |
+
"Fait partie de cette organisation": "Member of this organization",
|
| 35 |
+
"A été utilisé dans ce modèle": "Used in this model",
|
| 36 |
+
"A généré": "Generated",
|
| 37 |
+
"finetune": "Fine-tuned",
|
| 38 |
+
"adapter": "Adapted",
|
| 39 |
+
"quantized": "Quantized",
|
| 40 |
+
"merge": "Merged",
|
| 41 |
+
"other": "Other",
|
| 42 |
+
"unknown": "Other",
|
| 43 |
+
},
|
| 44 |
+
};
|
| 45 |
+
|
| 46 |
+
function edgeDisplayName(relation) {
|
| 47 |
+
const map = EDGE_LABEL_MAP[I18N_LANG] || EDGE_LABEL_MAP.fr;
|
| 48 |
+
return map[relation] || relation;
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
// Mapping relations → {couleur, tooltip}
|
| 52 |
const edgeInfos = {
|
| 53 |
"Fait partie de cette organisation": {
|
| 54 |
+
color: "#a05195",
|
| 55 |
+
tooltip_fr: "Cette personne est membre de cette organisation",
|
| 56 |
+
tooltip_en: "This person is a member of this organization"
|
| 57 |
},
|
| 58 |
"A publié": {
|
| 59 |
+
color: "#003f5c",
|
| 60 |
+
tooltip_fr: "Un auteur (personne ou organisation) a publié un modèle",
|
| 61 |
+
tooltip_en: "An author (person or organization) published a model"
|
| 62 |
},
|
| 63 |
"A généré": {
|
| 64 |
+
color: "#f50f0f",
|
| 65 |
+
tooltip_fr: "Le modèle source a été téléchargé et modifié afin de créer le modèle cible (type de transformation inconnu)",
|
| 66 |
+
tooltip_en: "The source model was downloaded and modified to create the target model (transformation type unknown)"
|
| 67 |
},
|
| 68 |
"finetune": {
|
| 69 |
+
color: "#cd6700",
|
| 70 |
+
tooltip_fr: "Ajustement : le modèle source est ré-entraîné sur un jeu de données spécifique afin de pouvoir être performant pour une tâche précise.",
|
| 71 |
+
tooltip_en: "Fine-tuning: the source model is retrained on a specific dataset to perform well on a precise task."
|
| 72 |
},
|
| 73 |
"adapter": {
|
| 74 |
+
color: "#238a00",
|
| 75 |
+
tooltip_fr: "Adaptation : méthode d'ajustement qui peut être utilisée avec peu de ressources de calcul.",
|
| 76 |
+
tooltip_en: "Adaptation: a fine-tuning method that can be used with limited computing resources."
|
| 77 |
},
|
| 78 |
"quantized": {
|
| 79 |
+
color: "#009194",
|
| 80 |
+
tooltip_fr: "Quantisation : la précision des poids du modèle source est réduite afin de diminuer son empreinte en mémoire.",
|
| 81 |
+
tooltip_en: "Quantization: the precision of the source model's weights is reduced to decrease its memory footprint."
|
| 82 |
},
|
| 83 |
"merge": {
|
| 84 |
+
color: "#c29bc2",
|
| 85 |
+
tooltip_fr: "Fusion : méthode visant à mélanger des couches de différents modèles pour améliorer leur performance.",
|
| 86 |
+
tooltip_en: "Merge: a method aiming to mix layers of different models to improve their performance."
|
| 87 |
},
|
| 88 |
"other": {
|
| 89 |
+
color: "#8f7340",
|
| 90 |
+
tooltip_fr: "Autre type de relation",
|
| 91 |
+
tooltip_en: "Other type of relation"
|
| 92 |
+
},
|
| 93 |
+
"unknown": {
|
| 94 |
+
color: "#8f7340",
|
| 95 |
+
tooltip_fr: "Autre type de relation",
|
| 96 |
+
tooltip_en: "Other type of relation"
|
| 97 |
}
|
| 98 |
};
|
| 99 |
|
|
|
|
| 103 |
function buildEdgeLegend() {
|
| 104 |
const legendContainer = document.getElementById("legend-edges");
|
| 105 |
legendContainer.innerHTML = ""; // reset
|
| 106 |
+
|
| 107 |
+
Object.entries(edgeInfos).forEach(([relation, {color, tooltip_fr, tooltip_en}]) => {
|
| 108 |
if (relation == "unknown") return;
|
| 109 |
const li = document.createElement("li");
|
| 110 |
li.className = "list-group-item d-flex align-items-center";
|
|
|
|
| 113 |
li.setAttribute("data-bs-toggle", "tooltip");
|
| 114 |
li.setAttribute("data-bs-placement", "top");
|
| 115 |
li.setAttribute("data-bs-html", "true");
|
| 116 |
+
const tooltipKey = I18N_LANG === 'en' ? 'tooltip_en' : 'tooltip_fr';
|
| 117 |
+
li.setAttribute("title", tooltip_fr !== undefined ? (I18N_LANG === 'en' ? tooltip_en : tooltip_fr) : (tooltip_en || tooltip_fr || ""));
|
| 118 |
+
|
| 119 |
const span = document.createElement("span");
|
| 120 |
span.className = "legend-color edge me-2";
|
| 121 |
span.style.backgroundColor = color;
|
| 122 |
+
|
| 123 |
li.appendChild(span);
|
| 124 |
+
li.appendChild(document.createTextNode(edgeDisplayName(relation)));
|
| 125 |
+
|
| 126 |
legendContainer.appendChild(li);
|
| 127 |
});
|
| 128 |
+
|
| 129 |
// nécessaire pour activer les tooltips Bootstrap dynamiques
|
| 130 |
const tooltipTriggerList = [].slice.call(legendContainer.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
| 131 |
tooltipTriggerList.map(el => new bootstrap.Tooltip(el));
|
|
|
|
| 152 |
infoCard.dataset.nodeId = attr.id;
|
| 153 |
|
| 154 |
// --- ÉTAPE 2: Construction des détails ---
|
| 155 |
+
const labels = {
|
| 156 |
+
name: I18N['js.node_info.name'] || "Nom :",
|
| 157 |
+
type: I18N['js.node_info.type'] || "Type :",
|
| 158 |
+
followers: I18N['js.node_info.followers'] || "Abonnés :",
|
| 159 |
+
downloads: I18N['js.node_info.downloads'] || "Téléchargements :",
|
| 160 |
+
created: I18N['js.node_info.created'] || "Créé le :",
|
| 161 |
+
task: I18N['js.node_info.task'] || "Tâche :",
|
| 162 |
+
dataset: I18N['js.node_info.dataset'] || "Dataset utilisé :",
|
| 163 |
+
undefined: I18N['js.node_info.undefined'] || "Non défini",
|
| 164 |
+
};
|
| 165 |
+
|
| 166 |
+
let infosHtml = `<p><strong>${labels.name}</strong> ${attr.id || labels.undefined}</p><p><strong>${labels.type}</strong> ${attr.dataCat}</p>`;
|
| 167 |
|
| 168 |
// ... (Le reste de la construction de infosHtml ne change pas) ...
|
| 169 |
if (["personne", "organisation", "Author"].includes(attr.dataCat)) {
|
| 170 |
+
if (attr.followers) infosHtml += `<p><strong>${labels.followers}</strong> ${formatNumberShort(attr.followers)}</p>`;
|
| 171 |
} else if (["Modèle", "Model"].includes(attr.dataCat)) {
|
| 172 |
+
if (attr.downloads) infosHtml += `<p><strong>${labels.downloads}</strong> ${formatNumberShort(attr.downloads)}</p>`;
|
| 173 |
+
if (attr.createdAt) infosHtml += `<p><strong>${labels.created}</strong> ${formatDateFr(attr.createdAt)}</p>`;
|
| 174 |
+
if (attr.task) infosHtml += `<p><strong>${labels.task}</strong> ${attr.task}</p>`;
|
| 175 |
+
if (attr.dataset) infosHtml += `<p><strong>${labels.dataset}</strong> ${attr.dataset}</p>`;
|
| 176 |
} else if (attr.dataCat === "Dataset") {
|
| 177 |
+
if (attr.downloads) infosHtml += `<p><strong>${labels.downloads}</strong> ${formatNumberShort(attr.downloads)}</p>`;
|
| 178 |
+
if (attr.createdAt_dataset) infosHtml += `<p><strong>${labels.created}</strong> ${formatDateFr(attr.createdAt_dataset)}</p>`;
|
| 179 |
}
|
| 180 |
|
| 181 |
// --- ÉTAPE 3: Création du lien ---
|
|
|
|
| 192 |
link.target = '_blank';
|
| 193 |
link.rel = 'noopener noreferrer';
|
| 194 |
link.className = 'stretched-link';
|
| 195 |
+
const seeLabel = (I18N['js.node_info.see_on_hf'] || "Voir {name} sur Hugging Face").replace('{name}', attr.id);
|
| 196 |
+
link.setAttribute('aria-label', seeLabel);
|
| 197 |
cardBody.appendChild(link);
|
| 198 |
}
|
| 199 |
|
|
|
|
| 210 |
|
| 211 |
|
| 212 |
/**
|
| 213 |
+
* Formate une chaîne de date ISO.
|
| 214 |
* @param {string} dateString - La date à formater.
|
| 215 |
+
* @returns {string} La date formatée ou "Date inconnue" / "Unknown date".
|
| 216 |
*/
|
| 217 |
function formatDateFr(dateString) {
|
| 218 |
+
if (!dateString || dateString === "inconnue") return I18N['js.node_info.unknown_date'] || "Date inconnue";
|
| 219 |
try {
|
| 220 |
+
const locale = I18N_LANG === 'en' ? 'en-GB' : 'fr-FR';
|
| 221 |
const options = { year: 'numeric', month: 'long', day: 'numeric' };
|
| 222 |
+
return new Date(dateString).toLocaleDateString(locale, options);
|
| 223 |
} catch (e) {
|
| 224 |
+
return I18N['js.node_info.unknown_date'] || "Date inconnue";
|
| 225 |
}
|
| 226 |
}
|
| 227 |
|
application_neo4j/templates/expert.html
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
-
<html lang="
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>
|
| 7 |
|
| 8 |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
| 9 |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
|
@@ -21,17 +21,21 @@
|
|
| 21 |
<header class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
|
| 22 |
<div class="container">
|
| 23 |
<a class="navbar-brand" href="/">
|
| 24 |
-
<span class="fw-bold">
|
| 25 |
-
<small class="d-block text-muted">
|
| 26 |
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
</div>
|
| 28 |
</header>
|
| 29 |
|
| 30 |
<main class="container mt-4 flex-grow-1">
|
| 31 |
<div class="row">
|
| 32 |
<div class="col-12">
|
| 33 |
-
<h1 class="display-5">
|
| 34 |
-
<p class="lead">
|
| 35 |
</div>
|
| 36 |
</div>
|
| 37 |
<div class="row g-3 mb-4">
|
|
@@ -42,44 +46,44 @@
|
|
| 42 |
<div class="row g-3 align-items-end">
|
| 43 |
<!-- Champ de recherche -->
|
| 44 |
<div class="col-12 col-md-5">
|
| 45 |
-
<label for="search-input" class="form-label fw-bold">
|
| 46 |
<div class="position-relative">
|
| 47 |
<input type="text" name="name" id="search-input" class="form-control"
|
| 48 |
-
placeholder="
|
| 49 |
value="{{ request.form.name or '' }}" required autocomplete="off" />
|
| 50 |
<ul id="suggestions-list" class="list-group position-absolute w-100" style="display: none; z-index: 1000;"></ul>
|
| 51 |
</div>
|
| 52 |
</div>
|
| 53 |
<!-- Filtres -->
|
| 54 |
<div class="col-12 col-md-3">
|
| 55 |
-
<label class="form-label fw-bold">
|
| 56 |
<div class="d-flex gap-3">
|
| 57 |
<div class="form-check">
|
| 58 |
<input class="form-check-input" type="checkbox" name="filters" value="Model" id="filter-model"
|
| 59 |
{% if 'Model' in search.filters %}checked{% endif %}>
|
| 60 |
-
<label class="form-check-label" for="filter-model">
|
| 61 |
</div>
|
| 62 |
<div class="form-check">
|
| 63 |
<input class="form-check-input" type="checkbox" name="filters" value="Dataset" id="filter-dataset"
|
| 64 |
{% if 'Dataset' in search.filters %}checked{% endif %}>
|
| 65 |
-
<label class="form-check-label" for="filter-dataset">
|
| 66 |
</div>
|
| 67 |
<div class="form-check">
|
| 68 |
<input class="form-check-input" type="checkbox" name="filters" value="Author" id="filter-author" {% if 'Author' in search.filters %}checked{% endif %}>
|
| 69 |
-
<label class="form-check-label" for="filter-author">
|
| 70 |
</div>
|
| 71 |
</div>
|
| 72 |
</div>
|
| 73 |
<!-- Profondeur de recherche -->
|
| 74 |
<div class="col-12 col-md-4">
|
| 75 |
-
<label class="form-label fw-bold">
|
| 76 |
<div class="d-flex align-items-center gap-3">
|
| 77 |
<div class="form-check form-switch">
|
| 78 |
<input class="form-check-input" type="checkbox" id="depth-unlimited" name="depth_unlimited" checked>
|
| 79 |
-
<label class="form-check-label" for="depth-unlimited">
|
| 80 |
</div>
|
| 81 |
<div class="input-group input-group-sm">
|
| 82 |
-
<span class="input-group-text">
|
| 83 |
<input class="form-control" type="number" name="depth" id="depth"
|
| 84 |
value="{{ request.form.depth }}" min="1" max="5" disabled>
|
| 85 |
</div>
|
|
@@ -89,7 +93,7 @@
|
|
| 89 |
<div class="row mt-4">
|
| 90 |
<div class="col-12">
|
| 91 |
<button type="submit" name="submit" value="findnode_expert" class="btn btn-primary w-100">
|
| 92 |
-
<i class="bi bi-search me-1"></i>
|
| 93 |
</button>
|
| 94 |
</div>
|
| 95 |
</div>
|
|
@@ -97,7 +101,7 @@
|
|
| 97 |
</div>
|
| 98 |
</div>
|
| 99 |
|
| 100 |
-
|
| 101 |
{% if message %}
|
| 102 |
<div class="alert alert-info mt-3"><p class="mb-0">{{ message }}</p></div>
|
| 103 |
{% endif %}
|
|
@@ -106,7 +110,7 @@
|
|
| 106 |
{% if graph_data and graph_data.nodes %}
|
| 107 |
<div id="genealogy-graph-section" class="mt-4">
|
| 108 |
<div class="row g-4">
|
| 109 |
-
<div class="card-header"><h5 class="card-title mb-0">
|
| 110 |
<!-- Colonne de gauche pour le graphe et la carte d'info -->
|
| 111 |
<div class="col-12 col-lg-8">
|
| 112 |
<div class="card position-relative">
|
|
@@ -118,7 +122,7 @@
|
|
| 118 |
class="card p-2 position-absolute top-0 start-0 shadow-sm"
|
| 119 |
style="width: 220px; background-color: rgba(255,255,255,0.95);">
|
| 120 |
<div class="d-flex justify-content-between align-items-center mb-2">
|
| 121 |
-
<strong>
|
| 122 |
<button class="btn btn-sm btn-light p-0"
|
| 123 |
type="button"
|
| 124 |
data-bs-toggle="collapse"
|
|
@@ -131,27 +135,27 @@
|
|
| 131 |
<!-- Contenu de la légende -->
|
| 132 |
<div id="legend-content" class="collapse show">
|
| 133 |
<div class="card mb-2">
|
| 134 |
-
<div class="card-header py-1"><h6 class="card-title mb-0">
|
| 135 |
<div class="card-body p-2">
|
| 136 |
<ul id="legend-nodes" class="list-group list-group-flush small">
|
| 137 |
<li class="list-group-item d-flex align-items-center py-1">
|
| 138 |
-
<span class="legend-color me-2" style="background-color: hsl(45, 65%, 52%);"></span>
|
| 139 |
</li>
|
| 140 |
<li class="list-group-item d-flex align-items-center py-1">
|
| 141 |
-
<span class="legend-color me-2" style="background-color: #007bff;"></span>
|
| 142 |
</li>
|
| 143 |
<li class="list-group-item d-flex align-items-center py-1">
|
| 144 |
-
<span class="legend-color me-2" style="background-color: #092d53;"></span>
|
| 145 |
</li>
|
| 146 |
<li class="list-group-item d-flex align-items-center py-1">
|
| 147 |
-
<span class="legend-color me-2" style="background-color:#7D7D7D;"></span>
|
| 148 |
</li>
|
| 149 |
</ul>
|
| 150 |
</div>
|
| 151 |
</div>
|
| 152 |
|
| 153 |
<div class="card">
|
| 154 |
-
<div class="card-header py-1"><h6 class="card-title mb-0">
|
| 155 |
<div class="card-body p-2">
|
| 156 |
<ul id="legend-edges" class="list-group list-group-flush small">
|
| 157 |
</ul>
|
|
@@ -163,7 +167,7 @@
|
|
| 163 |
</div>
|
| 164 |
<div id="node-info-card" class="card mt-3 card-interactive" style="display: none;">
|
| 165 |
<div class="card-body">
|
| 166 |
-
<h5 class="card-title">
|
| 167 |
<div id="node-details" class="mt-2"></div>
|
| 168 |
</div>
|
| 169 |
</div>
|
|
@@ -171,27 +175,27 @@
|
|
| 171 |
<div class="col-12 col-lg-4">
|
| 172 |
<!-- CORRECTION : Chaque section est dans sa propre carte -->
|
| 173 |
<div class="card mb-3">
|
| 174 |
-
<div class="card-header"><h5 class="card-title mb-0">
|
| 175 |
<div class="card-body" id="graph-filters-container">
|
| 176 |
-
<h6>
|
| 177 |
<div class="form-check form-switch">
|
| 178 |
<input class="form-check-input" type="checkbox" id="filter-node-model" value="Modèle" checked>
|
| 179 |
-
<label class="form-check-label" for="filter-node-model">
|
| 180 |
</div>
|
| 181 |
<div class="form-check form-switch">
|
| 182 |
<input class="form-check-input" type="checkbox" id="filter-node-person" value="personne" checked>
|
| 183 |
-
<label class="form-check-label" for="filter-node-person">
|
| 184 |
</div>
|
| 185 |
<div class="form-check form-switch">
|
| 186 |
<input class="form-check-input" type="checkbox" id="filter-node-org" value="organisation" checked>
|
| 187 |
-
<label class="form-check-label" for="filter-node-org">
|
| 188 |
</div>
|
| 189 |
<hr>
|
| 190 |
-
<h6>
|
| 191 |
<div id="edge-filters-container"></div>
|
| 192 |
</div>
|
| 193 |
</div>
|
| 194 |
-
<button id="export-btn" class="btn-modern">
|
| 195 |
</div>
|
| 196 |
</div>
|
| 197 |
<hr class="my-5">
|
|
@@ -199,23 +203,23 @@
|
|
| 199 |
<!-- CORRECTION : Tableau des modèles présents dans le graphe -->
|
| 200 |
<div class="row mt-4">
|
| 201 |
<div class="col-12">
|
| 202 |
-
<h4 class="h4">
|
| 203 |
<div class="table-responsive">
|
| 204 |
<table id="graph-models-table" class="table table-bordered table-striped table-hover" style="width:100%">
|
| 205 |
<thead>
|
| 206 |
<tr>
|
| 207 |
-
<th scope="col">
|
| 208 |
-
<th scope="col">
|
| 209 |
-
<th scope="col">
|
| 210 |
-
<th scope="col">
|
| 211 |
-
<th scope="col">
|
| 212 |
-
<th scope="col">
|
| 213 |
-
<th scope="col">
|
| 214 |
-
<th scope="col">
|
| 215 |
-
<th scope="col">
|
| 216 |
-
<th scope="col">
|
| 217 |
-
<th scope="col">
|
| 218 |
-
<th scope="col">
|
| 219 |
</tr>
|
| 220 |
</thead>
|
| 221 |
<tbody>
|
|
@@ -231,7 +235,7 @@
|
|
| 231 |
|
| 232 |
<footer class="bg-dark text-white text-center p-4 mt-auto">
|
| 233 |
<div class="container">
|
| 234 |
-
<p class="mb-0">
|
| 235 |
</div>
|
| 236 |
</footer>
|
| 237 |
|
|
@@ -242,6 +246,10 @@
|
|
| 242 |
<script src="https://unpkg.com/graphology@0.25.1/dist/graphology.umd.min.js"></script>
|
| 243 |
<script src="https://cdn.jsdelivr.net/npm/graphology-library/dist/graphology-library.min.js"></script>
|
| 244 |
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 245 |
<script src="{{ url_for('static', filename='js/script_expert.js') }}"></script>
|
| 246 |
<script>
|
| 247 |
document.addEventListener('DOMContentLoaded', function () {
|
|
@@ -250,4 +258,4 @@
|
|
| 250 |
});
|
| 251 |
</script>
|
| 252 |
</body>
|
| 253 |
-
</html>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
+
<html lang="{{ current_lang }}">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>{{ t('site.title_expert') }}</title>
|
| 7 |
|
| 8 |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
| 9 |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.css">
|
|
|
|
| 21 |
<header class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
|
| 22 |
<div class="container">
|
| 23 |
<a class="navbar-brand" href="/">
|
| 24 |
+
<span class="fw-bold">{{ t('site.nav_brand_model_expert') }}</span>
|
| 25 |
+
<small class="d-block text-muted">{{ t('site.nav_subtitle') }}</small>
|
| 26 |
</a>
|
| 27 |
+
<div class="d-flex gap-2">
|
| 28 |
+
<a href="{{ url_for('set_language_route', lang='fr') }}" class="btn btn-sm {{ 'btn-primary' if current_lang == 'fr' else 'btn-outline-primary' }}">{{ t('lang.fr') }}</a>
|
| 29 |
+
<a href="{{ url_for('set_language_route', lang='en') }}" class="btn btn-sm {{ 'btn-primary' if current_lang == 'en' else 'btn-outline-primary' }}">{{ t('lang.en') }}</a>
|
| 30 |
+
</div>
|
| 31 |
</div>
|
| 32 |
</header>
|
| 33 |
|
| 34 |
<main class="container mt-4 flex-grow-1">
|
| 35 |
<div class="row">
|
| 36 |
<div class="col-12">
|
| 37 |
+
<h1 class="display-5">{{ t('expert.page_title') }}</h1>
|
| 38 |
+
<p class="lead">{{ t('expert.page_lead') }}</p>
|
| 39 |
</div>
|
| 40 |
</div>
|
| 41 |
<div class="row g-3 mb-4">
|
|
|
|
| 46 |
<div class="row g-3 align-items-end">
|
| 47 |
<!-- Champ de recherche -->
|
| 48 |
<div class="col-12 col-md-5">
|
| 49 |
+
<label for="search-input" class="form-label fw-bold">{{ t('search.label_name') }}</label>
|
| 50 |
<div class="position-relative">
|
| 51 |
<input type="text" name="name" id="search-input" class="form-control"
|
| 52 |
+
placeholder="{{ t('expert.placeholder') }}"
|
| 53 |
value="{{ request.form.name or '' }}" required autocomplete="off" />
|
| 54 |
<ul id="suggestions-list" class="list-group position-absolute w-100" style="display: none; z-index: 1000;"></ul>
|
| 55 |
</div>
|
| 56 |
</div>
|
| 57 |
<!-- Filtres -->
|
| 58 |
<div class="col-12 col-md-3">
|
| 59 |
+
<label class="form-label fw-bold">{{ t('search.label_filters') }}</label>
|
| 60 |
<div class="d-flex gap-3">
|
| 61 |
<div class="form-check">
|
| 62 |
<input class="form-check-input" type="checkbox" name="filters" value="Model" id="filter-model"
|
| 63 |
{% if 'Model' in search.filters %}checked{% endif %}>
|
| 64 |
+
<label class="form-check-label" for="filter-model">{{ t('search.filter_model') }}</label>
|
| 65 |
</div>
|
| 66 |
<div class="form-check">
|
| 67 |
<input class="form-check-input" type="checkbox" name="filters" value="Dataset" id="filter-dataset"
|
| 68 |
{% if 'Dataset' in search.filters %}checked{% endif %}>
|
| 69 |
+
<label class="form-check-label" for="filter-dataset">{{ t('search.filter_dataset') }}</label>
|
| 70 |
</div>
|
| 71 |
<div class="form-check">
|
| 72 |
<input class="form-check-input" type="checkbox" name="filters" value="Author" id="filter-author" {% if 'Author' in search.filters %}checked{% endif %}>
|
| 73 |
+
<label class="form-check-label" for="filter-author">{{ t('expert.filter_author') }}</label>
|
| 74 |
</div>
|
| 75 |
</div>
|
| 76 |
</div>
|
| 77 |
<!-- Profondeur de recherche -->
|
| 78 |
<div class="col-12 col-md-4">
|
| 79 |
+
<label class="form-label fw-bold">{{ t('search.label_depth') }}</label>
|
| 80 |
<div class="d-flex align-items-center gap-3">
|
| 81 |
<div class="form-check form-switch">
|
| 82 |
<input class="form-check-input" type="checkbox" id="depth-unlimited" name="depth_unlimited" checked>
|
| 83 |
+
<label class="form-check-label" for="depth-unlimited">{{ t('search.depth_unlimited') }}</label>
|
| 84 |
</div>
|
| 85 |
<div class="input-group input-group-sm">
|
| 86 |
+
<span class="input-group-text">{{ t('search.depth_limited') }}</span>
|
| 87 |
<input class="form-control" type="number" name="depth" id="depth"
|
| 88 |
value="{{ request.form.depth }}" min="1" max="5" disabled>
|
| 89 |
</div>
|
|
|
|
| 93 |
<div class="row mt-4">
|
| 94 |
<div class="col-12">
|
| 95 |
<button type="submit" name="submit" value="findnode_expert" class="btn btn-primary w-100">
|
| 96 |
+
<i class="bi bi-search me-1"></i>{{ t('search.btn_search_simple') }}
|
| 97 |
</button>
|
| 98 |
</div>
|
| 99 |
</div>
|
|
|
|
| 101 |
</div>
|
| 102 |
</div>
|
| 103 |
|
| 104 |
+
|
| 105 |
{% if message %}
|
| 106 |
<div class="alert alert-info mt-3"><p class="mb-0">{{ message }}</p></div>
|
| 107 |
{% endif %}
|
|
|
|
| 110 |
{% if graph_data and graph_data.nodes %}
|
| 111 |
<div id="genealogy-graph-section" class="mt-4">
|
| 112 |
<div class="row g-4">
|
| 113 |
+
<div class="card-header"><h5 class="card-title mb-0">{{ t('expert.connected_component') }}</h5></div>
|
| 114 |
<!-- Colonne de gauche pour le graphe et la carte d'info -->
|
| 115 |
<div class="col-12 col-lg-8">
|
| 116 |
<div class="card position-relative">
|
|
|
|
| 122 |
class="card p-2 position-absolute top-0 start-0 shadow-sm"
|
| 123 |
style="width: 220px; background-color: rgba(255,255,255,0.95);">
|
| 124 |
<div class="d-flex justify-content-between align-items-center mb-2">
|
| 125 |
+
<strong>{{ t('expert.legend_title') }}</strong>
|
| 126 |
<button class="btn btn-sm btn-light p-0"
|
| 127 |
type="button"
|
| 128 |
data-bs-toggle="collapse"
|
|
|
|
| 135 |
<!-- Contenu de la légende -->
|
| 136 |
<div id="legend-content" class="collapse show">
|
| 137 |
<div class="card mb-2">
|
| 138 |
+
<div class="card-header py-1"><h6 class="card-title mb-0">{{ t('expert.legend_nodes_title') }}</h6></div>
|
| 139 |
<div class="card-body p-2">
|
| 140 |
<ul id="legend-nodes" class="list-group list-group-flush small">
|
| 141 |
<li class="list-group-item d-flex align-items-center py-1">
|
| 142 |
+
<span class="legend-color me-2" style="background-color: hsl(45, 65%, 52%);"></span>{{ t('expert.legend_dataset') }}
|
| 143 |
</li>
|
| 144 |
<li class="list-group-item d-flex align-items-center py-1">
|
| 145 |
+
<span class="legend-color me-2" style="background-color: #007bff;"></span>{{ t('expert.legend_person') }}
|
| 146 |
</li>
|
| 147 |
<li class="list-group-item d-flex align-items-center py-1">
|
| 148 |
+
<span class="legend-color me-2" style="background-color: #092d53;"></span>{{ t('expert.legend_org') }}
|
| 149 |
</li>
|
| 150 |
<li class="list-group-item d-flex align-items-center py-1">
|
| 151 |
+
<span class="legend-color me-2" style="background-color:#7D7D7D;"></span>{{ t('expert.legend_model') }}
|
| 152 |
</li>
|
| 153 |
</ul>
|
| 154 |
</div>
|
| 155 |
</div>
|
| 156 |
|
| 157 |
<div class="card">
|
| 158 |
+
<div class="card-header py-1"><h6 class="card-title mb-0">{{ t('expert.legend_edges_title') }}</h6></div>
|
| 159 |
<div class="card-body p-2">
|
| 160 |
<ul id="legend-edges" class="list-group list-group-flush small">
|
| 161 |
</ul>
|
|
|
|
| 167 |
</div>
|
| 168 |
<div id="node-info-card" class="card mt-3 card-interactive" style="display: none;">
|
| 169 |
<div class="card-body">
|
| 170 |
+
<h5 class="card-title">{{ t('search.node_info_title') }}</h5>
|
| 171 |
<div id="node-details" class="mt-2"></div>
|
| 172 |
</div>
|
| 173 |
</div>
|
|
|
|
| 175 |
<div class="col-12 col-lg-4">
|
| 176 |
<!-- CORRECTION : Chaque section est dans sa propre carte -->
|
| 177 |
<div class="card mb-3">
|
| 178 |
+
<div class="card-header"><h5 class="card-title mb-0">{{ t('expert.graph_filters') }}</h5></div>
|
| 179 |
<div class="card-body" id="graph-filters-container">
|
| 180 |
+
<h6>{{ t('expert.node_types') }}</h6>
|
| 181 |
<div class="form-check form-switch">
|
| 182 |
<input class="form-check-input" type="checkbox" id="filter-node-model" value="Modèle" checked>
|
| 183 |
+
<label class="form-check-label" for="filter-node-model">{{ t('expert.model_label') }}</label>
|
| 184 |
</div>
|
| 185 |
<div class="form-check form-switch">
|
| 186 |
<input class="form-check-input" type="checkbox" id="filter-node-person" value="personne" checked>
|
| 187 |
+
<label class="form-check-label" for="filter-node-person">{{ t('expert.person_label') }}</label>
|
| 188 |
</div>
|
| 189 |
<div class="form-check form-switch">
|
| 190 |
<input class="form-check-input" type="checkbox" id="filter-node-org" value="organisation" checked>
|
| 191 |
+
<label class="form-check-label" for="filter-node-org">{{ t('expert.org_label') }}</label>
|
| 192 |
</div>
|
| 193 |
<hr>
|
| 194 |
+
<h6>{{ t('expert.rel_types') }}</h6>
|
| 195 |
<div id="edge-filters-container"></div>
|
| 196 |
</div>
|
| 197 |
</div>
|
| 198 |
+
<button id="export-btn" class="btn-modern">{{ t('expert.btn_export') }}</button>
|
| 199 |
</div>
|
| 200 |
</div>
|
| 201 |
<hr class="my-5">
|
|
|
|
| 203 |
<!-- CORRECTION : Tableau des modèles présents dans le graphe -->
|
| 204 |
<div class="row mt-4">
|
| 205 |
<div class="col-12">
|
| 206 |
+
<h4 class="h4">{{ t('expert.models_in_graph') }}</h4>
|
| 207 |
<div class="table-responsive">
|
| 208 |
<table id="graph-models-table" class="table table-bordered table-striped table-hover" style="width:100%">
|
| 209 |
<thead>
|
| 210 |
<tr>
|
| 211 |
+
<th scope="col">{{ t('search.table_model') }}</th>
|
| 212 |
+
<th scope="col">{{ t('search.table_author') }}</th>
|
| 213 |
+
<th scope="col">{{ t('search.table_downloads') }}</th>
|
| 214 |
+
<th scope="col">{{ t('search.table_task') }}</th>
|
| 215 |
+
<th scope="col">{{ t('search.table_likes') }}</th>
|
| 216 |
+
<th scope="col">{{ t('search.table_date') }}</th>
|
| 217 |
+
<th scope="col">{{ t('search.table_dataset') }}</th>
|
| 218 |
+
<th scope="col">{{ t('search.table_license') }}</th>
|
| 219 |
+
<th scope="col">{{ t('search.table_distance') }}</th>
|
| 220 |
+
<th scope="col">{{ t('search.table_ascendants') }}</th>
|
| 221 |
+
<th scope="col">{{ t('search.table_descendants') }}</th>
|
| 222 |
+
<th scope="col">{{ t('search.table_citations') }}</th>
|
| 223 |
</tr>
|
| 224 |
</thead>
|
| 225 |
<tbody>
|
|
|
|
| 235 |
|
| 236 |
<footer class="bg-dark text-white text-center p-4 mt-auto">
|
| 237 |
<div class="container">
|
| 238 |
+
<p class="mb-0">{{ t('site.footer_expert') }}</p>
|
| 239 |
</div>
|
| 240 |
</footer>
|
| 241 |
|
|
|
|
| 246 |
<script src="https://unpkg.com/graphology@0.25.1/dist/graphology.umd.min.js"></script>
|
| 247 |
<script src="https://cdn.jsdelivr.net/npm/graphology-library/dist/graphology-library.min.js"></script>
|
| 248 |
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
|
| 249 |
+
<script>
|
| 250 |
+
window.__I18N_DATA = {{ js_i18n_data | tojson }};
|
| 251 |
+
window.__I18N_LANG = "{{ current_lang }}";
|
| 252 |
+
</script>
|
| 253 |
<script src="{{ url_for('static', filename='js/script_expert.js') }}"></script>
|
| 254 |
<script>
|
| 255 |
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
| 258 |
});
|
| 259 |
</script>
|
| 260 |
</body>
|
| 261 |
+
</html>
|
application_neo4j/templates/index.html
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
-
<html lang="
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>
|
| 7 |
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
| 8 |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
| 9 |
|
|
@@ -95,59 +95,69 @@
|
|
| 95 |
margin-top: 20px;
|
| 96 |
}
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
|
| 99 |
</style>
|
| 100 |
</head>
|
| 101 |
|
| 102 |
<body>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
<div class="welcome-container">
|
| 104 |
-
<h1>
|
| 105 |
-
<a href="{{ url_for('static', filename='notice/notice.pdf') }}" class="btn-modern" target="_blank">
|
| 106 |
</div>
|
| 107 |
<div class="container content-section">
|
| 108 |
<div class="row g-4">
|
| 109 |
<div class="col-md-6">
|
| 110 |
<div class="content-card">
|
| 111 |
-
<h4>
|
| 112 |
<p>
|
| 113 |
-
|
| 114 |
-
• À partir de quel(s) modèle(s) il a été dérivé (<strong>ascendants</strong>) <br>
|
| 115 |
-
• Quelles <strong>modifications</strong> ont été apportées (ajustement,quantisation, adaptation, fusion…)<br>
|
| 116 |
-
• Quels nouveaux modèles ont été produits à partir de celui-ci (<strong>descendants</strong> )<br><br>
|
| 117 |
-
Cela peut être représenté sous forme d'<strong>arbre généalogique</strong>, retraçant l’ensemble des étapes qui ont conduit à un modèle open source donné.
|
| 118 |
</p>
|
| 119 |
</div>
|
| 120 |
</div>
|
| 121 |
<div class="col-md-6">
|
| 122 |
<div class="content-card">
|
| 123 |
-
<h4>
|
| 124 |
<p>
|
| 125 |
-
|
| 126 |
-
• À partir de quelle(s) <strong>source(s) de données</strong> le dataset a été construit <br>
|
| 127 |
-
• Quels modèles ont été <strong>entrainés</strong> sur ce jeu de données<br>
|
| 128 |
-
• A quelles(s) organisation(s) appartient l'auteur qui a publié ce jeu de données<br> <br>
|
| 129 |
-
|
| 130 |
-
La généalogie retrace toutes les étapes et ramifications de l'utilisation d'un dataset donné.
|
| 131 |
</p>
|
| 132 |
</div>
|
| 133 |
</div>
|
| 134 |
<div class="col-md-6">
|
| 135 |
<div class="content-card">
|
| 136 |
-
<h4>
|
| 137 |
<p>
|
| 138 |
-
|
| 139 |
-
• Protéger la <strong>vie privée</strong> des personnes dont les données pourraient être mémorisées par un modèle ou contenues dans un dataset <br>
|
| 140 |
-
• Assurer la <strong>traçabilité</strong> sur les chaînes de modification des modèles<br> <br>
|
| 141 |
|
| 142 |
-
<h6>
|
| 143 |
<span class="highlight">
|
| 144 |
-
|
| 145 |
-
Sa requête est : "Qui est Alice Dupont ?", le chatbot renvoie son adresse et son numéro de téléphone.<br><br>
|
| 146 |
-
• Alice veut connaître l'<strong>impact de la mémorisation potentielle de ses données personnelles</strong>, ainsi que l'origine de ce modèle.<br><br>
|
| 147 |
-
Avec cet outil, Alice a accès :<br>
|
| 148 |
-
• aux modèles issus du modèle interrogé et publiés sur HuggingFace<br>
|
| 149 |
-
• aux modèles parents de ce modèle.<br><br>
|
| 150 |
-
(A condition que les liens entre modèles soient déclarés par les utiliateurs sur HuggingFace)
|
| 151 |
</span>
|
| 152 |
</p>
|
| 153 |
</div>
|
|
@@ -155,40 +165,40 @@
|
|
| 155 |
|
| 156 |
<div class="col-md-6">
|
| 157 |
<div class="content-card">
|
| 158 |
-
<h4>
|
| 159 |
<div class="buttons-container">
|
| 160 |
<!-- Ce bouton mène à la page de recherche -->
|
| 161 |
-
<a href="{{ url_for('findnode', filter='Model') }}" class="btn-modern">
|
| 162 |
-
<a href="{{ url_for('findnode', filter='Dataset') }}" class="btn-modern">
|
| 163 |
-
<a href="{{ url_for('findnode') }}" class="btn-modern">
|
| 164 |
-
<h4>
|
| 165 |
<div class="buttons-container">
|
| 166 |
<!-- Ce bouton mène à la page de recherche -->
|
| 167 |
-
<a href="{{ url_for('findnode_expert', filter='Model') }}" class="btn-modern">
|
| 168 |
</div>
|
| 169 |
</div>
|
| 170 |
-
<p><br/> <br/> <br/>
|
| 171 |
</div>
|
| 172 |
</div>
|
| 173 |
<div class="welcome-container">
|
| 174 |
-
<h2>
|
| 175 |
|
| 176 |
-
<p>
|
| 177 |
|
| 178 |
-
<p>
|
| 179 |
|
| 180 |
-
<p>
|
| 181 |
|
| 182 |
-
<p>
|
| 183 |
|
| 184 |
-
<p>
|
| 185 |
|
| 186 |
-
<p>
|
| 187 |
|
| 188 |
-
<p>
|
| 189 |
|
| 190 |
-
<p>
|
| 191 |
|
| 192 |
</div>
|
| 193 |
</body>
|
| 194 |
-
</html>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
+
<html lang="{{ current_lang }}">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8">
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>{{ t('site.title_home') }}</title>
|
| 7 |
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
|
| 8 |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
| 9 |
|
|
|
|
| 95 |
margin-top: 20px;
|
| 96 |
}
|
| 97 |
|
| 98 |
+
/* Language selector */
|
| 99 |
+
.lang-switch {
|
| 100 |
+
position: absolute;
|
| 101 |
+
top: 15px;
|
| 102 |
+
right: 20px;
|
| 103 |
+
display: flex;
|
| 104 |
+
gap: 5px;
|
| 105 |
+
}
|
| 106 |
+
.lang-switch a {
|
| 107 |
+
padding: 4px 10px;
|
| 108 |
+
border-radius: 4px;
|
| 109 |
+
text-decoration: none;
|
| 110 |
+
font-size: 0.85rem;
|
| 111 |
+
font-weight: bold;
|
| 112 |
+
color: #555;
|
| 113 |
+
background: #eee;
|
| 114 |
+
}
|
| 115 |
+
.lang-switch a.active {
|
| 116 |
+
background: #3498db;
|
| 117 |
+
color: #fff;
|
| 118 |
+
}
|
| 119 |
|
| 120 |
</style>
|
| 121 |
</head>
|
| 122 |
|
| 123 |
<body>
|
| 124 |
+
<!-- Language selector -->
|
| 125 |
+
<div class="lang-switch">
|
| 126 |
+
<a href="{{ url_for('set_language_route', lang='fr') }}" class="{{ 'active' if current_lang == 'fr' else '' }}">{{ t('lang.fr') }}</a>
|
| 127 |
+
<a href="{{ url_for('set_language_route', lang='en') }}" class="{{ 'active' if current_lang == 'en' else '' }}">{{ t('lang.en') }}</a>
|
| 128 |
+
</div>
|
| 129 |
+
|
| 130 |
<div class="welcome-container">
|
| 131 |
+
<h1>{{ t('home.welcome_title') }}</h1>
|
| 132 |
+
<a href="{{ url_for('static', filename='notice/notice.pdf') }}" class="btn-modern" target="_blank">{{ t('home.more_info') }}</a>
|
| 133 |
</div>
|
| 134 |
<div class="container content-section">
|
| 135 |
<div class="row g-4">
|
| 136 |
<div class="col-md-6">
|
| 137 |
<div class="content-card">
|
| 138 |
+
<h4>{{ t('home.what_is_model') }}</h4>
|
| 139 |
<p>
|
| 140 |
+
{{ t('home.what_is_model_text') | safe }}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
</p>
|
| 142 |
</div>
|
| 143 |
</div>
|
| 144 |
<div class="col-md-6">
|
| 145 |
<div class="content-card">
|
| 146 |
+
<h4>{{ t('home.what_is_dataset') }}</h4>
|
| 147 |
<p>
|
| 148 |
+
{{ t('home.what_is_dataset_text') | safe }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
</p>
|
| 150 |
</div>
|
| 151 |
</div>
|
| 152 |
<div class="col-md-6">
|
| 153 |
<div class="content-card">
|
| 154 |
+
<h4>{{ t('home.purpose') }}</h4>
|
| 155 |
<p>
|
| 156 |
+
{{ t('home.purpose_text') | safe }}
|
|
|
|
|
|
|
| 157 |
|
| 158 |
+
<h6>{{ t('home.alice_title') }}</h6>
|
| 159 |
<span class="highlight">
|
| 160 |
+
{{ t('home.alice_text') | safe }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 161 |
</span>
|
| 162 |
</p>
|
| 163 |
</div>
|
|
|
|
| 165 |
|
| 166 |
<div class="col-md-6">
|
| 167 |
<div class="content-card">
|
| 168 |
+
<h4>{{ t('home.find_label') }}</h4>
|
| 169 |
<div class="buttons-container">
|
| 170 |
<!-- Ce bouton mène à la page de recherche -->
|
| 171 |
+
<a href="{{ url_for('findnode', filter='Model') }}" class="btn-modern">{{ t('home.btn_model') }}</a>
|
| 172 |
+
<a href="{{ url_for('findnode', filter='Dataset') }}" class="btn-modern">{{ t('home.btn_dataset') }}</a>
|
| 173 |
+
<a href="{{ url_for('findnode') }}" class="btn-modern">{{ t('home.btn_unsure') }}"><br><br>
|
| 174 |
+
<h4>{{ t('home.expert_label') }}</h4>
|
| 175 |
<div class="buttons-container">
|
| 176 |
<!-- Ce bouton mène à la page de recherche -->
|
| 177 |
+
<a href="{{ url_for('findnode_expert', filter='Model') }}" class="btn-modern">{{ t('home.btn_expert') }}</a>
|
| 178 |
</div>
|
| 179 |
</div>
|
| 180 |
+
<p><br/> <br/> <br/>{{ t('home.download_date') }}</p>
|
| 181 |
</div>
|
| 182 |
</div>
|
| 183 |
<div class="welcome-container">
|
| 184 |
+
<h2>{{ t('home.notice_title') }}</h2>
|
| 185 |
|
| 186 |
+
<p>{{ t('home.notice_p1') }}</p>
|
| 187 |
|
| 188 |
+
<p>{{ t('home.notice_p2') }}</p>
|
| 189 |
|
| 190 |
+
<p>{{ t('home.notice_p3') }}</p>
|
| 191 |
|
| 192 |
+
<p>{{ t('home.notice_p4') }}</p>
|
| 193 |
|
| 194 |
+
<p>{{ t('home.notice_p5') }}</p>
|
| 195 |
|
| 196 |
+
<p>{{ t('home.notice_p6') }}</p>
|
| 197 |
|
| 198 |
+
<p>{{ t('home.notice_p7') | safe }}</p>
|
| 199 |
|
| 200 |
+
<p>{{ t('home.notice_p8') }}</p>
|
| 201 |
|
| 202 |
</div>
|
| 203 |
</body>
|
| 204 |
+
</html>
|
application_neo4j/templates/search.html
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
-
<html lang="
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>
|
| 7 |
|
| 8 |
<!-- CSS de Bootstrap (remplace DSFR) -->
|
| 9 |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
|
@@ -36,9 +36,13 @@
|
|
| 36 |
<header class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
|
| 37 |
<div class="container">
|
| 38 |
<a class="navbar-brand" href="/">
|
| 39 |
-
<span class="fw-bold">
|
| 40 |
-
<small class="d-block text-muted">
|
| 41 |
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
</div>
|
| 43 |
</header>
|
| 44 |
|
|
@@ -46,8 +50,8 @@
|
|
| 46 |
<!-- Titre principal et Formulaire (inchangés) -->
|
| 47 |
<div class="row">
|
| 48 |
<div class="col-12">
|
| 49 |
-
<h1 class="display-5">
|
| 50 |
-
<p class="lead">
|
| 51 |
</div>
|
| 52 |
</div>
|
| 53 |
|
|
@@ -59,10 +63,10 @@
|
|
| 59 |
<div class="row g-3 align-items-end">
|
| 60 |
<!-- Champ de recherche -->
|
| 61 |
<div class="col-12 col-md-5">
|
| 62 |
-
<label for="search-input" class="form-label fw-bold">
|
| 63 |
<div class="position-relative">
|
| 64 |
<input type="text" name="name" id="search-input" class="form-control"
|
| 65 |
-
placeholder="
|
| 66 |
value="{{ request.form.name or '' }}" required autocomplete="off" />
|
| 67 |
<ul id="suggestions-list" class="list-group position-absolute w-100" style="display: none; z-index: 1000;"></ul>
|
| 68 |
</div>
|
|
@@ -70,31 +74,31 @@
|
|
| 70 |
|
| 71 |
<!-- Filtres -->
|
| 72 |
<div class="col-12 col-md-3">
|
| 73 |
-
<label class="form-label fw-bold">
|
| 74 |
<div class="d-flex gap-3">
|
| 75 |
<div class="form-check">
|
| 76 |
<input class="form-check-input" type="checkbox" name="filters" value="Model" id="filter-model"
|
| 77 |
{% if 'Model' in search.filters %}checked{% endif %}>
|
| 78 |
-
<label class="form-check-label" for="filter-model">
|
| 79 |
</div>
|
| 80 |
<div class="form-check">
|
| 81 |
<input class="form-check-input" type="checkbox" name="filters" value="Dataset" id="filter-dataset"
|
| 82 |
{% if 'Dataset' in search.filters %}checked{% endif %}>
|
| 83 |
-
<label class="form-check-label" for="filter-dataset">
|
| 84 |
</div>
|
| 85 |
</div>
|
| 86 |
</div>
|
| 87 |
|
| 88 |
<!-- Profondeur de recherche -->
|
| 89 |
<div class="col-12 col-md-4">
|
| 90 |
-
<label class="form-label fw-bold">
|
| 91 |
<div class="d-flex align-items-center gap-3">
|
| 92 |
<div class="form-check form-switch">
|
| 93 |
<input class="form-check-input" type="checkbox" id="depth-unlimited" name="depth_unlimited" checked>
|
| 94 |
-
<label class="form-check-label" for="depth-unlimited">
|
| 95 |
</div>
|
| 96 |
<div class="input-group input-group-sm">
|
| 97 |
-
<span class="input-group-text">
|
| 98 |
<input class="form-control" type="number" name="depth" id="depth"
|
| 99 |
value="{{ request.form.depth }}" min="1" max="5" disabled>
|
| 100 |
</div>
|
|
@@ -106,7 +110,7 @@
|
|
| 106 |
<div class="row mt-4">
|
| 107 |
<div class="col-12">
|
| 108 |
<button type="submit" name="submit" value="find_node" class="btn btn-primary w-100">
|
| 109 |
-
<i class="bi bi-search me-1"></i>
|
| 110 |
</button>
|
| 111 |
</div>
|
| 112 |
</div>
|
|
@@ -127,19 +131,19 @@
|
|
| 127 |
<!-- COLONNE ASCENDANCE -->
|
| 128 |
<div class="col-12 col-lg-3">
|
| 129 |
<div class="d-flex justify-content-center gap-2 mb-3">
|
| 130 |
-
<h4 class="h5 text-center mb-3">
|
| 131 |
<!-- L'icône avec les attributs pour le tooltip -->
|
| 132 |
<i class="bi bi-info-circle-fill text-secondary align-middle ms-2"
|
| 133 |
data-bs-toggle="tooltip"
|
| 134 |
data-bs-placement="right"
|
| 135 |
-
title="
|
| 136 |
</i>
|
| 137 |
</h4>
|
| 138 |
</div>
|
| 139 |
<div class="card">
|
| 140 |
<div class="card-body p-2">
|
| 141 |
{% if not highlights.asc_unique_models %}
|
| 142 |
-
<p class="text-muted small m-2">
|
| 143 |
{% else %}
|
| 144 |
{% for model in highlights.asc_unique_models %}
|
| 145 |
<div class="card mb-2 position-relative ">
|
|
@@ -161,20 +165,20 @@
|
|
| 161 |
<!-- MODIFICATION : Ajout de la classe "text-break" pour empêcher le débordement -->
|
| 162 |
<h6 class="card-title small mb-1 text-break card-interactive">
|
| 163 |
<a href="https://huggingface.co/{{ model.name }}" target="_blank" rel="noopener noreferrer" class="stretched-link" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true"
|
| 164 |
-
title="<b>
|
| 165 |
-
<br>
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
>
|
| 172 |
{{ model.name }}
|
| 173 |
</a>
|
| 174 |
</h6>
|
| 175 |
<p class="card-text" style="font-size: 0.75em; line-height: 1.2;">
|
| 176 |
-
{{ "{:,.0f}".format((model.downloads or 0) | int).replace(',', ' ') }}
|
| 177 |
-
{{ "{:,.0f}".format((model.citation_count or 0) | int).replace(',', ' ') }} citations
|
| 178 |
</p>
|
| 179 |
</div>
|
| 180 |
</div>
|
|
@@ -192,7 +196,7 @@
|
|
| 192 |
|
| 193 |
<!-- COLONNE MODÈLE RECHERCHÉ (CENTRE) (inchangée) -->
|
| 194 |
<div class="col-12 col-lg-4 " id="center-column">
|
| 195 |
-
<h3 class="h5 text-center mb-3">
|
| 196 |
<a href="https://huggingface.co/{{ searched_node.id }}" target="_blank" rel="noopener noreferrer" class="text-white">
|
| 197 |
<div class="card card-recherche text-center border-2 ">
|
| 198 |
<div class="card-header card-recherche-header text-white">
|
|
@@ -204,46 +208,46 @@
|
|
| 204 |
<div class="row">
|
| 205 |
<div class="col">
|
| 206 |
<p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.ascendantsCount or 0) | int).replace(',', ' ') }}</p>
|
| 207 |
-
<p class="small text-muted">
|
| 208 |
|
| 209 |
<i class="bi bi-info-circle-fill ms-2"
|
| 210 |
data-bs-toggle="tooltip"
|
| 211 |
data-bs-placement="right"
|
| 212 |
-
title="
|
| 213 |
</i>
|
| 214 |
</p>
|
| 215 |
|
| 216 |
</div>
|
| 217 |
<div class="col">
|
| 218 |
<p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.descendantsCount or 0) | int).replace(',', ' ') }}</p>
|
| 219 |
-
<p class="small text-muted">
|
| 220 |
<i class="bi bi-info-circle-fill ms-2"
|
| 221 |
data-bs-toggle="tooltip"
|
| 222 |
data-bs-placement="right"
|
| 223 |
-
title="
|
| 224 |
</i>
|
| 225 |
</p>
|
| 226 |
</div>
|
| 227 |
</div>
|
| 228 |
<div class="row mt-2">
|
| 229 |
-
<div class="col"><p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.downloads or 0) | int).replace(',', ' ') }}</p><p class="small text-muted">
|
| 230 |
<div class="col"><p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.citationCount or 0) | int).replace(',', ' ') }}</p>
|
| 231 |
-
<p class="small text-muted">
|
| 232 |
<i class="bi bi-info-circle-fill ms-2"
|
| 233 |
data-bs-toggle="tooltip"
|
| 234 |
data-bs-placement="right"
|
| 235 |
-
title="
|
| 236 |
</i>
|
| 237 |
</p></div>
|
| 238 |
-
<div class="col"><p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.likes or 0) | int).replace(',', ' ') }}</p><p class="small text-muted">
|
| 239 |
</div>
|
| 240 |
<div class="mt-2">
|
| 241 |
-
{% if searched_node.createdAt %}<span class="badge bg-light text-dark">
|
| 242 |
-
{% if searched_node.task %}<span class="badge bg-light text-dark">
|
| 243 |
</div>
|
| 244 |
<hr>
|
| 245 |
</a>
|
| 246 |
-
<button id="show-graph-btn" class="btn btn-secondary"><i class="bi bi-diagram-3 me-1"></i>
|
| 247 |
</div>
|
| 248 |
</div>
|
| 249 |
</div>
|
|
@@ -256,12 +260,12 @@
|
|
| 256 |
<!-- COLONNE DESCENDANCE (DROITE) -->
|
| 257 |
<div class="col-12 col-lg-3">
|
| 258 |
<div class="d-flex justify-content-center gap-2 mb-3">
|
| 259 |
-
<h4 class="h5 text-center mb-3">
|
| 260 |
<!-- L'icône avec les attributs pour le tooltip -->
|
| 261 |
<i class="bi bi-info-circle-fill text-secondary align-middle ms-2"
|
| 262 |
data-bs-toggle="tooltip"
|
| 263 |
data-bs-placement="right"
|
| 264 |
-
title="
|
| 265 |
</i>
|
| 266 |
</h4>
|
| 267 |
</div>
|
|
@@ -293,26 +297,26 @@
|
|
| 293 |
data-bs-toggle="tooltip"
|
| 294 |
data-bs-placement="top"
|
| 295 |
data-bs-html="true"
|
| 296 |
-
title="<b>
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
| 302 |
-
|
| 303 |
{{ model.name }}
|
| 304 |
</a>
|
| 305 |
</h6>
|
| 306 |
<p class="card-text" style="font-size: 0.75em; line-height: 1.2;">
|
| 307 |
-
{{ "{:,.0f}".format((model.downloads or 0) | int).replace(',', ' ') }}
|
| 308 |
-
{{ "{:,.0f}".format((model.citation_count or 0) | int).replace(',', ' ') }} citations
|
| 309 |
</p>
|
| 310 |
</div>
|
| 311 |
</div>
|
| 312 |
</div>
|
| 313 |
{% endfor %}
|
| 314 |
{% else %}
|
| 315 |
-
<p class="text-muted small m-2">
|
| 316 |
{% endif %}
|
| 317 |
</div>
|
| 318 |
</div>
|
|
@@ -325,23 +329,23 @@
|
|
| 325 |
<hr class="mb-5">
|
| 326 |
<div class="col-12">
|
| 327 |
<div class="mb-5">
|
| 328 |
-
<h4 class="h4">
|
| 329 |
<div class="table-responsive">
|
| 330 |
<table id="descendance-table" class="table table-bordered table-striped table-hover">
|
| 331 |
<thead>
|
| 332 |
<tr>
|
| 333 |
-
<th scope="col">
|
| 334 |
-
<th scope="col">
|
| 335 |
-
<th scope="col">
|
| 336 |
-
<th scope="col">
|
| 337 |
-
<th scope="col">
|
| 338 |
-
<th scope="col">
|
| 339 |
-
<th scope="col">
|
| 340 |
-
<th scope="col">
|
| 341 |
-
<th scope="col">
|
| 342 |
-
<th scope="col">
|
| 343 |
-
<th scope="col">
|
| 344 |
-
<th scope="col">
|
| 345 |
</tr>
|
| 346 |
</thead>
|
| 347 |
</table>
|
|
@@ -349,23 +353,23 @@
|
|
| 349 |
</div>
|
| 350 |
<hr class="my-5">
|
| 351 |
<div>
|
| 352 |
-
<h4 class="h4">
|
| 353 |
<div class="table-responsive">
|
| 354 |
<table id="ascendance-table" class="table table-bordered table-striped table-hover">
|
| 355 |
<thead>
|
| 356 |
<tr>
|
| 357 |
-
<th scope="col">
|
| 358 |
-
<th scope="col">
|
| 359 |
-
<th scope="col">
|
| 360 |
-
<th scope="col">
|
| 361 |
-
<th scope="col">
|
| 362 |
-
<th scope="col">
|
| 363 |
-
<th scope="col">
|
| 364 |
-
<th scope="col">
|
| 365 |
-
<th scope="col">
|
| 366 |
-
<th scope="col">
|
| 367 |
-
<th scope="col">
|
| 368 |
-
<th scope="col">
|
| 369 |
</tr>
|
| 370 |
</thead>
|
| 371 |
</table>
|
|
@@ -378,30 +382,30 @@
|
|
| 378 |
<div class="row g-4">
|
| 379 |
<div class="col-12 col-lg-8">
|
| 380 |
<div class="card">
|
| 381 |
-
<div class="card-header"><h5 class="card-title mb-0">
|
| 382 |
<div class="card-body">
|
| 383 |
<div id="sigma-container" data-graph='{{ graph_data | tojson | safe }}'></div>
|
| 384 |
</div>
|
| 385 |
</div>
|
| 386 |
<div id="node-info-card" class="card mt-3 card-interactive" style="display: none;"> <div class="card-body">
|
| 387 |
-
<h5 class="card-title">
|
| 388 |
<div id="node-details" class="mt-2"></div>
|
| 389 |
</div>
|
| 390 |
</div>
|
| 391 |
</div>
|
| 392 |
<div class="col-12 col-lg-4">
|
| 393 |
<div class="card mb-3">
|
| 394 |
-
<div class="card-header"><h5 class="card-title mb-0">
|
| 395 |
<div class="card-body">
|
| 396 |
<ul id="legend-nodes" class="list-group list-group-flush">
|
| 397 |
-
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color: #007bff;"></span>
|
| 398 |
-
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color: #092d53;"></span>
|
| 399 |
-
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color:#7D7D7D;"></span>
|
| 400 |
</ul>
|
| 401 |
</div>
|
| 402 |
</div>
|
| 403 |
<div class="card">
|
| 404 |
-
<div class="card-header"><h5 class="card-title mb-0">
|
| 405 |
<div class="card-body">
|
| 406 |
<ul id="legend-edges" class="list-group list-group-flush"></ul>
|
| 407 |
</div>
|
|
@@ -415,7 +419,7 @@
|
|
| 415 |
<!-- Pied de page et scripts (inchangés) -->
|
| 416 |
<footer class="bg-dark text-white text-center p-4 mt-auto">
|
| 417 |
<div class="container">
|
| 418 |
-
<p class="mb-0">
|
| 419 |
</div>
|
| 420 |
</footer>
|
| 421 |
|
|
@@ -426,6 +430,10 @@
|
|
| 426 |
<script src="https://unpkg.com/graphology@0.25.1/dist/graphology.umd.min.js"></script>
|
| 427 |
<script src="https://cdn.jsdelivr.net/npm/graphology-library/dist/graphology-library.min.js"></script>
|
| 428 |
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 429 |
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
|
| 430 |
<script>
|
| 431 |
document.addEventListener('DOMContentLoaded', function () {
|
|
@@ -434,4 +442,4 @@
|
|
| 434 |
});
|
| 435 |
</script>
|
| 436 |
</body>
|
| 437 |
-
</html>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
+
<html lang="{{ current_lang }}">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>{{ t('site.title_search') }}</title>
|
| 7 |
|
| 8 |
<!-- CSS de Bootstrap (remplace DSFR) -->
|
| 9 |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
|
|
|
| 36 |
<header class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
|
| 37 |
<div class="container">
|
| 38 |
<a class="navbar-brand" href="/">
|
| 39 |
+
<span class="fw-bold">{{ t('site.nav_brand') }}</span>
|
| 40 |
+
<small class="d-block text-muted">{{ t('site.nav_subtitle_full') }}</small>
|
| 41 |
</a>
|
| 42 |
+
<div class="d-flex gap-2">
|
| 43 |
+
<a href="{{ url_for('set_language_route', lang='fr') }}" class="btn btn-sm {{ 'btn-primary' if current_lang == 'fr' else 'btn-outline-primary' }}">{{ t('lang.fr') }}</a>
|
| 44 |
+
<a href="{{ url_for('set_language_route', lang='en') }}" class="btn btn-sm {{ 'btn-primary' if current_lang == 'en' else 'btn-outline-primary' }}">{{ t('lang.en') }}</a>
|
| 45 |
+
</div>
|
| 46 |
</div>
|
| 47 |
</header>
|
| 48 |
|
|
|
|
| 50 |
<!-- Titre principal et Formulaire (inchangés) -->
|
| 51 |
<div class="row">
|
| 52 |
<div class="col-12">
|
| 53 |
+
<h1 class="display-5">{{ t('search.page_title') }}</h1>
|
| 54 |
+
<p class="lead">{{ t('search.page_lead_model') }}</p>
|
| 55 |
</div>
|
| 56 |
</div>
|
| 57 |
|
|
|
|
| 63 |
<div class="row g-3 align-items-end">
|
| 64 |
<!-- Champ de recherche -->
|
| 65 |
<div class="col-12 col-md-5">
|
| 66 |
+
<label for="search-input" class="form-label fw-bold">{{ t('search.label_name') }}</label>
|
| 67 |
<div class="position-relative">
|
| 68 |
<input type="text" name="name" id="search-input" class="form-control"
|
| 69 |
+
placeholder="{{ t('search.placeholder_model') }}"
|
| 70 |
value="{{ request.form.name or '' }}" required autocomplete="off" />
|
| 71 |
<ul id="suggestions-list" class="list-group position-absolute w-100" style="display: none; z-index: 1000;"></ul>
|
| 72 |
</div>
|
|
|
|
| 74 |
|
| 75 |
<!-- Filtres -->
|
| 76 |
<div class="col-12 col-md-3">
|
| 77 |
+
<label class="form-label fw-bold">{{ t('search.label_filters') }}</label>
|
| 78 |
<div class="d-flex gap-3">
|
| 79 |
<div class="form-check">
|
| 80 |
<input class="form-check-input" type="checkbox" name="filters" value="Model" id="filter-model"
|
| 81 |
{% if 'Model' in search.filters %}checked{% endif %}>
|
| 82 |
+
<label class="form-check-label" for="filter-model">{{ t('search.filter_model') }}</label>
|
| 83 |
</div>
|
| 84 |
<div class="form-check">
|
| 85 |
<input class="form-check-input" type="checkbox" name="filters" value="Dataset" id="filter-dataset"
|
| 86 |
{% if 'Dataset' in search.filters %}checked{% endif %}>
|
| 87 |
+
<label class="form-check-label" for="filter-dataset">{{ t('search.filter_dataset') }}</label>
|
| 88 |
</div>
|
| 89 |
</div>
|
| 90 |
</div>
|
| 91 |
|
| 92 |
<!-- Profondeur de recherche -->
|
| 93 |
<div class="col-12 col-md-4">
|
| 94 |
+
<label class="form-label fw-bold">{{ t('search.label_depth') }}</label>
|
| 95 |
<div class="d-flex align-items-center gap-3">
|
| 96 |
<div class="form-check form-switch">
|
| 97 |
<input class="form-check-input" type="checkbox" id="depth-unlimited" name="depth_unlimited" checked>
|
| 98 |
+
<label class="form-check-label" for="depth-unlimited">{{ t('search.depth_unlimited') }}</label>
|
| 99 |
</div>
|
| 100 |
<div class="input-group input-group-sm">
|
| 101 |
+
<span class="input-group-text">{{ t('search.depth_limited') }}</span>
|
| 102 |
<input class="form-control" type="number" name="depth" id="depth"
|
| 103 |
value="{{ request.form.depth }}" min="1" max="5" disabled>
|
| 104 |
</div>
|
|
|
|
| 110 |
<div class="row mt-4">
|
| 111 |
<div class="col-12">
|
| 112 |
<button type="submit" name="submit" value="find_node" class="btn btn-primary w-100">
|
| 113 |
+
<i class="bi bi-search me-1"></i>{{ t('search.btn_search') }}
|
| 114 |
</button>
|
| 115 |
</div>
|
| 116 |
</div>
|
|
|
|
| 131 |
<!-- COLONNE ASCENDANCE -->
|
| 132 |
<div class="col-12 col-lg-3">
|
| 133 |
<div class="d-flex justify-content-center gap-2 mb-3">
|
| 134 |
+
<h4 class="h5 text-center mb-3">{{ t('search.asc_section') }}
|
| 135 |
<!-- L'icône avec les attributs pour le tooltip -->
|
| 136 |
<i class="bi bi-info-circle-fill text-secondary align-middle ms-2"
|
| 137 |
data-bs-toggle="tooltip"
|
| 138 |
data-bs-placement="right"
|
| 139 |
+
title="{{ t('search.asc_tooltip') }}">
|
| 140 |
</i>
|
| 141 |
</h4>
|
| 142 |
</div>
|
| 143 |
<div class="card">
|
| 144 |
<div class="card-body p-2">
|
| 145 |
{% if not highlights.asc_unique_models %}
|
| 146 |
+
<p class="text-muted small m-2">{{ t('search.no_parent') }}</p>
|
| 147 |
{% else %}
|
| 148 |
{% for model in highlights.asc_unique_models %}
|
| 149 |
<div class="card mb-2 position-relative ">
|
|
|
|
| 165 |
<!-- MODIFICATION : Ajout de la classe "text-break" pour empêcher le débordement -->
|
| 166 |
<h6 class="card-title small mb-1 text-break card-interactive">
|
| 167 |
<a href="https://huggingface.co/{{ model.name }}" target="_blank" rel="noopener noreferrer" class="stretched-link" data-bs-toggle="tooltip" data-bs-placement="top" data-bs-html="true"
|
| 168 |
+
title="<b>{{ t('search.info_label') }}</b>
|
| 169 |
+
<br>{{ t('search.info_author') }} : {{ (model.name.split('/') | first) if '/' in model.name else t('search.info_unknown') }}<br>
|
| 170 |
+
{{ t('search.info_citations') }} : {{ '{:,.0f}'.format((model.citation_count or 0) | int).replace(',', ' ') }}<br>
|
| 171 |
+
{{ t('search.info_likes') }} : {{ model.likes or t('search.info_unknown') }}<br>
|
| 172 |
+
{{ t('search.info_date') }} : {{ model.createdAt or 'N/A' }}<br>
|
| 173 |
+
{{ t('search.info_task') }} : {{ model.task or t('search.info_unknown_task') }}<br>
|
| 174 |
+
{{ t('search.info_license') }} : {{ model.license or t('search.info_unknown_task') }}"
|
| 175 |
>
|
| 176 |
{{ model.name }}
|
| 177 |
</a>
|
| 178 |
</h6>
|
| 179 |
<p class="card-text" style="font-size: 0.75em; line-height: 1.2;">
|
| 180 |
+
{{ "{:,.0f}".format((model.downloads or 0) | int).replace(',', ' ') }} {{ t('search.downloads') }}<br>
|
| 181 |
+
{{ "{:,.0f}".format((model.citation_count or 0) | int).replace(',', ' ') }} {{ t('search.citations') }}
|
| 182 |
</p>
|
| 183 |
</div>
|
| 184 |
</div>
|
|
|
|
| 196 |
|
| 197 |
<!-- COLONNE MODÈLE RECHERCHÉ (CENTRE) (inchangée) -->
|
| 198 |
<div class="col-12 col-lg-4 " id="center-column">
|
| 199 |
+
<h3 class="h5 text-center mb-3">{{ t('search.searched_model') }}</h3>
|
| 200 |
<a href="https://huggingface.co/{{ searched_node.id }}" target="_blank" rel="noopener noreferrer" class="text-white">
|
| 201 |
<div class="card card-recherche text-center border-2 ">
|
| 202 |
<div class="card-header card-recherche-header text-white">
|
|
|
|
| 208 |
<div class="row">
|
| 209 |
<div class="col">
|
| 210 |
<p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.ascendantsCount or 0) | int).replace(',', ' ') }}</p>
|
| 211 |
+
<p class="small text-muted">{{ t('search.ascendants') }}
|
| 212 |
|
| 213 |
<i class="bi bi-info-circle-fill ms-2"
|
| 214 |
data-bs-toggle="tooltip"
|
| 215 |
data-bs-placement="right"
|
| 216 |
+
title="{{ t('search.ascendants_tooltip') }}">
|
| 217 |
</i>
|
| 218 |
</p>
|
| 219 |
|
| 220 |
</div>
|
| 221 |
<div class="col">
|
| 222 |
<p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.descendantsCount or 0) | int).replace(',', ' ') }}</p>
|
| 223 |
+
<p class="small text-muted">{{ t('search.descendants') }}
|
| 224 |
<i class="bi bi-info-circle-fill ms-2"
|
| 225 |
data-bs-toggle="tooltip"
|
| 226 |
data-bs-placement="right"
|
| 227 |
+
title="{{ t('search.descendants_tooltip') }}">
|
| 228 |
</i>
|
| 229 |
</p>
|
| 230 |
</div>
|
| 231 |
</div>
|
| 232 |
<div class="row mt-2">
|
| 233 |
+
<div class="col"><p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.downloads or 0) | int).replace(',', ' ') }}</p><p class="small text-muted">{{ t('search.downloads') }}</p></div>
|
| 234 |
<div class="col"><p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.citationCount or 0) | int).replace(',', ' ') }}</p>
|
| 235 |
+
<p class="small text-muted">{{ t('search.citations') }}
|
| 236 |
<i class="bi bi-info-circle-fill ms-2"
|
| 237 |
data-bs-toggle="tooltip"
|
| 238 |
data-bs-placement="right"
|
| 239 |
+
title="{{ t('search.citations_tooltip') }}">
|
| 240 |
</i>
|
| 241 |
</p></div>
|
| 242 |
+
<div class="col"><p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.likes or 0) | int).replace(',', ' ') }}</p><p class="small text-muted">{{ t('search.likes') }}</p></div>
|
| 243 |
</div>
|
| 244 |
<div class="mt-2">
|
| 245 |
+
{% if searched_node.createdAt %}<span class="badge bg-light text-dark">{{ t('search.published') }} {{ searched_node.createdAt }}</span>{% endif %}
|
| 246 |
+
{% if searched_node.task %}<span class="badge bg-light text-dark">{{ t('search.task') }} {{ searched_node.task }}</span>{% endif %}
|
| 247 |
</div>
|
| 248 |
<hr>
|
| 249 |
</a>
|
| 250 |
+
<button id="show-graph-btn" class="btn btn-secondary"><i class="bi bi-diagram-3 me-1"></i>{{ t('search.btn_graph') }}</button>
|
| 251 |
</div>
|
| 252 |
</div>
|
| 253 |
</div>
|
|
|
|
| 260 |
<!-- COLONNE DESCENDANCE (DROITE) -->
|
| 261 |
<div class="col-12 col-lg-3">
|
| 262 |
<div class="d-flex justify-content-center gap-2 mb-3">
|
| 263 |
+
<h4 class="h5 text-center mb-3">{{ t('search.desc_section') }}
|
| 264 |
<!-- L'icône avec les attributs pour le tooltip -->
|
| 265 |
<i class="bi bi-info-circle-fill text-secondary align-middle ms-2"
|
| 266 |
data-bs-toggle="tooltip"
|
| 267 |
data-bs-placement="right"
|
| 268 |
+
title="{{ t('search.desc_tooltip') }}">
|
| 269 |
</i>
|
| 270 |
</h4>
|
| 271 |
</div>
|
|
|
|
| 297 |
data-bs-toggle="tooltip"
|
| 298 |
data-bs-placement="top"
|
| 299 |
data-bs-html="true"
|
| 300 |
+
title="<b>{{ t('search.info_label') }}</b><br>
|
| 301 |
+
{{ t('search.info_author') }} : {{ (model.name.split('/') | first) if '/' in model.name else t('search.info_unknown') }}<br>
|
| 302 |
+
{{ t('search.info_downloads') }} : {{ '{:,.0f}'.format((model.downloads or 0) | int).replace(',', ' ') }}<br>
|
| 303 |
+
{{ t('search.info_likes') }} : {{ model.likes or t('search.info_unknown') }}<br>
|
| 304 |
+
{{ t('search.info_date') }} : {{ model.createdAt or 'N/A' }}<br>
|
| 305 |
+
{{ t('search.info_task') }} : {{ model.task or t('search.info_unknown_task') }}<br>
|
| 306 |
+
{{ t('search.info_license') }} : {{ model.license or t('search.info_unknown_task') }}">
|
| 307 |
{{ model.name }}
|
| 308 |
</a>
|
| 309 |
</h6>
|
| 310 |
<p class="card-text" style="font-size: 0.75em; line-height: 1.2;">
|
| 311 |
+
{{ "{:,.0f}".format((model.downloads or 0) | int).replace(',', ' ') }} {{ t('search.downloads') }}<br>
|
| 312 |
+
{{ "{:,.0f}".format((model.citation_count or 0) | int).replace(',', ' ') }} {{ t('search.citations') }}
|
| 313 |
</p>
|
| 314 |
</div>
|
| 315 |
</div>
|
| 316 |
</div>
|
| 317 |
{% endfor %}
|
| 318 |
{% else %}
|
| 319 |
+
<p class="text-muted small m-2">{{ t('search.no_derived') }}</p>
|
| 320 |
{% endif %}
|
| 321 |
</div>
|
| 322 |
</div>
|
|
|
|
| 329 |
<hr class="mb-5">
|
| 330 |
<div class="col-12">
|
| 331 |
<div class="mb-5">
|
| 332 |
+
<h4 class="h4">{{ t('search.desc_detail_title') }}</h4>
|
| 333 |
<div class="table-responsive">
|
| 334 |
<table id="descendance-table" class="table table-bordered table-striped table-hover">
|
| 335 |
<thead>
|
| 336 |
<tr>
|
| 337 |
+
<th scope="col">{{ t('search.table_model') }}</th>
|
| 338 |
+
<th scope="col">{{ t('search.table_author') }}</th>
|
| 339 |
+
<th scope="col">{{ t('search.table_downloads') }}</th>
|
| 340 |
+
<th scope="col">{{ t('search.table_task') }}</th>
|
| 341 |
+
<th scope="col">{{ t('search.table_likes') }}</th>
|
| 342 |
+
<th scope="col">{{ t('search.table_date') }}</th>
|
| 343 |
+
<th scope="col">{{ t('search.table_dataset') }}</th>
|
| 344 |
+
<th scope="col">{{ t('search.table_license') }}</th>
|
| 345 |
+
<th scope="col">{{ t('search.table_distance') }}</th>
|
| 346 |
+
<th scope="col">{{ t('search.table_ascendants') }}</th>
|
| 347 |
+
<th scope="col">{{ t('search.table_descendants') }}</th>
|
| 348 |
+
<th scope="col">{{ t('search.table_citations') }}</th>
|
| 349 |
</tr>
|
| 350 |
</thead>
|
| 351 |
</table>
|
|
|
|
| 353 |
</div>
|
| 354 |
<hr class="my-5">
|
| 355 |
<div>
|
| 356 |
+
<h4 class="h4">{{ t('search.asc_detail_title') }}</h4>
|
| 357 |
<div class="table-responsive">
|
| 358 |
<table id="ascendance-table" class="table table-bordered table-striped table-hover">
|
| 359 |
<thead>
|
| 360 |
<tr>
|
| 361 |
+
<th scope="col">{{ t('search.table_model') }}</th>
|
| 362 |
+
<th scope="col">{{ t('search.table_author') }}</th>
|
| 363 |
+
<th scope="col">{{ t('search.table_downloads') }}</th>
|
| 364 |
+
<th scope="col">{{ t('search.table_task') }}</th>
|
| 365 |
+
<th scope="col">{{ t('search.table_likes') }}</th>
|
| 366 |
+
<th scope="col">{{ t('search.table_date') }}</th>
|
| 367 |
+
<th scope="col">{{ t('search.table_dataset') }}</th>
|
| 368 |
+
<th scope="col">{{ t('search.table_license') }}</th>
|
| 369 |
+
<th scope="col">{{ t('search.table_distance') }}</th>
|
| 370 |
+
<th scope="col">{{ t('search.table_ascendants') }}</th>
|
| 371 |
+
<th scope="col">{{ t('search.table_descendants') }}</th>
|
| 372 |
+
<th scope="col">{{ t('search.table_citations') }}</th>
|
| 373 |
</tr>
|
| 374 |
</thead>
|
| 375 |
</table>
|
|
|
|
| 382 |
<div class="row g-4">
|
| 383 |
<div class="col-12 col-lg-8">
|
| 384 |
<div class="card">
|
| 385 |
+
<div class="card-header"><h5 class="card-title mb-0">{{ t('search.graph_visualization') }}</h5></div>
|
| 386 |
<div class="card-body">
|
| 387 |
<div id="sigma-container" data-graph='{{ graph_data | tojson | safe }}'></div>
|
| 388 |
</div>
|
| 389 |
</div>
|
| 390 |
<div id="node-info-card" class="card mt-3 card-interactive" style="display: none;"> <div class="card-body">
|
| 391 |
+
<h5 class="card-title">{{ t('search.node_info_title') }}</h5>
|
| 392 |
<div id="node-details" class="mt-2"></div>
|
| 393 |
</div>
|
| 394 |
</div>
|
| 395 |
</div>
|
| 396 |
<div class="col-12 col-lg-4">
|
| 397 |
<div class="card mb-3">
|
| 398 |
+
<div class="card-header"><h5 class="card-title mb-0">{{ t('search.legend_nodes') }}</h5></div>
|
| 399 |
<div class="card-body">
|
| 400 |
<ul id="legend-nodes" class="list-group list-group-flush">
|
| 401 |
+
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color: #007bff;"></span>{{ t('search.legend_person') }}</li>
|
| 402 |
+
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color: #092d53;"></span>{{ t('search.legend_org') }}</li>
|
| 403 |
+
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color:#7D7D7D;"></span>{{ t('search.legend_model') }}</li>
|
| 404 |
</ul>
|
| 405 |
</div>
|
| 406 |
</div>
|
| 407 |
<div class="card">
|
| 408 |
+
<div class="card-header"><h5 class="card-title mb-0">{{ t('search.legend_edges') }}</h5></div>
|
| 409 |
<div class="card-body">
|
| 410 |
<ul id="legend-edges" class="list-group list-group-flush"></ul>
|
| 411 |
</div>
|
|
|
|
| 419 |
<!-- Pied de page et scripts (inchangés) -->
|
| 420 |
<footer class="bg-dark text-white text-center p-4 mt-auto">
|
| 421 |
<div class="container">
|
| 422 |
+
<p class="mb-0">{{ t('site.footer') }}</p>
|
| 423 |
</div>
|
| 424 |
</footer>
|
| 425 |
|
|
|
|
| 430 |
<script src="https://unpkg.com/graphology@0.25.1/dist/graphology.umd.min.js"></script>
|
| 431 |
<script src="https://cdn.jsdelivr.net/npm/graphology-library/dist/graphology-library.min.js"></script>
|
| 432 |
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
|
| 433 |
+
<script>
|
| 434 |
+
window.__I18N_DATA = {{ js_i18n_data | tojson }};
|
| 435 |
+
window.__I18N_LANG = "{{ current_lang }}";
|
| 436 |
+
</script>
|
| 437 |
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
|
| 438 |
<script>
|
| 439 |
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
| 442 |
});
|
| 443 |
</script>
|
| 444 |
</body>
|
| 445 |
+
</html>
|
application_neo4j/templates/search_dataset.html
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
-
<html lang="
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>
|
| 7 |
|
| 8 |
<!-- CSS de Bootstrap (remplace DSFR) -->
|
| 9 |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
|
@@ -36,9 +36,13 @@
|
|
| 36 |
<header class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
|
| 37 |
<div class="container">
|
| 38 |
<a class="navbar-brand" href="/">
|
| 39 |
-
<span class="fw-bold">
|
| 40 |
-
<small class="d-block text-muted">
|
| 41 |
</a>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
</div>
|
| 43 |
</header>
|
| 44 |
|
|
@@ -46,8 +50,8 @@
|
|
| 46 |
<!-- Titre principal et Formulaire (inchangés) -->
|
| 47 |
<div class="row">
|
| 48 |
<div class="col-12">
|
| 49 |
-
<h1 class="display-5">
|
| 50 |
-
<p class="lead">
|
| 51 |
</div>
|
| 52 |
</div>
|
| 53 |
|
|
@@ -59,10 +63,10 @@
|
|
| 59 |
<div class="row g-3 align-items-end">
|
| 60 |
<!-- Champ de recherche -->
|
| 61 |
<div class="col-12 col-md-5">
|
| 62 |
-
<label for="search-input" class="form-label fw-bold">
|
| 63 |
<div class="position-relative">
|
| 64 |
<input type="text" name="name" id="search-input" class="form-control"
|
| 65 |
-
placeholder="
|
| 66 |
value="{{ request.form.name or '' }}" required autocomplete="off" />
|
| 67 |
<ul id="suggestions-list" class="list-group position-absolute w-100" style="display: none; z-index: 1000;"></ul>
|
| 68 |
</div>
|
|
@@ -70,31 +74,31 @@
|
|
| 70 |
|
| 71 |
<!-- Filtres -->
|
| 72 |
<div class="col-12 col-md-3">
|
| 73 |
-
<label class="form-label fw-bold">
|
| 74 |
<div class="d-flex gap-3">
|
| 75 |
<div class="form-check">
|
| 76 |
<input class="form-check-input" type="checkbox" name="filters" value="Model" id="filter-model"
|
| 77 |
{% if 'Model' in search.filters %}checked{% endif %}>
|
| 78 |
-
<label class="form-check-label" for="filter-model">
|
| 79 |
</div>
|
| 80 |
<div class="form-check">
|
| 81 |
<input class="form-check-input" type="checkbox" name="filters" value="Dataset" id="filter-dataset"
|
| 82 |
{% if 'Dataset' in search.filters %}checked{% endif %}>
|
| 83 |
-
<label class="form-check-label" for="filter-dataset">
|
| 84 |
</div>
|
| 85 |
</div>
|
| 86 |
</div>
|
| 87 |
|
| 88 |
<!-- Profondeur de recherche -->
|
| 89 |
<div class="col-12 col-md-4">
|
| 90 |
-
<label class="form-label fw-bold">
|
| 91 |
<div class="d-flex align-items-center gap-3">
|
| 92 |
<div class="form-check form-switch">
|
| 93 |
<input class="form-check-input" type="checkbox" id="depth-unlimited" name="depth_unlimited" checked>
|
| 94 |
-
<label class="form-check-label" for="depth-unlimited">
|
| 95 |
</div>
|
| 96 |
<div class="input-group input-group-sm">
|
| 97 |
-
<span class="input-group-text">
|
| 98 |
<input class="form-control" type="number" name="depth" id="depth"
|
| 99 |
value="{{ request.form.depth }}" min="1" max="5" disabled>
|
| 100 |
</div>
|
|
@@ -102,7 +106,7 @@
|
|
| 102 |
</div>
|
| 103 |
</div>
|
| 104 |
|
| 105 |
-
|
| 106 |
{% if message %}
|
| 107 |
<div class="alert alert-info mt-3"><p class="mb-0">{{ message }}</p></div>
|
| 108 |
{% endif %}
|
|
@@ -110,7 +114,7 @@
|
|
| 110 |
{% set searched_node = (graph_data.nodes | selectattr('id', 'equalto', search.name) | list | first) or {} %}
|
| 111 |
<div class="row justify-content-center align-items-stretch g-4">
|
| 112 |
<div class="col-12 col-lg-4 " id="center-column">
|
| 113 |
-
<h4 class="h5 text-center mb-3">
|
| 114 |
<a href="https://huggingface.co/datasets/{{ searched_node.id }}" target="_blank" rel="noopener noreferrer" class="text-white">
|
| 115 |
<div class="card card-recherche text-center border-primary border-2 ">
|
| 116 |
<div class="card-header bg-primary text-white">
|
|
@@ -122,20 +126,20 @@
|
|
| 122 |
<div class="row">
|
| 123 |
<div class="col">
|
| 124 |
<p class="fw-bold mb-0">{{ "{:,.0f}".format((graph_data.models_count[0] or 0) | int).replace(',', ' ') }}</p>
|
| 125 |
-
<p class="small text-muted">
|
| 126 |
<i class="bi bi-info-circle-fill ms-2"></i>
|
| 127 |
</p>
|
| 128 |
</div>
|
| 129 |
</div>
|
| 130 |
<div class="row mt-2">
|
| 131 |
-
<div class="col"><p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.downloads or 0) | int).replace(',', ' ') }}</p><p class="small text-muted">
|
| 132 |
</div>
|
| 133 |
<div class="mt-2">
|
| 134 |
-
{% if searched_node.createdAt_dataset %}<span class="badge bg-light text-dark">
|
| 135 |
</div>
|
| 136 |
<hr>
|
| 137 |
</a>
|
| 138 |
-
<button id="show-graph-btn" class="btn btn-secondary"><i class="bi bi-diagram-3 me-1"></i>
|
| 139 |
</div>
|
| 140 |
</div>
|
| 141 |
</div>
|
|
@@ -147,22 +151,22 @@
|
|
| 147 |
<hr class="mb-5">
|
| 148 |
<div class="col-12">
|
| 149 |
<div class="mb-5">
|
| 150 |
-
<h4 class="h4">
|
| 151 |
<div class="table-responsive">
|
| 152 |
<table id="train-table" class="table table-bordered table-striped table-hover">
|
| 153 |
<thead>
|
| 154 |
<tr>
|
| 155 |
-
<th scope="col">
|
| 156 |
-
<th scope="col">
|
| 157 |
-
<th scope="col">
|
| 158 |
-
<th scope="col">
|
| 159 |
-
<th scope="col">
|
| 160 |
-
<th scope="col">
|
| 161 |
-
<th scope="col">
|
| 162 |
-
<th scope="col">
|
| 163 |
-
<th scope="col">
|
| 164 |
-
<th scope="col">
|
| 165 |
-
<th scope="col">
|
| 166 |
</tr>
|
| 167 |
</thead>
|
| 168 |
<tbody>
|
|
@@ -177,31 +181,31 @@
|
|
| 177 |
<div class="row g-4">
|
| 178 |
<div class="col-12 col-lg-8">
|
| 179 |
<div class="card">
|
| 180 |
-
<div class="card-header"><h5 class="card-title mb-0">
|
| 181 |
<div class="card-body">
|
| 182 |
<div id="sigma-container" data-graph='{{ graph_data | tojson | safe }}'></div>
|
| 183 |
</div>
|
| 184 |
</div>
|
| 185 |
<div id="node-info-card" class="card mt-3 card-interactive" style="display: none;"> <div class="card-body">
|
| 186 |
-
<h5 class="card-title">
|
| 187 |
<div id="node-details" class="mt-2"></div>
|
| 188 |
</div>
|
| 189 |
</div>
|
| 190 |
</div>
|
| 191 |
<div class="col-12 col-lg-4">
|
| 192 |
<div class="card mb-3">
|
| 193 |
-
<div class="card-header"><h5 class="card-title mb-0">
|
| 194 |
<div class="card-body">
|
| 195 |
<ul id="legend-nodes" class="list-group list-group-flush">
|
| 196 |
-
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color: #ffc107;"></span>
|
| 197 |
-
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color: #007bff;"></span>
|
| 198 |
-
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color: #092d53;"></span>
|
| 199 |
-
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color:#7D7D7D;"></span>
|
| 200 |
</ul>
|
| 201 |
</div>
|
| 202 |
</div>
|
| 203 |
<div class="card">
|
| 204 |
-
<div class="card-header"><h5 class="card-title mb-0">
|
| 205 |
<div class="card-body">
|
| 206 |
<ul id="legend-edges" class="list-group list-group-flush"></ul>
|
| 207 |
</div>
|
|
@@ -214,7 +218,7 @@
|
|
| 214 |
<!-- Pied de page et scripts (inchangés) -->
|
| 215 |
<footer class="bg-dark text-white text-center p-4 mt-auto">
|
| 216 |
<div class="container">
|
| 217 |
-
<p class="mb-0">
|
| 218 |
</div>
|
| 219 |
</footer>
|
| 220 |
|
|
@@ -225,6 +229,10 @@
|
|
| 225 |
<script src="https://unpkg.com/graphology@0.25.1/dist/graphology.umd.min.js"></script>
|
| 226 |
<script src="https://cdn.jsdelivr.net/npm/graphology-library/dist/graphology-library.min.js"></script>
|
| 227 |
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
<script src="{{ url_for('static', filename='js/script_dataset.js') }}"></script>
|
| 229 |
<script>
|
| 230 |
document.addEventListener('DOMContentLoaded', function () {
|
|
@@ -233,4 +241,4 @@
|
|
| 233 |
});
|
| 234 |
</script>
|
| 235 |
</body>
|
| 236 |
-
</html>
|
|
|
|
| 1 |
<!DOCTYPE html>
|
| 2 |
+
<html lang="{{ current_lang }}">
|
| 3 |
<head>
|
| 4 |
<meta charset="UTF-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>{{ t('site.title_search') }}</title>
|
| 7 |
|
| 8 |
<!-- CSS de Bootstrap (remplace DSFR) -->
|
| 9 |
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
|
|
|
| 36 |
<header class="navbar navbar-expand-lg navbar-light bg-light border-bottom">
|
| 37 |
<div class="container">
|
| 38 |
<a class="navbar-brand" href="/">
|
| 39 |
+
<span class="fw-bold">{{ t('site.nav_brand') }}</span>
|
| 40 |
+
<small class="d-block text-muted">{{ t('site.nav_subtitle') }}</small>
|
| 41 |
</a>
|
| 42 |
+
<div class="d-flex gap-2">
|
| 43 |
+
<a href="{{ url_for('set_language_route', lang='fr') }}" class="btn btn-sm {{ 'btn-primary' if current_lang == 'fr' else 'btn-outline-primary' }}">{{ t('lang.fr') }}</a>
|
| 44 |
+
<a href="{{ url_for('set_language_route', lang='en') }}" class="btn btn-sm {{ 'btn-primary' if current_lang == 'en' else 'btn-outline-primary' }}">{{ t('lang.en') }}</a>
|
| 45 |
+
</div>
|
| 46 |
</div>
|
| 47 |
</header>
|
| 48 |
|
|
|
|
| 50 |
<!-- Titre principal et Formulaire (inchangés) -->
|
| 51 |
<div class="row">
|
| 52 |
<div class="col-12">
|
| 53 |
+
<h1 class="display-5">{{ t('search.page_title') }}</h1>
|
| 54 |
+
<p class="lead">{{ t('search.page_lead_dataset') }}</p>
|
| 55 |
</div>
|
| 56 |
</div>
|
| 57 |
|
|
|
|
| 63 |
<div class="row g-3 align-items-end">
|
| 64 |
<!-- Champ de recherche -->
|
| 65 |
<div class="col-12 col-md-5">
|
| 66 |
+
<label for="search-input" class="form-label fw-bold">{{ t('search.label_name') }}</label>
|
| 67 |
<div class="position-relative">
|
| 68 |
<input type="text" name="name" id="search-input" class="form-control"
|
| 69 |
+
placeholder="{{ t('search.placeholder_dataset') }}"
|
| 70 |
value="{{ request.form.name or '' }}" required autocomplete="off" />
|
| 71 |
<ul id="suggestions-list" class="list-group position-absolute w-100" style="display: none; z-index: 1000;"></ul>
|
| 72 |
</div>
|
|
|
|
| 74 |
|
| 75 |
<!-- Filtres -->
|
| 76 |
<div class="col-12 col-md-3">
|
| 77 |
+
<label class="form-label fw-bold">{{ t('search.label_filters') }}</label>
|
| 78 |
<div class="d-flex gap-3">
|
| 79 |
<div class="form-check">
|
| 80 |
<input class="form-check-input" type="checkbox" name="filters" value="Model" id="filter-model"
|
| 81 |
{% if 'Model' in search.filters %}checked{% endif %}>
|
| 82 |
+
<label class="form-check-label" for="filter-model">{{ t('search.filter_model') }}</label>
|
| 83 |
</div>
|
| 84 |
<div class="form-check">
|
| 85 |
<input class="form-check-input" type="checkbox" name="filters" value="Dataset" id="filter-dataset"
|
| 86 |
{% if 'Dataset' in search.filters %}checked{% endif %}>
|
| 87 |
+
<label class="form-check-label" for="filter-dataset">{{ t('search.filter_dataset') }}</label>
|
| 88 |
</div>
|
| 89 |
</div>
|
| 90 |
</div>
|
| 91 |
|
| 92 |
<!-- Profondeur de recherche -->
|
| 93 |
<div class="col-12 col-md-4">
|
| 94 |
+
<label class="form-label fw-bold">{{ t('search.label_depth') }}</label>
|
| 95 |
<div class="d-flex align-items-center gap-3">
|
| 96 |
<div class="form-check form-switch">
|
| 97 |
<input class="form-check-input" type="checkbox" id="depth-unlimited" name="depth_unlimited" checked>
|
| 98 |
+
<label class="form-check-label" for="depth-unlimited">{{ t('search.depth_unlimited') }}</label>
|
| 99 |
</div>
|
| 100 |
<div class="input-group input-group-sm">
|
| 101 |
+
<span class="input-group-text">{{ t('search.depth_limited') }}</span>
|
| 102 |
<input class="form-control" type="number" name="depth" id="depth"
|
| 103 |
value="{{ request.form.depth }}" min="1" max="5" disabled>
|
| 104 |
</div>
|
|
|
|
| 106 |
</div>
|
| 107 |
</div>
|
| 108 |
|
| 109 |
+
|
| 110 |
{% if message %}
|
| 111 |
<div class="alert alert-info mt-3"><p class="mb-0">{{ message }}</p></div>
|
| 112 |
{% endif %}
|
|
|
|
| 114 |
{% set searched_node = (graph_data.nodes | selectattr('id', 'equalto', search.name) | list | first) or {} %}
|
| 115 |
<div class="row justify-content-center align-items-stretch g-4">
|
| 116 |
<div class="col-12 col-lg-4 " id="center-column">
|
| 117 |
+
<h4 class="h5 text-center mb-3">{{ t('dataset.searched_title') }}</h4>
|
| 118 |
<a href="https://huggingface.co/datasets/{{ searched_node.id }}" target="_blank" rel="noopener noreferrer" class="text-white">
|
| 119 |
<div class="card card-recherche text-center border-primary border-2 ">
|
| 120 |
<div class="card-header bg-primary text-white">
|
|
|
|
| 126 |
<div class="row">
|
| 127 |
<div class="col">
|
| 128 |
<p class="fw-bold mb-0">{{ "{:,.0f}".format((graph_data.models_count[0] or 0) | int).replace(',', ' ') }}</p>
|
| 129 |
+
<p class="small text-muted">{{ t('dataset.models_using') }}
|
| 130 |
<i class="bi bi-info-circle-fill ms-2"></i>
|
| 131 |
</p>
|
| 132 |
</div>
|
| 133 |
</div>
|
| 134 |
<div class="row mt-2">
|
| 135 |
+
<div class="col"><p class="fw-bold mb-0">{{ "{:,.0f}".format((searched_node.downloads or 0) | int).replace(',', ' ') }}</p><p class="small text-muted">{{ t('search.downloads') }}</p></div>
|
| 136 |
</div>
|
| 137 |
<div class="mt-2">
|
| 138 |
+
{% if searched_node.createdAt_dataset %}<span class="badge bg-light text-dark">{{ t('search.published') }} {{ searched_node.createdAt_dataset }}</span>{% endif %}
|
| 139 |
</div>
|
| 140 |
<hr>
|
| 141 |
</a>
|
| 142 |
+
<button id="show-graph-btn" class="btn btn-secondary"><i class="bi bi-diagram-3 me-1"></i>{{ t('search.btn_graph') }}</button>
|
| 143 |
</div>
|
| 144 |
</div>
|
| 145 |
</div>
|
|
|
|
| 151 |
<hr class="mb-5">
|
| 152 |
<div class="col-12">
|
| 153 |
<div class="mb-5">
|
| 154 |
+
<h4 class="h4">{{ t('dataset.trained_title') }}</h4>
|
| 155 |
<div class="table-responsive">
|
| 156 |
<table id="train-table" class="table table-bordered table-striped table-hover">
|
| 157 |
<thead>
|
| 158 |
<tr>
|
| 159 |
+
<th scope="col">{{ t('search.table_model') }}</th>
|
| 160 |
+
<th scope="col">{{ t('search.table_author') }}</th>
|
| 161 |
+
<th scope="col">{{ t('search.table_downloads') }}</th>
|
| 162 |
+
<th scope="col">{{ t('search.table_task') }}</th>
|
| 163 |
+
<th scope="col">{{ t('search.table_likes') }}</th>
|
| 164 |
+
<th scope="col">{{ t('search.table_date') }}</th>
|
| 165 |
+
<th scope="col">{{ t('search.table_dataset') }}</th>
|
| 166 |
+
<th scope="col">{{ t('search.table_license') }}</th>
|
| 167 |
+
<th scope="col">{{ t('search.table_ascendants') }}</th>
|
| 168 |
+
<th scope="col">{{ t('search.table_descendants') }}</th>
|
| 169 |
+
<th scope="col">{{ t('search.table_citations') }}</th>
|
| 170 |
</tr>
|
| 171 |
</thead>
|
| 172 |
<tbody>
|
|
|
|
| 181 |
<div class="row g-4">
|
| 182 |
<div class="col-12 col-lg-8">
|
| 183 |
<div class="card">
|
| 184 |
+
<div class="card-header"><h5 class="card-title mb-0">{{ t('search.graph_visualization') }}</h5></div>
|
| 185 |
<div class="card-body">
|
| 186 |
<div id="sigma-container" data-graph='{{ graph_data | tojson | safe }}'></div>
|
| 187 |
</div>
|
| 188 |
</div>
|
| 189 |
<div id="node-info-card" class="card mt-3 card-interactive" style="display: none;"> <div class="card-body">
|
| 190 |
+
<h5 class="card-title">{{ t('search.node_info_title') }}</h5>
|
| 191 |
<div id="node-details" class="mt-2"></div>
|
| 192 |
</div>
|
| 193 |
</div>
|
| 194 |
</div>
|
| 195 |
<div class="col-12 col-lg-4">
|
| 196 |
<div class="card mb-3">
|
| 197 |
+
<div class="card-header"><h5 class="card-title mb-0">{{ t('search.legend_nodes') }}</h5></div>
|
| 198 |
<div class="card-body">
|
| 199 |
<ul id="legend-nodes" class="list-group list-group-flush">
|
| 200 |
+
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color: #ffc107;"></span>{{ t('search.legend_dataset') }}</li>
|
| 201 |
+
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color: #007bff;"></span>{{ t('search.legend_person') }}</li>
|
| 202 |
+
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color: #092d53;"></span>{{ t('search.legend_org') }}</li>
|
| 203 |
+
<li class="list-group-item d-flex align-items-center"><span class="legend-color me-2" style="background-color:#7D7D7D;"></span>{{ t('search.legend_model') }}</li>
|
| 204 |
</ul>
|
| 205 |
</div>
|
| 206 |
</div>
|
| 207 |
<div class="card">
|
| 208 |
+
<div class="card-header"><h5 class="card-title mb-0">{{ t('search.legend_edges') }}</h5></div>
|
| 209 |
<div class="card-body">
|
| 210 |
<ul id="legend-edges" class="list-group list-group-flush"></ul>
|
| 211 |
</div>
|
|
|
|
| 218 |
<!-- Pied de page et scripts (inchangés) -->
|
| 219 |
<footer class="bg-dark text-white text-center p-4 mt-auto">
|
| 220 |
<div class="container">
|
| 221 |
+
<p class="mb-0">{{ t('site.footer') }}</p>
|
| 222 |
</div>
|
| 223 |
</footer>
|
| 224 |
|
|
|
|
| 229 |
<script src="https://unpkg.com/graphology@0.25.1/dist/graphology.umd.min.js"></script>
|
| 230 |
<script src="https://cdn.jsdelivr.net/npm/graphology-library/dist/graphology-library.min.js"></script>
|
| 231 |
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
|
| 232 |
+
<script>
|
| 233 |
+
window.__I18N_DATA = {{ js_i18n_data | tojson }};
|
| 234 |
+
window.__I18N_LANG = "{{ current_lang }}";
|
| 235 |
+
</script>
|
| 236 |
<script src="{{ url_for('static', filename='js/script_dataset.js') }}"></script>
|
| 237 |
<script>
|
| 238 |
document.addEventListener('DOMContentLoaded', function () {
|
|
|
|
| 241 |
});
|
| 242 |
</script>
|
| 243 |
</body>
|
| 244 |
+
</html>
|