|
|
|
|
|
|
|
|
|
|
|
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 = ""; |
|
|
|
|
|
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"; |
|
|
|
|
|
|
|
|
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); |
|
|
}); |
|
|
|
|
|
|
|
|
const tooltipTriggerList = [].slice.call(legendContainer.querySelectorAll('[data-bs-toggle="tooltip"]')) |
|
|
tooltipTriggerList.map(el => new bootstrap.Tooltip(el)); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
const existingLink = cardBody.querySelector('.stretched-link'); |
|
|
if (existingLink) { |
|
|
existingLink.remove(); |
|
|
} |
|
|
|
|
|
|
|
|
infoCard.dataset.nodeId = attr.id; |
|
|
|
|
|
|
|
|
let infosHtml = `<p><strong>Nom :</strong> ${attr.id || "Non défini"}</p><p><strong>Type :</strong> ${attr.dataCat}</p>`; |
|
|
|
|
|
|
|
|
if (["personne", "organisation", "Author"].includes(attr.dataCat)) { |
|
|
if (attr.followers) infosHtml += `<p><strong>Abonnés :</strong> ${formatNumberShort(attr.followers)}</p>`; |
|
|
} else if (["Modèle", "Model"].includes(attr.dataCat)) { |
|
|
if (attr.downloads) infosHtml += `<p><strong>Téléchargements :</strong> ${formatNumberShort(attr.downloads)}</p>`; |
|
|
if (attr.createdAt) infosHtml += `<p><strong>Créé le :</strong> ${formatDateFr(attr.createdAt)}</p>`; |
|
|
if (attr.task) infosHtml += `<p><strong>Tâche :</strong> ${attr.task}</p>`; |
|
|
if (attr.dataset) infosHtml += `<p><strong>Dataset utilisé :</strong> ${attr.dataset}</p>`; |
|
|
} else if (attr.dataCat === "Dataset") { |
|
|
if (attr.downloads) infosHtml += `<p><strong>Téléchargements :</strong> ${formatNumberShort(attr.downloads)}</p>`; |
|
|
if (attr.createdAt_dataset) infosHtml += `<p><strong>Créé le :</strong> ${formatDateFr(attr.createdAt_dataset)}</p>`; |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
infoCard.style.display = 'block'; |
|
|
infoCard.scrollIntoView({ behavior: 'smooth', block: 'center' }); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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"; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function parseNumericValue(value) { |
|
|
if (typeof value === 'string') { |
|
|
|
|
|
const cleanedValue = value.replace(/[\s,]/g, ''); |
|
|
const num = parseInt(cleanedValue, 10); |
|
|
if (!isNaN(num)) { |
|
|
return { isNumber: true, value: num }; |
|
|
} |
|
|
} |
|
|
|
|
|
return { isNumber: false, value: value }; |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} else if (valA.isNumber && !valB.isNumber) { |
|
|
return -1; |
|
|
} else if (!valA.isNumber && valB.isNumber) { |
|
|
return 1; |
|
|
} else { |
|
|
|
|
|
return String(valA.value).localeCompare(String(valB.value)); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
jQuery.fn.dataTable.ext.order['numeric-string-desc'] = function (a, b) { |
|
|
return jQuery.fn.dataTable.ext.order['numeric-string-asc'](a, b) * -1; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
graph.forEachInNeighbor(current, (pred) => { |
|
|
if (!visited.has(pred)) { |
|
|
stack.push(pred); |
|
|
} |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
return visited; |
|
|
} |
|
|
|