github-actions[bot] commited on
Commit
7966658
·
1 Parent(s): 22cac68

Auto-deploy from GitHub: 4b8e769df06025c01a36d9a592c63e4f204f0638

Browse files
Files changed (1) hide show
  1. index.html +624 -490
index.html CHANGED
@@ -2,311 +2,168 @@
2
  <html lang="en">
3
 
4
  <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <title>TTT</title>
8
- <link rel="preconnect" href="https://fonts.googleapis.com">
9
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
 
 
 
10
  <link
11
- href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Outfit:wght@600;800&family=Permanent+Marker&family=Patrick+Hand&display=swap"
12
- rel="stylesheet">
13
- <style>
14
- :root {
15
- /* Colors */
16
- --bg: #334155;
17
- --surface: #5e3329;
18
- --surface-hover: #7c4436;
19
- --border: #2d1a15;
20
- --primary: #2d1a15;
21
- --primary-light: #a36555;
22
- --success: #14532d;
23
- --success-light: #86efac;
24
- --error: #7f1d1d;
25
- --error-light: #fca5a5;
26
- --text-main: #fde6d2;
27
- --text-bg: #cbd5e1;
28
- --text-dim: #f5d0b1;
29
-
30
- /* Fonts */
31
- --font-main: 'Inter', sans-serif;
32
- --font-header: 'Patrick Hand', cursive;
33
- --font-logo: 'Permanent Marker', cursive;
34
-
35
- /* Sizes & Spacing */
36
- --border-width: 3px;
37
- --radius-card: 12px;
38
- --radius-btn: 8px;
39
- --radius-pill: 4px;
40
- --radius-full: 999px;
41
- --spacing-sm: 0.5rem;
42
- --spacing-md: 1rem;
43
- --spacing-lg: 1.5rem;
44
-
45
- /* Shadows */
46
- --shadow-card: 4px 4px 0px 0px var(--border);
47
- --shadow-btn: 3px 3px 0px 0px var(--border);
48
- }
49
-
50
- * {
51
- margin: 0;
52
- padding: 0;
53
- box-sizing: border-box;
54
- font-family: var(--font-main);
55
  }
 
56
 
 
 
57
  body {
58
- background-color: var(--bg);
59
- color: var(--text-bg);
60
- height: 100vh;
61
- width: 100vw;
62
- margin: 0;
63
- padding: 0;
64
- display: flex;
65
- align-items: center;
66
- justify-content: center;
67
- overflow: hidden;
68
- line-height: 1.5;
69
  }
70
 
71
- .container {
72
- width: 90vw;
73
- height: 90vh;
74
- display: grid;
75
- grid-template-columns: repeat(12, 1fr);
76
- grid-template-rows: auto 1fr;
77
- gap: 1.5rem;
78
  }
79
 
80
- /* Paper Cards */
81
- .card {
82
- background: var(--surface);
83
- border: var(--border-width) solid var(--border);
84
- border-radius: 255px 15px 225px 15px/15px 225px 15px 255px;
85
- padding: var(--spacing-lg);
86
- transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
87
- position: relative;
88
- overflow: hidden;
89
- box-shadow: var(--shadow-card);
90
  }
91
 
92
- .card:hover {
93
- transform: translate(-2px, -2px);
94
- box-shadow: 6px 6px 0px 0px var(--border);
95
  }
96
 
97
- /* Header Card */
98
- .header-card {
99
- grid-column: span 12;
100
- display: flex;
101
- justify-content: space-between;
102
- align-items: center;
103
- padding: 1.25rem 2.5rem;
104
- }
105
-
106
- .logo-group h1 {
107
- font-family: 'Permanent Marker', cursive;
108
- font-size: 2.5rem;
109
- color: var(--text-main);
110
- letter-spacing: 0.05em;
111
- transform: rotate(-2deg);
112
- text-shadow: 2px 2px 0px var(--primary);
113
- }
114
-
115
- .status-badge {
116
- display: flex;
117
- align-items: center;
118
- gap: 0.5rem;
119
- font-size: 0.875rem;
120
- color: var(--text-main);
121
- font-weight: 700;
122
- padding: 0.5rem 1rem;
123
- background: rgba(15, 23, 42, 0.1);
124
- border-radius: 999px;
125
- border: 1px solid var(--border);
126
  }
127
 
128
- .status-dot {
129
- width: 8px;
130
- height: 8px;
131
- border-radius: 50%;
132
- background: var(--success);
133
  }
134
 
135
- /* Input Card */
136
- .input-card {
137
- grid-column: span 4;
138
- display: flex;
139
- flex-direction: column;
140
- gap: 1.25rem;
141
  }
142
 
143
- .field-group {
144
- display: flex;
145
- flex-direction: column;
146
- gap: 0.5rem;
147
  }
148
 
149
- .field-label {
150
- font-size: 0.75rem;
151
- font-weight: 800;
152
- color: var(--text-dim);
153
- text-transform: uppercase;
154
- letter-spacing: 0.05em;
155
  }
156
 
157
- textarea {
158
- width: 100%;
159
- background: rgba(0, 0, 0, 0.2);
160
- border: var(--border-width) solid var(--border);
161
- border-radius: var(--radius-btn);
162
- color: var(--text-main);
163
- padding: 0.8rem;
164
- font-family: 'Inter', sans-serif;
165
- font-size: 0.9rem;
166
- resize: none;
167
- outline: none;
168
- transition: all 0.2s ease;
169
  }
170
 
171
- textarea:focus {
172
- border-color: var(--primary-light);
173
- background: rgba(0, 0, 0, 0.3);
174
  }
175
 
176
- #systemPrompt { height: 80px; }
177
- #inputText { flex: 1; min-height: 150px; }
 
 
178
 
179
- .submit-btn {
180
- background: var(--primary);
181
- color: var(--text-main);
182
- border: var(--border-width) solid var(--border);
183
- padding: var(--spacing-md);
184
- border-radius: 255px 15px 225px 15px/15px 225px 15px 255px;
185
- font-weight: 700;
186
- cursor: pointer;
187
- transition: all 0.2s ease;
188
- box-shadow: var(--shadow-btn);
189
- text-transform: uppercase;
190
  }
191
 
192
- .submit-btn:hover {
193
- transform: translate(-1px, -1px);
194
- box-shadow: 4px 4px 0px 0px var(--border);
195
- }
196
 
197
- .submit-btn:disabled {
198
- opacity: 0.5;
199
- cursor: not-allowed;
200
- transform: none;
201
- box-shadow: none;
202
- }
203
 
204
- /* Queue Card */
205
- .queue-card {
206
- grid-column: span 8;
207
- display: flex;
208
- flex-direction: column;
209
  }
210
 
211
- .card-header {
212
- display: flex;
213
- justify-content: space-between;
214
- align-items: center;
215
- margin-bottom: 1.25rem;
216
  }
217
 
218
- .card-title {
219
- font-size: 1.6rem;
220
- font-weight: 700;
221
- font-family: 'Patrick Hand', cursive;
222
- color: var(--text-main);
223
- letter-spacing: 0.02em;
224
  }
225
 
226
- .refresh-btn {
227
- background: var(--primary);
228
- border: var(--border-width) solid var(--border);
229
- color: var(--text-main);
230
- padding: 0.4rem 1.2rem;
231
  border-radius: 255px 15px 225px 15px/15px 225px 15px 255px;
232
- font-size: 0.8rem;
233
- font-weight: 700;
234
- cursor: pointer;
235
- transition: all 0.2s;
236
- box-shadow: 2px 2px 0px 0px var(--border);
237
  }
238
 
239
- .refresh-btn:hover {
240
- transform: translate(-1px, -1px);
241
- box-shadow: 4px 4px 0px 0px var(--border);
242
- opacity: 0.95;
243
  }
244
 
245
- /* Table */
246
- .table-container {
247
- flex: 1;
248
- overflow-y: auto;
249
- margin-right: -0.5rem;
250
- padding-right: 0.5rem;
251
  }
252
 
253
- table {
254
- width: 100%;
255
- border-collapse: collapse;
256
- }
257
-
258
- th {
259
- text-align: left;
260
- padding: 0.75rem 0.5rem;
261
- font-size: 0.8rem;
262
- font-weight: 800;
263
- color: var(--text-main);
264
- text-transform: uppercase;
265
- border-bottom: var(--border-width) solid var(--border);
266
- }
267
-
268
- td {
269
- padding: 1rem 0.5rem;
270
- font-size: 0.875rem;
271
- font-weight: 600;
272
- border-bottom: 1px solid rgba(253, 230, 210, 0.1);
273
- color: var(--text-main);
274
- }
275
-
276
- .status-pill {
277
- padding: 0.25rem 0.6rem;
278
- border: 2px solid var(--border);
279
- border-radius: 4px;
280
- font-size: 0.7rem;
281
- font-weight: 800;
282
- text-transform: uppercase;
283
- }
284
-
285
- .pill-not_started { background: rgba(15, 23, 42, 0.15); color: var(--text-main); }
286
- .pill-processing { background: var(--primary-light); color: var(--primary); }
287
- .pill-completed { background: var(--success-light); color: var(--success); }
288
- .pill-failed { background: var(--error-light); color: var(--error); }
289
-
290
- .btn-view {
291
- background: var(--primary);
292
- border: 2px solid var(--border);
293
- color: var(--text-main);
294
- padding: 0.3rem 1rem;
295
- border-radius: 255px 15px 225px 15px/15px 225px 15px 255px;
296
- font-size: 0.75rem;
297
- font-weight: 700;
298
- cursor: pointer;
299
- transition: all 0.2s;
300
- box-shadow: 2px 2px 0px 0px var(--border);
301
  }
302
 
303
- .btn-view:hover {
304
- transform: translate(-1px, -1px);
305
- box-shadow: 4px 4px 0px 0px var(--border);
306
- opacity: 0.95;
307
  }
308
 
309
- /* Modal */
310
  .modal {
311
  position: fixed;
312
  inset: 0;
@@ -323,162 +180,369 @@
323
  }
324
 
325
  .modal-content {
326
- background: var(--surface);
327
- border: var(--border-width) solid var(--border);
328
  width: 100%;
329
- max-width: 800px;
330
- border-radius: 255px 15px 225px 15px/15px 225px 15px 255px;
331
  display: flex;
332
  flex-direction: column;
333
- max-height: 80vh;
334
- box-shadow: var(--shadow-card);
 
 
 
 
 
 
 
 
 
 
 
 
335
  }
336
 
337
- .status-modal-content {
338
- max-width: 400px;
339
- text-align: center;
340
- padding: var(--spacing-lg);
341
- }
342
- .status-icon { margin: 0 auto var(--spacing-md); font-size: 3rem; }
343
-
344
- /* Utilities */
345
- .flex-group { display: flex; gap: var(--spacing-md); align-items: center; }
346
- .btn-api-doc { cursor: pointer; background: var(--primary-light); border-color: var(--primary); }
347
- .text-bold { font-weight: 700; color: var(--text-main); }
348
- .text-dimmed { font-size: 0.8rem; color: var(--text-main); opacity: 0.8; }
349
- .text-center { text-align: center; padding: 3rem; color: var(--text-bg); }
350
- .progress-container { width: 100px; height: 4px; background: rgba(253, 230, 210, 0.1); border-radius: 2px; overflow: hidden; }
351
- .progress-bar { height: 100%; background: var(--text-main); }
352
-
353
  .modal-header {
354
  padding: 1.5rem 2rem;
355
- border-bottom: 1px solid var(--border);
356
  display: flex;
357
  justify-content: space-between;
358
  align-items: center;
 
 
 
 
 
 
 
 
 
359
  }
360
 
361
- .modal-body { padding: 2rem; overflow-y: auto; }
362
-
363
  pre {
364
- background: #47271f;
365
- border: var(--border-width) solid var(--border);
366
- padding: 1.25rem;
367
- border-radius: 0.75rem;
368
- font-family: 'Inter', sans-serif;
369
- font-size: 0.9rem;
370
- color: var(--text-main);
371
- white-space: pre-wrap;
372
- word-break: break-all;
 
 
373
  }
374
 
375
  .close-modal {
376
  background: transparent;
377
  border: none;
378
- color: var(--text-dim);
379
- font-size: 1.5rem;
380
  cursor: pointer;
 
381
  }
382
 
383
- /* Scrollbar */
384
- ::-webkit-scrollbar { width: 6px; }
385
- ::-webkit-scrollbar-track { background: transparent; }
386
- ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 10px; }
387
 
388
  .copy-btn {
389
- background: var(--primary);
390
- color: var(--text-main);
391
- border: 2px solid var(--border);
392
- padding: 0.2rem 0.8rem;
393
- border-radius: 255px 15px 225px 15px/15px 225px 15px 255px;
394
- font-size: 0.7rem;
395
  font-weight: 700;
396
- cursor: pointer;
397
- box-shadow: 2px 2px 0 var(--border);
398
  transition: all 0.2s;
 
399
  }
400
 
401
  .copy-btn:hover {
402
- transform: translate(-1px, -1px);
403
- box-shadow: 3px 3px 0 var(--border);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  }
405
 
406
- @media (max-width: 900px) {
407
- .container { grid-template-columns: 1fr; height: auto; overflow-y: auto; padding: 1rem; }
408
- .card { grid-column: span 12 !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  }
410
  </style>
411
  </head>
412
 
413
- <body>
414
- <div class="container">
415
- <!-- Header -->
416
- <header class="card header-card">
417
- <div class="logo-group">
418
- <h1>TTT</h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
  </div>
420
- <div class="flex-group">
421
- <div class="status-badge btn-api-doc" id="apiDocBtn">
422
- <span class="text-bold">API DOC</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
423
  </div>
424
- <div class="status-badge">
425
- <div class="status-dot" id="healthDot"></div>
426
- <span id="healthText">Service Online</span>
427
  </div>
428
  </div>
429
- </header>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
430
 
431
- <!-- Input -->
432
- <div class="card input-card">
433
- <div class="card-header">
434
- <span class="card-title">Generate</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  </div>
436
-
437
- <div class="field-group">
438
- <span class="field-label">System Prompt</span>
439
- <textarea id="systemPrompt" placeholder="You are a helpful assistant."></textarea>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  </div>
441
 
442
- <div class="field-group" style="flex:1; display:flex; flex-direction:column;">
443
- <span class="field-label">User Prompt</span>
444
- <textarea id="inputText" placeholder="What should I generate?"></textarea>
 
 
 
 
445
  </div>
446
 
447
- <button class="submit-btn" id="submitBtn">🚀 Submit Task</button>
448
- </div>
449
-
450
- <!-- Queue -->
451
- <div class="card queue-card">
452
- <div class="card-header">
453
- <span class="card-title">Recent Activity</span>
454
- <button class="refresh-btn" onclick="loadTasks()">Refresh</button>
455
  </div>
456
- <div class="table-container">
457
- <table>
458
- <thead>
459
- <tr>
460
- <th>Input</th>
461
- <th>Status</th>
462
- <th>Progress</th>
463
- <th>Est. Wait</th>
464
- <th>Action</th>
465
- </tr>
466
- </thead>
467
- <tbody id="queueBody">
468
- <!-- Data injected here -->
469
- </tbody>
470
- </table>
471
- </div>
472
- </div>
473
- </div>
474
 
475
- <!-- Modal -->
476
- <div class="modal" id="resultModal">
 
477
  <div class="modal-content">
 
 
 
 
 
 
 
 
 
 
 
 
478
  <div class="modal-header">
479
- <div style="display:flex; align-items:center; gap:1rem;">
480
- <span class="card-title">Result</span>
481
- <button class="copy-btn" id="copyBtn" onclick="copyResult()">📋 Copy</button>
482
  </div>
483
  <button class="close-modal" onclick="closeModal()">&times;</button>
484
  </div>
@@ -489,32 +553,108 @@
489
  </div>
490
 
491
  <!-- Status Modal -->
492
- <div class="modal" id="statusModal">
493
  <div class="modal-content status-modal-content">
494
- <div class="status-icon">🚀</div>
495
- <h2 class="card-title" id="statusMessage">Submitting...</h2>
496
- <p class="text-dimmed" id="statusSubMessage">Please wait while we queue your task.</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
497
  </div>
498
  </div>
499
 
500
  <script>
 
501
  const API_BASE = '/api';
502
-
503
- // Elements
504
- const submitBtn = document.getElementById('submitBtn');
505
- const inputText = document.getElementById('inputText');
506
- const systemPrompt = document.getElementById('systemPrompt');
507
- const queueBody = document.getElementById('queueBody');
508
- const statusModal = document.getElementById('statusModal');
509
- const statusMessage = document.getElementById('statusMessage');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
 
511
  async function uploadTask() {
512
- const text = inputText.value.trim();
513
  if (!text) return;
514
 
515
- statusModal.classList.add('active');
516
- statusMessage.innerText = "Submitting...";
517
- submitBtn.disabled = true;
518
 
519
  try {
520
  const res = await fetch(`${API_BASE}/tasks/upload`, {
@@ -522,145 +662,139 @@
522
  headers: { 'Content-Type': 'application/json' },
523
  body: JSON.stringify({
524
  text: text,
525
- system_prompt: systemPrompt.value.trim() || undefined
526
  })
527
  });
528
 
529
  if (res.ok) {
530
- statusMessage.innerText = "Success!";
531
- inputText.value = '';
532
  setTimeout(() => {
533
- statusModal.classList.remove('active');
534
  loadTasks();
535
- }, 800);
536
  } else {
537
- statusMessage.innerText = "Submission Failed";
538
- setTimeout(() => statusModal.classList.remove('active'), 1500);
539
  }
540
  } catch (err) {
541
- statusMessage.innerText = "Connection Error";
542
- setTimeout(() => statusModal.classList.remove('active'), 1500);
 
543
  } finally {
544
- submitBtn.disabled = false;
545
  }
546
  }
547
 
548
- submitBtn.onclick = uploadTask;
549
- inputText.onkeydown = (e) => { if (e.ctrlKey && e.key === 'Enter') uploadTask(); };
 
 
 
550
 
551
- async function loadTasks() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
  try {
553
- const res = await fetch(`${API_BASE}/tasks`);
554
- const tasks = await res.json();
555
- renderQueue(tasks);
556
- } catch (err) { console.error(err); }
 
 
 
 
 
 
 
 
557
  }
558
 
559
- function renderQueue(files) {
560
- if (files.length === 0) {
561
- queueBody.innerHTML = '<tr><td colspan="5" class="text-center">No tasks found</td></tr>';
 
562
  return;
563
  }
564
- queueBody.innerHTML = files.map(f => {
565
- let estWait = '—';
566
- if (f.status === 'not_started' && f.estimated_start_seconds !== null) {
567
- const s = f.estimated_start_seconds;
568
- estWait = s < 60 ? `${s}s` : `${Math.floor(s/60)}m`;
569
- if (f.queue_position) estWait = `#${f.queue_position} (${estWait})`;
570
- } else if (f.status === 'processing') {
571
- estWait = '⏳ Processing...';
572
- }
573
 
574
- const preview = f.filename.length > 50 ? f.filename.slice(0, 50) + '...' : f.filename;
 
 
 
 
 
 
 
 
 
 
 
575
 
576
  return `
577
- <tr>
578
- <td class="text-bold">${preview}</td>
579
- <td><span class="status-pill pill-${f.status}">${f.status.replace('_', ' ')}</span></td>
580
- <td>
581
- <div class="progress-container">
582
- <div class="progress-bar" style="width:${f.progress}%"></div>
 
 
 
 
 
 
 
583
  </div>
584
- </td>
585
- <td>${estWait}</td>
586
- <td>
587
- ${f.status === 'completed' ? `<button class="btn-view" onclick="showResult('${f.id}')">View</button>` : '—'}
588
- </td>
589
- </tr>`;
 
 
 
 
 
 
 
 
 
 
590
  }).join('');
591
  }
592
 
593
- async function showResult(id) {
594
- const res = await fetch(`${API_BASE}/tasks/${id}`);
595
- const data = await res.json();
596
- let text = data.result;
597
-
598
- try {
599
- const parsed = JSON.parse(text);
600
- text = parsed.text || text;
601
- } catch (e) { }
602
-
603
- document.getElementById('resultText').innerText = text || '(no result)';
604
- document.getElementById('resultModal').classList.add('active');
605
- }
606
-
607
- function closeModal() {
608
- document.getElementById('resultModal').classList.remove('active');
609
- }
610
-
611
- function copyResult() {
612
- const text = document.getElementById('resultText').innerText;
613
- const btn = document.getElementById('copyBtn');
614
- navigator.clipboard.writeText(text).then(() => {
615
- const orig = btn.innerText;
616
- btn.innerText = '✓ Copied!';
617
- setTimeout(() => { btn.innerText = orig; }, 2000);
618
- });
619
- }
620
-
621
- document.getElementById('apiDocBtn').onclick = () => {
622
  const doc = {
623
- "base_url": window.location.origin,
624
- "endpoints": [
625
- {
626
- "method": "POST",
627
- "path": "/api/tasks/upload",
628
- "desc": "Submit text generation task",
629
- "body": "application/json { text: string, system_prompt?: string }"
630
- },
631
- {
632
- "method": "GET",
633
- "path": "/api/tasks",
634
- "desc": "List all tasks"
635
- },
636
- {
637
- "method": "GET",
638
- "path": "/api/tasks/{task_id}",
639
- "desc": "Get task details & result"
640
- },
641
- {
642
- "method": "GET",
643
- "path": "/health",
644
- "desc": "Service health check"
645
- }
646
- ]
647
  };
648
- document.getElementById('resultText').innerText = JSON.stringify(doc, null, 2);
649
- document.querySelector('#resultModal .card-title').innerText = "API Documentation";
650
- document.getElementById('resultModal').classList.add('active');
651
  };
652
 
653
- // Health Check
654
- async function checkHealth() {
655
- try {
656
- const res = await fetch('/health');
657
- const data = await res.json();
658
- const dot = document.getElementById('healthDot');
659
- dot.style.background = data.status === 'healthy' ? 'var(--success)' : 'var(--error)';
660
- dot.style.boxShadow = `0 0 10px ${data.status === 'healthy' ? 'var(--success)' : 'var(--error)'}`;
661
- } catch (e) { }
662
- }
663
-
664
  // Init
665
  loadTasks();
666
  setInterval(loadTasks, 5000);
 
2
  <html lang="en">
3
 
4
  <head>
5
+ <meta charset="utf-8" />
6
+ <meta content="width=device-width, initial-scale=1.0" name="viewport" />
7
+ <title>TTT - Text To Text</title>
8
+
9
+ <!-- External Assets -->
10
+ <script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
11
+ <link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@300..700&family=Caveat:wght@400..700&display=swap"
12
+ rel="stylesheet" />
13
  <link
14
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap"
15
+ rel="stylesheet" />
16
+
17
+ <!-- Tailwind Configuration -->
18
+ <script id="tailwind-config">
19
+ tailwind.config = {
20
+ darkMode: "class",
21
+ theme: {
22
+ extend: {
23
+ colors: {
24
+ surface: "#e6f0fd",
25
+ "crayon-blue": "#2563eb",
26
+ "crayon-red": "#dc2626",
27
+ "crayon-green": "#16a34a",
28
+ "crayon-yellow": "#ca8a04",
29
+ "crayon-orange": "#ea580c",
30
+ "crayon-purple": "#7c3aed",
31
+ "crayon-dark": "#1A1A1A"
32
+ },
33
+ fontFamily: {
34
+ "fredoka": ["Fredoka", "sans-serif"],
35
+ "caveat": ["Caveat", "cursive"]
36
+ },
37
+ fontSize: {
38
+ "headline-lg": ["42px", { lineHeight: "1.1", fontWeight: "700" }],
39
+ "headline-md": ["28px", { lineHeight: "1.2", fontWeight: "600" }],
40
+ "body-lg": ["24px", { lineHeight: "1.5", fontWeight: "500" }],
41
+ "label-sm": ["18px", { lineHeight: "1.2", letterSpacing: "0.01em", fontWeight: "500" }],
42
+ "body-md": ["20px", { lineHeight: "1.5", fontWeight: "400" }]
43
+ }
44
+ },
45
+ },
 
 
 
 
 
 
 
 
 
 
 
 
46
  }
47
+ </script>
48
 
49
+ <style>
50
+ /* Sketchbook Styles */
51
  body {
52
+ background-color: #e6f0fd;
53
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='0.08'/%3E%3C/svg%3E");
54
+ color: #1A1A1A;
55
+ font-family: 'Fredoka', sans-serif;
 
 
 
 
 
 
 
56
  }
57
 
58
+ .bg-surface {
59
+ background-color: rgb(255 255 255 / 0%) !important;
60
+ backdrop-filter: blur(2px);
 
 
 
 
61
  }
62
 
63
+ .crayon-filter {
64
+ filter: url('#crayon-texture');
 
 
 
 
 
 
 
 
65
  }
66
 
67
+ .crayon-heavy {
68
+ filter: url('#crayon-heavy');
 
69
  }
70
 
71
+ .crayon-border-green {
72
+ border: 4px solid #16a34a;
73
+ border-radius: 12px 8px 15px 10px / 8px 14px 10px 12px;
74
+ filter: url('#crayon-texture');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  }
76
 
77
+ .task-card {
78
+ border: 3px solid rgba(124, 58, 237, 0.4);
79
+ border-radius: 20px 15px 25px 18px / 18px 25px 15px 20px;
80
+ filter: url('#crayon-texture');
 
81
  }
82
 
83
+ .crayon-border-blue {
84
+ border: 4px dashed #2563eb;
85
+ border-radius: 15px 10px 12px 18px / 12px 18px 15px 10px;
86
+ filter: url('#crayon-texture');
 
 
87
  }
88
 
89
+ .crayon-border-purple {
90
+ border: 4px solid #7c3aed;
91
+ border-radius: 10px 16px 12px 14px / 16px 12px 14px 10px;
92
+ filter: url('#crayon-texture');
93
  }
94
 
95
+ .crayon-button {
96
+ border: 4px solid #2563eb;
97
+ border-radius: 12px 8px 14px 10px / 8px 14px 10px 12px;
98
+ transition: all 0.2s ease;
99
+ filter: url('#crayon-texture');
100
+ cursor: pointer;
101
  }
102
 
103
+ .crayon-button:hover {
104
+ transform: scale(1.05) rotate(1deg);
105
+ box-shadow: 6px 6px 0px 0px rgba(0, 0, 0, 0.1);
 
 
 
 
 
 
 
 
 
106
  }
107
 
108
+ .crayon-button:hover .material-symbols-outlined.spin-on-hover {
109
+ animation: spin 2s linear infinite;
 
110
  }
111
 
112
+ @keyframes spin {
113
+ from {
114
+ transform: rotate(0deg);
115
+ }
116
 
117
+ to {
118
+ transform: rotate(360deg);
119
+ }
 
 
 
 
 
 
 
 
120
  }
121
 
122
+ @keyframes drift {
123
+ 0% {
124
+ transform: translateX(0);
125
+ }
126
 
127
+ 50% {
128
+ transform: translateX(20px);
129
+ }
 
 
 
130
 
131
+ 100% {
132
+ transform: translateX(0);
133
+ }
 
 
134
  }
135
 
136
+ .drift-slow {
137
+ animation: drift 8s ease-in-out infinite;
 
 
 
138
  }
139
 
140
+ .drift-medium {
141
+ animation: drift 5s ease-in-out infinite;
 
 
 
 
142
  }
143
 
144
+ .organic-shape {
 
 
 
 
145
  border-radius: 255px 15px 225px 15px/15px 225px 15px 255px;
146
+ filter: url('#crayon-texture');
 
 
 
 
147
  }
148
 
149
+ .scribble-fill-green {
150
+ background: repeating-linear-gradient(60deg, #16a34a, #16a34a 2px, #15803d 3px, #16a34a 4px);
 
 
151
  }
152
 
153
+ .scribble-fill-purple {
154
+ background: repeating-linear-gradient(60deg, #7c3aed, #7c3aed 2px, #6d28d9 3px, #7c3aed 4px);
 
 
 
 
155
  }
156
 
157
+ .progress-fill {
158
+ background: repeating-linear-gradient(80deg, #2563eb, #2563eb 2px, #1d4ed8 3px, #2563eb 5px);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  }
160
 
161
+ .material-symbols-outlined {
162
+ font-variation-settings: 'FILL' 1, 'wght' 700, 'GRAD' 0, 'opsz' 48;
163
+ filter: url('#crayon-texture');
 
164
  }
165
 
166
+ /* --- Modals --- */
167
  .modal {
168
  position: fixed;
169
  inset: 0;
 
180
  }
181
 
182
  .modal-content {
183
+ border: 4px solid #2563eb;
 
184
  width: 100%;
185
+ max-width: 900px;
186
+ border-radius: 24px;
187
  display: flex;
188
  flex-direction: column;
189
+ max-height: 85vh;
190
+ box-shadow: 12px 12px 0px 0px rgba(0, 0, 0, 0.1);
191
+ position: relative;
192
+ }
193
+
194
+ .modal-sketch-bg {
195
+ position: absolute;
196
+ inset: -8px;
197
+ border: 6px solid #2563eb;
198
+ backdrop-filter: blur(10px);
199
+ border-radius: 255px 15px 225px 15px/15px 225px 15px 255px;
200
+ z-index: -1;
201
+ filter: url('#crayon-texture');
202
+ pointer-events: none;
203
  }
204
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
205
  .modal-header {
206
  padding: 1.5rem 2rem;
207
+ border-bottom: 3px dashed #adc6ff;
208
  display: flex;
209
  justify-content: space-between;
210
  align-items: center;
211
+ position: relative;
212
+ z-index: 10;
213
+ }
214
+
215
+ .modal-body {
216
+ padding: 2rem;
217
+ overflow-y: auto;
218
+ position: relative;
219
+ z-index: 10;
220
  }
221
 
222
+ #resultText,
 
223
  pre {
224
+ border: 3px dashed #adc6ff !important;
225
+ padding: 2rem !important;
226
+ border-radius: 20px !important;
227
+ font-family: 'Fredoka', sans-serif !important;
228
+ font-size: 1.5rem !important;
229
+ font-weight: 600 !important;
230
+ color: #1A1A1A !important;
231
+ white-space: pre-wrap !important;
232
+ word-break: break-all !important;
233
+ line-height: 1.6 !important;
234
+ filter: url('#crayon-texture');
235
  }
236
 
237
  .close-modal {
238
  background: transparent;
239
  border: none;
240
+ color: #dc2626;
241
+ font-size: 3rem;
242
  cursor: pointer;
243
+ font-weight: 700;
244
  }
245
 
246
+ .text-headline-lg {
247
+ filter: url('#crayon-texture');
248
+ }
 
249
 
250
  .copy-btn {
251
+ background: #16a34a;
252
+ color: white;
253
+ padding: 0.5rem 1.5rem;
254
+ border-radius: 12px;
 
 
255
  font-weight: 700;
256
+ box-shadow: 4px 4px 0px 0px #15803d;
 
257
  transition: all 0.2s;
258
+ filter: url('#crayon-texture');
259
  }
260
 
261
  .copy-btn:hover {
262
+ transform: translate(-2px, -2px);
263
+ box-shadow: 6px 6px 0px 0px #15803d;
264
+ }
265
+
266
+ /* --- Specific UI Elements --- */
267
+ .status-modal-content {
268
+ max-width: 450px;
269
+ text-align: center;
270
+ padding: 3rem 2rem;
271
+ }
272
+
273
+ .status-modal-bg {
274
+ position: absolute;
275
+ inset: -12px;
276
+ border: 8px solid #2563eb;
277
+ background: #e6f0fd;
278
+ border-radius: 20px 40px 15px 35px / 35px 15px 40px 20px;
279
+ z-index: -1;
280
+ filter: url('#crayon-texture');
281
+ pointer-events: none;
282
+ }
283
+
284
+ .status-icon-container {
285
+ width: 100px;
286
+ height: 100px;
287
+ margin: 0 auto 1.5rem;
288
+ display: flex;
289
+ align-items: center;
290
+ justify-content: center;
291
+ border-radius: 20px 15px 25px 18px / 18px 25px 15px 20px;
292
+ border: 4px solid currentColor;
293
+ filter: url('#crayon-texture');
294
+ position: relative;
295
+ z-index: 20;
296
+ }
297
+
298
+ .status-icon-bg {
299
+ position: absolute;
300
+ inset: 0;
301
+ background: currentColor;
302
+ opacity: 0.15;
303
+ z-index: -1;
304
+ border-radius: inherit;
305
+ }
306
+
307
+ .modal-decoration {
308
+ position: absolute;
309
+ pointer-events: none;
310
+ opacity: 0.3;
311
+ z-index: 5;
312
+ filter: url('#crayon-texture');
313
+ }
314
+
315
+ .table-container::-webkit-scrollbar {
316
+ width: 10px;
317
+ }
318
+
319
+ .table-container::-webkit-scrollbar-track {
320
+ background: #f1f5f9;
321
+ border-radius: 10px;
322
+ }
323
+
324
+ .table-container::-webkit-scrollbar-thumb {
325
+ background: #cbd5e1;
326
+ border-radius: 10px;
327
+ border: 2px solid #f1f5f9;
328
  }
329
 
330
+ #inputText,
331
+ #systemPrompt {
332
+ background: rgba(255, 255, 255, 0.4);
333
+ border: 3px dashed #adc6ff;
334
+ border-radius: 20px;
335
+ padding: 1.5rem;
336
+ font-family: 'Fredoka', sans-serif;
337
+ font-size: 1.5rem;
338
+ color: #1A1A1A;
339
+ filter: url('#crayon-texture');
340
+ transition: all 0.2s;
341
+ font-weight: 600 !important;
342
+ white-space: pre-wrap !important;
343
+ word-break: break-all !important;
344
+ }
345
+
346
+ #inputText:focus,
347
+ #systemPrompt:focus {
348
+ border-color: #2563eb;
349
+ background: rgba(255, 255, 255, 0.7);
350
+ outline: none;
351
  }
352
  </style>
353
  </head>
354
 
355
+ <body class="h-screen flex flex-col overflow-hidden relative">
356
+ <!-- Main Background Decorations -->
357
+ <div class="fixed inset-0 pointer-events-none overflow-hidden -z-10 opacity-40">
358
+ <!-- Stars -->
359
+ <span
360
+ class="material-symbols-outlined absolute text-5xl text-crayon-yellow top-20 left-[10%] rotate-12 crayon-heavy animate-pulse">star</span>
361
+ <span
362
+ class="material-symbols-outlined absolute text-3xl text-crayon-orange top-[40%] left-[5%] -rotate-12 crayon-filter">star</span>
363
+ <span
364
+ class="material-symbols-outlined absolute text-4xl text-crayon-yellow bottom-[20%] left-[15%] rotate-45 animate-pulse">star</span>
365
+ <span
366
+ class="material-symbols-outlined absolute text-6xl text-crayon-orange top-[15%] right-[15%] rotate-[-15deg] crayon-heavy">star</span>
367
+ <span
368
+ class="material-symbols-outlined absolute text-3xl text-crayon-yellow bottom-[30%] right-[10%] rotate-12 animate-pulse">star</span>
369
+
370
+ <!-- Clouds -->
371
+ <span
372
+ class="material-symbols-outlined absolute text-[120px] text-crayon-blue top-[10%] left-[25%] opacity-20 drift-slow">cloud</span>
373
+ <span
374
+ class="material-symbols-outlined absolute text-[80px] text-crayon-purple bottom-[15%] left-[40%] opacity-10 drift-medium">cloud</span>
375
+ <span
376
+ class="material-symbols-outlined absolute text-[100px] text-crayon-blue top-[60%] right-[25%] opacity-15 drift-slow">cloud</span>
377
+ <span
378
+ class="material-symbols-outlined absolute text-[150px] text-crayon-purple top-[30%] right-[5%] opacity-10 drift-medium">cloud</span>
379
+
380
+ <!-- Hearts -->
381
+ <span
382
+ class="material-symbols-outlined absolute text-4xl text-crayon-red top-[25%] left-[18%] rotate-[-15deg] crayon-filter animate-pulse">favorite</span>
383
+ <span
384
+ class="material-symbols-outlined absolute text-2xl text-crayon-red bottom-[10%] right-[20%] rotate-12 crayon-filter">favorite</span>
385
+ <span
386
+ class="material-symbols-outlined absolute text-5xl text-crayon-red top-[70%] left-[8%] rotate-[10deg] animate-pulse">favorite</span>
387
+ </div>
388
+
389
+ <!-- SVG Filters -->
390
+ <svg height="0" width="0" style="position: absolute;">
391
+ <filter id="crayon-texture" x="-10%" y="-10%" width="120%" height="120%">
392
+ <feTurbulence type="fractalNoise" baseFrequency="0.4" numOctaves="3" result="noise" />
393
+ <feDisplacementMap in="SourceGraphic" in2="noise" scale="2.5" xChannelSelector="R" yChannelSelector="G" />
394
+ </filter>
395
+ <filter id="crayon-heavy" x="-10%" y="-10%" width="120%" height="120%">
396
+ <feTurbulence type="fractalNoise" baseFrequency="0.5" numOctaves="4" result="noise" />
397
+ <feDisplacementMap in="SourceGraphic" in2="noise" scale="4" xChannelSelector="R" yChannelSelector="G" />
398
+ </filter>
399
+ </svg>
400
+
401
+ <!-- Header -->
402
+ <header
403
+ class="bg-surface flex justify-between items-center w-[calc(100%-48px)] mx-6 mt-6 px-8 py-5 crayon-border-green z-10 shrink-0 organic-shape shadow-sm">
404
+ <div class="flex items-center gap-5">
405
+ <div
406
+ class="bg-crayon-green text-white w-14 h-14 rounded-2xl flex items-center justify-center border-[3px] border-crayon-green rotate-[-4deg] crayon-filter scribble-fill-green shadow-md">
407
+ <span class="material-symbols-outlined text-4xl">task_alt</span>
408
  </div>
409
+ <div class="flex flex-col -rotate-1">
410
+ <h1 class="text-headline-lg text-[#4c1d95] leading-none mb-1">TTT</h1>
411
+ <span class="text-label-sm text-[#4b5563] font-bold">Text To Text</span>
412
+ </div>
413
+ </div>
414
+
415
+ <div class="flex items-center gap-10 relative">
416
+ <div class="absolute -left-48 top-2 rotate-12 crayon-heavy">
417
+ <span class="material-symbols-outlined text-4xl text-crayon-yellow">star</span>
418
+ </div>
419
+ <div class="absolute -left-24 top-0 -rotate-6 crayon-heavy opacity-80">
420
+ <span class="material-symbols-outlined text-5xl text-crayon-blue">cloud</span>
421
+ </div>
422
+ <button id="apiDocBtn"
423
+ class="flex items-center gap-2 text-headline-md text-crayon-purple px-8 py-3 bg-surface crayon-button rotate-1 shadow-md">
424
+ <span class="material-symbols-outlined text-3xl">menu_book</span>
425
+ API DOC
426
+ </button>
427
+ <div
428
+ class="flex items-center gap-4 bg-surface px-6 py-3 rounded-full border-[4px] border-crayon-green organic-shape shadow-md">
429
+ <div id="healthDot" class="w-4 h-4 rounded-full bg-crayon-green shadow-[0_0_12px_rgba(22,163,74,0.5)]">
430
  </div>
431
+ <span id="healthText" class="text-headline-md text-crayon-green text-2xl">Service Online</span>
432
+ <div class="text-crayon-orange flex items-center justify-center rotate-[15deg]">
433
+ <span class="material-symbols-outlined text-4xl">light_mode</span>
434
  </div>
435
  </div>
436
+ </div>
437
+ </header>
438
+
439
+ <!-- Main Content -->
440
+ <main class="flex-1 flex overflow-hidden p-8 gap-8 mx-auto w-full relative">
441
+ <!-- Input Section -->
442
+ <section class="w-[450px] flex flex-col gap-8">
443
+ <div
444
+ class="flex flex-col gap-6 p-8 bg-surface crayon-border-blue flex-1 relative organic-shape shadow-sm hover:shadow-md transition-shadow">
445
+ <div class="flex items-center gap-4 mb-2 rotate-1">
446
+ <div
447
+ class="bg-crayon-blue text-white rounded-full w-14 h-14 flex items-center justify-center border-2 border-crayon-blue crayon-filter shadow-md">
448
+ <span class="material-symbols-outlined text-4xl">edit_note</span>
449
+ </div>
450
+ <div>
451
+ <h2 class="text-headline-md text-crayon-blue leading-none mb-1 flex items-center gap-2">
452
+ GENERATE
453
+ <span class="material-symbols-outlined text-crayon-red text-2xl rotate-12">favorite</span>
454
+ </h2>
455
+ <p class="text-label-sm text-[#6b7280]">Submit your task</p>
456
+ </div>
457
+ </div>
458
 
459
+ <div class="flex flex-col gap-4 flex-1">
460
+ <div class="flex flex-col gap-2">
461
+ <label class="text-label-sm text-crayon-purple font-bold px-2">System Prompt</label>
462
+ <textarea id="systemPrompt" placeholder="You are a helpful assistant..."
463
+ class="h-24 resize-none"></textarea>
464
+ </div>
465
+ <div class="flex flex-col gap-2 flex-1">
466
+ <label class="text-label-sm text-crayon-blue font-bold px-2">User Prompt</label>
467
+ <textarea id="inputText" placeholder="What should I generate?"
468
+ class="flex-1 resize-none"></textarea>
469
+ </div>
470
+ <button id="submitBtn"
471
+ class="flex items-center justify-center gap-3 text-headline-md text-white bg-crayon-blue py-5 crayon-button border-[4px] -rotate-1 shadow-md hover:scale-105 active:scale-95 transition-all">
472
+ <span class="material-symbols-outlined text-3xl">rocket_launch</span>
473
+ SUBMIT TASK
474
+ </button>
475
+ </div>
476
  </div>
477
+ </section>
478
+
479
+ <!-- Activity Section -->
480
+ <section
481
+ class="flex-1 flex flex-col bg-surface crayon-border-purple p-8 relative organic-shape overflow-hidden shadow-sm">
482
+ <div class="flex items-center justify-between mb-8">
483
+ <div class="flex items-center gap-4 rotate-1">
484
+ <div
485
+ class="bg-crayon-purple text-white rounded-full w-14 h-14 flex items-center justify-center border-[3px] border-crayon-purple shadow-md">
486
+ <span class="material-symbols-outlined text-4xl">schedule</span>
487
+ </div>
488
+ <div>
489
+ <h2 class="text-headline-lg text-[#4c1d95] leading-none mb-1">ACTIVITY</h2>
490
+ <p class="text-label-sm text-[#6b7280] font-bold">Your recently processed tasks</p>
491
+ </div>
492
+ </div>
493
+ <div class="relative w-40 h-20 flex items-center">
494
+ <svg class="absolute left-[-30px] top-4 w-32 h-16 crayon-filter text-crayon-yellow" fill="none"
495
+ stroke="currentColor" stroke-dasharray="6 6" stroke-linecap="round" stroke-width="3"
496
+ viewBox="0 0 100 50">
497
+ <path d="M10,40 Q40,50 60,30 T90,10" />
498
+ </svg>
499
+ <span
500
+ class="material-symbols-outlined text-5xl text-crayon-orange absolute right-0 top-0 rotate-12">send</span>
501
+ </div>
502
+ <button onclick="loadTasks()"
503
+ class="flex items-center gap-2 text-headline-md text-crayon-blue px-8 py-3 bg-surface crayon-button border-[4px] -rotate-1 shadow-md">
504
+ <span class="material-symbols-outlined text-3xl spin-on-hover">sync</span>
505
+ Refresh
506
+ </button>
507
  </div>
508
 
509
+ <!-- List Header -->
510
+ <div class="flex w-full px-6 pb-4 border-b-[5px] text-[#4c1d95] font-bold text-xl uppercase tracking-wider"
511
+ style="border-color: rgba(124, 58, 237, 0.4); border-radius: 20px 15px 25px 18px / 18px 25px 15px 20px; filter: url(#crayon-texture);">
512
+ <div class="w-1/2">INPUT PREVIEW</div>
513
+ <div class="w-1/6 text-center">STATUS</div>
514
+ <div class="w-1/4 text-center">PROGRESS</div>
515
+ <div class="w-[10%] text-center">ACTION</div>
516
  </div>
517
 
518
+ <!-- Task List Body -->
519
+ <div id="queueBody" class="flex flex-col gap-5 mt-6 overflow-y-auto flex-1 table-container pr-4">
520
+ <div class="text-center py-32 text-headline-md text-[#94a3b8] font-bold opacity-60">No tasks found
521
+ yet...</div>
 
 
 
 
522
  </div>
523
+ </section>
524
+ </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
525
 
526
+ <!-- Modals -->
527
+ <!-- Result & API Modal -->
528
+ <div id="resultModal" class="modal">
529
  <div class="modal-content">
530
+ <div class="modal-sketch-bg"></div>
531
+
532
+ <!-- Decorations -->
533
+ <span
534
+ class="material-symbols-outlined modal-decoration text-6xl text-crayon-yellow top-4 left-4 -rotate-12">star</span>
535
+ <span
536
+ class="material-symbols-outlined modal-decoration text-8xl text-crayon-blue top-12 right-20 opacity-20">cloud</span>
537
+ <span
538
+ class="material-symbols-outlined modal-decoration text-4xl text-crayon-orange bottom-10 left-10 rotate-45">star</span>
539
+ <span
540
+ class="material-symbols-outlined modal-decoration text-7xl text-crayon-purple bottom-4 right-4 -rotate-6 opacity-40">cloud</span>
541
+
542
  <div class="modal-header">
543
+ <div class="flex items-center gap-6">
544
+ <span id="modalTitle" class="text-headline-lg text-[#1e1b4b]">Task Result</span>
545
+ <button id="copyBtn" onclick="copyResult()" class="copy-btn">📋 Copy Text</button>
546
  </div>
547
  <button class="close-modal" onclick="closeModal()">&times;</button>
548
  </div>
 
553
  </div>
554
 
555
  <!-- Status Modal -->
556
+ <div id="statusModal" class="modal">
557
  <div class="modal-content status-modal-content">
558
+ <div id="statusBg" class="status-modal-bg"></div>
559
+
560
+ <span
561
+ class="material-symbols-outlined modal-decoration text-4xl text-crayon-yellow top-6 right-6 rotate-12">star</span>
562
+ <span
563
+ class="material-symbols-outlined modal-decoration text-6xl text-crayon-blue top-10 left-4 opacity-30">cloud</span>
564
+ <span
565
+ class="material-symbols-outlined modal-decoration text-3xl text-crayon-orange bottom-8 right-10 -rotate-12">star</span>
566
+ <span
567
+ class="material-symbols-outlined modal-decoration text-5xl text-crayon-purple bottom-10 left-8 rotate-6 opacity-30">cloud</span>
568
+
569
+ <div id="statusIconContainer" class="status-icon-container text-crayon-blue">
570
+ <div class="status-icon-bg"></div>
571
+ <span id="statusIcon" class="material-symbols-outlined text-6xl animate-bounce">rocket_launch</span>
572
+ </div>
573
+ <h2 id="statusMessage" class="text-headline-lg text-crayon-blue mb-2">Submitting...</h2>
574
+ <p id="statusSubMessage" class="text-body-lg text-[#4b5563]">Please wait while we queue your task.</p>
575
  </div>
576
  </div>
577
 
578
  <script>
579
+ // --- Configuration ---
580
  const API_BASE = '/api';
581
+
582
+ // --- DOM Elements ---
583
+ const UI = {
584
+ inputText: document.getElementById('inputText'),
585
+ systemPrompt: document.getElementById('systemPrompt'),
586
+ submitBtn: document.getElementById('submitBtn'),
587
+ queueBody: document.getElementById('queueBody'),
588
+ resultModal: document.getElementById('resultModal'),
589
+ statusModal: document.getElementById('statusModal'),
590
+ resultText: document.getElementById('resultText'),
591
+ modalTitle: document.getElementById('modalTitle'),
592
+ statusMessage: document.getElementById('statusMessage'),
593
+ statusSubMessage: document.getElementById('statusSubMessage'),
594
+ statusIcon: document.getElementById('statusIcon'),
595
+ statusIconContainer: document.getElementById('statusIconContainer'),
596
+ statusBg: document.getElementById('statusBg'),
597
+ healthDot: document.getElementById('healthDot'),
598
+ healthText: document.getElementById('healthText'),
599
+ apiDocBtn: document.getElementById('apiDocBtn')
600
+ };
601
+
602
+ // --- UI Helpers ---
603
+ function updateStatusModal(type, msg, subMsg) {
604
+ UI.statusMessage.innerText = msg;
605
+ UI.statusSubMessage.innerText = subMsg || "Processing your request, please wait.";
606
+ UI.statusIconContainer.className = "status-icon-container";
607
+ UI.statusIcon.className = "material-symbols-outlined text-6xl";
608
+ UI.statusBg.style.borderColor = "";
609
+
610
+ if (type === 'loading') {
611
+ UI.statusIconContainer.classList.add('text-crayon-blue');
612
+ UI.statusIcon.innerText = "rocket_launch";
613
+ UI.statusIcon.classList.add('animate-bounce');
614
+ UI.statusBg.style.borderColor = "#2563eb";
615
+ } else if (type === 'success') {
616
+ UI.statusIconContainer.classList.add('text-crayon-green');
617
+ UI.statusIcon.innerText = "check_circle";
618
+ UI.statusBg.style.borderColor = "#16a34a";
619
+ } else if (type === 'error') {
620
+ UI.statusIconContainer.classList.add('text-crayon-red');
621
+ UI.statusIcon.innerText = "error";
622
+ UI.statusBg.style.borderColor = "#dc2626";
623
+ }
624
+ }
625
+
626
+ function closeModal() {
627
+ UI.resultModal.classList.remove('active');
628
+ }
629
+
630
+ function copyResult() {
631
+ const text = UI.resultText.innerText;
632
+ const btn = document.getElementById('copyBtn');
633
+ navigator.clipboard.writeText(text).then(() => {
634
+ const orig = btn.innerText;
635
+ btn.innerText = '✓ Copied!';
636
+ setTimeout(() => { btn.innerText = orig; }, 2000);
637
+ });
638
+ }
639
+
640
+ // --- API Functions ---
641
+ async function loadTasks() {
642
+ try {
643
+ const res = await fetch(`${API_BASE}/tasks`);
644
+ const tasks = await res.json();
645
+ renderQueue(tasks);
646
+ } catch (err) {
647
+ console.error("Load tasks error:", err);
648
+ }
649
+ }
650
 
651
  async function uploadTask() {
652
+ const text = UI.inputText.value.trim();
653
  if (!text) return;
654
 
655
+ UI.statusModal.classList.add('active');
656
+ updateStatusModal('loading', "Submitting...", "Adding your task to the queue...");
657
+ UI.submitBtn.disabled = true;
658
 
659
  try {
660
  const res = await fetch(`${API_BASE}/tasks/upload`, {
 
662
  headers: { 'Content-Type': 'application/json' },
663
  body: JSON.stringify({
664
  text: text,
665
+ system_prompt: UI.systemPrompt.value.trim() || undefined
666
  })
667
  });
668
 
669
  if (res.ok) {
670
+ updateStatusModal('success', "Success!", "Task submitted successfully.");
671
+ UI.inputText.value = '';
672
  setTimeout(() => {
673
+ UI.statusModal.classList.remove('active');
674
  loadTasks();
675
+ }, 1200);
676
  } else {
677
+ updateStatusModal('error', "Submission Failed", "Something went wrong on the server.");
678
+ setTimeout(() => UI.statusModal.classList.remove('active'), 2000);
679
  }
680
  } catch (err) {
681
+ console.error("Upload error:", err);
682
+ updateStatusModal('error', "Connection Error ⚠️", "Could not reach the server.");
683
+ setTimeout(() => UI.statusModal.classList.remove('active'), 2000);
684
  } finally {
685
+ UI.submitBtn.disabled = false;
686
  }
687
  }
688
 
689
+ async function showResult(id) {
690
+ try {
691
+ const res = await fetch(`${API_BASE}/tasks/${id}`);
692
+ const data = await res.json();
693
+ let text = data.result;
694
 
695
+ UI.modalTitle.innerText = "Task Result";
696
+
697
+ try {
698
+ const parsed = JSON.parse(text);
699
+ text = parsed.text || text;
700
+ } catch (e) { }
701
+
702
+ UI.resultText.innerText = text || '(no result)';
703
+ UI.resultModal.classList.add('active');
704
+ } catch (err) {
705
+ console.error("Show result error:", err);
706
+ }
707
+ }
708
+
709
+ async function checkHealth() {
710
  try {
711
+ const res = await fetch('/health');
712
+ const data = await res.json();
713
+ const healthy = data.status === 'healthy';
714
+
715
+ UI.healthDot.className = `w-4 h-4 rounded-full ${healthy ? 'bg-crayon-green' : 'bg-crayon-red'} shadow-md`;
716
+ UI.healthText.innerText = healthy ? 'Service Online' : 'Service Down';
717
+ UI.healthText.className = `text-headline-md font-bold ${healthy ? 'text-crayon-green' : 'text-crayon-red'}`;
718
+ } catch (e) {
719
+ UI.healthDot.className = 'w-4 h-4 rounded-full bg-crayon-red shadow-md';
720
+ UI.healthText.innerText = 'Connection Error';
721
+ UI.healthText.className = 'text-headline-md font-bold text-crayon-red';
722
+ }
723
  }
724
 
725
+ // --- Renderers ---
726
+ function renderQueue(tasks) {
727
+ if (tasks.length === 0) {
728
+ UI.queueBody.innerHTML = '<div class="text-center py-32 text-headline-md text-[#94a3b8] font-bold opacity-60">No tasks found yet...</div>';
729
  return;
730
  }
 
 
 
 
 
 
 
 
 
731
 
732
+ UI.queueBody.innerHTML = tasks.map((t, i) => {
733
+ const rotate = i % 2 === 0 ? 'rotate-[0.3deg]' : '-rotate-[0.3deg]';
734
+ const status = t.status.toLowerCase();
735
+ const colors = {
736
+ completed: { text: 'crayon-green', bg: 'bg-[#f0fdf4]' },
737
+ failed: { text: 'crayon-red', bg: 'bg-[#fef2f2]' },
738
+ processing: { text: 'crayon-blue', bg: 'bg-[#eff6ff]' },
739
+ not_started: { text: 'crayon-purple', bg: 'bg-[#f5f3ff]' },
740
+ pending: { text: 'crayon-purple', bg: 'bg-[#f5f3ff]' }
741
+ };
742
+ const theme = colors[status] || colors.pending;
743
+ const preview = t.filename.length > 60 ? t.filename.slice(0, 60) + '...' : t.filename;
744
 
745
  return `
746
+ <div class="task-card flex items-center p-6 bg-surface hover:border-crayon-purple transition-colors shadow-sm ${rotate}">
747
+ <div class="flex items-center gap-5 w-1/2">
748
+ <div class="w-16 h-20 border-[3px] border-crayon-blue rounded-xl p-2 bg-surface flex shadow-sm organic-shape rotate-[-3deg] scribble-fill-purple opacity-20">
749
+ </div>
750
+ <div class="flex flex-col">
751
+ <span class="text-headline-md text-[#1A1A1A] leading-tight font-bold mb-1">${preview}</span>
752
+ <div class="text-label-sm text-[#94a3b8] font-bold">${t.id.substring(0, 12)}</div>
753
+ </div>
754
+ </div>
755
+ <div class="w-1/6 flex justify-center">
756
+ <div class="px-4 py-2 ${theme.bg} border-[3px] border-${theme.text} text-${theme.text} font-bold rounded-2xl uppercase tracking-tight crayon-filter">
757
+ ${status.replace('_', ' ')}
758
+ </div>
759
  </div>
760
+ <div class="w-1/4 flex items-center justify-center gap-4">
761
+ <div class="flex-1 h-6 border-[3px] border-[#adc6ff] rounded-full overflow-hidden bg-surface p-[2px] crayon-filter">
762
+ <div class="h-full rounded-full progress-fill shadow-sm" style="width:${t.progress}%"></div>
763
+ </div>
764
+ <span class="text-headline-md text-2xl text-[#1A1A1A] font-bold w-12 text-right">${status === 'completed' ? '' : t.progress + '%'}</span>
765
+ </div>
766
+ <div class="w-[10%] flex justify-center">
767
+ ${status === 'completed' ? `
768
+ <button onclick="showResult('${t.id}')" class="flex items-center gap-2 px-5 py-2.5 bg-white border-[3px] border-crayon-blue text-crayon-blue font-bold rounded-2xl hover:bg-crayon-blue hover:text-white transition-all shadow-sm crayon-filter">
769
+ <span class="material-symbols-outlined text-2xl">visibility</span>
770
+ VIEW
771
+ </button>
772
+ ` : '—'}
773
+ </div>
774
+ </div>
775
+ `;
776
  }).join('');
777
  }
778
 
779
+ // --- Event Listeners ---
780
+ UI.submitBtn.onclick = uploadTask;
781
+ UI.inputText.onkeydown = (e) => { if (e.ctrlKey && e.key === 'Enter') uploadTask(); };
782
+ UI.apiDocBtn.onclick = () => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
783
  const doc = {
784
+ base_url: window.location.origin,
785
+ endpoints: [
786
+ { method: "POST", path: "/api/tasks/upload", desc: "Submit text generation task", body: "{ text: string, system_prompt?: string }" },
787
+ { method: "GET", path: "/api/tasks", desc: "List all tasks" },
788
+ { method: "GET", path: "/api/tasks/{task_id}", desc: "Get task details & result" },
789
+ { method: "GET", path: "/health", desc: "Service health" }
790
+ ],
791
+ example_usage: `curl -X POST -H 'Content-Type: application/json' -d '{"text": "Hello"}' ${window.location.origin}/api/tasks/upload`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
792
  };
793
+ UI.resultText.innerText = JSON.stringify(doc, null, 2);
794
+ UI.modalTitle.innerText = "API Documentation";
795
+ UI.resultModal.classList.add('active');
796
  };
797
 
 
 
 
 
 
 
 
 
 
 
 
798
  // Init
799
  loadTasks();
800
  setInterval(loadTasks, 5000);