jamnif's picture
Upload static/app.js with huggingface_hub
3bd01f6 verified
/**
* Chronos-2 Zero-Shot Demo - Frontend
* Real-time input -> debounce -> fetch forecast -> chart update
* Tab: Forecast | Compare
*/
(function () {
const API_BASE = '';
const tsInput = document.getElementById('ts-input');
const sampleSelect = document.getElementById('sample-select');
const predStepsInput = document.getElementById('pred-steps');
const forecastValue = document.getElementById('forecast-value');
const forecastRange = document.getElementById('forecast-range');
const loadingEl = document.getElementById('loading');
const errorEl = document.getElementById('error');
const downloadBtn = document.getElementById('download-csv');
let chart = null;
let lastResult = null;
let debounceTimer = null;
// ---------------------------------------------------------------------------
// Tabs
// ---------------------------------------------------------------------------
function initTabs() {
const tabBtns = document.querySelectorAll('.tab-btn');
const tabContents = document.querySelectorAll('.tab-content');
tabBtns.forEach((btn) => {
btn.addEventListener('click', () => {
const tabId = btn.getAttribute('data-tab');
tabBtns.forEach((b) => b.classList.remove('active'));
tabContents.forEach((c) => {
c.classList.toggle('active', c.id === 'tab-' + tabId);
});
btn.classList.add('active');
});
});
}
initTabs();
// ---------------------------------------------------------------------------
// Parse
// ---------------------------------------------------------------------------
function parseValues(text) {
if (!text || !text.trim()) return [];
const parts = text.trim().split(/[\s,;\n]+/);
const values = [];
for (const p of parts) {
const n = parseFloat(p);
if (!isNaN(n)) values.push(n);
}
return values;
}
function debounce(fn, ms) {
return function () {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(fn, ms);
};
}
async function fetchForecast(values, predictionLength) {
const res = await fetch(`${API_BASE}/api/forecast`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ values, prediction_length: predictionLength }),
});
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: res.statusText }));
const d = err.detail;
const msg = Array.isArray(d)
? d.map((x) => x.msg || JSON.stringify(x)).join('; ')
: typeof d === 'string'
? d
: JSON.stringify(d || err);
throw new Error(msg || `HTTP ${res.status}`);
}
return res.json();
}
function setLoading(loading) {
loadingEl.style.display = loading ? 'block' : 'none';
errorEl.style.display = 'none';
}
function setError(msg) {
errorEl.textContent = msg;
errorEl.style.display = msg ? 'block' : 'none';
loadingEl.style.display = 'none';
}
function updateForecastDisplay(result) {
downloadBtn.disabled = !result || !result.forecast.length;
if (!result || !result.forecast.length) {
forecastValue.textContent = 'β€”';
forecastRange.textContent = '';
return;
}
const f = result.forecast[0];
forecastValue.textContent = f.median.toFixed(2);
forecastRange.textContent = ` (${f.low.toFixed(1)} ~ ${f.high.toFixed(1)})`;
if (result.forecast.length > 1) {
forecastRange.textContent += ` Β· ${result.forecast.length} steps`;
}
}
function updateChart(historical, forecast) {
const histLabels = (historical || []).map((_, i) => String(i));
const histData = (historical || []).map((h) => h.value);
const forecastLabels = (forecast || []).map((f) => String(f.index));
const forecastMedians = (forecast || []).map((f) => f.median);
const allLabels = histLabels.concat(forecastLabels);
const histFull = histData.concat(forecastMedians.map(() => null));
const fcastFull = histLabels.map(() => null).concat(forecastMedians);
if (!chart) {
chart = new Chart(document.getElementById('chart'), {
type: 'line',
data: {
labels: allLabels,
datasets: [
{
label: 'Historical',
data: histFull,
borderColor: 'rgb(59, 130, 246)',
fill: false,
tension: 0.2,
},
{
label: 'Forecast (median)',
data: fcastFull,
borderColor: 'rgb(239, 68, 68)',
borderDash: [5, 5],
fill: false,
tension: 0.2,
},
],
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: { legend: { position: 'top' } },
scales: {
x: { title: { display: true, text: 'Index' } },
y: { title: { display: true, text: 'Value' } },
},
},
});
} else {
chart.data.labels = allLabels;
chart.data.datasets[0].data = histFull;
chart.data.datasets[1].data = fcastFull;
chart.update();
}
}
async function triggerForecast() {
const values = parseValues(tsInput.value);
const predictionLength = Math.max(1, parseInt(predStepsInput.value, 10) || 1);
if (values.length === 0) {
lastResult = null;
updateForecastDisplay(null);
updateChart([], []);
setError('');
return;
}
setLoading(true);
setError('');
try {
const result = await fetchForecast(values, predictionLength);
lastResult = result;
updateForecastDisplay(result);
updateChart(result.historical, result.forecast);
} catch (err) {
setError(err.message || '예츑 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.');
lastResult = null;
updateForecastDisplay(null);
updateChart(
values.map((v, i) => ({ index: i, value: v })),
[]
);
} finally {
setLoading(false);
}
}
const debouncedForecast = debounce(triggerForecast, 500);
async function loadSample(name) {
try {
const res = await fetch(`${API_BASE}/static/samples/${name}.json`);
if (!res.ok) throw new Error('Sample not found');
const data = await res.json();
const values = data.values || data;
tsInput.value = Array.isArray(values) ? values.join(', ') : String(values);
debouncedForecast();
} catch (err) {
setError('μƒ˜ν”Œμ„ 뢈러올 수 μ—†μŠ΅λ‹ˆλ‹€.');
}
}
function downloadCsv() {
if (!lastResult || !lastResult.forecast.length) return;
const headers = ['index', 'median', 'low', 'high'];
const rows = lastResult.forecast.map((f) => [f.index, f.median, f.low, f.high].join(','));
const csv = [headers.join(','), ...rows].join('\n');
const blob = new Blob([csv], { type: 'text/csv' });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = 'chronos2_forecast.csv';
a.click();
URL.revokeObjectURL(a.href);
}
tsInput.addEventListener('input', debouncedForecast);
predStepsInput.addEventListener('change', debouncedForecast);
sampleSelect.addEventListener('change', function () {
const v = this.value;
if (v) {
loadSample(v);
this.value = '';
}
});
downloadBtn.addEventListener('click', downloadCsv);
updateForecastDisplay(null);
updateChart([], []);
// νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ GFK μƒ˜ν”Œ 데이터λ₯Ό κΈ°λ³Έκ°’μœΌλ‘œ λ‘œλ“œ
loadSample('gfk_sample');
})();