jamnif commited on
Commit
3bd01f6
ยท
verified ยท
1 Parent(s): 4b89406

Upload static/app.js with huggingface_hub

Browse files
Files changed (1) hide show
  1. static/app.js +239 -0
static/app.js ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /**
2
+ * Chronos-2 Zero-Shot Demo - Frontend
3
+ * Real-time input -> debounce -> fetch forecast -> chart update
4
+ * Tab: Forecast | Compare
5
+ */
6
+
7
+ (function () {
8
+ const API_BASE = '';
9
+
10
+ const tsInput = document.getElementById('ts-input');
11
+ const sampleSelect = document.getElementById('sample-select');
12
+ const predStepsInput = document.getElementById('pred-steps');
13
+ const forecastValue = document.getElementById('forecast-value');
14
+ const forecastRange = document.getElementById('forecast-range');
15
+ const loadingEl = document.getElementById('loading');
16
+ const errorEl = document.getElementById('error');
17
+ const downloadBtn = document.getElementById('download-csv');
18
+
19
+ let chart = null;
20
+ let lastResult = null;
21
+ let debounceTimer = null;
22
+
23
+ // ---------------------------------------------------------------------------
24
+ // Tabs
25
+ // ---------------------------------------------------------------------------
26
+ function initTabs() {
27
+ const tabBtns = document.querySelectorAll('.tab-btn');
28
+ const tabContents = document.querySelectorAll('.tab-content');
29
+
30
+ tabBtns.forEach((btn) => {
31
+ btn.addEventListener('click', () => {
32
+ const tabId = btn.getAttribute('data-tab');
33
+ tabBtns.forEach((b) => b.classList.remove('active'));
34
+ tabContents.forEach((c) => {
35
+ c.classList.toggle('active', c.id === 'tab-' + tabId);
36
+ });
37
+ btn.classList.add('active');
38
+ });
39
+ });
40
+ }
41
+ initTabs();
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // Parse
45
+ // ---------------------------------------------------------------------------
46
+ function parseValues(text) {
47
+ if (!text || !text.trim()) return [];
48
+ const parts = text.trim().split(/[\s,;\n]+/);
49
+ const values = [];
50
+ for (const p of parts) {
51
+ const n = parseFloat(p);
52
+ if (!isNaN(n)) values.push(n);
53
+ }
54
+ return values;
55
+ }
56
+
57
+ function debounce(fn, ms) {
58
+ return function () {
59
+ clearTimeout(debounceTimer);
60
+ debounceTimer = setTimeout(fn, ms);
61
+ };
62
+ }
63
+
64
+ async function fetchForecast(values, predictionLength) {
65
+ const res = await fetch(`${API_BASE}/api/forecast`, {
66
+ method: 'POST',
67
+ headers: { 'Content-Type': 'application/json' },
68
+ body: JSON.stringify({ values, prediction_length: predictionLength }),
69
+ });
70
+ if (!res.ok) {
71
+ const err = await res.json().catch(() => ({ detail: res.statusText }));
72
+ const d = err.detail;
73
+ const msg = Array.isArray(d)
74
+ ? d.map((x) => x.msg || JSON.stringify(x)).join('; ')
75
+ : typeof d === 'string'
76
+ ? d
77
+ : JSON.stringify(d || err);
78
+ throw new Error(msg || `HTTP ${res.status}`);
79
+ }
80
+ return res.json();
81
+ }
82
+
83
+ function setLoading(loading) {
84
+ loadingEl.style.display = loading ? 'block' : 'none';
85
+ errorEl.style.display = 'none';
86
+ }
87
+
88
+ function setError(msg) {
89
+ errorEl.textContent = msg;
90
+ errorEl.style.display = msg ? 'block' : 'none';
91
+ loadingEl.style.display = 'none';
92
+ }
93
+
94
+ function updateForecastDisplay(result) {
95
+ downloadBtn.disabled = !result || !result.forecast.length;
96
+ if (!result || !result.forecast.length) {
97
+ forecastValue.textContent = 'โ€”';
98
+ forecastRange.textContent = '';
99
+ return;
100
+ }
101
+ const f = result.forecast[0];
102
+ forecastValue.textContent = f.median.toFixed(2);
103
+ forecastRange.textContent = ` (${f.low.toFixed(1)} ~ ${f.high.toFixed(1)})`;
104
+ if (result.forecast.length > 1) {
105
+ forecastRange.textContent += ` ยท ${result.forecast.length} steps`;
106
+ }
107
+ }
108
+
109
+ function updateChart(historical, forecast) {
110
+ const histLabels = (historical || []).map((_, i) => String(i));
111
+ const histData = (historical || []).map((h) => h.value);
112
+ const forecastLabels = (forecast || []).map((f) => String(f.index));
113
+ const forecastMedians = (forecast || []).map((f) => f.median);
114
+
115
+ const allLabels = histLabels.concat(forecastLabels);
116
+ const histFull = histData.concat(forecastMedians.map(() => null));
117
+ const fcastFull = histLabels.map(() => null).concat(forecastMedians);
118
+
119
+ if (!chart) {
120
+ chart = new Chart(document.getElementById('chart'), {
121
+ type: 'line',
122
+ data: {
123
+ labels: allLabels,
124
+ datasets: [
125
+ {
126
+ label: 'Historical',
127
+ data: histFull,
128
+ borderColor: 'rgb(59, 130, 246)',
129
+ fill: false,
130
+ tension: 0.2,
131
+ },
132
+ {
133
+ label: 'Forecast (median)',
134
+ data: fcastFull,
135
+ borderColor: 'rgb(239, 68, 68)',
136
+ borderDash: [5, 5],
137
+ fill: false,
138
+ tension: 0.2,
139
+ },
140
+ ],
141
+ },
142
+ options: {
143
+ responsive: true,
144
+ maintainAspectRatio: true,
145
+ plugins: { legend: { position: 'top' } },
146
+ scales: {
147
+ x: { title: { display: true, text: 'Index' } },
148
+ y: { title: { display: true, text: 'Value' } },
149
+ },
150
+ },
151
+ });
152
+ } else {
153
+ chart.data.labels = allLabels;
154
+ chart.data.datasets[0].data = histFull;
155
+ chart.data.datasets[1].data = fcastFull;
156
+ chart.update();
157
+ }
158
+ }
159
+
160
+ async function triggerForecast() {
161
+ const values = parseValues(tsInput.value);
162
+ const predictionLength = Math.max(1, parseInt(predStepsInput.value, 10) || 1);
163
+
164
+ if (values.length === 0) {
165
+ lastResult = null;
166
+ updateForecastDisplay(null);
167
+ updateChart([], []);
168
+ setError('');
169
+ return;
170
+ }
171
+
172
+ setLoading(true);
173
+ setError('');
174
+
175
+ try {
176
+ const result = await fetchForecast(values, predictionLength);
177
+ lastResult = result;
178
+ updateForecastDisplay(result);
179
+ updateChart(result.historical, result.forecast);
180
+ } catch (err) {
181
+ setError(err.message || '์˜ˆ์ธก ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.');
182
+ lastResult = null;
183
+ updateForecastDisplay(null);
184
+ updateChart(
185
+ values.map((v, i) => ({ index: i, value: v })),
186
+ []
187
+ );
188
+ } finally {
189
+ setLoading(false);
190
+ }
191
+ }
192
+
193
+ const debouncedForecast = debounce(triggerForecast, 500);
194
+
195
+ async function loadSample(name) {
196
+ try {
197
+ const res = await fetch(`${API_BASE}/static/samples/${name}.json`);
198
+ if (!res.ok) throw new Error('Sample not found');
199
+ const data = await res.json();
200
+ const values = data.values || data;
201
+ tsInput.value = Array.isArray(values) ? values.join(', ') : String(values);
202
+ debouncedForecast();
203
+ } catch (err) {
204
+ setError('์ƒ˜ํ”Œ์„ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.');
205
+ }
206
+ }
207
+
208
+ function downloadCsv() {
209
+ if (!lastResult || !lastResult.forecast.length) return;
210
+ const headers = ['index', 'median', 'low', 'high'];
211
+ const rows = lastResult.forecast.map((f) => [f.index, f.median, f.low, f.high].join(','));
212
+ const csv = [headers.join(','), ...rows].join('\n');
213
+ const blob = new Blob([csv], { type: 'text/csv' });
214
+ const a = document.createElement('a');
215
+ a.href = URL.createObjectURL(blob);
216
+ a.download = 'chronos2_forecast.csv';
217
+ a.click();
218
+ URL.revokeObjectURL(a.href);
219
+ }
220
+
221
+ tsInput.addEventListener('input', debouncedForecast);
222
+ predStepsInput.addEventListener('change', debouncedForecast);
223
+
224
+ sampleSelect.addEventListener('change', function () {
225
+ const v = this.value;
226
+ if (v) {
227
+ loadSample(v);
228
+ this.value = '';
229
+ }
230
+ });
231
+
232
+ downloadBtn.addEventListener('click', downloadCsv);
233
+
234
+ updateForecastDisplay(null);
235
+ updateChart([], []);
236
+
237
+ // ํŽ˜์ด์ง€ ๋กœ๋“œ ์‹œ GFK ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ๋กœ๋“œ
238
+ loadSample('gfk_sample');
239
+ })();