aiframe-agent-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1600 @@
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>AI Frame Console</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Outfit:wght@400;600;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
10
+ <style>
11
+ :root {
12
+ --bg-primary: #080a0f;
13
+ --bg-surface: rgba(15, 19, 28, 0.7);
14
+ --bg-card: rgba(22, 28, 41, 0.85);
15
+ --border-color: rgba(255, 255, 255, 0.08);
16
+ --accent-color: #6366f1;
17
+ --accent-gradient: linear-gradient(135deg, #6366f1 0%, #4f46e5 100%);
18
+ --text-main: #f3f4f6;
19
+ --text-muted: #9ca3af;
20
+ --success-color: #10b981;
21
+ --error-color: #ef4444;
22
+ --warning-color: #f59e0b;
23
+ }
24
+
25
+ * {
26
+ box-sizing: border-box;
27
+ margin: 0;
28
+ padding: 0;
29
+ }
30
+
31
+ body {
32
+ background-color: var(--bg-primary);
33
+ background-image:
34
+ radial-gradient(at 0% 0%, rgba(99, 102, 241, 0.12) 0px, transparent 50%),
35
+ radial-gradient(at 100% 100%, rgba(79, 70, 229, 0.08) 0px, transparent 50%);
36
+ color: var(--text-main);
37
+ font-family: 'Inter', sans-serif;
38
+ min-height: 100vh;
39
+ overflow-x: hidden;
40
+ }
41
+
42
+ /* Main Layout */
43
+ .app-container {
44
+ display: grid;
45
+ grid-template-rows: 70px 1fr;
46
+ height: 100vh;
47
+ }
48
+
49
+ header {
50
+ background: rgba(8, 10, 15, 0.85);
51
+ backdrop-filter: blur(12px);
52
+ border-bottom: 1px solid var(--border-color);
53
+ display: flex;
54
+ align-items: center;
55
+ justify-content: space-between;
56
+ padding: 0 40px;
57
+ z-index: 100;
58
+ }
59
+
60
+ .logo-container {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 12px;
64
+ }
65
+
66
+ .logo-icon {
67
+ background: var(--accent-gradient);
68
+ width: 38px;
69
+ height: 38px;
70
+ border-radius: 10px;
71
+ display: flex;
72
+ align-items: center;
73
+ justify-content: center;
74
+ box-shadow: 0 0 15px rgba(99, 102, 241, 0.45);
75
+ }
76
+
77
+ .logo-text {
78
+ font-family: 'Outfit', sans-serif;
79
+ font-size: 24px;
80
+ font-weight: 800;
81
+ background: linear-gradient(to right, #ffffff, #9ca3af);
82
+ -webkit-background-clip: text;
83
+ -webkit-text-fill-color: transparent;
84
+ }
85
+
86
+ /* Tab buttons in header */
87
+ .nav-tabs {
88
+ display: flex;
89
+ gap: 8px;
90
+ }
91
+
92
+ .tab-btn {
93
+ background: transparent;
94
+ border: 1px solid transparent;
95
+ border-radius: 8px;
96
+ color: var(--text-muted);
97
+ cursor: pointer;
98
+ font-family: 'Outfit', sans-serif;
99
+ font-size: 14px;
100
+ font-weight: 600;
101
+ padding: 10px 20px;
102
+ transition: all 0.2s ease;
103
+ display: flex;
104
+ align-items: center;
105
+ gap: 8px;
106
+ }
107
+
108
+ .tab-btn:hover {
109
+ color: #ffffff;
110
+ background: rgba(255, 255, 255, 0.03);
111
+ }
112
+
113
+ .tab-btn.active {
114
+ background: rgba(99, 102, 241, 0.12);
115
+ border-color: rgba(99, 102, 241, 0.25);
116
+ color: #a5b4fc;
117
+ }
118
+
119
+ /* Main Console View Area */
120
+ .tab-content {
121
+ display: none;
122
+ height: 100%;
123
+ overflow: hidden;
124
+ }
125
+
126
+ .tab-content.active {
127
+ display: grid;
128
+ }
129
+
130
+ /* Shared Layout for Inner tabs (List left, Editor/View right) */
131
+ .two-column-layout {
132
+ grid-template-columns: 320px 1fr;
133
+ height: 100%;
134
+ }
135
+
136
+ .three-column-layout {
137
+ grid-template-columns: 300px 1fr 340px;
138
+ height: 100%;
139
+ }
140
+
141
+ .sidebar {
142
+ background: var(--bg-surface);
143
+ backdrop-filter: blur(16px);
144
+ border-right: 1px solid var(--border-color);
145
+ display: flex;
146
+ flex-direction: column;
147
+ padding: 24px;
148
+ overflow-y: auto;
149
+ }
150
+
151
+ .sidebar-right {
152
+ border-right: none;
153
+ border-left: 1px solid var(--border-color);
154
+ }
155
+
156
+ .content-area {
157
+ padding: 40px;
158
+ overflow-y: auto;
159
+ height: 100%;
160
+ }
161
+
162
+ .section-title {
163
+ font-family: 'Outfit', sans-serif;
164
+ font-size: 13px;
165
+ font-weight: 600;
166
+ text-transform: uppercase;
167
+ letter-spacing: 0.08em;
168
+ color: var(--text-muted);
169
+ margin-bottom: 12px;
170
+ border-bottom: 1px solid rgba(255,255,255,0.05);
171
+ padding-bottom: 6px;
172
+ }
173
+
174
+ /* Custom item lists with Actions */
175
+ .item-card-row {
176
+ background: rgba(255, 255, 255, 0.02);
177
+ border: 1px solid var(--border-color);
178
+ border-radius: 8px;
179
+ padding: 12px 16px;
180
+ margin-bottom: 10px;
181
+ cursor: pointer;
182
+ display: flex;
183
+ justify-content: space-between;
184
+ align-items: center;
185
+ transition: all 0.2s ease;
186
+ }
187
+
188
+ .item-card-row:hover {
189
+ background: rgba(255, 255, 255, 0.05);
190
+ border-color: rgba(255, 255, 255, 0.15);
191
+ }
192
+
193
+ .item-card-row.active {
194
+ background: rgba(99, 102, 241, 0.1);
195
+ border-color: rgba(99, 102, 241, 0.3);
196
+ }
197
+
198
+ .item-card-name {
199
+ font-size: 14px;
200
+ font-weight: 600;
201
+ color: #ffffff;
202
+ }
203
+
204
+ .item-card-desc {
205
+ font-size: 11px;
206
+ color: var(--text-muted);
207
+ margin-top: 2px;
208
+ }
209
+
210
+ .badge {
211
+ font-size: 10px;
212
+ font-weight: 600;
213
+ padding: 2px 6px;
214
+ border-radius: 4px;
215
+ text-transform: uppercase;
216
+ }
217
+
218
+ .badge-purple { background: rgba(99, 102, 241, 0.15); color: #818cf8; }
219
+ .badge-green { background: rgba(16, 185, 129, 0.15); color: #34d399; }
220
+ .badge-yellow { background: rgba(245, 158, 11, 0.15); color: #fbbf24; }
221
+
222
+ .btn-trash {
223
+ background: transparent;
224
+ border: none;
225
+ cursor: pointer;
226
+ color: var(--text-muted);
227
+ transition: color 0.2s ease;
228
+ display: flex;
229
+ align-items: center;
230
+ }
231
+
232
+ .btn-trash:hover {
233
+ color: var(--error-color);
234
+ }
235
+
236
+ /* Buttons */
237
+ .btn {
238
+ background: rgba(255, 255, 255, 0.05);
239
+ border: 1px solid rgba(255, 255, 255, 0.1);
240
+ border-radius: 8px;
241
+ color: var(--text-main);
242
+ cursor: pointer;
243
+ font-family: 'Inter', sans-serif;
244
+ font-size: 13px;
245
+ font-weight: 500;
246
+ padding: 10px 18px;
247
+ transition: all 0.2s ease;
248
+ display: inline-flex;
249
+ align-items: center;
250
+ gap: 8px;
251
+ }
252
+
253
+ .btn:hover {
254
+ background: rgba(255, 255, 255, 0.08);
255
+ border-color: rgba(255, 255, 255, 0.15);
256
+ }
257
+
258
+ .btn-primary {
259
+ background: var(--accent-gradient);
260
+ border: none;
261
+ box-shadow: 0 4px 12px rgba(79, 70, 229, 0.35);
262
+ }
263
+
264
+ .btn-primary:hover {
265
+ background: linear-gradient(135deg, #7c3aed 0%, #6366f1 100%);
266
+ transform: translateY(-1px);
267
+ }
268
+
269
+ .btn-danger {
270
+ background: rgba(239, 68, 68, 0.1);
271
+ border-color: rgba(239, 68, 68, 0.2);
272
+ color: var(--error-color);
273
+ }
274
+
275
+ .btn-danger:hover {
276
+ background: rgba(239, 68, 68, 0.2);
277
+ }
278
+
279
+ /* Forms */
280
+ .form-group {
281
+ margin-bottom: 20px;
282
+ }
283
+
284
+ .form-label {
285
+ display: block;
286
+ font-size: 13px;
287
+ font-weight: 500;
288
+ color: var(--text-muted);
289
+ margin-bottom: 8px;
290
+ }
291
+
292
+ .form-input {
293
+ width: 100%;
294
+ background: rgba(0, 0, 0, 0.25);
295
+ border: 1px solid var(--border-color);
296
+ border-radius: 8px;
297
+ color: var(--text-main);
298
+ font-family: 'Inter', sans-serif;
299
+ font-size: 14px;
300
+ padding: 12px;
301
+ transition: all 0.2s ease;
302
+ }
303
+
304
+ .form-input:focus {
305
+ border-color: var(--accent-color);
306
+ outline: none;
307
+ }
308
+
309
+ .checkbox-grid {
310
+ display: grid;
311
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
312
+ gap: 12px;
313
+ background: rgba(0, 0, 0, 0.15);
314
+ border: 1px solid var(--border-color);
315
+ border-radius: 8px;
316
+ padding: 16px;
317
+ }
318
+
319
+ .checkbox-label {
320
+ display: flex;
321
+ align-items: center;
322
+ gap: 8px;
323
+ font-size: 13px;
324
+ cursor: pointer;
325
+ color: var(--text-main);
326
+ }
327
+
328
+ /* Graph Canvas Flow */
329
+ .flow-container {
330
+ display: flex;
331
+ flex-direction: column;
332
+ gap: 8px;
333
+ position: relative;
334
+ padding: 10px 0;
335
+ }
336
+
337
+ .flow-step-card {
338
+ background: var(--bg-card);
339
+ border: 1px solid var(--border-color);
340
+ border-radius: 12px;
341
+ padding: 16px 20px;
342
+ position: relative;
343
+ z-index: 10;
344
+ transition: all 0.25s ease;
345
+ cursor: grab;
346
+ }
347
+
348
+ .flow-step-card:active {
349
+ cursor: grabbing;
350
+ }
351
+
352
+ .flow-step-card.running { border-color: var(--warning-color); box-shadow: 0 0 15px rgba(245, 158, 11, 0.25); }
353
+ .flow-step-card.success { border-color: var(--success-color); box-shadow: 0 0 15px rgba(16, 185, 129, 0.25); }
354
+ .flow-step-card.failed { border-color: var(--error-color); box-shadow: 0 0 15px rgba(239, 68, 68, 0.25); }
355
+
356
+ .drop-zone {
357
+ border: 1px dashed transparent;
358
+ border-radius: 8px;
359
+ height: 12px;
360
+ transition: all 0.2s ease;
361
+ display: flex;
362
+ align-items: center;
363
+ justify-content: center;
364
+ color: transparent;
365
+ font-size: 11px;
366
+ font-weight: 600;
367
+ z-index: 12;
368
+ position: relative;
369
+ }
370
+
371
+ .drop-zone.drag-over {
372
+ border-color: var(--accent-color);
373
+ background: rgba(99, 102, 241, 0.08);
374
+ height: 48px;
375
+ color: var(--accent-color);
376
+ }
377
+
378
+ /* Modal dialog */
379
+ .modal-overlay {
380
+ position: fixed;
381
+ top: 0;
382
+ left: 0;
383
+ right: 0;
384
+ bottom: 0;
385
+ background: rgba(0, 0, 0, 0.7);
386
+ backdrop-filter: blur(8px);
387
+ z-index: 1000;
388
+ display: flex;
389
+ align-items: center;
390
+ justify-content: center;
391
+ opacity: 0;
392
+ pointer-events: none;
393
+ transition: opacity 0.3s ease;
394
+ }
395
+
396
+ .modal-overlay.active {
397
+ opacity: 1;
398
+ pointer-events: auto;
399
+ }
400
+
401
+ .modal-card {
402
+ background: var(--bg-card);
403
+ border: 1px solid var(--border-color);
404
+ border-radius: 16px;
405
+ width: 500px;
406
+ padding: 30px;
407
+ box-shadow: 0 10px 30px rgba(0,0,0,0.5);
408
+ }
409
+
410
+ .modal-header {
411
+ font-family: 'Outfit', sans-serif;
412
+ font-size: 20px;
413
+ font-weight: 600;
414
+ margin-bottom: 20px;
415
+ color: #ffffff;
416
+ }
417
+
418
+ .modal-footer {
419
+ display: flex;
420
+ justify-content: flex-end;
421
+ gap: 12px;
422
+ margin-top: 24px;
423
+ }
424
+
425
+ /* Sidebar items in workflows */
426
+ .draggable-item {
427
+ background: rgba(255, 255, 255, 0.02);
428
+ border: 1px solid var(--border-color);
429
+ border-radius: 8px;
430
+ padding: 10px 14px;
431
+ margin-bottom: 8px;
432
+ font-size: 13px;
433
+ cursor: grab;
434
+ display: flex;
435
+ align-items: center;
436
+ justify-content: space-between;
437
+ transition: all 0.2s ease;
438
+ }
439
+
440
+ .draggable-item:hover {
441
+ background: rgba(255, 255, 255, 0.05);
442
+ border-color: rgba(255, 255, 255, 0.15);
443
+ }
444
+
445
+ /* Console logs output card */
446
+ .panel-card {
447
+ background: #07090e;
448
+ border: 1px solid var(--border-color);
449
+ border-radius: 12px;
450
+ overflow: hidden;
451
+ display: flex;
452
+ flex-direction: column;
453
+ height: 280px;
454
+ }
455
+
456
+ .panel-header {
457
+ background: rgba(255, 255, 255, 0.02);
458
+ border-bottom: 1px solid var(--border-color);
459
+ padding: 12px 20px;
460
+ font-size: 12px;
461
+ font-weight: 600;
462
+ color: var(--text-muted);
463
+ text-transform: uppercase;
464
+ letter-spacing: 0.05em;
465
+ }
466
+
467
+ .console-output {
468
+ font-family: 'JetBrains Mono', monospace;
469
+ font-size: 12px;
470
+ color: #a7f3d0;
471
+ padding: 16px;
472
+ overflow-y: auto;
473
+ flex: 1;
474
+ white-space: pre-wrap;
475
+ line-height: 1.6;
476
+ }
477
+ </style>
478
+ </head>
479
+ <body>
480
+ <div class="app-container">
481
+ <header>
482
+ <div class="logo-container">
483
+ <div class="logo-icon">
484
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5">
485
+ <polyline points="16 18 22 12 16 6"></polyline>
486
+ <polyline points="8 6 2 12 8 18"></polyline>
487
+ </svg>
488
+ </div>
489
+ <span class="logo-text">AI Frame Console</span>
490
+ </div>
491
+
492
+ <!-- Tab Navigator -->
493
+ <div class="nav-tabs">
494
+ <button class="tab-btn active" id="tab-btn-workflows" onclick="switchTab('workflows')">🌿 Workflows</button>
495
+ <button class="tab-btn" id="tab-btn-agents" onclick="switchTab('agents')">🤖 Agents</button>
496
+ <button class="tab-btn" id="tab-btn-skills" onclick="switchTab('skills')">🛠️ Skills</button>
497
+ <button class="tab-btn" id="tab-btn-executions" onclick="switchTab('executions')">⚡ Executions</button>
498
+ </div>
499
+ </header>
500
+
501
+ <!-- TAB 1: WORKFLOWS MANAGEMENT -->
502
+ <div class="tab-content active three-column-layout" id="tab-workflows">
503
+ <aside class="sidebar">
504
+ <div class="sidebar-section">
505
+ <h3 class="section-title">Workflows List</h3>
506
+ <button class="btn btn-primary" style="width: 100%; margin-bottom: 14px;" onclick="createNewWorkflow()">+ New Workflow</button>
507
+ <div id="workflows-list-container"></div>
508
+ </div>
509
+ <div class="sidebar-section">
510
+ <h3 class="section-title">Drag & Drop Agents</h3>
511
+ <div id="wf-draggable-agents"></div>
512
+ </div>
513
+ <div class="sidebar-section">
514
+ <h3 class="section-title">Drag & Drop Skills</h3>
515
+ <div id="wf-draggable-skills"></div>
516
+ </div>
517
+ </aside>
518
+
519
+ <main class="content-area">
520
+ <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 30px;">
521
+ <div>
522
+ <h1 class="workspace-title" id="wf-title" style="font-family: 'Outfit', sans-serif; font-size: 26px;">No Workflow</h1>
523
+ <p id="wf-desc" style="font-size: 13px; color: var(--text-muted); margin-top: 4px;"></p>
524
+ </div>
525
+ <div style="display: flex; gap: 10px;">
526
+ <button class="btn" onclick="addNewStepToActiveWorkflow()">
527
+ + Add Step
528
+ </button>
529
+ <button class="btn btn-primary" onclick="openRunModal()">
530
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5">
531
+ <polygon points="5 3 19 12 5 21 5 3"></polygon>
532
+ </svg> Run Workflow
533
+ </button>
534
+ </div>
535
+ </div>
536
+
537
+ <div class="flow-container" id="flow-steps-container">
538
+ <svg id="flow-svg-connections" style="position: absolute; top: 0; left: 0; width: 100%; pointer-events: none; z-index: 0;"></svg>
539
+ </div>
540
+ </main>
541
+
542
+ <aside class="sidebar sidebar-right">
543
+ <div class="sidebar-section" style="margin-bottom: 30px;">
544
+ <h3 class="section-title">Workflow Config</h3>
545
+ <form onsubmit="saveWorkflowMeta(event)">
546
+ <div class="form-group">
547
+ <label class="form-label">Workflow Name</label>
548
+ <input class="form-input" type="text" id="edit-wf-name" required>
549
+ </div>
550
+ <div class="form-group">
551
+ <label class="form-label">Description</label>
552
+ <textarea class="form-input" id="edit-wf-desc" style="height: 60px; resize: vertical;"></textarea>
553
+ </div>
554
+ <button class="btn" style="width: 100%;" type="submit">Save Workflow Config</button>
555
+ </form>
556
+ </div>
557
+
558
+ <div class="sidebar-section">
559
+ <h3 class="section-title">Step Config Editor</h3>
560
+ <form onsubmit="saveWorkflowStep(event)">
561
+ <input type="hidden" id="edit-step-idx">
562
+ <div class="form-group">
563
+ <label class="form-label">Step Name</label>
564
+ <input class="form-input" type="text" id="edit-step-name" required>
565
+ </div>
566
+ <div class="form-group">
567
+ <label class="form-label">Executor (Agent / Skill)</label>
568
+ <input class="form-input" type="text" id="edit-step-use">
569
+ </div>
570
+ <div class="form-group">
571
+ <label class="form-label">Agent Instruction</label>
572
+ <textarea class="form-input" id="edit-step-instruction" style="height: 100px; resize: vertical;"></textarea>
573
+ </div>
574
+ <div class="form-group">
575
+ <label class="form-label">Validation Command</label>
576
+ <input class="form-input" type="text" id="edit-step-val">
577
+ </div>
578
+ <div class="form-group">
579
+ <label class="form-label">On Failure Rollback Step</label>
580
+ <select class="form-input" id="edit-step-on-failure">
581
+ <option value="">None (Stop Workflow)</option>
582
+ </select>
583
+ </div>
584
+ <div style="display: flex; gap: 8px;">
585
+ <button class="btn btn-primary" style="flex: 1;" type="submit">Update Step</button>
586
+ <button class="btn btn-danger" style="padding: 10px 12px;" type="button" onclick="deleteActiveStepNode()" title="Xóa bước này">
587
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
588
+ </button>
589
+ </div>
590
+ </form>
591
+ </div>
592
+ </aside>
593
+ </div>
594
+
595
+ <!-- TAB 2: AGENTS MANAGEMENT -->
596
+ <div class="tab-content two-column-layout" id="tab-agents">
597
+ <aside class="sidebar">
598
+ <h3 class="section-title">Agents List</h3>
599
+ <button class="btn btn-primary" style="width: 100%; margin-bottom: 14px;" onclick="createNewAgent()">+ Add Agent</button>
600
+ <div id="agents-list-container"></div>
601
+ </aside>
602
+ <main class="content-area">
603
+ <div style="max-width: 800px;">
604
+ <h1 style="font-family: 'Outfit', sans-serif; font-size: 26px; margin-bottom: 30px;">Agent Config</h1>
605
+ <form onsubmit="saveAgentConfig(event)">
606
+ <input type="hidden" id="agent-is-new" value="false">
607
+ <div class="form-group">
608
+ <label class="form-label">Agent Key Name (e.g. tech_lead.yaml)</label>
609
+ <input class="form-input" type="text" id="agent-filename" placeholder="tech_lead.yaml" required>
610
+ </div>
611
+ <div class="form-group">
612
+ <label class="form-label">Display Name</label>
613
+ <input class="form-input" type="text" id="agent-name" required>
614
+ </div>
615
+ <div class="form-group">
616
+ <label class="form-label">Role Description</label>
617
+ <textarea class="form-input" id="agent-role" style="height: 80px; resize: vertical;" required></textarea>
618
+ </div>
619
+ <div class="form-group">
620
+ <label class="form-label">Allowed Skills</label>
621
+ <div class="checkbox-grid" id="agent-skills-checkboxes"></div>
622
+ </div>
623
+ <div class="form-group">
624
+ <label class="form-label">Allowed Sub-Agents</label>
625
+ <div class="checkbox-grid" id="agent-subagents-checkboxes"></div>
626
+ </div>
627
+ <div style="display: flex; gap: 12px; margin-top: 30px;">
628
+ <button class="btn btn-primary" type="submit">Save Agent Settings</button>
629
+ <button class="btn btn-danger" type="button" id="delete-agent-btn" onclick="deleteActiveAgent()">Delete Agent</button>
630
+ </div>
631
+ </form>
632
+ </div>
633
+ </main>
634
+ </div>
635
+
636
+ <!-- TAB 3: SKILLS MANAGEMENT -->
637
+ <div class="tab-content two-column-layout" id="tab-skills">
638
+ <aside class="sidebar">
639
+ <h3 class="section-title">Skills List</h3>
640
+ <button class="btn btn-primary" style="width: 100%; margin-bottom: 14px;" onclick="createNewSkill()">+ Add Skill</button>
641
+ <div id="skills-list-container"></div>
642
+ </aside>
643
+ <main class="content-area">
644
+ <div style="max-width: 800px;">
645
+ <h1 style="font-family: 'Outfit', sans-serif; font-size: 26px; margin-bottom: 30px;">Skill Configuration</h1>
646
+ <form onsubmit="saveSkillConfig(event)">
647
+ <input type="hidden" id="skill-is-new" value="false">
648
+ <div class="form-group">
649
+ <label class="form-label">Skill Key Name (e.g. test_command.yaml)</label>
650
+ <input class="form-input" type="text" id="skill-filename" placeholder="test_command.yaml" required>
651
+ </div>
652
+ <div class="form-group">
653
+ <label class="form-label">Skill Display Name</label>
654
+ <input class="form-input" type="text" id="skill-name" required>
655
+ </div>
656
+ <div class="form-group">
657
+ <label class="form-label">Type</label>
658
+ <select class="form-input" id="skill-type">
659
+ <option value="command">command</option>
660
+ <option value="tool">tool</option>
661
+ </select>
662
+ </div>
663
+ <div class="form-group">
664
+ <label class="form-label">Command / Executor Usage (e.g. npm test)</label>
665
+ <input class="form-input" type="text" id="skill-use" required>
666
+ </div>
667
+ <div class="form-group">
668
+ <label class="form-label">Description</label>
669
+ <textarea class="form-input" id="skill-description" style="height: 80px; resize: vertical;"></textarea>
670
+ </div>
671
+ <div style="display: flex; gap: 12px; margin-top: 30px;">
672
+ <button class="btn btn-primary" type="submit">Save Skill Settings</button>
673
+ <button class="btn btn-danger" type="button" id="delete-skill-btn" onclick="deleteActiveSkill()">Delete Skill</button>
674
+ </div>
675
+ </form>
676
+ </div>
677
+ </main>
678
+ </div>
679
+
680
+ <!-- TAB 4: EXECUTION MANAGEMENT -->
681
+ <div class="tab-content two-column-layout" id="tab-executions">
682
+ <aside class="sidebar">
683
+ <h3 class="section-title">Executions History</h3>
684
+ <div id="executions-list-container" style="overflow-y: auto; flex: 1;"></div>
685
+ </aside>
686
+ <main class="content-area" style="display: flex; flex-direction: column; gap: 30px;">
687
+ <div>
688
+ <h1 style="font-family: 'Outfit', sans-serif; font-size: 26px;" id="exec-title">No Execution Selected</h1>
689
+ <p id="exec-meta" style="font-size: 13px; color: var(--text-muted); margin-top: 4px;"></p>
690
+ </div>
691
+
692
+ <!-- Flowchart of execution -->
693
+ <div class="flow-container" id="exec-steps-container">
694
+ <svg id="exec-svg-connections" style="position: absolute; top: 0; left: 0; width: 100%; pointer-events: none; z-index: 0;"></svg>
695
+ </div>
696
+
697
+ <!-- Terminal Output -->
698
+ <div class="panel-card">
699
+ <div class="panel-header">Console Output</div>
700
+ <div class="console-output" id="exec-logs">Click an execution record from the list to view logs...</div>
701
+ </div>
702
+ </main>
703
+ </div>
704
+ </div>
705
+
706
+ <!-- RUN WORKFLOW MODAL FOR DYNAMIC PROMPT -->
707
+ <div class="modal-overlay" id="run-modal">
708
+ <div class="modal-card">
709
+ <div class="modal-header">Run Workflow Settings</div>
710
+ <div class="form-group">
711
+ <label class="form-label">Task Prompt (Replaces dynamic variable {{task_prompt}})</label>
712
+ <textarea class="form-input" id="run-modal-prompt" style="height: 120px; resize: vertical;" placeholder="Mô tả nhiệm vụ cụ thể cần thực hiện..."></textarea>
713
+ </div>
714
+ <div class="modal-footer">
715
+ <button class="btn" onclick="closeRunModal()">Cancel</button>
716
+ <button class="btn btn-primary" onclick="triggerWorkflowRunWithPrompt()">Run Execution</button>
717
+ </div>
718
+ </div>
719
+ </div>
720
+
721
+ <script>
722
+ let workflows = [];
723
+ let agents = [];
724
+ let skills = [];
725
+ let activeWfIndex = null;
726
+ let activeAgentFilename = null;
727
+ let activeSkillFilename = null;
728
+ let pollInterval = null;
729
+ let executionPoll = null;
730
+ let activeExecId = null;
731
+
732
+ function switchTab(tabId) {
733
+ document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active'));
734
+ document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
735
+
736
+ document.getElementById(`tab-btn-${tabId}`).classList.add('active');
737
+ document.getElementById(`tab-${tabId}`).classList.add('active');
738
+
739
+ if (pollInterval) { clearInterval(pollInterval); pollInterval = null; }
740
+ if (executionPoll) { clearInterval(executionPoll); executionPoll = null; }
741
+
742
+ if (tabId === 'workflows') {
743
+ loadWorkflows();
744
+ } else if (tabId === 'agents') {
745
+ loadAgentsTab();
746
+ } else if (tabId === 'skills') {
747
+ loadSkillsTab();
748
+ } else if (tabId === 'executions') {
749
+ loadExecutionsTab();
750
+ }
751
+ }
752
+
753
+ // --- Tab 1: Workflows ---
754
+ async function loadWorkflows() {
755
+ const response = await fetch('/api/workflows');
756
+ workflows = await response.json();
757
+ renderWorkflowsList();
758
+ if (workflows.length > 0) {
759
+ if (activeWfIndex === null || activeWfIndex >= workflows.length) {
760
+ selectWorkflow(0);
761
+ } else {
762
+ selectWorkflow(activeWfIndex);
763
+ }
764
+ } else {
765
+ // Clear UI
766
+ activeWfIndex = null;
767
+ document.getElementById('wf-title').innerText = 'No Workflow';
768
+ document.getElementById('wf-desc').innerText = '';
769
+ document.getElementById('edit-wf-name').value = '';
770
+ document.getElementById('edit-wf-desc').value = '';
771
+ document.getElementById('flow-steps-container').innerHTML = '<svg id="flow-svg-connections" style="position: absolute; top: 0; left: 0; width: 100%; pointer-events: none; z-index: 0;"></svg>';
772
+ clearStepEditor();
773
+ }
774
+ }
775
+
776
+ function renderWorkflowsList() {
777
+ const container = document.getElementById('workflows-list-container');
778
+ container.innerHTML = '';
779
+ workflows.forEach((wf, index) => {
780
+ const div = document.createElement('div');
781
+ div.className = `item-card-row ${index === activeWfIndex ? 'active' : ''}`;
782
+ div.onclick = () => selectWorkflow(index);
783
+ div.innerHTML = `
784
+ <div>
785
+ <div class="item-card-name">${wf.data?.name || wf.filename}</div>
786
+ <div class="item-card-desc">${wf.filename}</div>
787
+ </div>
788
+ <button class="btn-trash" onclick="deleteWorkflow('${wf.filename}', event)">
789
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
790
+ </button>
791
+ `;
792
+ container.appendChild(div);
793
+ });
794
+ }
795
+
796
+ function selectWorkflow(idx) {
797
+ activeWfIndex = idx;
798
+ renderWorkflowsList();
799
+ const wf = workflows[idx];
800
+ if (!wf) return;
801
+
802
+ document.getElementById('wf-title').innerText = wf.data?.name || wf.filename;
803
+ document.getElementById('wf-desc').innerText = wf.data?.description || 'No description.';
804
+
805
+ // Populate workflow meta inputs
806
+ document.getElementById('edit-wf-name').value = wf.data?.name || '';
807
+ document.getElementById('edit-wf-desc').value = wf.data?.description || '';
808
+
809
+ renderWorkflowGraph(wf.data?.steps || []);
810
+ clearStepEditor();
811
+ }
812
+
813
+ async function saveWorkflowMeta(e) {
814
+ e.preventDefault();
815
+ const wf = workflows[activeWfIndex];
816
+ if (!wf) return;
817
+
818
+ wf.data.name = document.getElementById('edit-wf-name').value;
819
+ wf.data.description = document.getElementById('edit-wf-desc').value;
820
+
821
+ document.getElementById('wf-title').innerText = wf.data.name;
822
+ document.getElementById('wf-desc').innerText = wf.data.description;
823
+
824
+ renderWorkflowsList();
825
+ await saveActiveWorkflow();
826
+ }
827
+
828
+ async function createNewWorkflow() {
829
+ const name = prompt('Enter new workflow filename (e.g. custom.yaml):', 'custom.yaml');
830
+ if (!name) return;
831
+ const template = {
832
+ name: name.replace('.yaml', ''),
833
+ description: 'Workflow tự định nghĩa',
834
+ steps: []
835
+ };
836
+ await fetch('/api/workflows', {
837
+ method: 'POST',
838
+ headers: { 'Content-Type': 'application/json' },
839
+ body: JSON.stringify({ filename: name, workflow: template })
840
+ });
841
+ activeWfIndex = null;
842
+ await loadWorkflows();
843
+ }
844
+
845
+ async function deleteWorkflow(filename, event) {
846
+ event.stopPropagation();
847
+ if (!confirm(`Xóa workflow: ${filename}?`)) return;
848
+ await fetch(`/api/workflows/${filename}`, { method: 'DELETE' });
849
+ activeWfIndex = null;
850
+ await loadWorkflows();
851
+ }
852
+
853
+ function renderWorkflowGraph(steps) {
854
+ const container = document.getElementById('flow-steps-container');
855
+ const svg = document.getElementById('flow-svg-connections');
856
+ container.innerHTML = '';
857
+ container.appendChild(svg);
858
+
859
+ steps.forEach((step, idx) => {
860
+ container.appendChild(createWfDropZone(idx));
861
+
862
+ const card = document.createElement('div');
863
+ card.className = 'flow-step-card';
864
+ card.setAttribute('draggable', 'true');
865
+ card.addEventListener('dragstart', (e) => {
866
+ e.dataTransfer.setData('drag-type', 'reorder');
867
+ e.dataTransfer.setData('drag-payload', idx.toString());
868
+ });
869
+ card.onclick = () => loadStepToEditor(idx);
870
+
871
+ card.innerHTML = `
872
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 8px;">
873
+ <strong style="color:#ffffff;">${step.name}</strong>
874
+ <div style="display:flex; align-items:center; gap: 8px;">
875
+ <span class="badge badge-purple">Step ${idx + 1}</span>
876
+ <button class="btn-trash" onclick="deleteStep(${idx}, event)" title="Xóa bước này">
877
+ <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg>
878
+ </button>
879
+ </div>
880
+ </div>
881
+ <div style="font-size:12px; color:var(--text-muted); margin-bottom: 8px;">
882
+ Executor: <strong>${step.use || 'Command'}</strong>
883
+ </div>
884
+ ${step.instruction ? `<div style="background:rgba(0,0,0,0.3); border-radius:6px; padding:8px 12px; font-size:12px; border-left:3px solid var(--accent-color);">${step.instruction}</div>` : ''}
885
+ `;
886
+ container.appendChild(card);
887
+ });
888
+
889
+ container.appendChild(createWfDropZone(steps.length));
890
+ setTimeout(drawWfGraphConnections, 50);
891
+ }
892
+
893
+ function createWfDropZone(idx) {
894
+ const dz = document.createElement('div');
895
+ dz.className = 'drop-zone';
896
+ dz.innerText = '+ Thả vào để chèn bước chạy ở đây';
897
+ dz.addEventListener('dragover', (e) => { e.preventDefault(); dz.classList.add('drag-over'); });
898
+ dz.addEventListener('dragleave', () => dz.classList.remove('drag-over'));
899
+ dz.addEventListener('drop', async (e) => {
900
+ e.preventDefault();
901
+ dz.classList.remove('drag-over');
902
+ const dragType = e.dataTransfer.getData('drag-type');
903
+ const dragPayload = e.dataTransfer.getData('drag-payload');
904
+
905
+ const wf = workflows[activeWfIndex];
906
+ const steps = wf.data.steps || [];
907
+
908
+ if (dragType === 'reorder') {
909
+ const src = parseInt(dragPayload, 10);
910
+ if (src !== idx) {
911
+ const [moved] = steps.splice(src, 1);
912
+ const ins = src < idx ? idx - 1 : idx;
913
+ steps.splice(ins, 0, moved);
914
+ renderWorkflowGraph(steps);
915
+ await saveActiveWorkflow();
916
+ }
917
+ return;
918
+ }
919
+
920
+ let newStep = null;
921
+ if (dragType === 'sidebar-agent') {
922
+ newStep = {
923
+ name: `Run Agent ${dragPayload.split('/').pop()}`,
924
+ use: dragPayload,
925
+ instruction: 'Nhập chỉ dẫn tại đây...'
926
+ };
927
+ } else if (dragType === 'sidebar-skill') {
928
+ newStep = {
929
+ name: `Run Skill ${dragPayload}`,
930
+ use: dragPayload
931
+ };
932
+ }
933
+
934
+ if (newStep) {
935
+ steps.splice(idx, 0, newStep);
936
+ renderWorkflowGraph(steps);
937
+ await saveActiveWorkflow();
938
+ }
939
+ });
940
+ return dz;
941
+ }
942
+
943
+ function drawWfGraphConnections() {
944
+ const container = document.getElementById('flow-steps-container');
945
+ const svg = document.getElementById('flow-svg-connections');
946
+ if (!svg) return;
947
+ svg.innerHTML = `
948
+ <defs>
949
+ <marker id="arrow-wf" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
950
+ <path d="M 0 1 L 10 5 L 0 9 z" fill="#6366f1" />
951
+ </marker>
952
+ </defs>
953
+ `;
954
+ svg.style.height = container.scrollHeight + 'px';
955
+
956
+ const cards = container.querySelectorAll('.flow-step-card');
957
+ for (let i = 0; i < cards.length - 1; i++) {
958
+ const c1 = cards[i];
959
+ const c2 = cards[i+1];
960
+
961
+ const x1 = c1.offsetLeft + c1.offsetWidth / 2;
962
+ const y1 = c1.offsetTop + c1.offsetHeight;
963
+ const x2 = c2.offsetLeft + c2.offsetWidth / 2;
964
+ const y2 = c2.offsetTop - 4;
965
+
966
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
967
+ line.setAttribute('x1', x1); line.setAttribute('y1', y1);
968
+ line.setAttribute('x2', x2); line.setAttribute('y2', y2);
969
+ line.setAttribute('stroke', '#6366f1');
970
+ line.setAttribute('stroke-width', '2');
971
+ line.setAttribute('marker-end', 'url(#arrow-wf)');
972
+ svg.appendChild(line);
973
+ }
974
+ }
975
+
976
+ async function saveActiveWorkflow() {
977
+ const wf = workflows[activeWfIndex];
978
+ await fetch('/api/workflows', {
979
+ method: 'POST',
980
+ headers: { 'Content-Type': 'application/json' },
981
+ body: JSON.stringify({ filename: wf.filename, workflow: wf.data })
982
+ });
983
+ }
984
+
985
+ function loadStepToEditor(idx) {
986
+ const wf = workflows[activeWfIndex];
987
+ const step = wf.data.steps[idx];
988
+ document.getElementById('edit-step-idx').value = idx;
989
+ document.getElementById('edit-step-name').value = step.name || '';
990
+ document.getElementById('edit-step-use').value = step.use || '';
991
+ document.getElementById('edit-step-instruction').value = step.instruction || '';
992
+ document.getElementById('edit-step-val').value = typeof step.validation === 'string' ? step.validation : (step.validation?.command || '');
993
+
994
+ const onFailureSelect = document.getElementById('edit-step-on-failure');
995
+ onFailureSelect.innerHTML = '<option value="">None (Stop Workflow)</option>';
996
+ for (let i = 0; i < idx; i++) {
997
+ const prevStep = wf.data.steps[i];
998
+ const stepNum = i + 1;
999
+ onFailureSelect.innerHTML += `<option value="Step ${stepNum}">Step ${stepNum}: ${prevStep.name}</option>`;
1000
+ }
1001
+ onFailureSelect.value = step.on_failure || '';
1002
+ }
1003
+
1004
+ async function saveWorkflowStep(e) {
1005
+ e.preventDefault();
1006
+ const idxStr = document.getElementById('edit-step-idx').value;
1007
+ if (idxStr === '') {
1008
+ alert('Vui lòng chọn một bước chạy trên đồ thị để sửa!');
1009
+ return;
1010
+ }
1011
+ const idx = parseInt(idxStr, 10);
1012
+ const wf = workflows[activeWfIndex];
1013
+ const step = wf.data.steps[idx];
1014
+
1015
+ step.name = document.getElementById('edit-step-name').value;
1016
+ step.use = document.getElementById('edit-step-use').value;
1017
+ step.instruction = document.getElementById('edit-step-instruction').value;
1018
+ const val = document.getElementById('edit-step-val').value;
1019
+ if (val) {
1020
+ step.validation = { command: val, expect_exit_code: 0 };
1021
+ } else {
1022
+ delete step.validation;
1023
+ }
1024
+
1025
+ const onFailureVal = document.getElementById('edit-step-on-failure').value;
1026
+ if (onFailureVal) {
1027
+ step.on_failure = onFailureVal;
1028
+ } else {
1029
+ delete step.on_failure;
1030
+ }
1031
+
1032
+ renderWorkflowGraph(wf.data.steps);
1033
+ await saveActiveWorkflow();
1034
+ }
1035
+
1036
+ async function deleteStep(idx, event) {
1037
+ if (event) event.stopPropagation();
1038
+ const wf = workflows[activeWfIndex];
1039
+ if (!wf) return;
1040
+ if (!confirm(`Xóa bước "${wf.data.steps[idx].name}"?`)) return;
1041
+ wf.data.steps.splice(idx, 1);
1042
+ renderWorkflowGraph(wf.data.steps);
1043
+ await saveActiveWorkflow();
1044
+
1045
+ const currentEditIdx = document.getElementById('edit-step-idx').value;
1046
+ if (currentEditIdx === idx.toString()) {
1047
+ clearStepEditor();
1048
+ }
1049
+ }
1050
+
1051
+ async function deleteActiveStepNode() {
1052
+ const idxStr = document.getElementById('edit-step-idx').value;
1053
+ if (idxStr === '') {
1054
+ alert('Vui lòng chọn một bước chạy trên đồ thị để xóa!');
1055
+ return;
1056
+ }
1057
+ const idx = parseInt(idxStr, 10);
1058
+ await deleteStep(idx);
1059
+ }
1060
+
1061
+ async function addNewStepToActiveWorkflow() {
1062
+ const wf = workflows[activeWfIndex];
1063
+ if (!wf) return;
1064
+ const stepName = prompt('Nhập tên bước chạy mới:', 'New Step');
1065
+ if (!stepName) return;
1066
+ const newStep = {
1067
+ name: stepName,
1068
+ use: 'tool/read_file',
1069
+ instruction: 'Đọc nội dung tệp tin'
1070
+ };
1071
+ if (!wf.data.steps) wf.data.steps = [];
1072
+ wf.data.steps.push(newStep);
1073
+ renderWorkflowGraph(wf.data.steps);
1074
+ await saveActiveWorkflow();
1075
+ loadStepToEditor(wf.data.steps.length - 1);
1076
+ }
1077
+
1078
+ function clearStepEditor() {
1079
+ document.getElementById('edit-step-idx').value = '';
1080
+ document.getElementById('edit-step-name').value = '';
1081
+ document.getElementById('edit-step-use').value = '';
1082
+ document.getElementById('edit-step-instruction').value = '';
1083
+ document.getElementById('edit-step-val').value = '';
1084
+ document.getElementById('edit-step-on-failure').innerHTML = '<option value="">None (Stop Workflow)</option>';
1085
+ }
1086
+
1087
+ // Modal handling
1088
+ function openRunModal() {
1089
+ document.getElementById('run-modal-prompt').value = '';
1090
+ document.getElementById('run-modal').classList.add('active');
1091
+ }
1092
+ function closeRunModal() {
1093
+ document.getElementById('run-modal').classList.remove('active');
1094
+ }
1095
+ async function triggerWorkflowRunWithPrompt() {
1096
+ closeRunModal();
1097
+ const wf = workflows[activeWfIndex];
1098
+ const promptText = document.getElementById('run-modal-prompt').value;
1099
+
1100
+ const res = await fetch('/api/run', {
1101
+ method: 'POST',
1102
+ headers: { 'Content-Type': 'application/json' },
1103
+ body: JSON.stringify({
1104
+ filename: wf.filename,
1105
+ inputs: { task_prompt: promptText }
1106
+ })
1107
+ });
1108
+ const data = await res.json();
1109
+ if (data.runId) {
1110
+ activeExecId = data.runId;
1111
+ }
1112
+ switchTab('executions');
1113
+ }
1114
+
1115
+
1116
+ // --- Tab 2: Agents ---
1117
+ async function loadAgentsTab() {
1118
+ const response = await fetch('/api/agents');
1119
+ agents = await response.json();
1120
+ renderAgentsList();
1121
+
1122
+ const skillsResponse = await fetch('/api/skills');
1123
+ skills = await skillsResponse.json();
1124
+
1125
+ renderAgentCheckboxes();
1126
+ if (agents.length > 0) {
1127
+ const found = agents.some(a => a.filename === activeAgentFilename);
1128
+ selectAgent(found ? activeAgentFilename : agents[0].filename);
1129
+ } else {
1130
+ clearAgentForm();
1131
+ }
1132
+ }
1133
+
1134
+ function renderAgentsList() {
1135
+ const container = document.getElementById('agents-list-container');
1136
+ container.innerHTML = '';
1137
+ agents.forEach(agent => {
1138
+ const div = document.createElement('div');
1139
+ div.className = `item-card-row ${agent.filename === activeAgentFilename ? 'active' : ''}`;
1140
+ div.onclick = () => selectAgent(agent.filename);
1141
+ div.innerHTML = `
1142
+ <div>
1143
+ <div class="item-card-name">${agent.data?.name || agent.filename}</div>
1144
+ <div class="item-card-desc">${agent.filename}</div>
1145
+ </div>
1146
+ `;
1147
+ container.appendChild(div);
1148
+ });
1149
+ }
1150
+
1151
+ function renderAgentCheckboxes() {
1152
+ const skillsGrid = document.getElementById('agent-skills-checkboxes');
1153
+ skillsGrid.innerHTML = '';
1154
+ skills.forEach(s => {
1155
+ skillsGrid.innerHTML += `
1156
+ <label class="checkbox-label">
1157
+ <input type="checkbox" name="agent-skills" value="${s.type}/${s.name}">
1158
+ <span>${s.name}</span>
1159
+ </label>
1160
+ `;
1161
+ });
1162
+
1163
+ const subAgentsGrid = document.getElementById('agent-subagents-checkboxes');
1164
+ subAgentsGrid.innerHTML = '';
1165
+ agents.forEach(a => {
1166
+ const key = a.filename.replace('.yaml', '');
1167
+ subAgentsGrid.innerHTML += `
1168
+ <label class="checkbox-label">
1169
+ <input type="checkbox" name="agent-subagents" value="agent/${key}">
1170
+ <span>${key}</span>
1171
+ </label>
1172
+ `;
1173
+ });
1174
+ }
1175
+
1176
+ function selectAgent(filename) {
1177
+ activeAgentFilename = filename;
1178
+ renderAgentsList();
1179
+ const agent = agents.find(a => a.filename === filename);
1180
+ if (!agent) return;
1181
+
1182
+ document.getElementById('agent-is-new').value = 'false';
1183
+ document.getElementById('agent-filename').value = agent.filename;
1184
+ document.getElementById('agent-filename').disabled = true;
1185
+ document.getElementById('agent-name').value = agent.data?.name || '';
1186
+ document.getElementById('agent-role').value = agent.data?.role || '';
1187
+ document.getElementById('delete-agent-btn').style.display = 'block';
1188
+
1189
+ // Uncheck all
1190
+ document.querySelectorAll('input[name="agent-skills"]').forEach(cb => cb.checked = false);
1191
+ document.querySelectorAll('input[name="agent-subagents"]').forEach(cb => cb.checked = false);
1192
+
1193
+ // Check allowed skills/tools
1194
+ const tools = agent.data?.tools || agent.data?.allowed_skills || [];
1195
+ tools.forEach(s => {
1196
+ const cb = document.querySelector(`input[name="agent-skills"][value="${s}"]`);
1197
+ if (cb) cb.checked = true;
1198
+ });
1199
+
1200
+ // Check allowed sub-agents
1201
+ const allowedSub = agent.data?.allowed_sub_agents || [];
1202
+ allowedSub.forEach(sa => {
1203
+ const cb = document.querySelector(`input[name="agent-subagents"][value="${sa}"]`);
1204
+ if (cb) cb.checked = true;
1205
+ });
1206
+ }
1207
+
1208
+ function createNewAgent() {
1209
+ clearAgentForm();
1210
+ }
1211
+
1212
+ function clearAgentForm() {
1213
+ document.getElementById('agent-is-new').value = 'true';
1214
+ document.getElementById('agent-filename').value = 'new_agent.yaml';
1215
+ document.getElementById('agent-filename').disabled = false;
1216
+ document.getElementById('agent-name').value = '';
1217
+ document.getElementById('agent-role').value = '';
1218
+ document.querySelectorAll('input[name="agent-skills"]').forEach(cb => cb.checked = false);
1219
+ document.querySelectorAll('input[name="agent-subagents"]').forEach(cb => cb.checked = false);
1220
+ document.getElementById('delete-agent-btn').style.display = 'none';
1221
+ }
1222
+
1223
+ async function saveAgentConfig(e) {
1224
+ e.preventDefault();
1225
+ let filename = document.getElementById('agent-filename').value.trim();
1226
+ if (!filename) return;
1227
+ if (!filename.endsWith('.yaml') && !filename.endsWith('.yml')) {
1228
+ filename += '.yaml';
1229
+ }
1230
+
1231
+ const tools = [];
1232
+ document.querySelectorAll('input[name="agent-skills"]:checked').forEach(cb => tools.push(cb.value));
1233
+
1234
+ const allowed_sub_agents = [];
1235
+ document.querySelectorAll('input[name="agent-subagents"]:checked').forEach(cb => allowed_sub_agents.push(cb.value));
1236
+
1237
+ const agent = {
1238
+ name: document.getElementById('agent-name').value,
1239
+ role: document.getElementById('agent-role').value,
1240
+ tools,
1241
+ allowed_sub_agents
1242
+ };
1243
+
1244
+ await fetch('/api/agents', {
1245
+ method: 'POST',
1246
+ headers: { 'Content-Type': 'application/json' },
1247
+ body: JSON.stringify({ filename, agent })
1248
+ });
1249
+
1250
+ activeAgentFilename = filename;
1251
+ await loadAgentsTab();
1252
+ await loadWorkflowsTabAssets();
1253
+ }
1254
+
1255
+ async function deleteActiveAgent() {
1256
+ if (!activeAgentFilename) return;
1257
+ if (!confirm(`Xóa Agent: ${activeAgentFilename}?`)) return;
1258
+ await fetch(`/api/agents/${activeAgentFilename}`, { method: 'DELETE' });
1259
+ activeAgentFilename = null;
1260
+ await loadAgentsTab();
1261
+ await loadWorkflowsTabAssets();
1262
+ }
1263
+
1264
+
1265
+ // --- Tab 3: Skills ---
1266
+ async function loadSkillsTab() {
1267
+ const response = await fetch('/api/skills');
1268
+ skills = await response.json();
1269
+ renderSkillsList();
1270
+ if (skills.length > 0) {
1271
+ const found = skills.some(s => s.filename === activeSkillFilename);
1272
+ const toSelect = found ? skills.find(s => s.filename === activeSkillFilename) : skills[0];
1273
+ selectSkill(toSelect);
1274
+ } else {
1275
+ clearSkillForm();
1276
+ }
1277
+ }
1278
+
1279
+ function renderSkillsList() {
1280
+ const container = document.getElementById('skills-list-container');
1281
+ container.innerHTML = '';
1282
+ skills.forEach(s => {
1283
+ const isBuiltin = !s.filename;
1284
+ const div = document.createElement('div');
1285
+ div.className = `item-card-row ${activeSkillFilename === s.filename ? 'active' : ''}`;
1286
+ div.onclick = () => selectSkill(s);
1287
+ div.innerHTML = `
1288
+ <div>
1289
+ <div class="item-card-name">${s.name}</div>
1290
+ <div class="item-card-desc">${isBuiltin ? 'Built-in Tool' : s.filename}</div>
1291
+ </div>
1292
+ <span class="badge ${s.type === 'tool' ? 'badge-green' : 'badge-yellow'}">${s.type}</span>
1293
+ `;
1294
+ container.appendChild(div);
1295
+ });
1296
+ }
1297
+
1298
+ function selectSkill(s) {
1299
+ activeSkillFilename = s.filename;
1300
+ renderSkillsList();
1301
+
1302
+ const isBuiltin = !s.filename;
1303
+ document.getElementById('skill-is-new').value = 'false';
1304
+ document.getElementById('skill-filename').value = s.filename || 'builtin';
1305
+ document.getElementById('skill-filename').disabled = true;
1306
+ document.getElementById('skill-name').value = s.name;
1307
+ document.getElementById('skill-name').disabled = isBuiltin;
1308
+ document.getElementById('skill-type').value = s.type;
1309
+ document.getElementById('skill-type').disabled = isBuiltin;
1310
+ document.getElementById('skill-use').value = s.use;
1311
+ document.getElementById('skill-use').disabled = isBuiltin;
1312
+ document.getElementById('skill-description').value = s.description || '';
1313
+ document.getElementById('skill-description').disabled = isBuiltin;
1314
+
1315
+ document.getElementById('delete-skill-btn').style.display = isBuiltin ? 'none' : 'block';
1316
+ }
1317
+
1318
+ function createNewSkill() {
1319
+ clearSkillForm();
1320
+ }
1321
+
1322
+ function clearSkillForm() {
1323
+ document.getElementById('skill-is-new').value = 'true';
1324
+ document.getElementById('skill-filename').value = 'custom_command.yaml';
1325
+ document.getElementById('skill-filename').disabled = false;
1326
+ document.getElementById('skill-name').value = '';
1327
+ document.getElementById('skill-name').disabled = false;
1328
+ document.getElementById('skill-type').value = 'command';
1329
+ document.getElementById('skill-type').disabled = false;
1330
+ document.getElementById('skill-use').value = '';
1331
+ document.getElementById('skill-use').disabled = false;
1332
+ document.getElementById('skill-description').value = '';
1333
+ document.getElementById('skill-description').disabled = false;
1334
+ document.getElementById('delete-skill-btn').style.display = 'none';
1335
+ }
1336
+
1337
+ async function saveSkillConfig(e) {
1338
+ e.preventDefault();
1339
+ let filename = document.getElementById('skill-filename').value.trim();
1340
+ if (!filename) return;
1341
+ if (!filename.endsWith('.yaml') && !filename.endsWith('.yml')) {
1342
+ filename += '.yaml';
1343
+ }
1344
+ const skill = {
1345
+ name: document.getElementById('skill-name').value,
1346
+ type: document.getElementById('skill-type').value,
1347
+ use: document.getElementById('skill-use').value,
1348
+ description: document.getElementById('skill-description').value
1349
+ };
1350
+
1351
+ await fetch('/api/skills', {
1352
+ method: 'POST',
1353
+ headers: { 'Content-Type': 'application/json' },
1354
+ body: JSON.stringify({ filename, skill })
1355
+ });
1356
+
1357
+ activeSkillFilename = filename;
1358
+ await loadSkillsTab();
1359
+ await loadWorkflowsTabAssets();
1360
+ }
1361
+
1362
+ async function deleteActiveSkill() {
1363
+ if (!activeSkillFilename) return;
1364
+ if (!confirm(`Xóa Skill: ${activeSkillFilename}?`)) return;
1365
+ await fetch(`/api/skills/${activeSkillFilename}`, { method: 'DELETE' });
1366
+ activeSkillFilename = null;
1367
+ await loadSkillsTab();
1368
+ await loadWorkflowsTabAssets();
1369
+ }
1370
+
1371
+
1372
+ // --- Tab 4: Executions ---
1373
+ async function loadExecutionsTab() {
1374
+ const response = await fetch('/api/history');
1375
+ const history = await response.json();
1376
+ renderExecutionsList(history);
1377
+ if (history.length > 0) {
1378
+ if (!activeExecId) {
1379
+ selectExecution(history[0]);
1380
+ } else {
1381
+ const found = history.find(h => h.id === activeExecId);
1382
+ if (found) {
1383
+ selectExecution(found);
1384
+ } else {
1385
+ selectExecution(history[0]);
1386
+ }
1387
+ }
1388
+ }
1389
+ }
1390
+
1391
+ function renderExecutionsList(history) {
1392
+ const container = document.getElementById('executions-list-container');
1393
+ container.innerHTML = '';
1394
+ history.forEach(run => {
1395
+ const div = document.createElement('div');
1396
+ div.className = `item-card-row ${run.id === activeExecId ? 'active' : ''}`;
1397
+ div.dataset.id = run.id;
1398
+ div.onclick = () => selectExecution(run);
1399
+
1400
+ const timeStr = new Date(run.startTime).toLocaleTimeString();
1401
+ div.innerHTML = `
1402
+ <div>
1403
+ <div class="item-card-name">${run.filename}</div>
1404
+ <div class="item-card-desc">${timeStr} - ID: ${run.id.slice(0, 12)}</div>
1405
+ </div>
1406
+ <span class="badge ${run.status === 'completed' ? 'badge-green' : (run.status === 'failed' ? 'badge-danger' : 'badge-yellow')}">${run.status}</span>
1407
+ `;
1408
+ container.appendChild(div);
1409
+ });
1410
+ }
1411
+
1412
+ function selectExecution(run) {
1413
+ activeExecId = run.id;
1414
+ document.querySelectorAll('#executions-list-container .item-card-row').forEach(row => {
1415
+ if (row.dataset.id === run.id) {
1416
+ row.classList.add('active');
1417
+ } else {
1418
+ row.classList.remove('active');
1419
+ }
1420
+ });
1421
+ document.getElementById('exec-title').innerText = `Execution Playback: ${run.filename}`;
1422
+ document.getElementById('exec-meta').innerText = `Run ID: ${run.id} | Started: ${new Date(run.startTime).toLocaleString()} | Status: ${run.status.toUpperCase()}`;
1423
+
1424
+ if (executionPoll) clearInterval(executionPoll);
1425
+
1426
+ const wf = workflows.find(w => w.filename === run.filename);
1427
+
1428
+ const updateUI = (currentRunState) => {
1429
+ if (wf) {
1430
+ renderExecutionGraph(wf.data.steps, currentRunState.stepStatuses || {});
1431
+ }
1432
+ const logs = document.getElementById('exec-logs');
1433
+ let logText = `🎞️ Execution Trace [ID: ${currentRunState.id || run.id}]\n`;
1434
+ if (currentRunState.inputs?.task_prompt) {
1435
+ logText += `👉 Input Prompt: "${currentRunState.inputs.task_prompt}"\n\n`;
1436
+ }
1437
+
1438
+ if (wf && wf.data.steps) {
1439
+ wf.data.steps.forEach((step, idx) => {
1440
+ const status = (currentRunState.stepStatuses || {})[idx] || 'pending';
1441
+ const icon = status === 'success' ? '✅' : (status === 'failed' ? '❌' : (status === 'running' ? '⏳' : '💤'));
1442
+ logText += `${icon} [Step ${idx + 1}] ${step.name} - ${status.toUpperCase()}\n`;
1443
+ if (step.use) {
1444
+ logText += ` Executor: ${step.use}\n`;
1445
+ }
1446
+ if (currentRunState.errorHistory && currentRunState.errorHistory[idx]) {
1447
+ const errs = currentRunState.errorHistory[idx];
1448
+ errs.forEach((err, errIdx) => {
1449
+ logText += ` ⚠️ Validation Error (Attempt ${errIdx + 1}): ${err.trim()}\n`;
1450
+ });
1451
+ }
1452
+ if (currentRunState.rollbackHistory && currentRunState.rollbackHistory[idx]) {
1453
+ logText += ` 🔄 Rollback Count: ${currentRunState.rollbackHistory[idx]} times\n`;
1454
+ }
1455
+ logText += '\n';
1456
+ });
1457
+ }
1458
+
1459
+ if (currentRunState.status === 'completed') {
1460
+ logText += `🎉 Workflow Completed Successfully!\n`;
1461
+ } else if (currentRunState.status === 'failed') {
1462
+ logText += `🚨 Workflow Execution Failed.\n`;
1463
+ } else {
1464
+ logText += `⏳ Workflow execution in progress...\n`;
1465
+ }
1466
+ logs.innerText = logText;
1467
+ };
1468
+
1469
+ // Initial render
1470
+ updateUI(run);
1471
+
1472
+ if (run.status === 'in_progress') {
1473
+ executionPoll = setInterval(async () => {
1474
+ const response = await fetch(`/api/workflows/${run.filename}/status`);
1475
+ const data = await response.json();
1476
+
1477
+ // Merge metadata from original run record
1478
+ data.id = run.id;
1479
+ data.inputs = run.inputs;
1480
+
1481
+ updateUI(data);
1482
+
1483
+ if (data.status !== 'in_progress') {
1484
+ clearInterval(executionPoll);
1485
+ loadExecutionsTab(); // Refresh sidebar list
1486
+ }
1487
+ }, 1000);
1488
+ }
1489
+ }
1490
+
1491
+ function renderExecutionGraph(steps, statuses) {
1492
+ const container = document.getElementById('exec-steps-container');
1493
+ const svg = document.getElementById('exec-svg-connections');
1494
+ container.innerHTML = '';
1495
+ container.appendChild(svg);
1496
+
1497
+ steps.forEach((step, idx) => {
1498
+ const status = statuses[idx] || 'pending';
1499
+ const card = document.createElement('div');
1500
+ card.className = `flow-step-card ${status}`;
1501
+ card.innerHTML = `
1502
+ <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom: 8px;">
1503
+ <strong style="color:#ffffff;">${step.name}</strong>
1504
+ <span class="badge ${status === 'success' ? 'badge-green' : (status === 'failed' ? 'badge-danger' : 'badge-yellow')}">${status}</span>
1505
+ </div>
1506
+ <div style="font-size:12px; color:var(--text-muted);">Executor: <strong>${step.use || 'Command'}</strong></div>
1507
+ `;
1508
+ container.appendChild(card);
1509
+ });
1510
+
1511
+ setTimeout(drawExecGraphConnections, 50);
1512
+ }
1513
+
1514
+ function drawExecGraphConnections() {
1515
+ const container = document.getElementById('exec-steps-container');
1516
+ const svg = document.getElementById('exec-svg-connections');
1517
+ if (!svg) return;
1518
+ svg.innerHTML = `
1519
+ <defs>
1520
+ <marker id="arrow-exec" viewBox="0 0 10 10" refX="6" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
1521
+ <path d="M 0 1 L 10 5 L 0 9 z" fill="#6366f1" />
1522
+ </marker>
1523
+ </defs>
1524
+ `;
1525
+ svg.style.height = container.scrollHeight + 'px';
1526
+
1527
+ const cards = container.querySelectorAll('.flow-step-card');
1528
+ for (let i = 0; i < cards.length - 1; i++) {
1529
+ const c1 = cards[i];
1530
+ const c2 = cards[i+1];
1531
+
1532
+ const x1 = c1.offsetLeft + c1.offsetWidth / 2;
1533
+ const y1 = c1.offsetTop + c1.offsetHeight;
1534
+ const x2 = c2.offsetLeft + c2.offsetWidth / 2;
1535
+ const y2 = c2.offsetTop - 4;
1536
+
1537
+ const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
1538
+ line.setAttribute('x1', x1); line.setAttribute('y1', y1);
1539
+ line.setAttribute('x2', x2); line.setAttribute('y2', y2);
1540
+ line.setAttribute('stroke', '#6366f1');
1541
+ line.setAttribute('stroke-width', '2');
1542
+ line.setAttribute('marker-end', 'url(#arrow-exec)');
1543
+ svg.appendChild(line);
1544
+ }
1545
+ }
1546
+
1547
+
1548
+ // --- Workspace Drag lists builder ---
1549
+ async function loadWorkflowsTabAssets() {
1550
+ const agentsRes = await fetch('/api/agents');
1551
+ const agentsData = await agentsRes.json();
1552
+ const agentsList = document.getElementById('wf-draggable-agents');
1553
+ agentsList.innerHTML = '';
1554
+ agentsData.forEach(agent => {
1555
+ const name = agent.data?.name || agent.filename.replace('.yaml', '');
1556
+ agentsList.innerHTML += `
1557
+ <div class="draggable-item" draggable="true" data-type="sidebar-agent" data-payload="agent/${name}">
1558
+ <span>${name}</span>
1559
+ <span class="badge badge-purple">Agent</span>
1560
+ </div>
1561
+ `;
1562
+ });
1563
+
1564
+ const skillsRes = await fetch('/api/skills');
1565
+ const skillsData = await skillsRes.json();
1566
+ const skillsList = document.getElementById('wf-draggable-skills');
1567
+ skillsList.innerHTML = '';
1568
+ skillsData.forEach(s => {
1569
+ skillsList.innerHTML += `
1570
+ <div class="draggable-item" draggable="true" data-type="sidebar-skill" data-payload="${s.use}">
1571
+ <span>${s.name}</span>
1572
+ <span class="badge ${s.type === 'tool' ? 'badge-green' : 'badge-yellow'}">${s.type}</span>
1573
+ </div>
1574
+ `;
1575
+ });
1576
+
1577
+ // Bind drag starts
1578
+ document.querySelectorAll('.draggable-item').forEach(item => {
1579
+ item.addEventListener('dragstart', (e) => {
1580
+ e.dataTransfer.setData('drag-type', item.getAttribute('data-type'));
1581
+ e.dataTransfer.setData('drag-payload', item.getAttribute('data-payload'));
1582
+ });
1583
+ });
1584
+ }
1585
+
1586
+ // Window event listeners
1587
+ window.addEventListener('resize', () => {
1588
+ drawWfGraphConnections();
1589
+ drawExecGraphConnections();
1590
+ });
1591
+
1592
+ // Init
1593
+ async function init() {
1594
+ await loadWorkflows();
1595
+ await loadWorkflowsTabAssets();
1596
+ }
1597
+ init();
1598
+ </script>
1599
+ </body>
1600
+ </html>