File size: 7,061 Bytes
e4361b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
001b8e9
 
 
 
e4361b1
 
 
001b8e9
 
 
e4361b1
 
001b8e9
e4361b1
 
 
 
001b8e9
 
 
 
 
 
e4361b1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
document.addEventListener('DOMContentLoaded', () => {
    const configSelect = document.getElementById('config-select');
    const tbody = document.getElementById('metrics-tbody');
    const tfoot = document.getElementById('metrics-tfoot');
    const instancesList = document.getElementById('instances-list');
    const instancesTitle = document.getElementById('instances-title');

    let currentConfig = null;
    let selectedCell = null;

    // Initialize
    fetchConfigs();

    configSelect.addEventListener('change', (e) => {
        currentConfig = e.target.value;
        loadMetrics(currentConfig);
        clearInstances();
    });

    async function fetchConfigs() {
        try {
            const res = await fetch('/api/configs');
            const data = await res.json();
            
            configSelect.innerHTML = '';
            
            if (data.configs.length === 0) {
                const opt = document.createElement('option');
                opt.text = 'Aucune configuration trouvée';
                opt.disabled = true;
                configSelect.appendChild(opt);
                return;
            }

            const defaultTarget = "CyberAggAdo Global — Contexte";
            let defaultFound = false;

            data.configs.forEach((conf) => {
                const opt = document.createElement('option');
                opt.value = conf;
                opt.text = conf;
                
                // Try exact match or match containing 'CyberAggAdo Global' and 'Contexte'
                if (conf === defaultTarget || (conf.includes("CyberAggAdo Global") && conf.includes("Contexte"))) {
                    opt.selected = true;
                    currentConfig = conf;
                    defaultFound = true;
                }
                configSelect.appendChild(opt);
            });

            // Fallback if target not found
            if (!defaultFound && data.configs.length > 0) {
                configSelect.options[0].selected = true;
                currentConfig = data.configs[0];
            }

            if (currentConfig) {
                loadMetrics(currentConfig);
            }
            
        } catch (err) {
            console.error('Failed to fetch configs', err);
            configSelect.innerHTML = '<option disabled selected>Erreur de chargement</option>';
        }
    }

    async function loadMetrics(configName) {
        try {
            const res = await fetch(`/api/configs/${encodeURIComponent(configName)}/metrics`);
            const data = await res.json();
            
            renderTable(data.metrics, data.macro_f1);
        } catch (err) {
            console.error('Failed to load metrics', err);
            tbody.innerHTML = '<tr><td colspan="8">Erreur lors du chargement des données.</td></tr>';
        }
    }

    function getMetricColor(value) {
        if (value >= 0.8) return 'var(--metric-good)';
        if (value >= 0.5) return 'var(--metric-ok)';
        return 'var(--metric-bad)';
    }

    function renderTable(metrics, macroF1) {
        tbody.innerHTML = '';
        
        metrics.forEach(m => {
            const tr = document.createElement('tr');
            
            const f1Color = getMetricColor(m.f1);
            const precColor = getMetricColor(m.precision);
            const recColor = getMetricColor(m.recall);

            tr.innerHTML = `
                <td>${escapeHtml(m.display_name)}</td>
                <td style="color: ${f1Color}">${m.f1.toFixed(3)}</td>
                <td style="color: ${precColor}">${m.precision.toFixed(3)}</td>
                <td style="color: ${recColor}">${m.recall.toFixed(3)}</td>
                <td class="cell-clickable" data-label="${escapeHtml(m.label)}" data-outcome="fn">${m.fn}</td>
                <td class="cell-clickable" data-label="${escapeHtml(m.label)}" data-outcome="fp">${m.fp}</td>
                <td class="cell-clickable" data-label="${escapeHtml(m.label)}" data-outcome="tn">${m.tn}</td>
                <td class="cell-clickable" data-label="${escapeHtml(m.label)}" data-outcome="tp">${m.tp}</td>
            `;
            
            tbody.appendChild(tr);
        });

        const macroColor = getMetricColor(macroF1);
        tfoot.innerHTML = `
            <tr>
                <td style="font-style:italic;">Macro avg</td>
                <td style="color: ${macroColor}">${macroF1.toFixed(3)}</td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
                <td></td>
            </tr>
        `;

        // Attach event listeners to clickable cells
        const clickableCells = tbody.querySelectorAll('.cell-clickable');
        clickableCells.forEach(cell => {
            cell.addEventListener('click', onCellClick);
        });
    }

    async function onCellClick(e) {
        if (!currentConfig) return;

        const cell = e.currentTarget;
        const label = cell.getAttribute('data-label');
        const outcome = cell.getAttribute('data-outcome');

        // Manage selection state
        if (selectedCell) {
            selectedCell.classList.remove('cell-selected');
        }
        selectedCell = cell;
        selectedCell.classList.add('cell-selected');

        try {
            const res = await fetch(`/api/configs/${encodeURIComponent(currentConfig)}/instances?label=${encodeURIComponent(label)}&outcome=${encodeURIComponent(outcome)}`);
            const data = await res.json();
            
            instancesTitle.textContent = data.title;
            
            instancesList.innerHTML = '';
            if (data.texts && data.texts.length > 0) {
                data.texts.forEach(text => {
                    const div = document.createElement('div');
                    div.className = 'instance-item';
                    div.textContent = text;
                    instancesList.appendChild(div);
                });
            } else {
                instancesList.innerHTML = '<div class="instances-empty">Aucune phrase à afficher.</div>';
            }
            
        } catch (err) {
            console.error('Failed to load instances', err);
            instancesTitle.textContent = "Erreur de chargement";
            instancesList.innerHTML = '<div class="instances-empty">Impossible de charger les instances.</div>';
        }
    }

    function clearInstances() {
        instancesTitle.textContent = "Cliquez sur une cellule pour voir les instances";
        instancesList.innerHTML = '<div class="instances-empty">Aucune phrase à afficher.</div>';
        if (selectedCell) {
            selectedCell.classList.remove('cell-selected');
            selectedCell = null;
        }
    }

    function escapeHtml(unsafe) {
        return (unsafe || '').toString()
             .replace(/&/g, "&amp;")
             .replace(/</g, "&lt;")
             .replace(/>/g, "&gt;")
             .replace(/"/g, "&quot;")
             .replace(/'/g, "&#039;");
    }
});