Kode untuk Program Optimisasi Gedung:
<!doctype html>
<html lang="id">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>AI Building Optimization โ Frontend</title>
<style>
:root{--bg:#0b1220;--card:#071229;--accent:#1f6feb;--muted:#9fbbe8;--text:#e6eef8}
body{margin:0;font-family:Inter,system-ui,Arial;background:linear-gradient(180deg,#071029 0%, #031022 100%);color:var(--text);padding:18px}
.wrap{max-width:1100px;margin:0 auto}
header h1{margin:0;font-size:20px}
p.lead{margin:6px 0 18px;color:var(--muted)}
.grid{display:grid;grid-template-columns:1fr 420px;gap:18px}
.card{background:var(--card);padding:14px;border-radius:12px;box-shadow:0 6px 18px rgba(2,6,23,.6)}
.flow{height:460px;overflow:auto;padding:8px;border-radius:10px;background:linear-gradient(90deg,#031026, #041329)}
.controls{display:flex;flex-direction:column;gap:8px}
label{font-size:13px;color:var(--muted)}
input[type="text"], textarea, select {width:100%;padding:8px;border-radius:8px;border:1px solid rgba(255,255,255,0.04);background:transparent;color:var(--text)}
button{background:var(--accent);border:none;color:white;padding:8px 10px;border-radius:8px;cursor:pointer}
.btn-ghost{background:transparent;border:1px solid rgba(255,255,255,0.06)}
.row{display:flex;gap:8px}
.small{font-size:13px;color:var(--muted)}
pre{white-space:pre-wrap;background:#021028;padding:10px;border-radius:8px;color:#cfe8ff;max-height:220px;overflow:auto}
.status{padding:6px 10px;border-radius:8px;background:rgba(255,255,255,0.02);font-size:13px}
.success{color:#8ee68a} .warn{color:#ffd27a} .err{color:#ff9a9a}
.flex{display:flex;align-items:center;gap:8px}
.muted{color:var(--muted)}
</style>
</head>
<body>
<div class="wrap">
<header>
<h1>Front-end: AI Assistant โ Optimisasi Gedung</h1>
<p class="lead">Demo UI untuk alur: buat proyek โ upload โ validasi โ preprocessing โ optimisasi. Mode <strong>Mock</strong> tersedia untuk testing tanpa backend.</p>
</header>
<div class="grid" style="margin-top:12px">
<div class="card">
<div style="display:flex;justify-content:space-between;align-items:center">
<div>
<div class="small muted">Project control</div>
<h3 style="margin:4px 0 10px">Buat / Kelola Proyek</h3>
</div>
<div class="flex">
<label class="small muted">Mock mode</label>
<input id="mockToggle" type="checkbox" checked />
</div>
</div>
<div class="controls" style="margin-top:8px">
<label>Nama proyek</label>
<input id="projName" type="text" placeholder="Contoh: Gedung Kampus A" />
<label>Deskripsi / metadata (JSON singkat)</label>
<textarea id="projMeta" rows="3" placeholder='{"project_name":"Gedung A","target":"energy"}'></textarea>
<label>Upload CSV (opsional) โ format demo: zone,area,u_value,internal_gain</label>
<input id="fileInput" type="file" accept=".csv" />
<div class="row" style="margin-top:6px">
<button id="btnCreate">Buat Project</button>
<button id="btnValidate" class="btn-ghost">Validasi</button>
<button id="btnPreprocess" class="btn-ghost">Preprocess</button>
<button id="btnOptimize" class="btn-ghost">Optimize</button>
</div>
<div style="display:flex;gap:8px;margin-top:8px">
<label class="small muted">API base</label>
<input id="apiBase" type="text" value="http://localhost:5000" />
</div>
<div style="margin-top:8px" class="small muted">
<strong>Project ID:</strong> <span id="projectId">โ</span>
</div>
</div>
<div style="margin-top:12px">
<div class="small muted">Log / Response</div>
<pre id="log">Ready โ gunakan tombol di atas.</pre>
</div>
</div>
<div class="card">
<div style="display:flex;justify-content:space-between;align-items:center">
<div>
<div class="small muted">Flowchart</div>
<h3 style="margin:4px 0 8px">Diagram Alur</h3>
</div>
</div>
<div class="flow" id="flowWrap">
<!-- Inline SVG flowchart copied/adapted from earlier -->
<svg viewBox="0 0 1200 760" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:430px" role="img" aria-label="Flowchart">
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z" fill="#6fb0ff"/>
</marker>
</defs>
<g class="node" id="start" transform="translate(50,40)">
<rect x="0" y="0" width="220" height="50" rx="10" fill="#0b1220" stroke="#1f6feb" stroke-width="1.5"/>
<text x="110" y="24" text-anchor="middle" font-size="13" font-weight="600" fill="#cfe8ff">Mulai / Start</text>
</g>
<g class="node" id="input" transform="translate(320,40)">
<rect x="0" y="0" width="320" height="80" rx="10" fill="#0b1220" stroke="#1f6feb" stroke-width="1.5"/>
<text x="160" y="22" text-anchor="middle" font-size="13" font-weight="600" fill="#cfe8ff">Input dari Pengguna</text>
<text x="160" y="44" text-anchor="middle" font-size="12" fill="#b7d6ff">Denah/BIM, HVAC, material, target</text>
</g>
<path d="M270 65 L320 65" stroke="#6fb0ff" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<g class="node" id="validate" transform="translate(680,40)">
<rect x="0" y="0" width="260" height="80" rx="10" fill="#0b1220" stroke="#1f6feb" stroke-width="1.5"/>
<text x="130" y="22" text-anchor="middle" font-size="13" font-weight="600" fill="#cfe8ff">Validasi Data</text>
<text x="130" y="44" text-anchor="middle" font-size="12" fill="#b7d6ff">Cek format, kelengkapan, unit</text>
</g>
<path d="M520 85 L520 180" stroke="#6fb0ff" stroke-width="2" fill="none" marker-end="url(#arrow)"/>
<g class="node" id="preprocess" transform="translate(50,180)">
<rect x="0" y="0" width="300" height="90" rx="10" fill="#0b1220" stroke="#1f6feb" stroke-width="1.5"/>
<text x="150" y="22" text-anchor="middle" font-size="13" font-weight="600" fill="#cfe8ff">Data Preprocessing</text>
<text x="150" y="44" text-anchor="middle" font-size="12" fill="#b7d6ff">Normalisasi, ekstraksi fitur, zonasi</text>
</g>
<g class="node" id="ai" transform="translate(380,180)">
<rect x="0" y="0" width="420" height="140" rx="10" fill="#0b1220" stroke="#1f6feb" stroke-width="1.5"/>
<text x="210" y="22" text-anchor="middle" font-size="13" font-weight="600" fill="#cfe8ff">Modul Analisis & Prediksi</text>
<text x="210" y="48" text-anchor="middle" font-size="12" fill="#b7d6ff">Energy model, thermal, optimisasi</text>
</g>
<g class="node" id="eval" transform="translate(840,180)">
<rect x="0" y="0" width="300" height="100" rx="10" fill="#0b1220" stroke="#1f6feb" stroke-width="1.5"/>
<text x="150" y="22" text-anchor="middle" font-size="13" font-weight="600" fill="#cfe8ff">Evaluasi & Optimisasi</text>
<text x="150" y="48" text-anchor="middle" font-size="12" fill="#b7d6ff">Simulasi & cari parameter optimal</text>
</g>
<g class="node" id="rec" transform="translate(300,360)">
<rect x="0" y="0" width="600" height="110" rx="10" fill="#0b1220" stroke="#1f6feb" stroke-width="1.5"/>
<text x="300" y="26" text-anchor="middle" font-size="13" font-weight="600" fill="#cfe8ff">Hasil Rekomendasi</text>
<text x="300" y="52" text-anchor="middle" font-size="12" fill="#b7d6ff">Saran HVAC, desain, estimasi penghematan</text>
</g>
<g class="node" id="output" transform="translate(320,500)">
<rect x="0" y="0" width="420" height="80" rx="10" fill="#0b1220" stroke="#1f6feb" stroke-width="1.5"/>
<text x="210" y="26" text-anchor="middle" font-size="13" font-weight="600" fill="#cfe8ff">Output ke Pengguna</text>
<text x="210" y="50" text-anchor="middle" font-size="12" fill="#b7d6ff">Report, grafik, rekomendasi</text>
</g>
<g class="node" id="end" transform="translate(760,500)">
<rect x="0" y="0" width="160" height="60" rx="10" fill="#0b1220" stroke="#1f6feb" stroke-width="1.5"/>
<text x="80" y="32" text-anchor="middle" font-size="13" font-weight="600" fill="#cfe8ff">Selesai</text>
</g>
</svg>
</div>
<div style="margin-top:10px">
<div class="small muted">Klik kotak pada diagram untuk lihat ringkasan di bawah.</div>
<div style="margin-top:8px" id="nodeInfo" class="status">Klik node diagram...</div>
</div>
</div>
</div>
<div style="margin-top:14px" class="card">
<h3 style="margin:0 0 8px">Hasil Ringkasan / Preview</h3>
<div style="display:flex;gap:12px">
<div style="flex:1">
<div class="small muted">Preview fitur (CSV) / Data simulasi</div>
<pre id="preview">Tidak ada preview.</pre>
</div>
<div style="width:280px">
<div class="small muted">Hasil optimisasi</div>
<pre id="resultPane">Belum ada hasil.</pre>
</div>
</div>
</div>
</div>
<script>
/* ---------- Configuration ---------- */
const apiBaseInput = document.getElementById('apiBase');
let API_BASE = apiBaseInput.value.replace(/\/+$/, '');
const MOCK_DEFAULT = true;
/* ---------- Helpers ---------- */
function log(msg){
const el = document.getElementById('log');
el.textContent = (new Date()).toLocaleTimeString() + ' โ ' + msg + "\n\n" + el.textContent;
}
function setProjectId(id){
document.getElementById('projectId').textContent = id || 'โ';
}
function showPreview(txt){ document.getElementById('preview').textContent = txt; }
function showResult(txt){ document.getElementById('resultPane').textContent = txt; }
function showNodeInfo(key){
const map = {
start: 'Mulai: init app & pilih proyek.',
input: 'Input: upload BIM/CSV, masukkan metadata proyek.',
validate: 'Validasi data: format, kolom, unit.',
preprocess: 'Preprocess: normalisasi, fitur per zona.',
ai: 'AI: simulasi energi & prediksi.',
eval: 'Evaluasi: simulasi skenario & ranking solusi.',
rec: 'Rekomendasi: daftar tindakan & estimasi penghematan.',
output: 'Output: laporan, grafik, eksport.',
end: 'Selesai: simpan proyek & versi.'
};
document.getElementById('nodeInfo').textContent = map[key] || '(tidak ada)';
}
/* ---------- UI wiring ---------- */
document.querySelectorAll('.node').forEach(g=>{
g.style.cursor = 'pointer';
g.addEventListener('click', ()=> showNodeInfo(g.id));
});
const mockToggle = document.getElementById('mockToggle');
mockToggle.checked = MOCK_DEFAULT;
document.getElementById('apiBase').addEventListener('change', e=>{
API_BASE = e.target.value.replace(/\/+$/, '');
log('API base: ' + API_BASE);
});
/* ---------- Mock simulation (client-side) ---------- */
function mock_new_project(metadata, fileName){
const id = 'mock-' + Math.random().toString(36).slice(2,9);
return Promise.resolve({project_id: id, status: 'created'});
}
function mock_validate(projId){
return Promise.resolve({ok:true, report:{checks:[['csv_columns',true]]}});
}
function mock_preprocess(projId, file){
// produce fake dataframe preview
const df = [
{zone:'A',area:40,u_value:1.2,internal_gain:200},
{zone:'B',area:30,u_value:0.9,internal_gain:150},
{zone:'C',area:30,u_value:1.5,internal_gain:180}
];
return Promise.resolve({status:'preprocessed', preview:df});
}
function mock_optimize(projId, budget=80){
const best = {
best_config:{insulation_level:2,window_area_factor:0.85,hvac_setpoint:24.0},
best_score:12345,
estimated_energy_kwh: 3456,
estimated_cost: 3200
};
return new Promise(resolve => setTimeout(()=>resolve({status:'done', best}),700));
}
/* ---------- API calls ---------- */
async function api_fetch(path, opts){
const url = API_BASE + path;
const resp = await fetch(url, opts);
if(!resp.ok){
const txt = await resp.text();
throw new Error(`HTTP ${resp.status} - ${txt}`);
}
return resp.json();
}
/* ---------- Buttons logic ---------- */
let CURRENT_PROJECT = null;
let UPLOADED_FILE = null;
document.getElementById('fileInput').addEventListener('change', (e)=>{
UPLOADED_FILE = e.target.files && e.target.files[0];
if(UPLOADED_FILE) log('File siap: ' + UPLOADED_FILE.name);
});
document.getElementById('btnCreate').addEventListener('click', async ()=>{
const name = document.getElementById('projName').value.trim();
const metaRaw = document.getElementById('projMeta').value.trim();
const meta = metaRaw || JSON.stringify({project_name:name || 'Proyek Demo'});
const useMock = mockToggle.checked;
try{
if(useMock){
const res = await mock_new_project(meta, UPLOADED_FILE && UPLOADED_FILE.name);
CURRENT_PROJECT = res.project_id;
setProjectId(CURRENT_PROJECT);
log('Mock: project created: ' + CURRENT_PROJECT);
showPreview('No file preview in mock create.');
showResult('Project created (mock).');
return;
}
// real API: multipart form-data
const form = new FormData();
form.append('metadata', meta);
if(UPLOADED_FILE) form.append('file', UPLOADED_FILE);
const res = await api_fetch('/api/new_project', {method:'POST', body: form});
CURRENT_PROJECT = res.project_id;
setProjectId(CURRENT_PROJECT);
log('Project created: ' + CURRENT_PROJECT);
showResult('Project created on backend.');
}catch(err){
log('ERR create: ' + err.message);
}
});
document.getElementById('btnValidate').addEventListener('click', async ()=>{
if(!CURRENT_PROJECT) return log('Buat project dulu.');
const useMock = mockToggle.checked;
try{
if(useMock){
const res = await mock_validate(CURRENT_PROJECT);
log('Mock validation OK');
showResult(JSON.stringify(res.report, null, 2));
return;
}
const res = await api_fetch(`/api/validate/${CURRENT_PROJECT}`);
log('Validate result: ' + (res.ok? 'OK' : 'INVALID'));
showResult(JSON.stringify(res.report, null, 2));
}catch(err){
log('ERR validate: ' + err.message);
}
});
document.getElementById('btnPreprocess').addEventListener('click', async ()=>{
if(!CURRENT_PROJECT) return log('Buat project dulu.');
const useMock = mockToggle.checked;
try{
if(useMock){
const res = await mock_preprocess(CURRENT_PROJECT);
log('Mock preprocess OK');
showPreview(JSON.stringify(res.preview, null, 2));
return;
}
const res = await api_fetch(`/api/preprocess/${CURRENT_PROJECT}`, {method:'POST'});
log('Preprocess done');
showPreview(JSON.stringify(res.features_preview || res, null, 2));
}catch(err){
log('ERR preprocess: ' + err.message);
}
});
document.getElementById('btnOptimize').addEventListener('click', async ()=>{
if(!CURRENT_PROJECT) return log('Buat project dulu.');
const useMock = mockToggle.checked;
try{
if(useMock){
const res = await mock_optimize(CURRENT_PROJECT, 120);
log('Mock optimize done');
showResult(JSON.stringify(res.best, null, 2));
return;
}
// example payload
const payload = {budget: 120, objectives: {energy:1.0, cost:0.01}};
const res = await api_fetch(`/api/optimize/${CURRENT_PROJECT}`, {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify(payload)
});
log('Optimize done');
showResult(JSON.stringify(res.best, null, 2));
}catch(err){
log('ERR optimize: ' + err.message);
}
});
/* ---------- On load ---------- */
document.addEventListener('DOMContentLoaded', ()=>{
log('UI siap. Mock mode: ' + (mockToggle.checked? 'ON' : 'OFF'));
});
</script>
</body>
</html>