Spaces:
Sleeping
Sleeping
File size: 12,282 Bytes
b544dec |
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 |
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aplikasi Pengecek Fakta Berita AI</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; scroll-behavior: smooth; }
.result-card { border-left-width: 4px; transition: all 0.3s ease-in-out; }
.hoax { border-color: #ef4444; background-color: #fee2e2; }
.fakta { border-color: #22c55e; background-color: #dcfce7; }
.loader { border: 4px solid #f3f3f3; border-radius: 50%; border-top: 4px solid #3b82f6; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 20px auto; }
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
.model-vote-card { transition: opacity 0.5s ease-in; }
</style>
</head>
<body class="bg-gray-50 text-gray-800">
<div class="container mx-auto p-4 sm:p-6 lg:p-8 max-w-5xl">
<header class="mb-10 text-center">
<h1 class="text-4xl sm:text-5xl font-bold text-blue-600">Pengecek Fakta Berita AI</h1>
<p class="text-gray-600 mt-3 text-lg">Analisis keaslian berita dari sebuah link menggunakan model AI terlatih.</p>
</header>
<!-- Input Section -->
<div id="main-app" class="bg-white p-6 rounded-lg shadow-lg mb-8">
<h2 class="text-2xl font-semibold mb-5 text-gray-700">Analisis Berita Baru</h2>
<div class="mb-4">
<label for="newsURL" class="block text-sm font-medium text-gray-700 mb-2">Masukkan Link Berita:</label>
<input type="url" id="newsURL" class="w-full p-3 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-shadow" placeholder="Contoh: https://www.situsberita.com/judul-artikel...">
</div>
<button id="checkFactButton" class="w-full bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-4 rounded-md transition-all duration-150 ease-in-out shadow-md hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:bg-gray-400">
<span id="button-text">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 inline-block mr-2 -mt-1" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /></svg>
Cek Keaslian Berita
</span>
<span id="button-loader" class="hidden">Menganalisis...</span>
</button>
<!-- Result Area -->
<div id="result-area" class="mt-6 hidden">
<div id="loader" class="loader hidden"></div>
<div id="result-content" class="hidden">
<!-- Hasil akan ditampilkan di sini -->
</div>
</div>
</div>
<!-- Comparison Section -->
<div class="bg-white p-6 rounded-lg shadow-lg">
<h2 class="text-2xl font-semibold mb-2 text-gray-700">Perbandingan Kinerja Model</h2>
<p class="text-gray-500 mb-6">Hasil evaluasi dari kelima metode AI pada data uji.</p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 items-center">
<div class="w-full h-80"><canvas id="f1ScoreChart"></canvas></div>
<div class="overflow-x-auto"><table id="metricsTable" class="min-w-full divide-y divide-gray-200 border"></table></div>
</div>
</div>
</div>
<script>
// --- DATA HASIL EVALUASI ANDA ---
const evaluationResults = [
{ "Model": "Bagging (Ensemble)", "Accuracy": 0.9507, "F1-score": 0.9504 },
{ "Model": "ELECTRA", "Accuracy": 0.9483, "F1-score": 0.9482 },
{ "Model": "BERT", "Accuracy": 0.9466, "F1-score": 0.9465 },
{ "Model": "XLNet", "Accuracy": 0.9455, "F1-score": 0.9454 },
{ "Model": "RoBERTa", "Accuracy": 0.9356, "F1-score": 0.9355 }
];
// --- Render Tabel dan Diagram (Tidak ada perubahan di sini) ---
const table = document.getElementById('metricsTable');
table.innerHTML = `<thead class="bg-gray-50"><tr><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Model</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">F1-Score</th><th class="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Akurasi</th></tr></thead><tbody class="bg-white divide-y divide-gray-200">${evaluationResults.map(r=>`<tr><td class="px-4 py-3 whitespace-nowrap text-sm font-semibold text-gray-900">${r.Model}</td><td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600">${r['F1-score'].toFixed(4)}</td><td class="px-4 py-3 whitespace-nowrap text-sm text-gray-600">${r.Accuracy.toFixed(4)}</td></tr>`).join('')}</tbody>`;
const ctx = document.getElementById('f1ScoreChart').getContext('2d');
const f1ScoreChart = new Chart(ctx, {type: 'bar', data: {labels: evaluationResults.map(r => r.Model).reverse(), datasets: [{label: 'F1-Score (Weighted)', data: evaluationResults.map(r => r['F1-score']).reverse(), backgroundColor: ['rgba(167, 139, 250, 0.7)','rgba(96, 165, 250, 0.7)','rgba(52, 211, 153, 0.7)','rgba(251, 191, 36, 0.7)','rgba(248, 113, 113, 0.7)'].reverse(), borderColor: ['rgba(167, 139, 250, 1)','rgba(96, 165, 250, 1)','rgba(52, 211, 153, 1)','rgba(251, 191, 36, 1)','rgba(248, 113, 113, 1)'].reverse(), borderWidth: 1, borderRadius: 4}]}, options: {indexAxis: 'y', responsive: true, maintainAspectRatio: false, plugins: {legend: {display: false}, tooltip: {callbacks: {label: (c) => ` F1-Score: ${c.raw.toFixed(4)}`}}}, scales: {x: {min: 0.9, title: {display: true, text: 'F1-Score (Weighted)'}}}}});
// --- Logika Interaktif Aplikasi (Diperbarui dengan Penanganan Error yang Lebih Baik) ---
const checkButton = document.getElementById('checkFactButton');
const newsUrlInput = document.getElementById('newsURL');
const resultArea = document.getElementById('result-area');
const loader = document.getElementById('loader');
const resultContent = document.getElementById('result-content');
const buttonText = document.getElementById('button-text');
const buttonLoader = document.getElementById('button-loader');
checkButton.addEventListener('click', async () => {
const url = newsUrlInput.value.trim();
if (url === '') {
alert('Silakan masukkan link berita terlebih dahulu.');
return;
}
try {
new URL(url);
} catch (_) {
alert('Format link tidak valid. Pastikan menggunakan http:// atau https://');
return;
}
resultArea.style.display = 'block';
resultContent.style.display = 'none';
loader.style.display = 'block';
checkButton.disabled = true;
buttonText.classList.add('hidden');
buttonLoader.classList.remove('hidden');
try {
const response = await fetch('/predict', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ url: url })
});
// PERBAIKAN: Periksa status response SEBELUM mencoba parse JSON
if (!response.ok) {
// Coba baca error dari body sebagai JSON, jika gagal, gunakan status text
let errorMessage = `HTTP error! Status: ${response.status} ${response.statusText}`;
try {
const errorData = await response.json();
errorMessage = errorData.error || errorMessage;
} catch (e) {
// Biarkan errorMessage yang sudah ada jika body bukan JSON
}
throw new Error(errorMessage);
}
// Jika response.ok, maka kita aman untuk parse JSON
const data = await response.json();
displayResults(data);
} catch (error) {
console.error('Error:', error);
resultContent.innerHTML = `<div class="result-card hoax p-4 rounded-r-md"><h3 class="font-bold text-lg text-red-800">Terjadi Kesalahan</h3><p class="text-sm text-red-700">Gagal memproses link. Pastikan link valid dan dapat diakses publik. Error: ${error.message}</p></div>`;
resultContent.style.display = 'block';
} finally {
loader.style.display = 'none';
checkButton.disabled = false;
buttonText.classList.remove('hidden');
buttonLoader.classList.add('hidden');
}
});
// Fungsi displayResults tidak perlu diubah
function displayResults(data) {
let finalResultHtml = '';
let individualResultsHtml = '';
const ensembleResult = data['Bagging (Ensemble)'];
if (ensembleResult) {
const isHoax = ensembleResult.prediction === 'Hoax';
const cardClass = isHoax ? 'hoax' : 'fakta';
const title = isHoax ? 'Prediksi Akhir: KEMUNGKINAN BESAR HOAX' : 'Prediksi Akhir: KEMUNGKINAN BESAR FAKTA';
const description = isHoax ? 'Berdasarkan voting mayoritas model, konten ini memiliki ciri disinformasi.' : 'Berdasarkan voting mayoritas model, konten ini kemungkinan besar faktual.';
const confidence = ensembleResult.confidence;
finalResultHtml = `<div class="result-card ${cardClass} p-5 rounded-md mb-6 shadow-md"><h3 class="font-bold text-xl">${title}</h3><p class="text-base mt-2">${description}</p><p class="text-sm mt-3"><strong>Tingkat Kesepakatan Model (Confidence):</strong> ${confidence}</p></div>`;
}
individualResultsHtml += '<h4 class="text-lg font-semibold mb-3 text-gray-600">Hasil per Model:</h4><div class="grid grid-cols-1 md:grid-cols-2 gap-4">';
for (const modelName in data) {
if (modelName !== 'Bagging (Ensemble)') {
const result = data[modelName];
const isHoax = result.prediction === 'Hoax';
const icon = isHoax ? '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-red-600" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" /></svg>' : '<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-green-600" viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd" /></svg>';
individualResultsHtml += `<div class="model-vote-card border border-gray-200 p-3 rounded-lg shadow-sm"><div class="font-semibold text-gray-800 flex items-center">${icon} ${modelName}</div><div class="text-sm text-gray-500 pl-7">Prediksi: <span class="font-medium ${isHoax ? 'text-red-700' : 'text-green-700'}">${result.prediction}</span></div><div class="text-sm text-gray-500 pl-7">Confidence: <span class="font-medium">${result.confidence}</span></div></div>`;
}
}
individualResultsHtml += '</div>';
resultContent.innerHTML = finalResultHtml + individualResultsHtml;
resultContent.style.display = 'block';
}
</script>
</body>
</html>
```
|