RexTRO111 commited on
Commit
6c3cead
·
verified ·
1 Parent(s): 9e67340

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +654 -18
index.html CHANGED
@@ -1,19 +1,655 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
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>JSONL Dataset Maker</title>
7
+ <!-- Tailwind CSS -->
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <!-- Google Fonts Inter -->
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Fira+Code:wght@400;500&display=swap" rel="stylesheet">
11
+ <style>
12
+ body {
13
+ font-family: 'Inter', sans-serif;
14
+ }
15
+ .code-font {
16
+ font-family: 'Fira Code', monospace;
17
+ }
18
+ </style>
19
+ </head>
20
+ <body class="bg-slate-950 text-slate-100 min-h-screen flex flex-col selection:bg-indigo-500 selection:text-white">
21
+
22
+ <!-- Header -->
23
+ <header class="border-b border-slate-800 bg-slate-900/50 backdrop-blur-md sticky top-0 z-50 px-6 py-4">
24
+ <div class="max-w-7xl mx-auto flex flex-col sm:flex-row justify-between items-center gap-4">
25
+ <div class="flex items-center gap-3">
26
+ <div class="h-10 w-10 rounded-xl bg-gradient-to-tr from-indigo-500 to-violet-500 flex items-center justify-center shadow-lg shadow-indigo-500/20">
27
+ <svg class="w-6 h-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
28
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
29
+ </svg>
30
+ </div>
31
+ <div>
32
+ <h1 class="text-xl font-bold tracking-tight bg-gradient-to-r from-white to-slate-400 bg-clip-text text-transparent">JSONL Dataset Architect</h1>
33
+ <p class="text-xs text-slate-400">Build high-quality multi-turn or single-turn AI training data</p>
34
+ </div>
35
+ </div>
36
+
37
+ <div class="flex items-center gap-3">
38
+ <span class="text-xs bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 px-3 py-1.5 rounded-full font-medium">
39
+ Active Dataset Model
40
+ </span>
41
+ </div>
42
+ </div>
43
+ </header>
44
+
45
+ <!-- Main Content Grid -->
46
+ <main class="flex-1 max-w-7xl w-full mx-auto p-4 md:p-6 grid grid-cols-1 lg:grid-cols-12 gap-6">
47
+
48
+ <!-- Left Column: Controls and Input Form (7 cols) -->
49
+ <section class="lg:col-span-7 flex flex-col gap-6">
50
+
51
+ <!-- Format Selector Card -->
52
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-xl">
53
+ <label class="block text-sm font-semibold text-slate-300 mb-3">Choose Dataset Format</label>
54
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3">
55
+ <div class="relative">
56
+ <select id="formatSelector" onchange="handleFormatChange()" class="w-full bg-slate-950 border border-slate-800 rounded-xl px-4 py-3 text-sm text-slate-200 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all cursor-pointer appearance-none">
57
+ <option value="reasoning">Reasoning Messages (with think tags)</option>
58
+ <option value="messages">Messages (Normal, no reasoning)</option>
59
+ <option value="sharegpt">ShareGPT Conversations</option>
60
+ <option value="alpaca">Alpaca (Single Turn)</option>
61
+ <option value="custom">Custom JSON Structure</option>
62
+ </select>
63
+ <div class="pointer-events-none absolute inset-y-0 right-0 flex items-center px-4 text-slate-400">
64
+ <svg class="fill-current h-4 w-4" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path d="M9.293 12.95l.707.707L15.657 8l-1.414-1.414L10 10.828 5.757 6.586 4.343 8z"/></svg>
65
+ </div>
66
+ </div>
67
+ <div id="formatBadge" class="flex items-center justify-center px-4 py-3 bg-indigo-950/30 text-indigo-400 border border-indigo-900/50 rounded-xl text-xs font-semibold text-center">
68
+ Multi-turn conversations supported
69
+ </div>
70
+ </div>
71
+ </div>
72
+
73
+ <!-- Current Entry Editor Form -->
74
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-xl flex-1 flex flex-col gap-5">
75
+ <div class="flex justify-between items-center pb-4 border-b border-slate-800">
76
+ <h2 class="text-md font-bold text-slate-200 flex items-center gap-2">
77
+ <svg class="w-5 h-5 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
78
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
79
+ </svg>
80
+ Construct Current Line Entry
81
+ </h2>
82
+ <span id="turnsCount" class="text-xs bg-slate-800 px-2.5 py-1 rounded-md text-slate-400 font-medium">
83
+ 0 Turns Created
84
+ </span>
85
+ </div>
86
+
87
+ <!-- Input Fields Container -->
88
+ <div id="dynamicInputsContainer" class="flex-1 space-y-4 max-h-[50vh] overflow-y-auto pr-1">
89
+ <!-- Loaded dynamically via JavaScript based on Format Selector -->
90
+ </div>
91
+
92
+ <!-- Add Turn controls (hidden for Alpaca format) -->
93
+ <div id="turnControls" class="flex flex-wrap gap-2 pt-2 border-t border-slate-800/50">
94
+ <button type="button" onclick="addTurn('user')" class="flex-1 min-w-[120px] bg-slate-800 hover:bg-slate-750 text-slate-200 hover:text-white px-4 py-2.5 rounded-xl text-xs font-semibold flex items-center justify-center gap-2 transition-all border border-slate-700/50">
95
+ <svg class="w-4 h-4 text-emerald-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
96
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m0-6H6" />
97
+ </svg>
98
+ Add User Turn
99
+ </button>
100
+ <button type="button" onclick="addTurn('assistant')" class="flex-1 min-w-[120px] bg-slate-800 hover:bg-slate-750 text-slate-200 hover:text-white px-4 py-2.5 rounded-xl text-xs font-semibold flex items-center justify-center gap-2 transition-all border border-slate-700/50">
101
+ <svg class="w-4 h-4 text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
102
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m0-6H6" />
103
+ </svg>
104
+ Add Assistant Turn
105
+ </button>
106
+ </div>
107
+
108
+ <!-- Submission Controls -->
109
+ <div class="flex gap-3 pt-3">
110
+ <button type="button" onclick="resetCurrentForm()" class="px-5 py-3 rounded-xl text-xs font-bold bg-slate-950 hover:bg-slate-900 border border-slate-800 text-slate-400 hover:text-slate-200 transition-all">
111
+ Reset Editor
112
+ </button>
113
+ <button type="button" onclick="commitEntry()" class="flex-1 bg-gradient-to-r from-indigo-500 to-violet-500 hover:from-indigo-600 hover:to-violet-600 text-white px-6 py-3 rounded-xl text-xs font-bold shadow-lg shadow-indigo-500/15 hover:shadow-indigo-500/25 transition-all flex items-center justify-center gap-2">
114
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
115
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
116
+ </svg>
117
+ Add Line to JSONL
118
+ </button>
119
+ </div>
120
+ </div>
121
+ </section>
122
+
123
+ <!-- Right Column: Live Preview & Line Manager (5 cols) -->
124
+ <section class="lg:col-span-5 flex flex-col gap-6">
125
+
126
+ <!-- Live Preview -->
127
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-xl flex flex-col h-[400px]">
128
+ <div class="flex justify-between items-center pb-4 border-b border-slate-800">
129
+ <div class="flex items-center gap-2">
130
+ <div class="h-2 w-2 rounded-full bg-emerald-500 animate-pulse"></div>
131
+ <h2 class="text-md font-bold text-slate-200">Live Preview</h2>
132
+ </div>
133
+ <button onclick="copyToClipboard()" class="bg-slate-800 hover:bg-indigo-600 hover:text-white text-slate-300 px-3 py-1.5 rounded-lg text-xs font-semibold flex items-center gap-1.5 transition-all border border-slate-700/50">
134
+ <svg id="copyIcon" class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
135
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
136
+ </svg>
137
+ <span id="copyText">Copy Output</span>
138
+ </button>
139
+ </div>
140
+
141
+ <!-- JSONL Preview Output -->
142
+ <div class="flex-1 overflow-auto mt-4 bg-slate-950 border border-slate-850 rounded-xl p-4 relative">
143
+ <pre id="jsonlPreview" class="code-font text-xs text-indigo-300 whitespace-pre-wrap break-all select-all leading-relaxed h-full">{}</pre>
144
+ <div id="emptyState" class="absolute inset-0 flex flex-col items-center justify-center bg-slate-950 p-6 text-center">
145
+ <svg class="w-12 h-12 text-slate-700 mb-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
146
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
147
+ </svg>
148
+ <span class="text-sm font-semibold text-slate-500">No JSON Lines Created</span>
149
+ <span class="text-xs text-slate-600 mt-1">Populate input fields and add a line to start building your dataset.</span>
150
+ </div>
151
+ </div>
152
+ </div>
153
+
154
+ <!-- List Management -->
155
+ <div class="bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-xl flex-1 flex flex-col min-h-[250px]">
156
+ <div class="flex justify-between items-center pb-4 border-b border-slate-800">
157
+ <h2 class="text-sm font-bold text-slate-200">Lines in Dataset</h2>
158
+ <button onclick="clearDataset()" class="text-rose-400 hover:text-rose-300 text-xs font-semibold transition-all">
159
+ Clear All Lines
160
+ </button>
161
+ </div>
162
+
163
+ <!-- Lines List Area -->
164
+ <div id="datasetLinesContainer" class="flex-1 overflow-y-auto space-y-2 mt-4 max-h-[250px] pr-1">
165
+ <!-- Items mapped via JS -->
166
+ </div>
167
+ </div>
168
+ </section>
169
+ </main>
170
+
171
+ <!-- Custom Modal/Toast Notifications -->
172
+ <div id="toast" class="fixed bottom-6 right-6 z-50 bg-slate-900 border border-slate-800 px-4 py-3 rounded-xl shadow-2xl flex items-center gap-3 transform translate-y-20 opacity-0 transition-all duration-300 pointer-events-none">
173
+ <div id="toastIcon" class="h-6 w-6 rounded-full flex items-center justify-center"></div>
174
+ <p id="toastMessage" class="text-xs font-medium text-slate-200"></p>
175
+ </div>
176
+
177
+ <!-- Hidden input helper for secure clipboard manipulation -->
178
+ <textarea id="clipboardHelper" class="sr-only" aria-hidden="true"></textarea>
179
+
180
+ <!-- App Logic Script -->
181
+ <script>
182
+ // Configuration options
183
+ const APP_STATE = {
184
+ currentFormat: 'reasoning',
185
+ dataset: [],
186
+ currentTurns: [],
187
+ // Custom format structures
188
+ customKeys: ['prompt', 'response']
189
+ };
190
+
191
+ // DOM elements
192
+ const formatSelector = document.getElementById('formatSelector');
193
+ const formatBadge = document.getElementById('formatBadge');
194
+ const turnsCount = document.getElementById('turnsCount');
195
+ const dynamicInputsContainer = document.getElementById('dynamicInputsContainer');
196
+ const turnControls = document.getElementById('turnControls');
197
+ const jsonlPreview = document.getElementById('jsonlPreview');
198
+ const emptyState = document.getElementById('emptyState');
199
+ const datasetLinesContainer = document.getElementById('datasetLinesContainer');
200
+ const clipboardHelper = document.getElementById('clipboardHelper');
201
+ const toastElement = document.getElementById('toast');
202
+ const toastMsg = document.getElementById('toastMessage');
203
+ const toastIcon = document.getElementById('toastIcon');
204
+
205
+ // Setup onload
206
+ window.onload = function() {
207
+ handleFormatChange();
208
+ };
209
+
210
+ function handleFormatChange() {
211
+ APP_STATE.currentFormat = formatSelector.value;
212
+ resetCurrentForm();
213
+
214
+ // Adjust metadata badge and controls based on selection
215
+ if (APP_STATE.currentFormat === 'alpaca') {
216
+ formatBadge.innerText = "Single-turn instruction data format";
217
+ formatBadge.className = "flex items-center justify-center px-4 py-3 bg-indigo-950/30 text-indigo-400 border border-indigo-900/50 rounded-xl text-xs font-semibold text-center";
218
+ turnControls.classList.add('hidden');
219
+ turnsCount.classList.add('hidden');
220
+ buildAlpacaInputs();
221
+ } else if (APP_STATE.currentFormat === 'custom') {
222
+ formatBadge.innerText = "Custom single-turn Key-Value mapping";
223
+ formatBadge.className = "flex items-center justify-center px-4 py-3 bg-indigo-950/30 text-indigo-400 border border-indigo-900/50 rounded-xl text-xs font-semibold text-center";
224
+ turnControls.classList.add('hidden');
225
+ turnsCount.classList.add('hidden');
226
+ buildCustomInputs();
227
+ } else {
228
+ formatBadge.innerText = "Multi-turn conversations supported";
229
+ formatBadge.className = "flex items-center justify-center px-4 py-3 bg-indigo-950/30 text-indigo-400 border border-indigo-900/50 rounded-xl text-xs font-semibold text-center";
230
+ turnControls.classList.remove('hidden');
231
+ turnsCount.classList.remove('hidden');
232
+
233
+ // Add first turn default structure
234
+ addTurn('system');
235
+ addTurn('user');
236
+ addTurn('assistant');
237
+ }
238
+ }
239
+
240
+ function addTurn(role) {
241
+ const turnId = 'turn_' + Date.now() + '_' + Math.floor(Math.random() * 1000);
242
+ const turnObj = { id: turnId, role: role, content: '' };
243
+
244
+ if (role === 'assistant' && APP_STATE.currentFormat === 'reasoning') {
245
+ turnObj.reasoning = '';
246
+ }
247
+
248
+ APP_STATE.currentTurns.push(turnObj);
249
+ renderInputs();
250
+ updateTurnsBadge();
251
+ }
252
+
253
+ function removeTurn(id) {
254
+ APP_STATE.currentTurns = APP_STATE.currentTurns.filter(turn => turn.id !== id);
255
+ renderInputs();
256
+ updateTurnsBadge();
257
+ }
258
+
259
+ function updateTurnContent(id, field, value) {
260
+ const index = APP_STATE.currentTurns.findIndex(turn => turn.id === id);
261
+ if (index !== -1) {
262
+ APP_STATE.currentTurns[index][field] = value;
263
+ }
264
+ }
265
+
266
+ function updateTurnsBadge() {
267
+ const userAndAssistantTurns = APP_STATE.currentTurns.filter(t => t.role !== 'system').length;
268
+ turnsCount.innerText = `${userAndAssistantTurns} Turn${userAndAssistantTurns !== 1 ? 's' : ''} Created`;
269
+ }
270
+
271
+ function buildAlpacaInputs() {
272
+ dynamicInputsContainer.innerHTML = `
273
+ <div class="space-y-4">
274
+ <div>
275
+ <label class="block text-xs font-bold text-slate-400 mb-1.5 uppercase tracking-wide">Instruction</label>
276
+ <textarea id="alp_instruction" placeholder="What should the AI do?" class="w-full bg-slate-950 border border-slate-800 rounded-xl p-3.5 text-sm text-slate-200 placeholder-slate-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 min-h-[90px] transition-all resize-y"></textarea>
277
+ </div>
278
+ <div>
279
+ <label class="block text-xs font-bold text-slate-400 mb-1.5 uppercase tracking-wide">Input (Optional context)</label>
280
+ <textarea id="alp_input" placeholder="e.g. context text, variables, or background data" class="w-full bg-slate-950 border border-slate-800 rounded-xl p-3.5 text-sm text-slate-200 placeholder-slate-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 min-h-[80px] transition-all resize-y"></textarea>
281
+ </div>
282
+ <div>
283
+ <label class="block text-xs font-bold text-slate-400 mb-1.5 uppercase tracking-wide">Output</label>
284
+ <textarea id="alp_output" placeholder="The golden response output" class="w-full bg-slate-950 border border-slate-800 rounded-xl p-3.5 text-sm text-slate-200 placeholder-slate-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 min-h-[100px] transition-all resize-y"></textarea>
285
+ </div>
286
+ </div>
287
+ `;
288
+ }
289
+
290
+ function buildCustomInputs() {
291
+ let fieldsHtml = '';
292
+ APP_STATE.customKeys.forEach((key, index) => {
293
+ fieldsHtml += `
294
+ <div class="flex items-center gap-3 bg-slate-950/40 p-3 rounded-xl border border-slate-850">
295
+ <div class="flex-1">
296
+ <input type="text" value="${key}" placeholder="Key name" onchange="updateCustomKeyName(${index}, this.value)" class="code-font bg-slate-950 border border-slate-800 rounded-lg px-2 py-1 text-xs text-indigo-400 focus:outline-none focus:ring-1 focus:ring-indigo-500 focus:border-indigo-500 mb-1.5 font-semibold">
297
+ <textarea id="custom_val_${index}" placeholder="Value content..." class="w-full bg-slate-950 border border-slate-800 rounded-lg p-2 text-xs text-slate-200 placeholder-slate-600 focus:outline-none focus:ring-2 focus:ring-indigo-500 min-h-[60px] resize-y"></textarea>
298
+ </div>
299
+ <button type="button" onclick="removeCustomField(${index})" class="text-rose-500 hover:text-rose-400 hover:bg-slate-900 p-2 rounded-lg transition-all" title="Remove dynamic key">
300
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
301
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
302
+ </svg>
303
+ </button>
304
+ </div>
305
+ `;
306
+ });
307
+
308
+ dynamicInputsContainer.innerHTML = `
309
+ <div class="space-y-4">
310
+ <div class="flex justify-between items-center">
311
+ <label class="block text-xs font-bold text-slate-400 uppercase tracking-wide">Mapped Custom Keys</label>
312
+ <button type="button" onclick="addCustomField()" class="text-indigo-400 hover:text-indigo-300 text-xs font-semibold flex items-center gap-1">
313
+ + Add Key Field
314
+ </button>
315
+ </div>
316
+ <div class="space-y-3">
317
+ ${fieldsHtml}
318
+ </div>
319
+ </div>
320
+ `;
321
+ }
322
+
323
+ function addCustomField() {
324
+ const nextIndex = APP_STATE.customKeys.length + 1;
325
+ APP_STATE.customKeys.push(`key_${nextIndex}`);
326
+ buildCustomInputs();
327
+ }
328
+
329
+ function removeCustomField(index) {
330
+ APP_STATE.customKeys.splice(index, 1);
331
+ buildCustomInputs();
332
+ }
333
+
334
+ function updateCustomKeyName(index, newName) {
335
+ const standardizedName = newName.trim().replace(/[^a-zA-Z0-9_]/g, '');
336
+ APP_STATE.customKeys[index] = standardizedName || `key_${index + 1}`;
337
+ }
338
+
339
+ function renderInputs() {
340
+ if (APP_STATE.currentFormat === 'alpaca' || APP_STATE.currentFormat === 'custom') {
341
+ return; // Managed individually
342
+ }
343
+
344
+ dynamicInputsContainer.innerHTML = '';
345
+
346
+ APP_STATE.currentTurns.forEach((turn, index) => {
347
+ const isSystem = turn.role === 'system';
348
+ const isUser = turn.role === 'user' || turn.role === 'human';
349
+
350
+ // Set style accents based on role
351
+ let roleLabel = turn.role.toUpperCase();
352
+ let borderTheme = 'border-slate-800';
353
+ let tagTheme = 'bg-slate-950 border border-slate-850 text-slate-400';
354
+ let inputPlaceholder = 'Enter system prompt instructions...';
355
+
356
+ if (isUser) {
357
+ borderTheme = 'border-emerald-950/40 bg-emerald-950/5';
358
+ tagTheme = 'bg-emerald-950/40 text-emerald-400 border border-emerald-900/50';
359
+ inputPlaceholder = 'What would the human/user ask the model?';
360
+ } else if (turn.role === 'assistant' || turn.role === 'gpt') {
361
+ borderTheme = 'border-indigo-950/40 bg-indigo-950/5';
362
+ tagTheme = 'bg-indigo-950/40 text-indigo-400 border border-indigo-900/50';
363
+ inputPlaceholder = 'Model response goes here...';
364
+ }
365
+
366
+ // Compile reasoning fields html inside reasoning format assistant inputs
367
+ let reasoningFieldHtml = '';
368
+ if (turn.role === 'assistant' && APP_STATE.currentFormat === 'reasoning') {
369
+ reasoningFieldHtml = `
370
+ <div class="mt-2.5">
371
+ <label class="block text-[10px] font-semibold text-slate-500 mb-1">Reasoning Content (Wrapped inside think tags)</label>
372
+ <textarea placeholder="Write the model reasoning trajectory..." oninput="updateTurnContent('${turn.id}', 'reasoning', this.value)" class="w-full bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-xs text-indigo-300 placeholder-indigo-950 focus:outline-none focus:ring-1 focus:ring-indigo-500 min-h-[60px] resize-y">${turn.reasoning || ''}</textarea>
373
+ </div>
374
+ `;
375
+ }
376
+
377
+ const turnDiv = document.createElement('div');
378
+ turnDiv.className = `p-4 rounded-xl border ${borderTheme} flex flex-col gap-2 relative group transition-all`;
379
+ turnDiv.innerHTML = `
380
+ <div class="flex justify-between items-center">
381
+ <span class="text-[10px] font-bold px-2.5 py-1 rounded-md ${tagTheme} tracking-wide uppercase">
382
+ ${roleLabel}
383
+ </span>
384
+
385
+ <button type="button" onclick="removeTurn('${turn.id}')" class="text-slate-600 hover:text-rose-400 hover:bg-slate-900 p-1 rounded-lg transition-all" title="Delete turn">
386
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
387
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
388
+ </svg>
389
+ </button>
390
+ </div>
391
+
392
+ ${reasoningFieldHtml}
393
+
394
+ <div class="mt-1">
395
+ <textarea placeholder="${inputPlaceholder}" oninput="updateTurnContent('${turn.id}', 'content', this.value)" class="w-full bg-slate-950 border border-slate-800 rounded-lg p-2.5 text-xs text-slate-200 placeholder-slate-600 focus:outline-none focus:ring-1 focus:ring-indigo-500 min-h-[70px] resize-y">${turn.content || ''}</textarea>
396
+ </div>
397
+ `;
398
+
399
+ dynamicInputsContainer.appendChild(turnDiv);
400
+ });
401
+ }
402
+
403
+ function commitEntry() {
404
+ let newEntry = null;
405
+
406
+ if (APP_STATE.currentFormat === 'alpaca') {
407
+ const instruction = document.getElementById('alp_instruction').value.trim();
408
+ const input = document.getElementById('alp_input').value.trim();
409
+ const output = document.getElementById('alp_output').value.trim();
410
+
411
+ if (!instruction || !output) {
412
+ showToast('error', 'Instruction and Output are required fields');
413
+ return;
414
+ }
415
+
416
+ newEntry = {
417
+ instruction: instruction,
418
+ input: input,
419
+ output: output
420
+ };
421
+ } else if (APP_STATE.currentFormat === 'custom') {
422
+ newEntry = {};
423
+ let validationError = false;
424
+
425
+ APP_STATE.customKeys.forEach((key, index) => {
426
+ const textVal = document.getElementById(`custom_val_${index}`).value;
427
+ newEntry[key] = textVal;
428
+ if (!key.trim()) validationError = true;
429
+ });
430
+
431
+ if (validationError || APP_STATE.customKeys.length === 0) {
432
+ showToast('error', 'Please configure proper key values first');
433
+ return;
434
+ }
435
+ } else {
436
+ // Handling conversational formats: Messages, Reasoning, ShareGPT
437
+ const list = [];
438
+ let hasFilledTurn = false;
439
+
440
+ APP_STATE.currentTurns.forEach(turn => {
441
+ if (APP_STATE.currentFormat === 'sharegpt') {
442
+ // Transform role for ShareGPT
443
+ let sRole = 'human';
444
+ if (turn.role === 'system') sRole = 'system';
445
+ else if (turn.role === 'assistant') sRole = 'gpt';
446
+
447
+ list.push({
448
+ from: sRole,
449
+ value: turn.content
450
+ });
451
+ if (turn.content.trim()) hasFilledTurn = true;
452
+ } else {
453
+ // Messages & Reasoning
454
+ let messageContent = turn.content;
455
+
456
+ // If reasoning model format and role is assistant, prep prepended think tag
457
+ if (APP_STATE.currentFormat === 'reasoning' && turn.role === 'assistant') {
458
+ const rawReasoning = turn.reasoning || '';
459
+ messageContent = `<think>${rawReasoning}</think>\n${turn.content}`;
460
+ }
461
+
462
+ list.push({
463
+ role: turn.role,
464
+ content: messageContent
465
+ });
466
+ if (turn.content.trim()) hasFilledTurn = true;
467
+ }
468
+ });
469
+
470
+ if (!hasFilledTurn) {
471
+ showToast('error', 'Cannot add empty turn array into dataset');
472
+ return;
473
+ }
474
+
475
+ if (APP_STATE.currentFormat === 'sharegpt') {
476
+ newEntry = { conversations: list };
477
+ } else {
478
+ newEntry = { messages: list };
479
+ }
480
+ }
481
+
482
+ // Append to array state
483
+ APP_STATE.dataset.push(newEntry);
484
+ updatePreview();
485
+ renderDatasetLinesList();
486
+ showToast('success', 'Entry added to dataset successfully');
487
+
488
+ // Keep system templates on dynamic inputs but flush current contents for fresh loop
489
+ resetCurrentContentKeepStructure();
490
+ }
491
+
492
+ function resetCurrentContentKeepStructure() {
493
+ if (APP_STATE.currentFormat === 'alpaca') {
494
+ document.getElementById('alp_instruction').value = '';
495
+ document.getElementById('alp_input').value = '';
496
+ document.getElementById('alp_output').value = '';
497
+ } else if (APP_STATE.currentFormat === 'custom') {
498
+ APP_STATE.customKeys.forEach((_, index) => {
499
+ const textarea = document.getElementById(`custom_val_${index}`);
500
+ if (textarea) textarea.value = '';
501
+ });
502
+ } else {
503
+ APP_STATE.currentTurns.forEach(turn => {
504
+ turn.content = '';
505
+ if (turn.reasoning !== undefined) {
506
+ turn.reasoning = '';
507
+ }
508
+ });
509
+ renderInputs();
510
+ }
511
+ }
512
+
513
+ function resetCurrentForm() {
514
+ APP_STATE.currentTurns = [];
515
+ if (APP_STATE.currentFormat === 'alpaca') {
516
+ buildAlpacaInputs();
517
+ } else if (APP_STATE.currentFormat === 'custom') {
518
+ buildCustomInputs();
519
+ } else {
520
+ addTurn('system');
521
+ addTurn('user');
522
+ addTurn('assistant');
523
+ }
524
+ }
525
+
526
+ function updatePreview() {
527
+ if (APP_STATE.dataset.length === 0) {
528
+ jsonlPreview.innerText = '{}';
529
+ emptyState.classList.remove('hidden');
530
+ return;
531
+ }
532
+
533
+ emptyState.classList.add('hidden');
534
+ const lines = APP_STATE.dataset.map(item => JSON.stringify(item));
535
+ jsonlPreview.innerText = lines.join('\n');
536
+ }
537
+
538
+ function renderDatasetLinesList() {
539
+ datasetLinesContainer.innerHTML = '';
540
+
541
+ APP_STATE.dataset.forEach((entry, idx) => {
542
+ let entryHeader = `Line #${idx + 1}`;
543
+ let entryDetails = '';
544
+
545
+ if (entry.messages) {
546
+ const turnsCount = entry.messages.filter(m => m.role !== 'system').length;
547
+ entryDetails = `${turnsCount} multi-turn message interaction(s)`;
548
+ } else if (entry.conversations) {
549
+ const turnsCount = entry.conversations.filter(m => m.from !== 'system').length;
550
+ entryDetails = `${turnsCount} conversation exchange(s)`;
551
+ } else if (entry.instruction) {
552
+ entryDetails = 'Alpaca single-turn structured entry';
553
+ } else {
554
+ entryDetails = 'Custom flat key-value dynamic format';
555
+ }
556
+
557
+ const entryDiv = document.createElement('div');
558
+ entryDiv.className = "flex justify-between items-center bg-slate-950 p-3 rounded-xl border border-slate-850 hover:border-indigo-900/50 transition-all";
559
+ entryDiv.innerHTML = `
560
+ <div class="truncate pr-4">
561
+ <p class="text-xs font-bold text-slate-300">${entryHeader}</p>
562
+ <p class="text-[10px] text-slate-500 truncate">${entryDetails}</p>
563
+ </div>
564
+ <button onclick="removeDatasetLine(${idx})" class="text-rose-500 hover:text-rose-400 hover:bg-slate-900 p-1.5 rounded-lg transition-all" title="Delete dataset line">
565
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
566
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
567
+ </svg>
568
+ </button>
569
+ `;
570
+
571
+ datasetLinesContainer.appendChild(entryDiv);
572
+ });
573
+ }
574
+
575
+ function removeDatasetLine(index) {
576
+ APP_STATE.dataset.splice(index, 1);
577
+ updatePreview();
578
+ renderDatasetLinesList();
579
+ showToast('success', 'Dataset line removed');
580
+ }
581
+
582
+ function clearDataset() {
583
+ if (APP_STATE.dataset.length === 0) return;
584
+ APP_STATE.dataset = [];
585
+ updatePreview();
586
+ renderDatasetLinesList();
587
+ showToast('success', 'Dataset cleared completely');
588
+ }
589
+
590
+ function copyToClipboard() {
591
+ if (APP_STATE.dataset.length === 0) {
592
+ showToast('error', 'Dataset is empty. Add entries first.');
593
+ return;
594
+ }
595
+
596
+ const rawText = jsonlPreview.innerText;
597
+ clipboardHelper.value = rawText;
598
+ clipboardHelper.select();
599
+ clipboardHelper.setSelectionRange(0, 99999); // Mobile compatibility
600
+
601
+ try {
602
+ document.execCommand('copy');
603
+
604
+ // Show copied UI indicator temporary swap
605
+ const copyText = document.getElementById('copyText');
606
+ const copyIcon = document.getElementById('copyIcon');
607
+
608
+ copyText.innerText = 'Copied!';
609
+ copyIcon.innerHTML = `
610
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
611
+ `;
612
+
613
+ setTimeout(() => {
614
+ copyText.innerText = 'Copy Output';
615
+ copyIcon.innerHTML = `
616
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3" />
617
+ `;
618
+ }, 1500);
619
+
620
+ showToast('success', 'JSONL output copied to clipboard!');
621
+ } catch (err) {
622
+ showToast('error', 'Failed to copy text safely.');
623
+ }
624
+ }
625
+
626
+ function showToast(type, message) {
627
+ // Apply theme config for toast icon
628
+ if (type === 'success') {
629
+ toastIcon.className = "h-6 w-6 rounded-full flex items-center justify-center bg-emerald-950 border border-emerald-900 text-emerald-400";
630
+ toastIcon.innerHTML = `
631
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
632
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
633
+ </svg>
634
+ `;
635
+ } else {
636
+ toastIcon.className = "h-6 w-6 rounded-full flex items-center justify-center bg-rose-950 border border-rose-900 text-rose-400";
637
+ toastIcon.innerHTML = `
638
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
639
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M6 18L18 6M6 6l12 12" />
640
+ </svg>
641
+ `;
642
+ }
643
+
644
+ toastMsg.innerText = message;
645
+
646
+ // Pop Animation
647
+ toastElement.classList.remove('opacity-0', 'translate-y-20', 'pointer-events-none');
648
+
649
+ setTimeout(() => {
650
+ toastElement.classList.add('opacity-0', 'translate-y-20', 'pointer-events-none');
651
+ }, 3000);
652
+ }
653
+ </script>
654
+ </body>
655
  </html>