wop commited on
Commit
4499469
·
verified ·
1 Parent(s): 488d8f3

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +969 -820
index.html CHANGED
@@ -3,8 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Self-Distillation Dataset Generator</title>
7
- <script src="https://cdn.jsdelivr.net/npm/papaparse@5.4.1/papaparse.min.js"></script>
8
  <style>
9
  * {
10
  margin: 0;
@@ -12,1026 +11,1176 @@
12
  box-sizing: border-box;
13
  }
14
 
 
 
 
 
 
 
 
 
 
 
 
15
  body {
16
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
17
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 
18
  min-height: 100vh;
19
- padding: 20px;
20
- color: #333;
 
21
  }
22
 
23
  .container {
24
  max-width: 1400px;
25
  margin: 0 auto;
26
- display: grid;
27
- grid-template-columns: 1fr 1fr;
 
 
 
28
  gap: 20px;
29
  }
30
 
31
- @media (max-width: 1024px) {
32
- .container {
33
- grid-template-columns: 1fr;
34
- }
35
  }
36
 
37
- .panel {
38
- background: white;
39
- border-radius: 12px;
40
- padding: 24px;
41
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
 
42
  }
43
 
44
- .settings-panel {
45
- background: #f8f9fa;
46
- border: 2px solid #e0e0e0;
 
47
  }
48
 
49
- h1 {
50
- color: white;
51
- margin-bottom: 20px;
52
- grid-column: 1 / -1;
53
- text-align: center;
54
- font-size: 2.5em;
55
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.2);
56
  }
57
 
58
- h2 {
59
- color: #667eea;
60
- margin-bottom: 16px;
61
- font-size: 1.3em;
62
- border-bottom: 3px solid #667eea;
63
- padding-bottom: 8px;
 
 
64
  }
65
 
66
- .form-group {
67
- margin-bottom: 16px;
68
  }
69
 
70
- label {
71
- display: block;
72
- margin-bottom: 6px;
73
- font-weight: 600;
74
- color: #555;
75
  }
76
 
77
- input[type="text"],
78
- input[type="password"],
79
- textarea {
80
  width: 100%;
81
- padding: 10px 12px;
82
- border: 2px solid #ddd;
83
- border-radius: 6px;
84
- font-size: 14px;
85
- transition: all 0.3s ease;
86
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
87
- }
88
-
89
- input[type="text"]:focus,
90
- input[type="password"]:focus,
91
- textarea:focus {
92
  outline: none;
93
- border-color: #667eea;
94
- box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
 
 
 
95
  }
96
 
97
- textarea {
98
- resize: vertical;
99
- min-height: 200px;
100
- font-size: 16px;
101
  }
102
 
103
- .emotion-selector {
104
- display: grid;
105
- grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
106
- gap: 10px;
107
- margin-top: 12px;
 
 
 
 
108
  }
109
 
110
- .emotion-btn {
111
- padding: 12px;
112
- border: 2px solid #ddd;
113
- border-radius: 8px;
114
- background: white;
115
- cursor: pointer;
116
- font-size: 14px;
117
- font-weight: 600;
118
- transition: all 0.2s ease;
119
  display: flex;
120
- flex-direction: column;
121
  align-items: center;
122
- gap: 6px;
123
- position: relative;
124
- }
125
-
126
- .emotion-btn:hover {
127
- border-color: #667eea;
128
- transform: translateY(-2px);
129
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
130
  }
131
 
132
- .emotion-btn.active {
133
- background: #667eea;
134
- color: white;
135
- border-color: #667eea;
136
  }
137
 
138
- .emotion-intensity {
139
- display: none;
140
- margin-top: 8px;
141
- padding: 8px;
142
- background: #f0f0f0;
 
143
  border-radius: 6px;
144
- }
145
-
146
- .emotion-btn.active + .emotion-intensity {
147
- display: block;
148
- }
149
-
150
- .intensity-slider {
151
- width: 100%;
152
- height: 6px;
153
- border-radius: 3px;
154
- background: #ddd;
155
  outline: none;
156
- -webkit-appearance: none;
157
  }
158
 
159
- .intensity-slider::-webkit-slider-thumb {
160
- -webkit-appearance: none;
161
- appearance: none;
162
- width: 18px;
163
- height: 18px;
164
- border-radius: 50%;
165
- background: #667eea;
166
- cursor: pointer;
167
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
168
  }
169
 
170
- .intensity-slider::-moz-range-thumb {
171
- width: 18px;
172
- height: 18px;
173
- border-radius: 50%;
174
- background: #667eea;
175
- cursor: pointer;
176
- border: none;
177
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
178
  }
179
 
180
- .intensity-value {
181
  display: flex;
182
  justify-content: space-between;
183
- margin-top: 6px;
184
- font-size: 12px;
185
- color: #666;
186
- }
187
-
188
- .selected-emotions {
189
- margin-top: 16px;
190
- padding: 12px;
191
- background: #f0f0f0;
192
- border-radius: 6px;
193
- min-height: 40px;
194
  }
195
 
196
- .emotion-tag {
197
- display: inline-block;
198
- background: #667eea;
199
- color: white;
200
- padding: 6px 12px;
201
- border-radius: 20px;
202
- margin: 4px;
203
- font-size: 12px;
204
- font-weight: 600;
205
  }
206
 
207
- .emotion-tag .intensity {
208
- opacity: 0.8;
209
- margin-left: 4px;
210
  }
211
 
212
- .emotion-tag .remove {
 
 
 
 
 
213
  cursor: pointer;
214
- margin-left: 6px;
215
- font-weight: bold;
216
  }
217
 
218
- button {
219
- padding: 12px 20px;
220
- border: none;
221
- border-radius: 6px;
222
- font-size: 14px;
223
- font-weight: 600;
224
- cursor: pointer;
225
- transition: all 0.3s ease;
226
  }
227
 
228
  .btn-primary {
229
- background: #667eea;
230
- color: white;
231
- width: 100%;
232
  }
233
 
234
  .btn-primary:hover {
235
- background: #5568d3;
236
- transform: translateY(-2px);
237
- box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
238
- }
239
-
240
- .btn-primary:disabled {
241
- background: #ccc;
242
- cursor: not-allowed;
243
- transform: none;
244
- }
245
-
246
- .btn-secondary {
247
- background: #f0f0f0;
248
- color: #333;
249
- border: 2px solid #ddd;
250
- }
251
-
252
- .btn-secondary:hover {
253
- background: #e0e0e0;
254
- }
255
-
256
- .btn-success {
257
- background: #48bb78;
258
- color: white;
259
  }
260
 
261
- .btn-success:hover {
262
- background: #38a169;
263
- }
264
-
265
- .btn-danger {
266
- background: #f56565;
267
- color: white;
268
- }
269
-
270
- .btn-danger:hover {
271
- background: #e53e3e;
272
- }
273
-
274
- .button-group {
275
- display: flex;
276
- gap: 10px;
277
- margin-top: 16px;
278
- }
279
-
280
- .button-group button {
281
- flex: 1;
282
- }
283
-
284
- .status-message {
285
- padding: 12px;
286
- border-radius: 6px;
287
- margin-bottom: 16px;
288
- display: none;
289
- animation: slideIn 0.3s ease;
290
- }
291
-
292
- @keyframes slideIn {
293
- from {
294
- opacity: 0;
295
- transform: translateY(-10px);
296
- }
297
- to {
298
- opacity: 1;
299
- transform: translateY(0);
300
- }
301
  }
302
 
303
- .status-message.show {
 
 
304
  display: block;
305
  }
306
 
307
- .status-message.success {
308
- background: #c6f6d5;
309
- color: #22543d;
310
- border-left: 4px solid #48bb78;
311
- }
312
-
313
- .status-message.error {
314
- background: #fed7d7;
315
- color: #742a2a;
316
- border-left: 4px solid #f56565;
317
  }
318
 
319
- .status-message.info {
320
- background: #bee3f8;
321
- color: #2c5282;
322
- border-left: 4px solid #4299e1;
 
 
 
 
323
  }
324
 
325
- .preview-panel {
326
- background: #f9fafb;
327
- border: 2px dashed #ddd;
 
 
328
  border-radius: 8px;
329
- padding: 16px;
330
- margin-top: 16px;
331
- max-height: 300px;
332
- overflow-y: auto;
333
  }
334
 
335
- .preview-item {
336
- background: white;
337
- padding: 12px;
338
- border-radius: 6px;
339
- margin-bottom: 12px;
340
- border-left: 4px solid #667eea;
341
  }
342
 
343
- .preview-text {
344
- margin-bottom: 8px;
345
- color: #333;
346
- line-height: 1.4;
 
 
347
  }
348
 
349
- .preview-emotions {
350
- display: flex;
351
- flex-wrap: wrap;
352
- gap: 6px;
 
353
  }
354
 
355
- .emotion-badge {
356
- background: #667eea;
357
- color: white;
358
- padding: 4px 8px;
359
- border-radius: 4px;
360
- font-size: 11px;
361
- font-weight: 600;
362
  }
363
 
364
- .stats {
365
- display: grid;
366
- grid-template-columns: repeat(2, 1fr);
367
- gap: 12px;
368
- margin-top: 16px;
369
  }
370
 
371
- .stat-card {
372
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
373
- color: white;
374
- padding: 16px;
375
- border-radius: 8px;
376
- text-align: center;
377
  }
378
 
379
- .stat-value {
380
- font-size: 2em;
381
- font-weight: bold;
382
- margin-bottom: 4px;
 
 
 
 
383
  }
384
 
385
- .stat-label {
386
- font-size: 12px;
387
- opacity: 0.9;
388
  }
389
 
390
- .tabs {
391
  display: flex;
392
  gap: 10px;
393
- margin-bottom: 20px;
394
- border-bottom: 2px solid #ddd;
395
- }
396
-
397
- .tab-btn {
398
- padding: 10px 16px;
399
- background: none;
400
- border: none;
401
- border-bottom: 3px solid transparent;
402
- cursor: pointer;
403
- font-weight: 600;
404
- color: #999;
405
- transition: all 0.2s ease;
406
- }
407
-
408
- .tab-btn.active {
409
- color: #667eea;
410
- border-bottom-color: #667eea;
411
  }
412
 
413
- .tab-content {
 
 
 
 
 
 
 
 
 
 
 
414
  display: none;
415
  }
416
 
417
- .tab-content.active {
418
  display: block;
419
  }
420
 
421
- .emotion-stats {
422
- display: grid;
423
- grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
424
- gap: 10px;
425
- margin-top: 12px;
426
- }
427
-
428
- .emotion-stat {
429
- background: #f0f0f0;
430
- padding: 10px;
431
  border-radius: 6px;
432
- text-align: center;
433
- }
434
-
435
- .emotion-stat-name {
436
- font-weight: 600;
437
- font-size: 12px;
438
- color: #667eea;
439
- }
440
-
441
- .emotion-stat-count {
442
- font-size: 1.5em;
443
- font-weight: bold;
444
- color: #333;
445
- }
446
-
447
- .emotion-stat-avg {
448
- font-size: 11px;
449
- color: #999;
450
- }
451
-
452
- .export-format {
453
- display: grid;
454
- grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
455
- gap: 10px;
456
- margin-top: 12px;
457
- }
458
-
459
- .format-checkbox {
460
- display: flex;
461
- align-items: center;
462
- gap: 8px;
463
  }
464
 
465
- .format-checkbox input[type="checkbox"] {
466
- width: 18px;
467
- height: 18px;
468
- cursor: pointer;
469
  }
470
 
471
- .config-success {
472
- background: #c6f6d5;
473
- color: #22543d;
474
- padding: 12px;
475
- border-radius: 6px;
476
- margin-top: 8px;
477
- font-size: 12px;
 
 
 
 
 
 
478
  }
479
  </style>
480
  </head>
481
  <body>
482
- <h1>📝 Self-Distillation Dataset Generator</h1>
483
-
484
  <div class="container">
485
- <!-- Settings Panel -->
486
- <div class="panel settings-panel">
487
- <h2>⚙️ Configuration</h2>
488
-
489
- <div class="tabs">
490
- <button class="tab-btn active" onclick="switchTab(event, 'settings')">Settings</button>
491
- <button class="tab-btn" onclick="switchTab(event, 'data')">Data</button>
 
 
 
 
 
 
492
  </div>
 
493
 
494
- <div id="settings" class="tab-content active">
495
- <div class="form-group">
496
- <label>HuggingFace Dataset ID</label>
497
- <input type="text" id="datasetId" placeholder="username/dataset-name" value="">
498
- <small style="color: #999; margin-top: 4px; display: block;">Format: user/my-dataset-name</small>
499
- </div>
500
-
501
- <div class="form-group">
502
- <label>HuggingFace Write Token</label>
503
- <input type="password" id="hfToken" placeholder="hf_xxxxxxxxxxxxx" value="">
504
- <small style="color: #999; margin-top: 4px; display: block;">Your token is stored only in browser storage</small>
505
- </div>
506
-
507
- <button class="btn-primary" onclick="saveConfig()">💾 Save Configuration</button>
508
-
509
- <div id="configStatus"></div>
510
-
511
- <hr style="margin: 20px 0; border: none; border-top: 2px solid #ddd;">
512
 
513
- <h2 style="margin-top: 20px;">📊 Statistics</h2>
514
- <div class="stats">
515
- <div class="stat-card">
516
- <div class="stat-value" id="entriesCount">0</div>
517
- <div class="stat-label">Total Entries</div>
518
- </div>
519
- <div class="stat-card">
520
- <div class="stat-value" id="emotionCount">0</div>
521
- <div class="stat-label">Emotions Tagged</div>
522
- </div>
523
  </div>
524
-
525
- <h2 style="margin-top: 20px;">Emotion Distribution</h2>
526
- <div id="emotionStats" class="emotion-stats"></div>
527
  </div>
528
-
529
- <div id="data" class="tab-content">
530
- <h3 style="color: #667eea; margin-bottom: 16px;">📥 Import/Export</h3>
531
-
532
- <div class="form-group">
533
- <label>Export Formats</label>
534
- <div class="export-format">
535
- <div class="format-checkbox">
536
- <input type="checkbox" id="exportParquet" checked>
537
- <label for="exportParquet" style="margin: 0; font-weight: 400;">Parquet</label>
538
- </div>
539
- <div class="format-checkbox">
540
- <input type="checkbox" id="exportJSON" checked>
541
- <label for="exportJSON" style="margin: 0; font-weight: 400;">JSON</label>
542
- </div>
543
- <div class="format-checkbox">
544
- <input type="checkbox" id="exportCSV" checked>
545
- <label for="exportCSV" style="margin: 0; font-weight: 400;">CSV</label>
546
- </div>
547
- </div>
548
- </div>
549
-
550
- <div class="button-group">
551
- <button class="btn-secondary" onclick="exportData()">📥 Export Local</button>
552
- <button class="btn-success" onclick="uploadToHF()">☁️ Upload to HF</button>
553
- </div>
554
-
555
- <div class="button-group" style="margin-top: 10px;">
556
- <button class="btn-danger" onclick="clearAllData()">🗑️ Clear All Data</button>
557
- </div>
558
-
559
- <div id="statusMessage" class="status-message"></div>
560
-
561
- <h3 style="color: #667eea; margin-bottom: 16px; margin-top: 24px;">📋 Recent Entries</h3>
562
- <div class="preview-panel" id="previewPanel">
563
- <p style="color: #999; text-align: center;">No entries yet. Start writing!</p>
564
  </div>
565
  </div>
566
- </div>
567
-
568
- <!-- Writing Panel -->
569
- <div class="panel">
570
- <h2>✍️ Write & Tag</h2>
571
 
572
- <div class="form-group">
573
- <label>What's on your mind?</label>
574
- <textarea id="textInput" placeholder="Write anything... Your thoughts, feelings, experiences..."></textarea>
 
 
 
 
 
575
  </div>
 
576
 
577
- <div class="form-group">
578
- <label>How are you feeling? (Select one or more)</label>
579
- <div class="emotion-selector" id="emotionSelector"></div>
580
- <div class="selected-emotions" id="selectedEmotions">
581
- <span style="color: #999;">No emotions selected yet</span>
 
 
 
 
 
 
 
582
  </div>
583
  </div>
584
 
585
- <div class="button-group">
586
- <button class="btn-primary" onclick="saveEntry()">💾 Save Entry</button>
587
- <button class="btn-secondary" onclick="clearForm()">🔄 Clear</button>
 
 
588
  </div>
589
 
590
- <div id="entryStatus" class="status-message"></div>
591
-
592
- <h3 style="color: #667eea; margin-top: 24px; margin-bottom: 12px;">✨ Real-time Preview</h3>
593
- <div class="preview-panel" id="livePreview" style="max-height: 200px;">
594
- <div style="color: #999; text-align: center; padding: 20px;">
595
- Start typing to see preview
596
- </div>
597
- </div>
598
  </div>
599
  </div>
600
 
 
 
 
601
  <script>
602
- // Emotion definitions with colors
603
- const EMOTIONS = [
604
- { name: 'Happy', emoji: '😊', color: '#FFD700' },
605
- { name: 'Sad', emoji: '😢', color: '#4A90E2' },
606
- { name: 'Angry', emoji: '😠', color: '#E74C3C' },
607
- { name: 'Anxious', emoji: '😰', color: '#FF6B6B' },
608
- { name: 'Calm', emoji: '😌', color: '#2ECC71' },
609
- { name: 'Excited', emoji: '🤩', color: '#FF6348' },
610
- { name: 'Confused', emoji: '😕', color: '#9B59B6' },
611
- { name: 'Grateful', emoji: '🙏', color: '#F39C12' },
612
- { name: 'Motivated', emoji: '💪', color: '#27AE60' },
613
- { name: 'Lonely', emoji: '😔', color: '#34495E' },
614
- { name: 'Hopeful', emoji: '🌟', color: '#3498DB' },
615
- { name: 'Stressed', emoji: '😫', color: '#E67E22' },
 
 
 
 
 
 
616
  ];
 
617
 
618
- // State
619
- let selectedEmotions = {};
620
- let entries = [];
621
-
622
- // Initialize
623
- function init() {
624
- renderEmotionButtons();
625
- loadFromLocalStorage();
626
- updateStats();
627
- loadConfig();
628
- }
629
-
630
- function renderEmotionButtons() {
631
- const container = document.getElementById('emotionSelector');
632
- container.innerHTML = EMOTIONS.map(emotion => `
633
- <div>
634
- <button class="emotion-btn" onclick="toggleEmotion('${emotion.name}')">
635
- <span style="font-size: 24px;">${emotion.emoji}</span>
636
- <span>${emotion.name}</span>
637
- </button>
638
- <div class="emotion-intensity">
639
- <input type="range" class="intensity-slider" min="1" max="100" value="50"
640
- id="intensity-${emotion.name}"
641
- onchange="updateIntensity('${emotion.name}', this.value)"
642
- oninput="updateIntensity('${emotion.name}', this.value)">
643
- <div class="intensity-value">
644
- <span>Intensity:</span>
645
- <span id="intensity-value-${emotion.name}">50%</span>
646
- </div>
647
- </div>
648
- </div>
649
- `).join('');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
650
  }
651
 
652
- function toggleEmotion(emotionName) {
653
- if (selectedEmotions[emotionName]) {
654
- delete selectedEmotions[emotionName];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
655
  } else {
656
- selectedEmotions[emotionName] = 50;
 
 
657
  }
658
- updateEmotionUI();
659
  }
660
 
661
- function updateIntensity(emotionName, value) {
662
- selectedEmotions[emotionName] = parseInt(value);
663
- document.getElementById(`intensity-value-${emotionName}`).textContent = value + '%';
664
- updateEmotionUI();
665
- }
666
-
667
- function updateEmotionUI() {
668
- // Update button states
669
- document.querySelectorAll('.emotion-btn').forEach(btn => {
670
- const emotionName = btn.querySelector('span:last-child').textContent;
671
- if (selectedEmotions[emotionName]) {
672
- btn.classList.add('active');
673
- } else {
674
- btn.classList.remove('active');
 
 
 
 
 
 
 
 
 
 
 
675
  }
676
  });
 
 
 
677
 
678
- // Update selected emotions display
679
- const selected = Object.entries(selectedEmotions);
680
- if (selected.length === 0) {
681
- document.getElementById('selectedEmotions').innerHTML =
682
- '<span style="color: #999;">No emotions selected yet</span>';
683
- } else {
684
- document.getElementById('selectedEmotions').innerHTML = selected
685
- .map(([name, intensity]) => {
686
- const emotion = EMOTIONS.find(e => e.name === name);
687
- return `
688
- <span class="emotion-tag">
689
- ${emotion.emoji} ${name}
690
- <span class="intensity">${intensity}%</span>
691
- <span class="remove" onclick="toggleEmotion('${name}')">✕</span>
692
- </span>
693
- `;
694
- }).join('');
695
- }
696
 
697
- updateLivePreview();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
698
  }
699
 
700
- function updateLivePreview() {
701
- const text = document.getElementById('textInput').value;
702
- const preview = document.getElementById('livePreview');
703
 
704
- if (!text.trim()) {
705
- preview.innerHTML = `
706
- <div style="color: #999; text-align: center; padding: 20px;">
707
- Start typing to see preview
708
- </div>
709
- `;
710
- return;
711
  }
712
-
713
- const emotions = Object.entries(selectedEmotions);
714
- const emotionHtml = emotions.length > 0
715
- ? emotions.map(([name, intensity]) => {
716
- const emotion = EMOTIONS.find(e => e.name === name);
717
- return `<span class="emotion-badge">${emotion.emoji} ${name} (${intensity}%)</span>`;
718
- }).join('')
719
- : '<span style="color: #999; font-size: 11px;">No emotions tagged</span>';
720
-
721
- preview.innerHTML = `
722
- <div class="preview-item">
723
- <div class="preview-text">${escapeHtml(text.substring(0, 150))}${text.length > 150 ? '...' : ''}</div>
724
- <div class="preview-emotions">${emotionHtml}</div>
725
- </div>
726
- `;
727
- }
728
-
729
- function saveEntry() {
730
- const text = document.getElementById('textInput').value.trim();
731
 
732
- if (!text) {
733
- showMessage('Please write something first', 'error', 'entryStatus');
734
- return;
 
 
 
 
 
735
  }
736
-
737
- const entry = {
738
- id: Date.now(),
739
- timestamp: new Date().toISOString(),
740
- text: text,
741
- emotions: { ...selectedEmotions }
742
- };
743
-
744
- entries.push(entry);
745
- saveToLocalStorage();
746
- updateStats();
747
- updatePreview();
748
- clearForm();
749
- showMessage('✅ Entry saved successfully!', 'success', 'entryStatus');
750
  }
751
 
752
- function clearForm() {
753
- document.getElementById('textInput').value = '';
754
- selectedEmotions = {};
755
- updateEmotionUI();
756
- }
757
-
758
- function updatePreview() {
759
- const preview = document.getElementById('previewPanel');
760
 
761
- if (entries.length === 0) {
762
- preview.innerHTML = '<p style="color: #999; text-align: center;">No entries yet. Start writing!</p>';
763
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
764
  }
765
-
766
- preview.innerHTML = entries.slice().reverse().slice(0, 10).map(entry => {
767
- const emotionHtml = Object.entries(entry.emotions)
768
- .map(([name, intensity]) => {
769
- const emotion = EMOTIONS.find(e => e.name === name);
770
- return `<span class="emotion-badge">${emotion.emoji} ${name} (${intensity}%)</span>`;
771
- }).join('');
772
-
773
- return `
774
- <div class="preview-item">
775
- <div class="preview-text">${escapeHtml(entry.text.substring(0, 100))}${entry.text.length > 100 ? '...' : ''}</div>
776
- <div style="font-size: 11px; color: #999; margin-bottom: 8px;">
777
- ${new Date(entry.timestamp).toLocaleString()}
778
- </div>
779
- <div class="preview-emotions">${emotionHtml || '<span style="color: #999; font-size: 11px;">No emotions tagged</span>'}</div>
780
- </div>
781
- `;
782
- }).join('');
783
- }
784
-
785
- function updateStats() {
786
- document.getElementById('entriesCount').textContent = entries.length;
787
 
788
- const emotionStats = {};
789
- entries.forEach(entry => {
790
- Object.entries(entry.emotions).forEach(([emotion, intensity]) => {
791
- if (!emotionStats[emotion]) {
792
- emotionStats[emotion] = { count: 0, totalIntensity: 0 };
793
- }
794
- emotionStats[emotion].count += 1;
795
- emotionStats[emotion].totalIntensity += intensity;
796
- });
797
- });
798
-
799
- document.getElementById('emotionCount').textContent = Object.keys(emotionStats).length;
800
-
801
- const statsHtml = EMOTIONS
802
- .filter(e => emotionStats[e.name])
803
- .map(emotion => {
804
- const stats = emotionStats[emotion.name];
805
- const avg = Math.round(stats.totalIntensity / stats.count);
806
- return `
807
- <div class="emotion-stat">
808
- <div style="font-size: 20px;">${emotion.emoji}</div>
809
- <div class="emotion-stat-name">${emotion.name}</div>
810
- <div class="emotion-stat-count">${stats.count}</div>
811
- <div class="emotion-stat-avg">Avg: ${avg}%</div>
812
- </div>
813
- `;
814
- }).join('');
815
-
816
- document.getElementById('emotionStats').innerHTML = statsHtml ||
817
- '<p style="color: #999; grid-column: 1/-1; text-align: center;">No emotions recorded yet</p>';
818
- }
819
-
820
- function saveConfig() {
821
- const datasetId = document.getElementById('datasetId').value.trim();
822
- const hfToken = document.getElementById('hfToken').value.trim();
823
-
824
- if (!datasetId || !hfToken) {
825
- showMessage('Please fill in all configuration fields', 'error', 'configStatus');
826
- return;
827
  }
828
-
829
- localStorage.setItem('hf_dataset_id', datasetId);
830
- localStorage.setItem('hf_token', hfToken);
831
 
832
- const status = document.getElementById('configStatus');
833
- status.innerHTML = '<div class="config-success">✅ Configuration saved to browser storage</div>';
834
- setTimeout(() => status.innerHTML = '', 3000);
 
 
 
 
 
 
 
835
  }
836
 
837
- function loadConfig() {
838
- const datasetId = localStorage.getItem('hf_dataset_id');
839
- const hfToken = localStorage.getItem('hf_token');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
840
 
841
- if (datasetId) document.getElementById('datasetId').value = datasetId;
842
- if (hfToken) document.getElementById('hfToken').value = hfToken;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
843
  }
844
 
845
- async function uploadToHF() {
846
- const datasetId = localStorage.getItem('hf_dataset_id');
847
- const hfToken = localStorage.getItem('hf_token');
 
 
848
 
849
- if (!datasetId || !hfToken) {
850
- showMessage('Please configure HuggingFace credentials first', 'error', 'statusMessage');
851
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
852
  }
 
 
 
853
 
854
- if (entries.length === 0) {
855
- showMessage('No entries to upload', 'error', 'statusMessage');
856
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
857
  }
 
858
 
859
- showMessage('⏳ Uploading to HuggingFace...', 'info', 'statusMessage');
860
-
861
- try {
862
- // Convert to Parquet format
863
- const dataset = entries.map(entry => ({
864
- id: entry.id.toString(),
865
- timestamp: entry.timestamp,
866
- text: entry.text,
867
- emotions: JSON.stringify(entry.emotions)
868
- }));
869
-
870
- // Create CSV for upload (HF can parse this)
871
- const csv = convertToCSV(dataset);
872
-
873
- // Upload to HuggingFace
874
- const formData = new FormData();
875
- formData.append('files', new Blob([csv], { type: 'text/csv' }), 'data.csv');
876
-
877
- const response = await fetch(
878
- `https://huggingface.co/api/datasets/${datasetId}/upload/files`,
879
- {
880
- method: 'POST',
881
- headers: {
882
- 'Authorization': `Bearer ${hfToken}`
883
- },
884
- body: formData
885
- }
886
- );
887
 
888
- if (response.ok) {
889
- showMessage('✅ Successfully uploaded to HuggingFace!', 'success', 'statusMessage');
890
- } else {
891
- throw new Error(`HTTP ${response.status}`);
 
 
 
 
 
 
 
892
  }
893
- } catch (error) {
894
- showMessage(`❌ Upload failed: ${error.message}`, 'error', 'statusMessage');
895
  }
896
  }
897
 
898
- function exportData() {
899
- if (entries.length === 0) {
900
- showMessage('No entries to export', 'error', 'statusMessage');
901
- return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
902
  }
 
 
 
903
 
904
- const formats = {
905
- json: document.getElementById('exportJSON').checked,
906
- csv: document.getElementById('exportCSV').checked,
907
- parquet: document.getElementById('exportParquet').checked
 
 
 
 
 
 
 
 
 
908
  };
 
 
 
 
 
 
909
 
910
- const dataset = entries.map(entry => ({
911
- id: entry.id.toString(),
912
- timestamp: entry.timestamp,
913
- text: entry.text,
914
- emotions: JSON.stringify(entry.emotions)
915
- }));
916
-
917
- if (formats.json) {
918
- downloadFile(
919
- JSON.stringify(dataset, null, 2),
920
- 'dataset.json',
921
- 'application/json'
922
- );
923
- }
924
-
925
- if (formats.csv) {
926
- const csv = convertToCSV(dataset);
927
- downloadFile(csv, 'dataset.csv', 'text/csv');
928
- }
929
 
930
- if (formats.parquet) {
931
- // Note: True Parquet generation requires a library. This creates JSON as placeholder
932
- const parquetLike = generateParquetJSON(dataset);
933
- downloadFile(
934
- JSON.stringify(parquetLike, null, 2),
935
- 'dataset.parquet.json',
936
- 'application/json'
937
- );
938
- }
939
 
940
- showMessage('✅ Data exported successfully!', 'success', 'statusMessage');
 
 
 
 
 
 
 
941
  }
942
 
943
- function convertToCSV(data) {
944
- const headers = Object.keys(data[0]);
945
- const csv = [
946
- headers.join(','),
947
- ...data.map(row =>
948
- headers.map(header => {
949
- const value = row[header];
950
- const escaped = String(value).replace(/"/g, '""');
951
- return `"${escaped}"`;
952
- }).join(',')
953
- )
954
- ].join('\n');
955
 
956
- return csv;
 
 
 
 
957
  }
958
 
959
- function generateParquetJSON(data) {
960
- // Parquet-like JSON format with metadata
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
961
  return {
962
- schema: {
963
- fields: [
964
- { name: 'id', type: 'string' },
965
- { name: 'timestamp', type: 'string' },
966
- { name: 'text', type: 'string' },
967
- { name: 'emotions', type: 'string' }
968
- ]
969
- },
970
- data: data
971
  };
972
  }
973
 
974
- function downloadFile(content, filename, type) {
975
- const blob = new Blob([content], { type });
 
 
 
 
 
 
 
976
  const url = URL.createObjectURL(blob);
977
  const a = document.createElement('a');
978
  a.href = url;
979
- a.download = filename;
980
- document.body.appendChild(a);
981
  a.click();
982
- document.body.removeChild(a);
983
  URL.revokeObjectURL(url);
984
  }
985
 
986
- function clearAllData() {
987
- if (confirm('Are you sure? This will delete all entries permanently.')) {
988
- entries = [];
989
- saveToLocalStorage();
990
- updateStats();
991
- updatePreview();
992
- showMessage('🗑️ All data cleared', 'success', 'statusMessage');
993
- }
994
- }
995
-
996
- function saveToLocalStorage() {
997
- localStorage.setItem('dataset_entries', JSON.stringify(entries));
998
- }
999
-
1000
- function loadFromLocalStorage() {
1001
- const stored = localStorage.getItem('dataset_entries');
1002
- entries = stored ? JSON.parse(stored) : [];
1003
- updatePreview();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1004
  }
1005
 
1006
- function showMessage(text, type, elementId) {
1007
- const el = document.getElementById(elementId);
1008
- el.textContent = text;
1009
- el.className = `status-message ${type} show`;
1010
- setTimeout(() => el.classList.remove('show'), 4000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1011
  }
1012
 
1013
- function switchTab(event, tabName) {
1014
- // Hide all tabs
1015
- document.querySelectorAll('.tab-content').forEach(tab => {
1016
- tab.classList.remove('active');
1017
- });
1018
- document.querySelectorAll('.tab-btn').forEach(btn => {
1019
- btn.classList.remove('active');
1020
- });
1021
-
1022
- // Show selected tab
1023
- document.getElementById(tabName).classList.add('active');
1024
- event.target.classList.add('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1025
  }
1026
 
1027
- function escapeHtml(text) {
1028
- const div = document.createElement('div');
1029
- div.textContent = text;
1030
- return div.innerHTML;
 
 
 
 
 
 
 
 
 
 
1031
  }
1032
 
1033
- // Initialize on page load
1034
- window.addEventListener('load', init);
1035
  </script>
1036
  </body>
1037
  </html>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Self-Distillation | Emotional Timeline Editor</title>
 
7
  <style>
8
  * {
9
  margin: 0;
 
11
  box-sizing: border-box;
12
  }
13
 
14
+ :root {
15
+ --bg-primary: #0a0a0f;
16
+ --bg-secondary: #12121a;
17
+ --bg-tertiary: #1a1a25;
18
+ --text-primary: #e0e0e8;
19
+ --text-secondary: #8888a0;
20
+ --accent: #6366f1;
21
+ --border: #2a2a3a;
22
+ --timeline-bg: #0d0d14;
23
+ }
24
+
25
  body {
26
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
27
+ background: var(--bg-primary);
28
+ color: var(--text-primary);
29
  min-height: 100vh;
30
+ display: flex;
31
+ flex-direction: column;
32
+ overflow-x: hidden;
33
  }
34
 
35
  .container {
36
  max-width: 1400px;
37
  margin: 0 auto;
38
+ width: 100%;
39
+ padding: 20px;
40
+ flex: 1;
41
+ display: flex;
42
+ flex-direction: column;
43
  gap: 20px;
44
  }
45
 
46
+ header {
47
+ text-align: center;
48
+ padding: 20px 0;
 
49
  }
50
 
51
+ h1 {
52
+ font-size: 1.8rem;
53
+ font-weight: 300;
54
+ letter-spacing: 2px;
55
+ color: var(--text-primary);
56
+ opacity: 0.9;
57
  }
58
 
59
+ .subtitle {
60
+ font-size: 0.9rem;
61
+ color: var(--text-secondary);
62
+ margin-top: 8px;
63
  }
64
 
65
+ /* Text Editor Section */
66
+ .editor-section {
67
+ position: relative;
68
+ transition: all 0.3s ease;
 
 
 
69
  }
70
 
71
+ .editor-container {
72
+ position: relative;
73
+ background: var(--bg-secondary);
74
+ border: 2px solid var(--border);
75
+ border-radius: 12px;
76
+ padding: 30px;
77
+ min-height: 300px;
78
+ transition: all 0.3s ease;
79
  }
80
 
81
+ .editor-container.emotion-active {
82
+ animation: pulse 2s ease-in-out infinite;
83
  }
84
 
85
+ @keyframes pulse {
86
+ 0%, 100% { border-color: var(--border); }
87
+ 50% { border-color: var(--current-emotion-color, var(--accent)); }
 
 
88
  }
89
 
90
+ #textEditor {
 
 
91
  width: 100%;
92
+ min-height: 240px;
93
+ background: transparent;
94
+ border: none;
 
 
 
 
 
 
 
 
95
  outline: none;
96
+ color: var(--text-primary);
97
+ font-size: 1.1rem;
98
+ line-height: 1.8;
99
+ resize: vertical;
100
+ font-family: inherit;
101
  }
102
 
103
+ #textEditor::placeholder {
104
+ color: var(--text-secondary);
105
+ opacity: 0.5;
 
106
  }
107
 
108
+ .cursor-indicator {
109
+ position: absolute;
110
+ top: 10px;
111
+ right: 15px;
112
+ font-size: 0.75rem;
113
+ color: var(--text-secondary);
114
+ background: var(--bg-tertiary);
115
+ padding: 4px 10px;
116
+ border-radius: 4px;
117
  }
118
 
119
+ /* Emotion Input */
120
+ .emotion-input-section {
 
 
 
 
 
 
 
121
  display: flex;
122
+ gap: 10px;
123
  align-items: center;
124
+ padding: 15px;
125
+ background: var(--bg-secondary);
126
+ border-radius: 8px;
127
+ margin-bottom: 10px;
 
 
 
 
128
  }
129
 
130
+ .emotion-input-section label {
131
+ font-size: 0.9rem;
132
+ color: var(--text-secondary);
133
+ white-space: nowrap;
134
  }
135
 
136
+ #emotionInput {
137
+ flex: 1;
138
+ background: var(--bg-tertiary);
139
+ border: 1px solid var(--border);
140
+ color: var(--text-primary);
141
+ padding: 10px 15px;
142
  border-radius: 6px;
143
+ font-size: 0.95rem;
 
 
 
 
 
 
 
 
 
 
144
  outline: none;
145
+ transition: border-color 0.2s;
146
  }
147
 
148
+ #emotionInput:focus {
149
+ border-color: var(--accent);
 
 
 
 
 
 
 
150
  }
151
 
152
+ /* Timeline Section */
153
+ .timeline-section {
154
+ background: var(--bg-secondary);
155
+ border-radius: 12px;
156
+ padding: 20px;
157
+ border: 1px solid var(--border);
 
 
158
  }
159
 
160
+ .timeline-header {
161
  display: flex;
162
  justify-content: space-between;
163
+ align-items: center;
164
+ margin-bottom: 15px;
 
 
 
 
 
 
 
 
 
165
  }
166
 
167
+ .timeline-title {
168
+ font-size: 1rem;
169
+ color: var(--text-secondary);
170
+ font-weight: 500;
 
 
 
 
 
171
  }
172
 
173
+ .timeline-controls {
174
+ display: flex;
175
+ gap: 10px;
176
  }
177
 
178
+ .btn {
179
+ background: var(--bg-tertiary);
180
+ border: 1px solid var(--border);
181
+ color: var(--text-primary);
182
+ padding: 8px 16px;
183
+ border-radius: 6px;
184
  cursor: pointer;
185
+ font-size: 0.85rem;
186
+ transition: all 0.2s;
187
  }
188
 
189
+ .btn:hover {
190
+ background: var(--accent);
191
+ border-color: var(--accent);
 
 
 
 
 
192
  }
193
 
194
  .btn-primary {
195
+ background: var(--accent);
196
+ border-color: var(--accent);
 
197
  }
198
 
199
  .btn-primary:hover {
200
+ background: #5558e6;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  }
202
 
203
+ /* Timeline Canvas */
204
+ .timeline-wrapper {
205
+ position: relative;
206
+ background: var(--timeline-bg);
207
+ border-radius: 8px;
208
+ overflow: hidden;
209
+ cursor: crosshair;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
210
  }
211
 
212
+ #timelineCanvas {
213
+ width: 100%;
214
+ height: 400px;
215
  display: block;
216
  }
217
 
218
+ .timeline-overlay {
219
+ position: absolute;
220
+ top: 0;
221
+ left: 0;
222
+ right: 0;
223
+ bottom: 0;
224
+ pointer-events: none;
 
 
 
225
  }
226
 
227
+ .cursor-line {
228
+ position: absolute;
229
+ top: 0;
230
+ bottom: 0;
231
+ width: 2px;
232
+ background: rgba(255, 255, 255, 0.3);
233
+ pointer-events: none;
234
+ display: none;
235
  }
236
 
237
+ /* Instructions */
238
+ .instructions {
239
+ margin-top: 15px;
240
+ padding: 15px;
241
+ background: var(--bg-tertiary);
242
  border-radius: 8px;
243
+ font-size: 0.85rem;
244
+ color: var(--text-secondary);
245
+ line-height: 1.6;
 
246
  }
247
 
248
+ .instructions strong {
249
+ color: var(--text-primary);
 
 
 
 
250
  }
251
 
252
+ /* Export Section */
253
+ .export-section {
254
+ background: var(--bg-secondary);
255
+ border-radius: 12px;
256
+ padding: 20px;
257
+ border: 1px solid var(--border);
258
  }
259
 
260
+ .export-header {
261
+ font-size: 1rem;
262
+ color: var(--text-secondary);
263
+ margin-bottom: 15px;
264
+ font-weight: 500;
265
  }
266
 
267
+ .hf-config {
268
+ display: grid;
269
+ grid-template-columns: 1fr 1fr;
270
+ gap: 15px;
271
+ margin-bottom: 15px;
 
 
272
  }
273
 
274
+ .input-group {
275
+ display: flex;
276
+ flex-direction: column;
277
+ gap: 5px;
 
278
  }
279
 
280
+ .input-group label {
281
+ font-size: 0.85rem;
282
+ color: var(--text-secondary);
 
 
 
283
  }
284
 
285
+ .input-group input {
286
+ background: var(--bg-tertiary);
287
+ border: 1px solid var(--border);
288
+ color: var(--text-primary);
289
+ padding: 10px;
290
+ border-radius: 6px;
291
+ font-size: 0.9rem;
292
+ outline: none;
293
  }
294
 
295
+ .input-group input:focus {
296
+ border-color: var(--accent);
 
297
  }
298
 
299
+ .export-buttons {
300
  display: flex;
301
  gap: 10px;
302
+ flex-wrap: wrap;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  }
304
 
305
+ .json-preview {
306
+ margin-top: 15px;
307
+ background: var(--bg-tertiary);
308
+ border-radius: 8px;
309
+ padding: 15px;
310
+ max-height: 300px;
311
+ overflow-y: auto;
312
+ font-family: 'Courier New', monospace;
313
+ font-size: 0.85rem;
314
+ white-space: pre-wrap;
315
+ word-break: break-all;
316
+ color: var(--text-secondary);
317
  display: none;
318
  }
319
 
320
+ .json-preview.visible {
321
  display: block;
322
  }
323
 
324
+ /* Point Info Tooltip */
325
+ .point-tooltip {
326
+ position: absolute;
327
+ background: var(--bg-tertiary);
328
+ border: 1px solid var(--border);
 
 
 
 
 
329
  border-radius: 6px;
330
+ padding: 10px;
331
+ font-size: 0.8rem;
332
+ color: var(--text-primary);
333
+ pointer-events: none;
334
+ z-index: 100;
335
+ display: none;
336
+ min-width: 150px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  }
338
 
339
+ .point-tooltip.visible {
340
+ display: block;
 
 
341
  }
342
 
343
+ /* Responsive */
344
+ @media (max-width: 768px) {
345
+ .hf-config {
346
+ grid-template-columns: 1fr;
347
+ }
348
+
349
+ .container {
350
+ padding: 10px;
351
+ }
352
+
353
+ #timelineCanvas {
354
+ height: 300px;
355
+ }
356
  }
357
  </style>
358
  </head>
359
  <body>
 
 
360
  <div class="container">
361
+ <header>
362
+ <h1>SELF-DISTILLATION</h1>
363
+ <div class="subtitle">Write freely. Layer emotion. Distill meaning.</div>
364
+ </header>
365
+
366
+ <!-- Text Editor -->
367
+ <div class="editor-section">
368
+ <div class="editor-container" id="editorContainer">
369
+ <textarea
370
+ id="textEditor"
371
+ placeholder="Begin writing your thoughts here... The timeline below will grow as you write."
372
+ ></textarea>
373
+ <div class="cursor-indicator" id="cursorIndicator">Char: 0</div>
374
  </div>
375
+ </div>
376
 
377
+ <!-- Emotion Input -->
378
+ <div class="emotion-input-section">
379
+ <label>Active Emotion:</label>
380
+ <input
381
+ type="text"
382
+ id="emotionInput"
383
+ placeholder="Type any emotion (e.g., 'Deep Nostalgia', 'Static Buzz')..."
384
+ value="Joy"
385
+ />
386
+ </div>
 
 
 
 
 
 
 
 
387
 
388
+ <!-- Timeline -->
389
+ <div class="timeline-section">
390
+ <div class="timeline-header">
391
+ <div class="timeline-title">Emotional Flow Timeline</div>
392
+ <div class="timeline-controls">
393
+ <button class="btn" onclick="clearAllPoints()">Clear All</button>
394
+ <button class="btn" onclick="toggleJSONPreview()">Toggle JSON</button>
 
 
 
395
  </div>
 
 
 
396
  </div>
397
+
398
+ <div class="timeline-wrapper" id="timelineWrapper">
399
+ <canvas id="timelineCanvas"></canvas>
400
+ <div class="timeline-overlay">
401
+ <div class="cursor-line" id="cursorLine"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
402
  </div>
403
  </div>
 
 
 
 
 
404
 
405
+ <div class="instructions">
406
+ <strong>How to use:</strong><br>
407
+ <strong>Click</strong> on the timeline to add an emotion point<br>
408
+ • <strong>Drag vertically</strong> to adjust intensity (0.0 - 1.0)<br>
409
+ • <strong>Drag horizontally</strong> to adjust timing/offset<br>
410
+ • <strong>Drag handles</strong> (small circles) to create fades and bumps<br>
411
+ • <strong>Right-click</strong> a point to delete it<br>
412
+ • Move cursor in text to see emotional intensity at that position
413
  </div>
414
+ </div>
415
 
416
+ <!-- Export Section -->
417
+ <div class="export-section">
418
+ <div class="export-header">Export & HuggingFace Integration</div>
419
+
420
+ <div class="hf-config">
421
+ <div class="input-group">
422
+ <label>HuggingFace Token</label>
423
+ <input type="password" id="hfToken" placeholder="hf_xxxxxxxxxxxx" />
424
+ </div>
425
+ <div class="input-group">
426
+ <label>Dataset Repository ID</label>
427
+ <input type="text" id="hfRepoId" placeholder="username/dataset-name" />
428
  </div>
429
  </div>
430
 
431
+ <div class="export-buttons">
432
+ <button class="btn btn-primary" onclick="exportJSON()">Export JSON</button>
433
+ <button class="btn" onclick="generateParquet()">Generate Parquet</button>
434
+ <button class="btn" onclick="generateJinja2Card()">Generate Dataset Card</button>
435
+ <button class="btn btn-primary" onclick="pushToHF()">Push to HuggingFace</button>
436
  </div>
437
 
438
+ <div class="json-preview" id="jsonPreview"></div>
 
 
 
 
 
 
 
439
  </div>
440
  </div>
441
 
442
+ <!-- Tooltip -->
443
+ <div class="point-tooltip" id="pointTooltip"></div>
444
+
445
  <script>
446
+ // ==================== STATE MANAGEMENT ====================
447
+ const state = {
448
+ points: [],
449
+ selectedPoint: null,
450
+ currentEmotion: 'Joy',
451
+ isDragging: false,
452
+ dragMode: null, // 'move', 'intensity', 'handleIn', 'handleOut'
453
+ dragStartPos: null,
454
+ dragStartValue: null,
455
+ canvasWidth: 0,
456
+ canvasHeight: 400,
457
+ textLength: 0
458
+ };
459
+
460
+ // Color palette for emotions
461
+ const emotionColors = {};
462
+ const colorPalette = [
463
+ '#ff6b6b', '#4ecdc4', '#45b7d1', '#96ceb4', '#ffeaa7',
464
+ '#dda0dd', '#98d8c8', '#f7dc6f', '#bb8fce', '#85c1e9',
465
+ '#f8b739', '#52be80', '#af7ac5', '#ec7063', '#5dade2'
466
  ];
467
+ let colorIndex = 0;
468
 
469
+ function getEmotionColor(emotion) {
470
+ if (!emotionColors[emotion]) {
471
+ emotionColors[emotion] = colorPalette[colorIndex % colorPalette.length];
472
+ colorIndex++;
473
+ }
474
+ return emotionColors[emotion];
475
+ }
476
+
477
+ // ==================== DOM ELEMENTS ====================
478
+ const textEditor = document.getElementById('textEditor');
479
+ const emotionInput = document.getElementById('emotionInput');
480
+ const canvas = document.getElementById('timelineCanvas');
481
+ const ctx = canvas.getContext('2d');
482
+ const cursorLine = document.getElementById('cursorLine');
483
+ const cursorIndicator = document.getElementById('cursorIndicator');
484
+ const editorContainer = document.getElementById('editorContainer');
485
+ const pointTooltip = document.getElementById('pointTooltip');
486
+ const jsonPreview = document.getElementById('jsonPreview');
487
+
488
+ // ==================== CANVAS SETUP ====================
489
+ function initCanvas() {
490
+ const wrapper = document.getElementById('timelineWrapper');
491
+ canvas.width = wrapper.clientWidth;
492
+ canvas.height = state.canvasHeight;
493
+ state.canvasWidth = canvas.width;
494
+ drawTimeline();
495
+ }
496
+
497
+ window.addEventListener('resize', () => {
498
+ initCanvas();
499
+ });
500
+
501
+ // ==================== TEXT EDITOR EVENTS ====================
502
+ textEditor.addEventListener('input', updateTextLength);
503
+ textEditor.addEventListener('click', updateCursorPosition);
504
+ textEditor.addEventListener('keyup', updateCursorPosition);
505
+
506
+ function updateTextLength() {
507
+ state.textLength = textEditor.value.length;
508
+ initCanvas();
509
+ }
510
+
511
+ function updateCursorPosition() {
512
+ const cursorPos = textEditor.selectionStart;
513
+ cursorIndicator.textContent = `Char: ${cursorPos}`;
514
+
515
+ // Update cursor line on timeline
516
+ if (state.textLength > 0) {
517
+ const x = (cursorPos / state.textLength) * state.canvasWidth;
518
+ cursorLine.style.left = x + 'px';
519
+ cursorLine.style.display = 'block';
520
+
521
+ // Calculate weighted emotion at cursor
522
+ updateEditorGlow(cursorPos);
523
+ }
524
  }
525
 
526
+ function updateEditorGlow(cursorPos) {
527
+ const activeEmotions = getActiveEmotionsAtPosition(cursorPos);
528
+
529
+ if (activeEmotions.length > 0) {
530
+ // Calculate weighted average color
531
+ let totalIntensity = 0;
532
+ let r = 0, g = 0, b = 0;
533
+
534
+ activeEmotions.forEach(em => {
535
+ const color = hexToRgb(em.color);
536
+ totalIntensity += em.intensity;
537
+ r += color.r * em.intensity;
538
+ g += color.g * em.intensity;
539
+ b += color.b * em.intensity;
540
+ });
541
+
542
+ if (totalIntensity > 0) {
543
+ r = Math.round(r / totalIntensity);
544
+ g = Math.round(g / totalIntensity);
545
+ b = Math.round(b / totalIntensity);
546
+
547
+ const glowColor = `rgba(${r}, ${g}, ${b}, 0.3)`;
548
+ editorContainer.style.boxShadow = `0 0 20px ${glowColor}`;
549
+ editorContainer.style.borderColor = `rgba(${r}, ${g}, ${b}, 0.6)`;
550
+ editorContainer.classList.add('emotion-active');
551
+
552
+ document.documentElement.style.setProperty('--current-emotion-color',
553
+ `rgb(${r}, ${g}, ${b})`);
554
+ }
555
  } else {
556
+ editorContainer.style.boxShadow = 'none';
557
+ editorContainer.style.borderColor = '';
558
+ editorContainer.classList.remove('emotion-active');
559
  }
 
560
  }
561
 
562
+ function getActiveEmotionsAtPosition(charIndex) {
563
+ const active = [];
564
+ const posRatio = charIndex / state.textLength;
565
+
566
+ state.points.forEach(point => {
567
+ if (posRatio >= point.start && posRatio <= point.end) {
568
+ let intensity;
569
+ if (posRatio <= point.peak) {
570
+ // Rising phase
571
+ const t = (posRatio - point.start) / (point.peak - point.start || 0.001);
572
+ intensity = point.startIntensity + (point.peakIntensity - point.startIntensity) * t;
573
+ } else {
574
+ // Falling phase
575
+ const t = (posRatio - point.peak) / (point.end - point.peak || 0.001);
576
+ intensity = point.peakIntensity + (point.endIntensity - point.peakIntensity) * t;
577
+ }
578
+ intensity = Math.max(0, Math.min(1, intensity));
579
+
580
+ if (intensity > 0.05) {
581
+ active.push({
582
+ emotion: point.emotion,
583
+ intensity: intensity,
584
+ color: point.color
585
+ });
586
+ }
587
  }
588
  });
589
+
590
+ return active;
591
+ }
592
 
593
+ // ==================== EMOTION INPUT ====================
594
+ emotionInput.addEventListener('change', (e) => {
595
+ state.currentEmotion = e.target.value.trim() || 'Unknown';
596
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
597
 
598
+ // ==================== TIMELINE DRAWING ====================
599
+ function drawTimeline() {
600
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
601
+
602
+ // Draw grid
603
+ drawGrid();
604
+
605
+ // Draw emotion curves
606
+ state.points.forEach((point, index) => {
607
+ drawEmotionCurve(point, index);
608
+ });
609
+
610
+ // Draw points and handles
611
+ state.points.forEach((point, index) => {
612
+ drawPointControls(point, index);
613
+ });
614
  }
615
 
616
+ function drawGrid() {
617
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.05)';
618
+ ctx.lineWidth = 1;
619
 
620
+ // Horizontal lines (intensity levels)
621
+ for (let i = 0; i <= 10; i++) {
622
+ const y = (i / 10) * canvas.height;
623
+ ctx.beginPath();
624
+ ctx.moveTo(0, y);
625
+ ctx.lineTo(canvas.width, y);
626
+ ctx.stroke();
627
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
628
 
629
+ // Vertical lines (character positions)
630
+ const numLines = Math.min(20, Math.ceil(state.textLength / 50));
631
+ for (let i = 0; i <= numLines; i++) {
632
+ const x = (i / numLines) * canvas.width;
633
+ ctx.beginPath();
634
+ ctx.moveTo(x, 0);
635
+ ctx.lineTo(x, canvas.height);
636
+ ctx.stroke();
637
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
638
  }
639
 
640
+ function drawEmotionCurve(point, index) {
641
+ const isSelected = state.selectedPoint === index;
642
+ const alpha = isSelected ? 0.8 : 0.4;
 
 
 
 
 
643
 
644
+ ctx.strokeStyle = point.color;
645
+ ctx.globalAlpha = alpha;
646
+ ctx.lineWidth = isSelected ? 3 : 2;
647
+
648
+ // Create gradient fill
649
+ const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);
650
+ gradient.addColorStop(0, point.color + '40');
651
+ gradient.addColorStop(1, point.color + '00');
652
+
653
+ // Draw curve
654
+ ctx.beginPath();
655
+
656
+ const startX = point.start * canvas.width;
657
+ const peakX = point.peak * canvas.width;
658
+ const endX = point.end * canvas.width;
659
+
660
+ const startY = (1 - point.startIntensity) * canvas.height;
661
+ const peakY = (1 - point.peakIntensity) * canvas.height;
662
+ const endY = (1 - point.endIntensity) * canvas.height;
663
+
664
+ ctx.moveTo(startX, startY);
665
+
666
+ // Smooth curve through peak
667
+ if (peakX - startX > 10) {
668
+ ctx.bezierCurveTo(
669
+ startX + (peakX - startX) * 0.5, startY,
670
+ startX + (peakX - startX) * 0.5, peakY,
671
+ peakX, peakY
672
+ );
673
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
674
 
675
+ if (endX - peakX > 10) {
676
+ ctx.bezierCurveTo(
677
+ peakX + (endX - peakX) * 0.5, peakY,
678
+ peakX + (endX - peakX) * 0.5, endY,
679
+ endX, endY
680
+ );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
681
  }
 
 
 
682
 
683
+ ctx.stroke();
684
+
685
+ // Fill under curve
686
+ ctx.lineTo(endX, canvas.height);
687
+ ctx.lineTo(startX, canvas.height);
688
+ ctx.closePath();
689
+ ctx.fillStyle = gradient;
690
+ ctx.fill();
691
+
692
+ ctx.globalAlpha = 1;
693
  }
694
 
695
+ function drawPointControls(point, index) {
696
+ const isSelected = state.selectedPoint === index;
697
+
698
+ // Main point (at peak)
699
+ const peakX = point.peak * canvas.width;
700
+ const peakY = (1 - point.peakIntensity) * canvas.height;
701
+
702
+ ctx.beginPath();
703
+ ctx.arc(peakX, peakY, isSelected ? 8 : 6, 0, Math.PI * 2);
704
+ ctx.fillStyle = point.color;
705
+ ctx.fill();
706
+ ctx.strokeStyle = '#fff';
707
+ ctx.lineWidth = 2;
708
+ ctx.stroke();
709
+
710
+ // Start handle
711
+ const startX = point.start * canvas.width;
712
+ const startY = (1 - point.startIntensity) * canvas.height;
713
 
714
+ ctx.beginPath();
715
+ ctx.arc(startX, startY, 4, 0, Math.PI * 2);
716
+ ctx.fillStyle = isSelected ? '#fff' : point.color;
717
+ ctx.fill();
718
+
719
+ // End handle
720
+ const endX = point.end * canvas.width;
721
+ const endY = (1 - point.endIntensity) * canvas.height;
722
+
723
+ ctx.beginPath();
724
+ ctx.arc(endX, endY, 4, 0, Math.PI * 2);
725
+ ctx.fillStyle = isSelected ? '#fff' : point.color;
726
+ ctx.fill();
727
+
728
+ // Labels
729
+ if (isSelected) {
730
+ ctx.fillStyle = '#fff';
731
+ ctx.font = '11px sans-serif';
732
+ ctx.fillText(point.emotion, peakX + 10, peakY - 10);
733
+ }
734
  }
735
 
736
+ // ==================== MOUSE INTERACTIONS ====================
737
+ canvas.addEventListener('mousedown', handleMouseDown);
738
+ canvas.addEventListener('mousemove', handleMouseMove);
739
+ canvas.addEventListener('mouseup', handleMouseUp);
740
+ canvas.addEventListener('contextmenu', handleRightClick);
741
 
742
+ function handleMouseDown(e) {
743
+ const rect = canvas.getBoundingClientRect();
744
+ const x = e.clientX - rect.left;
745
+ const y = e.clientY - rect.top;
746
+
747
+ // Check if clicking on existing point or handle
748
+ const clickedElement = findClickedElement(x, y);
749
+
750
+ if (clickedElement) {
751
+ state.selectedPoint = clickedElement.index;
752
+ state.isDragging = true;
753
+ state.dragMode = clickedElement.type;
754
+ state.dragStartPos = { x, y };
755
+
756
+ if (clickedElement.type === 'move') {
757
+ state.dragStartValue = { ...state.points[clickedElement.index] };
758
+ }
759
+ } else {
760
+ // Add new point
761
+ addNewPoint(x, y);
762
  }
763
+
764
+ drawTimeline();
765
+ }
766
 
767
+ function handleMouseMove(e) {
768
+ const rect = canvas.getBoundingClientRect();
769
+ const x = e.clientX - rect.left;
770
+ const y = e.clientY - rect.top;
771
+
772
+ if (state.isDragging && state.selectedPoint !== null) {
773
+ const point = state.points[state.selectedPoint];
774
+ const dx = x - state.dragStartPos.x;
775
+ const dy = y - state.dragStartPos.y;
776
+
777
+ switch (state.dragMode) {
778
+ case 'move':
779
+ // Horizontal move
780
+ const deltaRatio = dx / canvas.width;
781
+ const newPeak = Math.max(0, Math.min(1,
782
+ state.dragStartValue.peak + deltaRatio));
783
+ const shift = newPeak - state.dragStartValue.peak;
784
+
785
+ point.peak = newPeak;
786
+ point.start = Math.max(0, state.dragStartValue.start + shift);
787
+ point.end = Math.min(1, state.dragStartValue.end + shift);
788
+ break;
789
+
790
+ case 'intensity':
791
+ // Vertical intensity change
792
+ const deltaIntensity = -dy / canvas.height;
793
+ point.peakIntensity = Math.max(0, Math.min(1,
794
+ state.dragStartValue.peakIntensity + deltaIntensity));
795
+ break;
796
+
797
+ case 'handleIn':
798
+ // Adjust start position and intensity
799
+ point.start = Math.max(0, Math.min(point.peak - 0.01, x / canvas.width));
800
+ point.startIntensity = Math.max(0, Math.min(1, 1 - y / canvas.height));
801
+ break;
802
+
803
+ case 'handleOut':
804
+ // Adjust end position and intensity
805
+ point.end = Math.min(1, Math.max(point.peak + 0.01, x / canvas.width));
806
+ point.endIntensity = Math.max(0, Math.min(1, 1 - y / canvas.height));
807
+ break;
808
+ }
809
+
810
+ drawTimeline();
811
+ updateEditorGlow(textEditor.selectionStart);
812
+ } else {
813
+ // Show tooltip on hover
814
+ const hoveredElement = findClickedElement(x, y);
815
+ if (hoveredElement) {
816
+ showTooltip(e.clientX, e.clientY, state.points[hoveredElement.index]);
817
+ } else {
818
+ hideTooltip();
819
+ }
820
  }
821
+ }
822
 
823
+ function handleMouseUp() {
824
+ state.isDragging = false;
825
+ state.dragMode = null;
826
+ state.dragStartPos = null;
827
+ state.dragStartValue = null;
828
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
829
 
830
+ function handleRightClick(e) {
831
+ e.preventDefault();
832
+ const rect = canvas.getBoundingClientRect();
833
+ const x = e.clientX - rect.left;
834
+ const y = e.clientY - rect.top;
835
+
836
+ const clickedElement = findClickedElement(x, y);
837
+ if (clickedElement) {
838
+ state.points.splice(clickedElement.index, 1);
839
+ if (state.selectedPoint === clickedElement.index) {
840
+ state.selectedPoint = null;
841
  }
842
+ drawTimeline();
 
843
  }
844
  }
845
 
846
+ function findClickedElement(x, y) {
847
+ const threshold = 10;
848
+
849
+ for (let i = state.points.length - 1; i >= 0; i--) {
850
+ const point = state.points[i];
851
+
852
+ // Check peak point
853
+ const peakX = point.peak * canvas.width;
854
+ const peakY = (1 - point.peakIntensity) * canvas.height;
855
+ const distPeak = Math.sqrt(Math.pow(x - peakX, 2) + Math.pow(y - peakY, 2));
856
+
857
+ if (distPeak < threshold) {
858
+ return { index: i, type: 'move' };
859
+ }
860
+
861
+ // Check start handle
862
+ const startX = point.start * canvas.width;
863
+ const startY = (1 - point.startIntensity) * canvas.height;
864
+ const distStart = Math.sqrt(Math.pow(x - startX, 2) + Math.pow(y - startY, 2));
865
+
866
+ if (distStart < threshold) {
867
+ return { index: i, type: 'handleIn' };
868
+ }
869
+
870
+ // Check end handle
871
+ const endX = point.end * canvas.width;
872
+ const endY = (1 - point.endIntensity) * canvas.height;
873
+ const distEnd = Math.sqrt(Math.pow(x - endX, 2) + Math.pow(y - endY, 2));
874
+
875
+ if (distEnd < threshold) {
876
+ return { index: i, type: 'handleOut' };
877
+ }
878
  }
879
+
880
+ return null;
881
+ }
882
 
883
+ function addNewPoint(x, y) {
884
+ const ratio = x / canvas.width;
885
+ const intensity = 1 - y / canvas.height;
886
+
887
+ const newPoint = {
888
+ emotion: state.currentEmotion,
889
+ color: getEmotionColor(state.currentEmotion),
890
+ start: Math.max(0, ratio - 0.1),
891
+ peak: ratio,
892
+ end: Math.min(1, ratio + 0.1),
893
+ startIntensity: Math.max(0, intensity - 0.2),
894
+ peakIntensity: intensity,
895
+ endIntensity: Math.max(0, intensity - 0.2)
896
  };
897
+
898
+ state.points.push(newPoint);
899
+ state.selectedPoint = state.points.length - 1;
900
+
901
+ drawTimeline();
902
+ }
903
 
904
+ // ==================== TOOLTIP ====================
905
+ function showTooltip(x, y, point) {
906
+ pointTooltip.innerHTML = `
907
+ <strong>${point.emotion}</strong><br>
908
+ Intensity: ${point.peakIntensity.toFixed(2)}<br>
909
+ Range: ${(point.start * 100).toFixed(0)}% - ${(point.end * 100).toFixed(0)}%
910
+ `;
911
+ pointTooltip.style.left = (x + 15) + 'px';
912
+ pointTooltip.style.top = (y - 10) + 'px';
913
+ pointTooltip.classList.add('visible');
914
+ }
 
 
 
 
 
 
 
 
915
 
916
+ function hideTooltip() {
917
+ pointTooltip.classList.remove('visible');
918
+ }
 
 
 
 
 
 
919
 
920
+ // ==================== UTILITY FUNCTIONS ====================
921
+ function hexToRgb(hex) {
922
+ const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);
923
+ return result ? {
924
+ r: parseInt(result[1], 16),
925
+ g: parseInt(result[2], 16),
926
+ b: parseInt(result[3], 16)
927
+ } : { r: 0, g: 0, b: 0 };
928
  }
929
 
930
+ function clearAllPoints() {
931
+ if (confirm('Clear all emotion points?')) {
932
+ state.points = [];
933
+ state.selectedPoint = null;
934
+ drawTimeline();
935
+ }
936
+ }
 
 
 
 
 
937
 
938
+ function toggleJSONPreview() {
939
+ jsonPreview.classList.toggle('visible');
940
+ if (jsonPreview.classList.contains('visible')) {
941
+ exportJSON();
942
+ }
943
  }
944
 
945
+ // ==================== DATA EXPORT ====================
946
+ function generateDataSchema() {
947
+ const text = textEditor.value;
948
+ const emotionalFlow = [];
949
+
950
+ // Group points by emotion
951
+ const emotionGroups = {};
952
+ state.points.forEach(point => {
953
+ if (!emotionGroups[point.emotion]) {
954
+ emotionGroups[point.emotion] = [];
955
+ }
956
+
957
+ const startChar = Math.floor(point.start * text.length);
958
+ const endChar = Math.floor(point.end * text.length);
959
+
960
+ emotionGroups[point.emotion].push({
961
+ start_char: startChar,
962
+ end_char: endChar,
963
+ start_intensity: parseFloat(point.startIntensity.toFixed(2)),
964
+ peak_intensity: parseFloat(point.peakIntensity.toFixed(2)),
965
+ end_intensity: parseFloat(point.endIntensity.toFixed(2))
966
+ });
967
+ });
968
+
969
+ // Convert to schema format
970
+ Object.keys(emotionGroups).forEach(emotion => {
971
+ emotionalFlow.push({
972
+ label: emotion,
973
+ curves: emotionGroups[emotion]
974
+ });
975
+ });
976
+
977
  return {
978
+ text: text,
979
+ emotional_flow: emotionalFlow
 
 
 
 
 
 
 
980
  };
981
  }
982
 
983
+ function exportJSON() {
984
+ const data = generateDataSchema();
985
+ const jsonStr = JSON.stringify(data, null, 2);
986
+
987
+ jsonPreview.textContent = jsonStr;
988
+ jsonPreview.classList.add('visible');
989
+
990
+ // Also download file
991
+ const blob = new Blob([jsonStr], { type: 'application/json' });
992
  const url = URL.createObjectURL(blob);
993
  const a = document.createElement('a');
994
  a.href = url;
995
+ a.download = 'self-distillation-' + Date.now() + '.json';
 
996
  a.click();
 
997
  URL.revokeObjectURL(url);
998
  }
999
 
1000
+ function generateParquet() {
1001
+ const data = generateDataSchema();
1002
+
1003
+ // Convert to flat format suitable for parquet
1004
+ const rows = [];
1005
+ data.emotional_flow.forEach(emotion => {
1006
+ emotion.curves.forEach(curve => {
1007
+ rows.push({
1008
+ text: data.text,
1009
+ emotion_label: emotion.label,
1010
+ start_char: curve.start_char,
1011
+ end_char: curve.end_char,
1012
+ start_intensity: curve.start_intensity,
1013
+ peak_intensity: curve.peak_intensity,
1014
+ end_intensity: curve.end_intensity
1015
+ });
1016
+ });
1017
+ });
1018
+
1019
+ // Create CSV as intermediate format (parquet requires library)
1020
+ let csv = 'text,emotion_label,start_char,end_char,start_intensity,peak_intensity,end_intensity\\n';
1021
+ rows.forEach(row => {
1022
+ csv += `"${row.text.replace(/"/g, '""')}","${row.emotion_label}",${row.start_char},${row.end_char},${row.start_intensity},${row.peak_intensity},${row.end_intensity}\\n`;
1023
+ });
1024
+
1025
+ const blob = new Blob([csv], { type: 'text/csv' });
1026
+ const url = URL.createObjectURL(blob);
1027
+ const a = document.createElement('a');
1028
+ a.href = url;
1029
+ a.download = 'self-distillation-' + Date.now() + '.csv';
1030
+ a.click();
1031
+ URL.revokeObjectURL(url);
1032
+
1033
+ alert('CSV exported (convert to Parquet using pandas: df.to_parquet())');
1034
  }
1035
 
1036
+ function generateJinja2Card() {
1037
+ const data = generateDataSchema();
1038
+ const repoId = document.getElementById('hfRepoId').value || 'my-dataset';
1039
+
1040
+ const cardContent = `---
1041
+ dataset_info:
1042
+ features:
1043
+ - name: text
1044
+ dtype: string
1045
+ - name: emotional_flow
1046
+ sequence:
1047
+ - name: label
1048
+ dtype: string
1049
+ - name: curves
1050
+ sequence:
1051
+ - name: start_char
1052
+ dtype: int32
1053
+ - name: end_char
1054
+ dtype: int32
1055
+ - name: start_intensity
1056
+ dtype: float32
1057
+ - name: peak_intensity
1058
+ dtype: float32
1059
+ - name: end_intensity
1060
+ dtype: float32
1061
+ splits:
1062
+ - name: train
1063
+ num_bytes: ${new Blob([JSON.stringify(data)]).size}
1064
+ num_examples: 1
1065
+ task_categories:
1066
+ - text-classification
1067
+ - text-generation
1068
+ language:
1069
+ - en
1070
+ license: mit
1071
+ pretty_name: Self-Distillation Dataset
1072
+ ---
1073
+
1074
+ # Self-Distillation Dataset
1075
+
1076
+ This dataset contains text annotated with emotional flow curves.
1077
+
1078
+ ## Dataset Summary
1079
+
1080
+ Text samples with character-level emotional metadata including intensity curves with fades and peaks.
1081
+
1082
+ ## Data Fields
1083
+
1084
+ - **text**: The original text content
1085
+ - **emotional_flow**: Array of emotional annotations
1086
+ - **label**: Emotion name (e.g., "Joy", "Anxiety")
1087
+ - **curves**: Array of intensity curves
1088
+ - **start_char**: Starting character position
1089
+ - **end_char**: Ending character position
1090
+ - **start_intensity**: Initial intensity (0.0-1.0)
1091
+ - **peak_intensity**: Peak intensity (0.0-1.0)
1092
+ - **end_intensity**: Final intensity (0.0-1.0)
1093
+
1094
+ ## Usage
1095
+
1096
+ \`\`\`python
1097
+ from datasets import load_dataset
1098
+
1099
+ dataset = load_dataset("${repoId}")
1100
+ print(dataset['train'][0])
1101
+ \`\`\`
1102
+
1103
+ ## Creation
1104
+
1105
+ Created using the Self-Distillation web app with DAW-style emotional timeline editing.
1106
+ `;
1107
+
1108
+ const blob = new Blob([cardContent], { type: 'text/markdown' });
1109
+ const url = URL.createObjectURL(blob);
1110
+ const a = document.createElement('a');
1111
+ a.href = url;
1112
+ a.download = 'README.md';
1113
+ a.click();
1114
+ URL.revokeObjectURL(url);
1115
  }
1116
 
1117
+ async function pushToHF() {
1118
+ const token = document.getElementById('hfToken').value;
1119
+ const repoId = document.getElementById('hfRepoId').value;
1120
+
1121
+ if (!token || !repoId) {
1122
+ alert('Please enter both HF Token and Repository ID');
1123
+ return;
1124
+ }
1125
+
1126
+ // Save to localStorage
1127
+ localStorage.setItem('hf_token', token);
1128
+ localStorage.setItem('hf_repo_id', repoId);
1129
+
1130
+ const data = generateDataSchema();
1131
+ const jsonStr = JSON.stringify(data, null, 2);
1132
+
1133
+ try {
1134
+ // Create repository if it doesn't exist
1135
+ await fetch(`https://huggingface.co/api/repos/create`, {
1136
+ method: 'POST',
1137
+ headers: {
1138
+ 'Authorization': `Bearer ${token}`,
1139
+ 'Content-Type': 'application/json'
1140
+ },
1141
+ body: JSON.stringify({
1142
+ name: repoId.split('/')[1],
1143
+ type: 'dataset',
1144
+ private: false
1145
+ })
1146
+ });
1147
+
1148
+ // Upload file
1149
+ const filename = `data-${Date.now()}.json`;
1150
+ await fetch(`https://huggingface.co/api/datasets/${repoId}/upload/${filename}`, {
1151
+ method: 'POST',
1152
+ headers: {
1153
+ 'Authorization': `Bearer ${token}`,
1154
+ 'Content-Type': 'application/json'
1155
+ },
1156
+ body: jsonStr
1157
+ });
1158
+
1159
+ alert('Successfully pushed to HuggingFace!');
1160
+ } catch (error) {
1161
+ console.error('Error pushing to HF:', error);
1162
+ alert('Error: ' + error.message + '\\n\\nMake sure your token has write permissions.');
1163
+ }
1164
  }
1165
 
1166
+ // ==================== INITIALIZATION ====================
1167
+ function init() {
1168
+ // Load saved HF credentials
1169
+ const savedToken = localStorage.getItem('hf_token');
1170
+ const savedRepo = localStorage.getItem('hf_repo_id');
1171
+
1172
+ if (savedToken) document.getElementById('hfToken').value = savedToken;
1173
+ if (savedRepo) document.getElementById('hfRepoId').value = savedRepo;
1174
+
1175
+ // Initialize canvas
1176
+ initCanvas();
1177
+
1178
+ // Set initial text length
1179
+ updateTextLength();
1180
  }
1181
 
1182
+ // Start the app
1183
+ init();
1184
  </script>
1185
  </body>
1186
  </html>