// --- FONCTIONS UTILITAIRES GLOBALES --- // Ces fonctions sont utilisées à plusieurs endroits et sont donc définies en premier. // Mapping relations → {couleur, tooltip} const edgeInfos = { "Fait partie de cette organisation": { color: "#a05195",name : "Fait partie de cette organisation", tooltip: "Cette personne est membre de cette organisation" }, "A publié": { color: "#003f5c",name : "A publié", tooltip: "Un auteur (personne ou organistaion) a publié un modèle" }, "A généré": { color: "#f50f0f",name : "A permis de générer", tooltip: "Le modèle source a été téléchargé et modifié afin de créer le modèle cible (type de transformation inconnu)" }, "finetune": { color: "#cd6700",name : "Finetune", tooltip: "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." }, "adapter": { color: "#238a00",name : "Adapter", tooltip: "Adaptation : méthode d’ajustement qui peut être utilisée avec peu de ressources de calcul." }, "quantized": { color: "#009194",name : "Quantized", tooltip:"Quantisation : la précision des poids du modèle source est réduite afin de diminuer son empreinte en mémoire." }, "merge": { color: "#c29bc2",name : "Merge", tooltip:"Fusion : méthode visant à mélanger des couches de différents modèles pour améliorer leur performance." }, "other": { color: "#8f7340",name : "Autre", tooltip: "Autre type de relation" } , "unknown": { color: "#8f7340",name : "Autre", tooltip: "Autre type de relation" } }; function buildEdgeLegend() { const legendContainer = document.getElementById("legend-edges"); legendContainer.innerHTML = ""; // reset Object.entries(edgeInfos).forEach(([relation, {color, name,tooltip}]) => { if (relation == "unknown") return; const li = document.createElement("li"); li.className = "list-group-item d-flex align-items-center"; // ajout Bootstrap tooltip li.setAttribute("data-bs-toggle", "tooltip"); li.setAttribute("data-bs-placement", "top"); li.setAttribute("data-bs-html", "true"); li.setAttribute("title", tooltip); const span = document.createElement("span"); span.className = "legend-color edge me-2"; span.style.backgroundColor = color; li.appendChild(span); li.appendChild(document.createTextNode(name)); legendContainer.appendChild(li); }); // nécessaire pour activer les tooltips Bootstrap dynamiques const tooltipTriggerList = [].slice.call(legendContainer.querySelectorAll('[data-bs-toggle="tooltip"]')) tooltipTriggerList.map(el => new bootstrap.Tooltip(el)); } /** * Affiche la carte d'information pour un nœud donné. * @param {object} attr - Les attributs du nœud à afficher. */ function showNodeInfo(attr) { const infoCard = document.getElementById("node-info-card"); const cardBody = infoCard.querySelector('.card-body'); const detailsContainer = document.getElementById("node-details"); if (!infoCard || !detailsContainer || !cardBody) return; // --- ÉTAPE 1: Nettoyage --- const existingLink = cardBody.querySelector('.stretched-link'); if (existingLink) { existingLink.remove(); } // On stocke l'ID du nœud pour l'animation au survol infoCard.dataset.nodeId = attr.id; // --- ÉTAPE 2: Construction des détails --- let infosHtml = `

Nom : ${attr.id || "Non défini"}

Type : ${attr.dataCat}

`; // ... (Le reste de la construction de infosHtml ne change pas) ... if (["personne", "organisation", "Author"].includes(attr.dataCat)) { if (attr.followers) infosHtml += `

Abonnés : ${formatNumberShort(attr.followers)}

`; } else if (["Modèle", "Model"].includes(attr.dataCat)) { if (attr.downloads) infosHtml += `

Téléchargements : ${formatNumberShort(attr.downloads)}

`; if (attr.createdAt) infosHtml += `

Créé le : ${formatDateFr(attr.createdAt)}

`; if (attr.task) infosHtml += `

Tâche : ${attr.task}

`; if (attr.dataset) infosHtml += `

Dataset utilisé : ${attr.dataset}

`; } else if (attr.dataCat === "Dataset") { if (attr.downloads) infosHtml += `

Téléchargements : ${formatNumberShort(attr.downloads)}

`; if (attr.createdAt_dataset) infosHtml += `

Créé le : ${formatDateFr(attr.createdAt_dataset)}

`; } // --- ÉTAPE 3: Création du lien --- let huggingFaceUrl = null; if (["Modèle", "Model","personne", "organisation", "Author"].includes(attr.dataCat)) { huggingFaceUrl = `https://huggingface.co/${attr.id}`; } else if (attr.dataCat === "Dataset") { huggingFaceUrl = `https://huggingface.co/datasets/${attr.id}`; } if (huggingFaceUrl) { const link = document.createElement('a'); link.href = huggingFaceUrl; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.className = 'stretched-link'; link.setAttribute('aria-label', `Voir ${attr.id} sur Hugging Face`); cardBody.appendChild(link); } detailsContainer.innerHTML = infosHtml; // Affichage et défilement de la carte infoCard.style.display = 'block'; infoCard.scrollIntoView({ behavior: 'smooth', block: 'center' }); } /** * Formate une chaîne de date ISO en format français (ex: 1 janvier 2024). * @param {string} dateString - La date à formater. * @returns {string} La date formatée ou "Date inconnue". */ function formatDateFr(dateString) { if (!dateString || dateString === "inconnue") return "Date inconnue"; try { const options = { year: 'numeric', month: 'long', day: 'numeric' }; return new Date(dateString).toLocaleDateString('fr-FR', options); } catch (e) { return "Date inconnue"; } } // --- PLUGIN DE TRI PERSONNALISÉ POUR DATATABLES --- // Gère les colonnes avec des nombres et des chaînes de caractères (ex: "Inconnu") // Place toujours les chaînes après les nombres lors d'un tri ascendant. // Fonction pour parser la valeur : enlève les espaces et convertit en nombre si possible function parseNumericValue(value) { if (typeof value === 'string') { // Enlève les espaces (pour les nombres comme "12 345") et les virgules const cleanedValue = value.replace(/[\s,]/g, ''); const num = parseInt(cleanedValue, 10); if (!isNaN(num)) { return { isNumber: true, value: num }; } } // Si ce n'est pas une chaîne numérique, on le traite comme du texte return { isNumber: false, value: value }; } // Définition du tri ascendant jQuery.fn.dataTable.ext.order['numeric-string-asc'] = function (a, b) { const valA = parseNumericValue(a); const valB = parseNumericValue(b); if (valA.isNumber && valB.isNumber) { return valA.value - valB.value; // Tri numérique standard } else if (valA.isNumber && !valB.isNumber) { return -1; // Les nombres viennent AVANT les chaînes } else if (!valA.isNumber && valB.isNumber) { return 1; // Les chaînes viennent APRÈS les nombres } else { // Les deux sont des chaînes, on fait un tri alphabétique return String(valA.value).localeCompare(String(valB.value)); } }; // Le tri descendant est simplement l'inverse de l'ascendant jQuery.fn.dataTable.ext.order['numeric-string-desc'] = function (a, b) { return jQuery.fn.dataTable.ext.order['numeric-string-asc'](a, b) * -1; }; /** * Formate un grand nombre en une version courte (ex: 1.2M, 50k). * @param {number} n - Le nombre à formater. * @returns {string} Le nombre formaté. */ function formatNumberShort(n) { if (!n || n === 0) return ""; if (n >= 1000000000) return (n / 1000000000).toFixed(1) + 'B'; if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M'; if (n >= 1000) return (n / 1000).toFixed(0) + 'k'; return n.toString(); } function getAllPredecessors(graph, nodeId) { const visited = new Set(); const stack = [nodeId]; while (stack.length > 0) { const current = stack.pop(); if (!visited.has(current)) { visited.add(current); // On parcourt les prédécesseurs directs graph.forEachInNeighbor(current, (pred) => { if (!visited.has(pred)) { stack.push(pred); } }); } } return visited; }