File size: 8,790 Bytes
f0806e2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
// --- 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 = `<p><strong>Nom :</strong> ${attr.id || "Non défini"}</p><p><strong>Type :</strong> ${attr.dataCat}</p>`;
    
    // ... (Le reste de la construction de infosHtml ne change pas) ...
    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>`;
    }
 
    // --- É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;
}