wop commited on
Commit
58342e8
·
verified ·
1 Parent(s): 3e03411

Create static/index.html

Browse files
Files changed (1) hide show
  1. static/index.html +670 -0
static/index.html ADDED
@@ -0,0 +1,670 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Human Essence - Emotional Flow Visualizer</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
16
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
17
+ min-height: 100vh;
18
+ color: #333;
19
+ }
20
+
21
+ .container {
22
+ max-width: 1400px;
23
+ margin: 0 auto;
24
+ padding: 20px;
25
+ }
26
+
27
+ header {
28
+ background: rgba(255, 255, 255, 0.95);
29
+ backdrop-filter: blur(10px);
30
+ border-radius: 15px;
31
+ padding: 30px;
32
+ margin-bottom: 20px;
33
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
34
+ }
35
+
36
+ h1 {
37
+ color: #667eea;
38
+ font-size: 2.5em;
39
+ margin-bottom: 10px;
40
+ text-align: center;
41
+ }
42
+
43
+ .subtitle {
44
+ text-align: center;
45
+ color: #666;
46
+ font-size: 1.1em;
47
+ }
48
+
49
+ .stats-bar {
50
+ display: flex;
51
+ justify-content: space-around;
52
+ margin-top: 20px;
53
+ padding: 15px;
54
+ background: #f8f9fa;
55
+ border-radius: 10px;
56
+ }
57
+
58
+ .stat-item {
59
+ text-align: center;
60
+ }
61
+
62
+ .stat-value {
63
+ font-size: 2em;
64
+ font-weight: bold;
65
+ color: #667eea;
66
+ }
67
+
68
+ .stat-label {
69
+ color: #666;
70
+ font-size: 0.9em;
71
+ }
72
+
73
+ .controls {
74
+ background: rgba(255, 255, 255, 0.95);
75
+ backdrop-filter: blur(10px);
76
+ border-radius: 15px;
77
+ padding: 20px;
78
+ margin-bottom: 20px;
79
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
80
+ }
81
+
82
+ .control-group {
83
+ display: flex;
84
+ gap: 15px;
85
+ align-items: center;
86
+ flex-wrap: wrap;
87
+ }
88
+
89
+ button {
90
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
91
+ color: white;
92
+ border: none;
93
+ padding: 12px 24px;
94
+ border-radius: 8px;
95
+ cursor: pointer;
96
+ font-size: 1em;
97
+ transition: transform 0.2s, box-shadow 0.2s;
98
+ }
99
+
100
+ button:hover {
101
+ transform: translateY(-2px);
102
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
103
+ }
104
+
105
+ button:disabled {
106
+ opacity: 0.5;
107
+ cursor: not-allowed;
108
+ }
109
+
110
+ input[type="number"] {
111
+ padding: 10px;
112
+ border: 2px solid #ddd;
113
+ border-radius: 8px;
114
+ font-size: 1em;
115
+ width: 100px;
116
+ }
117
+
118
+ select {
119
+ padding: 10px;
120
+ border: 2px solid #ddd;
121
+ border-radius: 8px;
122
+ font-size: 1em;
123
+ min-width: 200px;
124
+ }
125
+
126
+ .entries-list {
127
+ background: rgba(255, 255, 255, 0.95);
128
+ backdrop-filter: blur(10px);
129
+ border-radius: 15px;
130
+ padding: 20px;
131
+ margin-bottom: 20px;
132
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
133
+ }
134
+
135
+ .entry-item {
136
+ padding: 15px;
137
+ margin-bottom: 10px;
138
+ background: #f8f9fa;
139
+ border-radius: 10px;
140
+ cursor: pointer;
141
+ transition: all 0.3s;
142
+ border-left: 4px solid transparent;
143
+ }
144
+
145
+ .entry-item:hover {
146
+ background: #e9ecef;
147
+ border-left-color: #667eea;
148
+ transform: translateX(5px);
149
+ }
150
+
151
+ .entry-item.active {
152
+ background: #667eea;
153
+ color: white;
154
+ border-left-color: #764ba2;
155
+ }
156
+
157
+ .entry-preview {
158
+ font-size: 0.9em;
159
+ overflow: hidden;
160
+ text-overflow: ellipsis;
161
+ white-space: nowrap;
162
+ }
163
+
164
+ .visualization {
165
+ background: rgba(255, 255, 255, 0.95);
166
+ backdrop-filter: blur(10px);
167
+ border-radius: 15px;
168
+ padding: 30px;
169
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
170
+ min-height: 400px;
171
+ }
172
+
173
+ .text-display {
174
+ font-size: 1.2em;
175
+ line-height: 1.8;
176
+ margin-bottom: 30px;
177
+ padding: 20px;
178
+ background: #f8f9fa;
179
+ border-radius: 10px;
180
+ }
181
+
182
+ .word {
183
+ display: inline-block;
184
+ padding: 2px 4px;
185
+ margin: 2px;
186
+ border-radius: 4px;
187
+ transition: all 0.3s;
188
+ cursor: pointer;
189
+ }
190
+
191
+ .word:hover {
192
+ transform: scale(1.1);
193
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
194
+ }
195
+
196
+ .emotion-chart {
197
+ margin-top: 30px;
198
+ }
199
+
200
+ .chart-container {
201
+ position: relative;
202
+ height: 300px;
203
+ margin-bottom: 20px;
204
+ }
205
+
206
+ canvas {
207
+ width: 100%;
208
+ height: 100%;
209
+ }
210
+
211
+ .emotion-legend {
212
+ display: flex;
213
+ flex-wrap: wrap;
214
+ gap: 15px;
215
+ margin-top: 20px;
216
+ }
217
+
218
+ .legend-item {
219
+ display: flex;
220
+ align-items: center;
221
+ gap: 8px;
222
+ }
223
+
224
+ .legend-color {
225
+ width: 20px;
226
+ height: 20px;
227
+ border-radius: 4px;
228
+ }
229
+
230
+ .loading {
231
+ text-align: center;
232
+ padding: 40px;
233
+ color: #666;
234
+ }
235
+
236
+ .error {
237
+ background: #fee;
238
+ color: #c33;
239
+ padding: 15px;
240
+ border-radius: 8px;
241
+ margin: 10px 0;
242
+ }
243
+
244
+ .pagination {
245
+ display: flex;
246
+ justify-content: center;
247
+ gap: 10px;
248
+ margin-top: 20px;
249
+ }
250
+
251
+ @keyframes fadeIn {
252
+ from { opacity: 0; transform: translateY(20px); }
253
+ to { opacity: 1; transform: translateY(0); }
254
+ }
255
+
256
+ .fade-in {
257
+ animation: fadeIn 0.5s ease-out;
258
+ }
259
+ </style>
260
+ </head>
261
+ <body>
262
+ <div class="container">
263
+ <header>
264
+ <h1>🎭 Human Essence</h1>
265
+ <p class="subtitle">Emotional Flow Visualizer</p>
266
+ <div class="stats-bar" id="statsBar">
267
+ <div class="stat-item">
268
+ <div class="stat-value" id="totalEntries">-</div>
269
+ <div class="stat-label">Total Entries</div>
270
+ </div>
271
+ <div class="stat-item">
272
+ <div class="stat-value" id="withText">-</div>
273
+ <div class="stat-label">With Text</div>
274
+ </div>
275
+ <div class="stat-item">
276
+ <div class="stat-value" id="withEmotions">-</div>
277
+ <div class="stat-label">With Emotions</div>
278
+ </div>
279
+ </div>
280
+ </header>
281
+
282
+ <div class="controls">
283
+ <div class="control-group">
284
+ <button onclick="loadStats()">📊 Load Stats</button>
285
+ <button onclick="loadEntries()">📝 Load Entries</button>
286
+ <select id="emotionFilter" onchange="filterByEmotion()">
287
+ <option value="">All Emotions</option>
288
+ </select>
289
+ <input type="number" id="limitInput" value="50" min="1" max="500" placeholder="Limit">
290
+ <button onclick="previousPage()" id="prevBtn">← Previous</button>
291
+ <span id="pageInfo">Page 1</span>
292
+ <button onclick="nextPage()" id="nextBtn">Next →</button>
293
+ </div>
294
+ </div>
295
+
296
+ <div class="entries-list" id="entriesList">
297
+ <div class="loading">Click "Load Entries" to start exploring</div>
298
+ </div>
299
+
300
+ <div class="visualization" id="visualization">
301
+ <div class="loading">Select an entry to visualize its emotional flow</div>
302
+ </div>
303
+ </div>
304
+
305
+ <script>
306
+ let currentOffset = 0;
307
+ let currentLimit = 50;
308
+ let totalEntries = 0;
309
+ let selectedEntryIndex = null;
310
+ let entries = [];
311
+ let emotionColors = {};
312
+ let availableEmotions = new Set();
313
+
314
+ // Color palette for emotions
315
+ const emotionPalette = [
316
+ '#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8',
317
+ '#F7DC6F', '#BB8FCE', '#85C1E2', '#F8B739', '#52B788',
318
+ '#E63946', '#A8DADC', '#457B9D', '#1D3557', '#F4A261',
319
+ '#2A9D8F', '#E9C46A', '#F4A261', '#E76F51', '#264653'
320
+ ];
321
+
322
+ function getColorForEmotion(emotion) {
323
+ if (!emotionColors[emotion]) {
324
+ const index = Object.keys(emotionColors).length % emotionPalette.length;
325
+ emotionColors[emotion] = emotionPalette[index];
326
+ }
327
+ return emotionColors[emotion];
328
+ }
329
+
330
+ async function loadStats() {
331
+ try {
332
+ const response = await fetch('/api/stats');
333
+ const stats = await response.json();
334
+
335
+ document.getElementById('totalEntries').textContent = stats.total_entries;
336
+ document.getElementById('withText').textContent = stats.entries_with_text;
337
+ document.getElementById('withEmotions').textContent = stats.entries_with_emotions;
338
+
339
+ // Update emotion filter dropdown
340
+ const filterSelect = document.getElementById('emotionFilter');
341
+ filterSelect.innerHTML = '<option value="">All Emotions</option>';
342
+
343
+ for (const [emotion, count] of Object.entries(stats.emotion_distribution)) {
344
+ availableEmotions.add(emotion);
345
+ const option = document.createElement('option');
346
+ option.value = emotion;
347
+ option.textContent = `${emotion} (${count})`;
348
+ filterSelect.appendChild(option);
349
+ }
350
+ } catch (error) {
351
+ console.error('Error loading stats:', error);
352
+ }
353
+ }
354
+
355
+ async function loadEntries() {
356
+ const limit = parseInt(document.getElementById('limitInput').value) || 50;
357
+ currentLimit = limit;
358
+
359
+ try {
360
+ const response = await fetch(`/api/dataset?limit=${limit}&offset=${currentOffset}`);
361
+ const data = await response.json();
362
+
363
+ entries = data.entries;
364
+ totalEntries = data.total;
365
+
366
+ displayEntries(entries);
367
+ updatePagination();
368
+ } catch (error) {
369
+ console.error('Error loading entries:', error);
370
+ document.getElementById('entriesList').innerHTML =
371
+ '<div class="error">Failed to load entries. Please try again.</div>';
372
+ }
373
+ }
374
+
375
+ function displayEntries(entriesToDisplay) {
376
+ const container = document.getElementById('entriesList');
377
+
378
+ if (entriesToDisplay.length === 0) {
379
+ container.innerHTML = '<div class="loading">No entries found</div>';
380
+ return;
381
+ }
382
+
383
+ container.innerHTML = entriesToDisplay.map((entry, idx) => {
384
+ const globalIdx = currentOffset + idx;
385
+ const preview = entry.text ? entry.text.substring(0, 100) : '(empty)';
386
+ const hasEmotions = entry.emotional_flow && entry.emotional_flow.length > 0;
387
+
388
+ return `
389
+ <div class="entry-item ${selectedEntryIndex === globalIdx ? 'active' : ''}"
390
+ onclick="selectEntry(${globalIdx})">
391
+ <div class="entry-preview">${preview}${entry.text && entry.text.length > 100 ? '...' : ''}</div>
392
+ ${hasEmotions ? `<small>${entry.emotional_flow.length} emotion(s)</small>` : ''}
393
+ </div>
394
+ `;
395
+ }).join('');
396
+ }
397
+
398
+ function selectEntry(index) {
399
+ selectedEntryIndex = index;
400
+ const entry = entries[index - currentOffset];
401
+
402
+ if (!entry) return;
403
+
404
+ // Update active state in list
405
+ document.querySelectorAll('.entry-item').forEach((item, idx) => {
406
+ item.classList.toggle('active', currentOffset + idx === index);
407
+ });
408
+
409
+ visualizeEntry(entry);
410
+ }
411
+
412
+ function visualizeEntry(entry) {
413
+ const container = document.getElementById('visualization');
414
+
415
+ if (!entry.text) {
416
+ container.innerHTML = '<div class="loading">This entry has no text</div>';
417
+ return;
418
+ }
419
+
420
+ // Split text into words
421
+ const words = entry.text.split(/\\s+/);
422
+
423
+ // Build word display with emotion coloring
424
+ let wordHTML = '';
425
+ const emotionData = {};
426
+
427
+ if (entry.emotional_flow && entry.emotional_flow.length > 0) {
428
+ // Collect all emotion data by word index
429
+ entry.emotional_flow.forEach(flow => {
430
+ const color = getColorForEmotion(flow.label);
431
+
432
+ flow.curves.forEach(curve => {
433
+ for (let i = curve.start_word; i <= curve.end_word; i++) {
434
+ if (!emotionData[i]) {
435
+ emotionData[i] = [];
436
+ }
437
+ emotionData[i].push({
438
+ label: flow.label,
439
+ intensity: curve.peak_intensity,
440
+ color: color
441
+ });
442
+ }
443
+ });
444
+ });
445
+ }
446
+
447
+ // Create word spans with styling
448
+ wordHTML = words.map((word, idx) => {
449
+ const emotions = emotionData[idx];
450
+ let style = '';
451
+ let title = word;
452
+
453
+ if (emotions && emotions.length > 0) {
454
+ // Use the strongest emotion for this word
455
+ const strongest = emotions.reduce((max, e) =>
456
+ e.intensity > max.intensity ? e : max, emotions[0]);
457
+
458
+ const opacity = 0.3 + (strongest.intensity * 0.7);
459
+ style = `background-color: ${strongest.color}${Math.round(opacity * 255).toString(16).padStart(2, '0')};
460
+ border: 2px solid ${strongest.color};`;
461
+ title = `${word}\\n${emotions.map(e => `${e.label}: ${(e.intensity * 100).toFixed(1)}%`).join('\\n')}`;
462
+ }
463
+
464
+ return `<span class="word" style="${style}" title="${title}">${word}</span>`;
465
+ }).join(' ');
466
+
467
+ // Create charts for each emotion
468
+ let chartsHTML = '';
469
+ if (entry.emotional_flow && entry.emotional_flow.length > 0) {
470
+ chartsHTML = '<div class="emotion-chart">';
471
+ entry.emotional_flow.forEach((flow, flowIdx) => {
472
+ const color = getColorForEmotion(flow.label);
473
+ chartsHTML += `
474
+ <h3 style="color: ${color}; margin-bottom: 10px;">${flow.label}</h3>
475
+ <div class="chart-container">
476
+ <canvas id="chart-${flowIdx}"></canvas>
477
+ </div>
478
+ `;
479
+ });
480
+ chartsHTML += '</div>';
481
+
482
+ // Add legend
483
+ chartsHTML += '<div class="emotion-legend">';
484
+ entry.emotional_flow.forEach(flow => {
485
+ const color = getColorForEmotion(flow.label);
486
+ chartsHTML += `
487
+ <div class="legend-item">
488
+ <div class="legend-color" style="background-color: ${color}"></div>
489
+ <span>${flow.label}</span>
490
+ </div>
491
+ `;
492
+ });
493
+ chartsHTML += '</div>';
494
+ }
495
+
496
+ container.innerHTML = `
497
+ <div class="fade-in">
498
+ <h2 style="margin-bottom: 20px; color: #667eea;">Text Analysis</h2>
499
+ <div class="text-display">${wordHTML}</div>
500
+ ${chartsHTML}
501
+ </div>
502
+ `;
503
+
504
+ // Draw charts after DOM is updated
505
+ if (entry.emotional_flow && entry.emotional_flow.length > 0) {
506
+ setTimeout(() => {
507
+ entry.emotional_flow.forEach((flow, flowIdx) => {
508
+ drawChart(`chart-${flowIdx}`, flow);
509
+ });
510
+ }, 100);
511
+ }
512
+ }
513
+
514
+ function drawChart(canvasId, flow) {
515
+ const canvas = document.getElementById(canvasId);
516
+ if (!canvas) return;
517
+
518
+ const ctx = canvas.getContext('2d');
519
+ const rect = canvas.parentElement.getBoundingClientRect();
520
+ canvas.width = rect.width;
521
+ canvas.height = rect.height;
522
+
523
+ const color = getColorForEmotion(flow.label);
524
+ const curves = flow.curves;
525
+
526
+ if (curves.length === 0) return;
527
+
528
+ // Find max word index
529
+ const maxWord = Math.max(...curves.map(c => c.end_word));
530
+ const padding = 40;
531
+ const chartWidth = canvas.width - padding * 2;
532
+ const chartHeight = canvas.height - padding * 2;
533
+
534
+ // Clear canvas
535
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
536
+
537
+ // Draw axes
538
+ ctx.strokeStyle = '#ddd';
539
+ ctx.lineWidth = 1;
540
+ ctx.beginPath();
541
+ ctx.moveTo(padding, padding);
542
+ ctx.lineTo(padding, canvas.height - padding);
543
+ ctx.lineTo(canvas.width - padding, canvas.height - padding);
544
+ ctx.stroke();
545
+
546
+ // Draw grid lines
547
+ ctx.strokeStyle = '#eee';
548
+ for (let i = 0; i <= 4; i++) {
549
+ const y = padding + (chartHeight * i / 4);
550
+ ctx.beginPath();
551
+ ctx.moveTo(padding, y);
552
+ ctx.lineTo(canvas.width - padding, y);
553
+ ctx.stroke();
554
+
555
+ // Y-axis labels
556
+ ctx.fillStyle = '#666';
557
+ ctx.font = '12px Arial';
558
+ ctx.textAlign = 'right';
559
+ ctx.fillText((1 - i/4).toFixed(1), padding - 10, y + 4);
560
+ }
561
+
562
+ // Draw intensity curve
563
+ ctx.strokeStyle = color;
564
+ ctx.lineWidth = 3;
565
+ ctx.beginPath();
566
+
567
+ curves.forEach((curve, idx) => {
568
+ const x = padding + (curve.start_word / maxWord) * chartWidth;
569
+ const y = padding + chartHeight - (curve.peak_intensity * chartHeight);
570
+
571
+ if (idx === 0) {
572
+ ctx.moveTo(x, y);
573
+ } else {
574
+ ctx.lineTo(x, y);
575
+ }
576
+ });
577
+
578
+ ctx.stroke();
579
+
580
+ // Draw points
581
+ curves.forEach(curve => {
582
+ const x = padding + (curve.start_word / maxWord) * chartWidth;
583
+ const y = padding + chartHeight - (curve.peak_intensity * chartHeight);
584
+
585
+ ctx.fillStyle = color;
586
+ ctx.beginPath();
587
+ ctx.arc(x, y, 5, 0, Math.PI * 2);
588
+ ctx.fill();
589
+
590
+ // Add word index label
591
+ ctx.fillStyle = '#666';
592
+ ctx.font = '10px Arial';
593
+ ctx.textAlign = 'center';
594
+ ctx.fillText(curve.start_word, x, canvas.height - padding + 15);
595
+ });
596
+
597
+ // X-axis label
598
+ ctx.fillStyle = '#666';
599
+ ctx.font = '12px Arial';
600
+ ctx.textAlign = 'center';
601
+ ctx.fillText('Word Index', canvas.width / 2, canvas.height - 10);
602
+
603
+ // Y-axis label
604
+ ctx.save();
605
+ ctx.translate(15, canvas.height / 2);
606
+ ctx.rotate(-Math.PI / 2);
607
+ ctx.fillText('Intensity', 0, 0);
608
+ ctx.restore();
609
+ }
610
+
611
+ function filterByEmotion() {
612
+ const filter = document.getElementById('emotionFilter').value;
613
+ currentOffset = 0;
614
+
615
+ if (!filter) {
616
+ loadEntries();
617
+ return;
618
+ }
619
+
620
+ // Client-side filtering for now
621
+ const filtered = entries.filter(entry =>
622
+ entry.emotional_flow &&
623
+ entry.emotional_flow.some(flow => flow.label === filter)
624
+ );
625
+
626
+ displayEntries(filtered);
627
+ document.getElementById('pageInfo').textContent = 'Filtered View';
628
+ }
629
+
630
+ function updatePagination() {
631
+ const currentPage = Math.floor(currentOffset / currentLimit) + 1;
632
+ const totalPages = Math.ceil(totalEntries / currentLimit);
633
+
634
+ document.getElementById('pageInfo').textContent =
635
+ `Page ${currentPage} of ${totalPages}`;
636
+ document.getElementById('prevBtn').disabled = currentOffset === 0;
637
+ document.getElementById('nextBtn').disabled = currentOffset + currentLimit >= totalEntries;
638
+ }
639
+
640
+ function previousPage() {
641
+ if (currentOffset > 0) {
642
+ currentOffset -= currentLimit;
643
+ loadEntries();
644
+ }
645
+ }
646
+
647
+ function nextPage() {
648
+ if (currentOffset + currentLimit < totalEntries) {
649
+ currentOffset += currentLimit;
650
+ loadEntries();
651
+ }
652
+ }
653
+
654
+ // Load stats on page load
655
+ window.addEventListener('load', () => {
656
+ loadStats();
657
+ });
658
+
659
+ // Handle window resize for charts
660
+ window.addEventListener('resize', () => {
661
+ if (selectedEntryIndex !== null) {
662
+ const entry = entries[selectedEntryIndex - currentOffset];
663
+ if (entry) {
664
+ visualizeEntry(entry);
665
+ }
666
+ }
667
+ });
668
+ </script>
669
+ </body>
670
+ </html>