Sirius16's picture
Upload 25 files
79aeec8 verified
Raw
History Blame Contribute Delete
29.6 kB
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ResNet CIFAR10 - Research Deployment</title>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
background-color: #f8fafc;
color: #334155;
}
.hero-section {
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
color: white;
padding: 70px 8%;
text-align: center;
}
.hero-title {
font-size: 32px;
font-weight: 700;
margin-bottom: 12px;
line-height: 1.4;
}
.hero-subtitle {
font-size: 18px;
color: #cbd5e0;
font-weight: 500;
letter-spacing: 0.5px;
}
.support-section {
background: white;
padding: 40px 8%;
text-align: center;
border-bottom: 1px solid #e2e8f0;
}
.support-title {
font-size: 14px;
font-weight: 700;
color: #64748b;
letter-spacing: 2px;
text-transform: uppercase;
margin-bottom: 30px;
}
.logos-container {
display: flex;
justify-content: center;
align-items: center;
gap: 60px;
flex-wrap: wrap;
}
.logo-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.logo-img {
height: 80px;
}
.logo-text {
font-size: 12px;
font-weight: 600;
color: #475569;
max-width: 250px;
}
.about-section {
padding: 60px 8%;
background: #f1f5f9;
display: flex;
flex-direction: column;
align-items: center;
}
.section-header {
text-align: center;
margin-bottom: 40px;
}
.section-header h2 {
font-size: 28px;
color: #1e293b;
margin-bottom: 10px;
font-weight: 700;
}
.section-header p {
color: #64748b;
font-size: 15px;
}
.class-grid {
display: flex;
flex-wrap: wrap;
gap: 15px;
justify-content: center;
max-width: 1000px;
}
.class-card {
background: white;
padding: 20px 15px;
border-radius: 16px;
text-align: center;
box-shadow: 0 4px 6px rgba(0,0,0,0.02);
border: 1px solid #e2e8f0;
width: 140px;
cursor: pointer;
transition: transform 0.2s, border-color 0.2s;
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.class-card:hover {
transform: translateY(-5px);
border-color: #3b82f6;
box-shadow: 0 10px 15px rgba(59, 130, 246, 0.1);
}
.class-emoji {
font-size: 40px;
line-height: 1;
}
.class-card span {
display: block;
font-weight: 700;
color: #334155;
font-size: 15px;
}
.detection-section {
padding: 60px 8%;
display: flex;
gap: 30px;
align-items: stretch;
}
.panel {
background: white;
border-radius: 20px;
padding: 30px;
border: 1px solid #e2e8f0;
box-shadow: 0 10px 25px rgba(0,0,0,0.02);
flex: 1;
display: flex;
flex-direction: column;
}
.panel-header {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 25px;
color: #1a202c;
font-size: 1.1rem;
font-weight: 600;
}
.panel-svg {
color: #3b82f6;
display: flex;
}
.upload-area {
border: 2px dashed #cbd5e0;
border-radius: 15px;
height: 400px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
background: #ffffff;
transition: 0.3s;
position: relative;
}
.upload-area:hover {
border-color: #3b82f6;
background: #f8fafc;
}
.plus-icon {
font-size: 50px;
color: #94a3b8;
font-weight: 300;
line-height: 1;
margin-bottom: 15px;
}
.upload-text-main {
font-weight: 700;
color: #334155;
font-size: 16px;
margin-bottom: 5px;
}
.upload-text-sub {
color: #94a3b8;
font-size: 12px;
}
#image-preview-container {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
padding: 20px;
}
#image-preview {
max-width: 100%;
max-height: 280px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 4px 10px rgba(0,0,0,0.1);
}
.btn-change {
background: white;
border: 1px solid #cbd5e0;
padding: 8px 20px;
border-radius: 8px;
font-weight: 500;
color: #475569;
cursor: pointer;
}
.btn-change:hover {
border-color: #3b82f6;
color: #3b82f6;
}
.result-panel {
flex: 1.2;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
text-align: center;
}
.empty-icon {
width: 50px;
height: 50px;
stroke: #cbd5e0;
stroke-width: 1.5;
fill: none;
margin-bottom: 15px;
}
.empty-title {
font-weight: 700;
color: #334155;
margin-bottom: 8px;
}
.empty-desc {
color: #94a3b8;
font-size: 13px;
max-width: 250px;
line-height: 1.6;
}
.cards-row {
display: flex;
gap: 15px;
margin-bottom: 20px;
}
.card {
flex: 1;
background: #f8fafc;
border: 1px solid #e2e8f0;
border-radius: 15px;
padding: 20px;
}
.model-label {
font-size: 11px;
font-weight: 700;
color: #94a3b8;
text-transform: uppercase;
margin-bottom: 5px;
}
.class-name {
font-size: 22px;
font-weight: 700;
color: #1e293b;
text-transform: capitalize;
}
.conf-badge {
font-size: 13px;
color: #10b981;
font-weight: 600;
}
.prob-list {
margin-top: 15px;
border-top: 1px solid #e2e8f0;
padding-top: 15px;
display: flex;
flex-direction: column;
gap: 6px;
max-height: 180px;
overflow-y: auto;
}
.prob-item {
display: flex;
align-items: center;
font-size: 11px;
}
.prob-label {
width: 70px;
text-transform: capitalize;
font-weight: 500;
}
.prob-bar-bg {
flex: 1;
height: 6px;
background: #e2e8f0;
border-radius: 3px;
margin: 0 10px;
overflow: hidden;
}
.prob-bar-fill {
height: 100%;
background: #3b82f6;
border-radius: 3px;
transition: width 0.5s;
}
.prob-val {
width: 35px;
text-align: right;
font-weight: 600;
}
.interpretation {
background: #eff6ff;
padding: 20px;
border-radius: 15px;
border-left: 5px solid #3b82f6;
margin-top: auto;
}
.inter-title {
font-weight: 700;
color: #1e40af;
margin-bottom: 5px;
display: flex;
align-items: center;
gap: 8px;
}
.inter-text {
font-size: 13px;
line-height: 1.5;
color: #1e3a8a;
}
.loader {
display: none;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.spinner {
width: 40px;
height: 40px;
border: 4px solid #f3f3f3;
border-top: 4px solid #3b82f6;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 15px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
backdrop-filter: blur(4px);
z-index: 2000;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s;
}
.modal.show {
opacity: 1;
}
.modal-box {
background: white;
padding: 40px;
border-radius: 20px;
text-align: center;
max-width: 450px;
width: 90%;
position: relative;
transform: translateY(20px);
transition: transform 0.3s;
}
.modal.show .modal-box {
transform: translateY(0);
}
.close-btn {
position: absolute;
top: 15px;
right: 20px;
font-size: 28px;
cursor: pointer;
color: #94a3b8;
line-height: 1;
}
.close-btn:hover {
color: #ef4444;
}
.class-modal-emoji {
font-size: 60px;
margin-bottom: 15px;
}
.class-modal-title {
font-size: 24px;
font-weight: 700;
color: #1e293b;
margin-bottom: 10px;
}
.class-modal-desc {
color: #475569;
font-size: 14px;
line-height: 1.6;
}
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: #f1f5f9; border-radius: 4px; }
::-webkit-scrollbar-thumb { background: #cbd5e0; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #94a3b8; }
</style>
</head>
<body>
<section class="hero-section">
<h1 class="hero-title">Deployment ResNet-MLP dan ResNet-KAN pada CIFAR10</h1>
<div class="hero-subtitle">Rakyan (NPM)</div>
</section>
<section class="support-section">
<h3 class="support-title">Penelitian Ini Didukung Oleh</h3>
<div class="logos-container">
<div class="logo-item">
<img src="/static/DSC UI.png" class="logo-img" alt="DSC Logo">
<p class="logo-text">Data Science Center, FMIPA UI</p>
</div>
<div class="logo-item" style="border-left: 2px solid #e2e8f0; padding-left: 40px;">
<img src="/static/BrainAI.png" class="logo-img" alt="BrainAI Logo">
<p class="logo-text">BrainAI Lab, Departemen Matematika FMIPA UI</p>
</div>
</div>
</section>
<section class="about-section">
<div class="section-header">
<h2>About CIFAR-10 Dataset</h2>
<p>Dataset ini terdiri dari 60.000 citra berwarna 32x32 dalam 10 kelas berbeda.</p>
</div>
<div class="class-grid">
<div class="class-card" onclick="openClassModal('Pesawat', '✈️', 'Citra yang merepresentasikan pesawat terbang, termasuk jet komersial, pesawat tempur, hingga pesawat baling-baling ringan.')">
<div class="class-emoji">✈️</div>
<span>Pesawat</span>
</div>
<div class="class-card" onclick="openClassModal('Mobil', '🚗', 'Kendaraan roda empat untuk penumpang seperti sedan, hatchback, dan SUV. Kelas ini tidak mencakup truk atau kendaraan alat berat.')">
<div class="class-emoji">🚗</div>
<span>Mobil</span>
</div>
<div class="class-card" onclick="openClassModal('Burung', '🐦', 'Berbagai spesies burung dari yang berukuran kecil seperti pipit hingga yang besar seperti elang atau unta.')">
<div class="class-emoji">🐦</div>
<span>Burung</span>
</div>
<div class="class-card" onclick="openClassModal('Kucing', '🐱', 'Mamalia karnivora berukuran kecil dari keluarga Felidae, mencakup berbagai ras kucing domestik.')">
<div class="class-emoji">🐱</div>
<span>Kucing</span>
</div>
<div class="class-card" onclick="openClassModal('Rusa', '🦌', 'Hewan mamalia pemamah biak yang termasuk dalam famili Cervidae, mencakup berbagai jenis rusa dan kijang.')">
<div class="class-emoji">🦌</div>
<span>Rusa</span>
</div>
<div class="class-card" onclick="openClassModal('Anjing', '🐶', 'Mamalia karnivora yang telah didomestikasi dari serigala, mencakup berbagai ras dan ukuran.')">
<div class="class-emoji">🐶</div>
<span>Anjing</span>
</div>
<div class="class-card" onclick="openClassModal('Katak', '🐸', 'Amfibi tak berekor yang pandai melompat, mencakup berbagai spesies katak dan kodok di alam liar.')">
<div class="class-emoji">🐸</div>
<span>Katak</span>
</div>
<div class="class-card" onclick="openClassModal('Kuda', '🐴', 'Mamalia berkuku satu yang sering digunakan untuk berkuda, pacuan, maupun hewan pekerja beban.')">
<div class="class-emoji">🐴</div>
<span>Kuda</span>
</div>
<div class="class-card" onclick="openClassModal('Kapal', '🚢', 'Kendaraan air berukuran besar maupun kecil, termasuk kapal pesiar, perahu nelayan, kargo, dan feri.')">
<div class="class-emoji">🚢</div>
<span>Kapal</span>
</div>
<div class="class-card" onclick="openClassModal('Truk', '🚚', 'Kendaraan bermotor berukuran besar yang dirancang khusus untuk mengangkut barang, muatan, atau kargo berat.')">
<div class="class-emoji">🚚</div>
<span>Truk</span>
</div>
</div>
</section>
<section class="detection-section">
<div class="panel">
<div class="panel-header">
<div class="panel-svg">
<svg viewBox="0 0 24 24" width="22" height="22" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="17 8 12 3 7 8"></polyline>
<line x1="12" y1="3" x2="12" y2="15"></line>
</svg>
</div>
Input Citra
</div>
<div class="upload-area" id="drop-zone">
<div id="prompt" style="text-align: center;">
<div class="plus-icon">+</div>
<div class="upload-text-main">Pilih Gambar Objek</div>
<div class="upload-text-sub">Format: JPG, PNG (Max 5MB)</div>
</div>
<div id="image-preview-container">
<img id="image-preview" src="#" alt="Preview">
<button class="btn-change" id="btn-change">Ganti Gambar</button>
</div>
<input type="file" id="file-input" accept="image/jpeg, image/png" hidden>
</div>
</div>
<div class="panel result-panel">
<div class="panel-header">
<div class="panel-svg">
<svg viewBox="0 0 24 24" width="22" height="22" stroke="currentColor" stroke-width="2.5" fill="none" stroke-linecap="round" stroke-linejoin="round">
<polygon points="12 2 2 7 12 12 22 7 12 2"></polygon>
<polyline points="2 17 12 22 22 17"></polyline>
<polyline points="2 12 12 17 22 12"></polyline>
</svg>
</div>
Hasil ResNet
</div>
<div class="empty-state" id="empty-state">
<svg class="empty-icon" viewBox="0 0 24 24" stroke-linejoin="round" stroke-linecap="round">
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path>
</svg>
<div class="empty-title">Belum Ada Data</div>
<div class="empty-desc">Silakan unggah gambar objek di panel sebelah kiri untuk memulai proses deteksi.</div>
</div>
<div class="loader" id="loader">
<div class="spinner"></div>
<div style="color: #64748b; font-size: 14px;">Memproses prediksi ResNet...</div>
</div>
<div id="results" style="display: none; height: 100%; flex-direction: column;">
<div class="cards-row">
<div class="card">
<p class="model-label">ResNet-MLP</p>
<p class="class-name" id="mlp-name">-</p>
<p class="conf-badge"><span id="mlp-conf">-</span>%</p>
<div class="prob-list" id="mlp-probs"></div>
</div>
<div class="card">
<p class="model-label">ResNet-KAN</p>
<p class="class-name" id="kan-name">-</p>
<p class="conf-badge"><span id="kan-conf">-</span>%</p>
<div class="prob-list" id="kan-probs"></div>
</div>
</div>
<div class="interpretation" id="inter-box">
<div class="inter-title" id="inter-title"></div>
<div class="inter-text" id="inter-text"></div>
</div>
</div>
</div>
</section>
<div class="modal" id="classModal">
<div class="modal-box">
<span class="close-btn" onclick="closeModal('classModal')">&times;</span>
<div class="class-modal-emoji" id="c-emoji"></div>
<div class="class-modal-title" id="c-title"></div>
<div class="class-modal-desc" id="c-desc"></div>
</div>
</div>
<div class="modal" id="warningModal">
<div class="modal-box">
<span class="close-btn" onclick="closeModal('warningModal')">&times;</span>
<h2 style="color: #f59e0b; margin-bottom: 10px; font-weight: 700;">⚠️ Kemungkinan Objek Asing</h2>
<p style="color: #475569; font-size: 15px; line-height: 1.6;">Citra ini kemungkinan besar berada di luar 10 kelas CIFAR-10 karena tingkat konfidensi yang rendah. Hasil probabilitas di bawah tetap ditampilkan sebagai referensi.</p>
</div>
</div>
<script>
const dropZone = document.getElementById('drop-zone');
const fileInput = document.getElementById('file-input');
const prompt = document.getElementById('prompt');
const previewContainer = document.getElementById('image-preview-container');
const preview = document.getElementById('image-preview');
const btnChange = document.getElementById('btn-change');
const emptyState = document.getElementById('empty-state');
const loader = document.getElementById('loader');
const results = document.getElementById('results');
dropZone.addEventListener('click', (e) => {
if (e.target !== btnChange) {
fileInput.click();
}
});
btnChange.addEventListener('click', (e) => {
e.stopPropagation();
fileInput.click();
});
fileInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (file) handleFile(file);
});
dropZone.addEventListener('dragover', (e) => {
e.preventDefault();
dropZone.style.borderColor = '#3b82f6';
dropZone.style.backgroundColor = '#f8fafc';
});
dropZone.addEventListener('dragleave', () => {
dropZone.style.borderColor = '#cbd5e0';
dropZone.style.backgroundColor = '#ffffff';
});
dropZone.addEventListener('drop', (e) => {
e.preventDefault();
dropZone.style.borderColor = '#cbd5e0';
dropZone.style.backgroundColor = '#ffffff';
if (e.dataTransfer.files.length) {
fileInput.files = e.dataTransfer.files;
handleFile(e.dataTransfer.files[0]);
}
});
function handleFile(file) {
if (file.type === 'image/jpeg' || file.type === 'image/png') {
const reader = new FileReader();
reader.onload = (e) => {
preview.src = e.target.result;
prompt.style.display = 'none';
previewContainer.style.display = 'flex';
dropZone.style.borderStyle = 'solid';
upload(file);
};
reader.readAsDataURL(file);
}
}
function renderProbs(containerId, probs) {
const container = document.getElementById(containerId);
container.innerHTML = '';
probs.forEach(p => {
let barColor = '#3b82f6';
if(p.confidence > 70) barColor = '#10b981';
else if(p.confidence < 15) barColor = '#94a3b8';
let indonesianClass = p.class;
const mapId = {
'airplane': 'Pesawat', 'automobile': 'Mobil', 'bird': 'Burung', 'cat': 'Kucing',
'deer': 'Rusa', 'dog': 'Anjing', 'frog': 'Katak', 'horse': 'Kuda',
'ship': 'Kapal', 'truck': 'Truk'
};
if(mapId[p.class]) indonesianClass = mapId[p.class];
container.innerHTML += `
<div class="prob-item">
<span class="prob-label">${indonesianClass}</span>
<div class="prob-bar-bg">
<div class="prob-bar-fill" style="width: ${p.confidence}%; background-color: ${barColor};"></div>
</div>
<span class="prob-val">${p.confidence}%</span>
</div>
`;
});
}
function translateClass(enClass) {
const mapId = {
'airplane': 'Pesawat', 'automobile': 'Mobil', 'bird': 'Burung', 'cat': 'Kucing',
'deer': 'Rusa', 'dog': 'Anjing', 'frog': 'Katak', 'horse': 'Kuda',
'ship': 'Kapal', 'truck': 'Truk'
};
return mapId[enClass] || enClass;
}
function upload(file) {
emptyState.style.display = 'none';
results.style.display = 'none';
loader.style.display = 'flex';
const formData = new FormData();
formData.append('file', file);
fetch('/api/predict', {
method: 'POST',
body: formData
})
.then(res => res.json())
.then(data => {
loader.style.display = 'none';
if (data.status === 'error') {
alert("Terjadi kesalahan dari server.");
emptyState.style.display = 'flex';
return;
}
results.style.display = 'flex';
const mlpClass = translateClass(data.model_1.class);
const kanClass = translateClass(data.model_2.class);
document.getElementById('mlp-name').innerText = mlpClass;
document.getElementById('mlp-conf').innerText = data.model_1.confidence;
document.getElementById('kan-name').innerText = kanClass;
document.getElementById('kan-conf').innerText = data.model_2.confidence;
renderProbs('mlp-probs', data.model_1.all_probs);
renderProbs('kan-probs', data.model_2.all_probs);
const interBox = document.getElementById('inter-box');
const it = document.getElementById('inter-title');
const ix = document.getElementById('inter-text');
if(mlpClass === kanClass) {
interBox.style.backgroundColor = '#f0fdf4';
interBox.style.borderLeftColor = '#22c55e';
it.style.color = '#15803d';
ix.style.color = '#166534';
it.innerHTML = 'Konsisten';
ix.innerHTML = `Kedua model bilang bahwa objek ini adalah <strong>${mlpClass}</strong>.`;
} else {
interBox.style.backgroundColor = '#fff7ed';
interBox.style.borderLeftColor = '#f97316';
it.style.color = '#c2410c';
ix.style.color = '#9a3412';
it.innerHTML = 'Perbedaan Prediksi';
ix.innerHTML = `Model ResNet-MLP bilang ini <strong>${mlpClass}</strong>, sedangkan ResNet-KAN bilang ini <strong>${kanClass}</strong>.`;
}
if (data.is_outlier) {
showModal('warningModal');
}
})
.catch(err => {
loader.style.display = 'none';
emptyState.style.display = 'flex';
alert("Gagal terhubung ke server.");
});
}
function openClassModal(title, emoji, desc) {
document.getElementById('c-title').innerText = title;
document.getElementById('c-emoji').innerText = emoji;
document.getElementById('c-desc').innerText = desc;
showModal('classModal');
}
function showModal(id) {
const modal = document.getElementById(id);
modal.style.display = 'flex';
setTimeout(() => modal.classList.add('show'), 10);
}
function closeModal(id) {
const modal = document.getElementById(id);
modal.classList.remove('show');
setTimeout(() => modal.style.display = 'none', 300);
}
window.onclick = function(event) {
if (event.target.classList.contains('modal')) {
closeModal(event.target.id);
}
}
</script>
</body>
</html>