@adiholick/nvstudio-mcp 2.2.2 → 2.3.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.
- package/README.md +43 -47
- package/agent-plugin/mcp_config.json +8 -0
- package/agent-plugin/plugin.json +6 -0
- package/agent-plugin/rules/AGENTS.md +87 -0
- package/agent-plugin/skills/nvstudio-assets-terrain-guide/SKILL.md +27 -0
- package/agent-plugin/skills/nvstudio-gui-standards/SKILL.md +419 -0
- package/agent-plugin/skills/nvstudio-mcp-guide/SKILL.md +69 -0
- package/agent-plugin/skills/nvstudio-roblox-sync-guide/SKILL.md +16 -0
- package/dist/build-plugin.js +55 -0
- package/dist/index.js +988 -146
- package/dist/luau-validator.js +99 -34
- package/dist/roblox-bridge.js +390 -72
- package/dist/roblox-docs.js +264 -0
- package/dist/task-queue.js +57 -6
- package/dist/ws-gateway.js +579 -0
- package/package.json +7 -3
- package/public/index.html +634 -109
- package/studio-plugin/Main.server.lua +484 -53
- package/studio-plugin/batch_create_instances.lua +128 -0
- package/studio-plugin/batch_set_properties.lua +69 -0
- package/studio-plugin/get_properties.lua +25 -2
- package/studio-plugin/insert_asset.lua +85 -12
- package/studio-plugin/nvstudio_mcp.rbxmx +1393 -68
- package/studio-plugin/preview_asset.lua +99 -0
- package/studio-plugin/scan_security.lua +81 -0
- package/studio-plugin/security_scanner.lua +346 -0
- package/studio-plugin/set_properties.lua +10 -0
- package/studio-plugin/ui_manager.lua +1 -1
package/public/index.html
CHANGED
|
@@ -3,33 +3,41 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8">
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>nvstudio-mcp | Bridge Dashboard</title>
|
|
6
|
+
<title>nvstudio-mcp v2.3 | Bridge Dashboard</title>
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
8
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=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
|
9
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
10
10
|
<style>
|
|
11
11
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
12
12
|
|
|
13
13
|
:root {
|
|
14
|
-
--bg: #
|
|
15
|
-
--surface: #
|
|
16
|
-
--surface-glass: rgba(
|
|
17
|
-
--surface-card: #
|
|
18
|
-
--surface-hover: #
|
|
14
|
+
--bg: #0c0c0e;
|
|
15
|
+
--surface: #121216;
|
|
16
|
+
--surface-glass: rgba(18, 18, 22, 0.75);
|
|
17
|
+
--surface-card: #18181d;
|
|
18
|
+
--surface-hover: #222228;
|
|
19
19
|
--border: rgba(255, 255, 255, 0.08);
|
|
20
|
-
--border-highlight: rgba(255, 255, 255, 0.
|
|
20
|
+
--border-highlight: rgba(255, 255, 255, 0.16);
|
|
21
21
|
|
|
22
|
-
--text: #
|
|
22
|
+
--text: #f4f4f6;
|
|
23
23
|
--text-muted: #a1a1aa;
|
|
24
24
|
--text-dim: #71717a;
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
--accent
|
|
26
|
+
/* NV-Series Accent Colors */
|
|
27
|
+
--accent: #00aaff;
|
|
28
|
+
--accent-glow: rgba(0, 170, 255, 0.25);
|
|
29
|
+
--cyan-glow: rgba(0, 255, 255, 0.2);
|
|
28
30
|
--success: #10b981;
|
|
29
31
|
--success-glow: rgba(16, 185, 129, 0.25);
|
|
30
32
|
--error: #ef4444;
|
|
31
33
|
--error-glow: rgba(239, 68, 68, 0.2);
|
|
32
|
-
--
|
|
34
|
+
--warning: #f59e0b;
|
|
35
|
+
--warning-glow: rgba(245, 158, 11, 0.25);
|
|
36
|
+
|
|
37
|
+
--task: #a855f7;
|
|
38
|
+
--batch: #00aaff;
|
|
39
|
+
--security: #f59e0b;
|
|
40
|
+
--docs: #10b981;
|
|
33
41
|
--response: #14b8a6;
|
|
34
42
|
--system: #a1a1aa;
|
|
35
43
|
|
|
@@ -50,7 +58,7 @@
|
|
|
50
58
|
.layout {
|
|
51
59
|
display: flex;
|
|
52
60
|
height: 100vh;
|
|
53
|
-
background: radial-gradient(circle at 50% 0%, rgba(
|
|
61
|
+
background: radial-gradient(circle at 50% 0%, rgba(24, 24, 32, 1) 0%, rgba(12, 12, 14, 1) 100%);
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
/* ── SIDEBAR ── */
|
|
@@ -58,7 +66,7 @@
|
|
|
58
66
|
width: var(--sidebar-w);
|
|
59
67
|
min-width: var(--sidebar-w);
|
|
60
68
|
background: var(--surface-glass);
|
|
61
|
-
backdrop-filter: blur(
|
|
69
|
+
backdrop-filter: blur(16px);
|
|
62
70
|
border-right: 1px solid var(--border);
|
|
63
71
|
display: flex;
|
|
64
72
|
flex-direction: column;
|
|
@@ -77,11 +85,11 @@
|
|
|
77
85
|
}
|
|
78
86
|
.sidebar-logo .logo-icon {
|
|
79
87
|
width: 38px; height: 38px;
|
|
80
|
-
background: linear-gradient(135deg, #
|
|
88
|
+
background: linear-gradient(135deg, #0088cc 0%, #00aaff 100%);
|
|
81
89
|
border-radius: 10px;
|
|
82
90
|
display: flex; align-items: center; justify-content: center;
|
|
83
91
|
flex-shrink: 0;
|
|
84
|
-
box-shadow: 0 4px
|
|
92
|
+
box-shadow: 0 4px 14px var(--accent-glow);
|
|
85
93
|
}
|
|
86
94
|
.sidebar-logo .logo-icon svg { color: #fff; width: 22px; height: 22px; }
|
|
87
95
|
|
|
@@ -108,7 +116,7 @@
|
|
|
108
116
|
display: flex;
|
|
109
117
|
align-items: center;
|
|
110
118
|
gap: 12px;
|
|
111
|
-
background: rgba(0, 0, 0, 0.
|
|
119
|
+
background: rgba(0, 0, 0, 0.25);
|
|
112
120
|
}
|
|
113
121
|
.status-indicator-ring {
|
|
114
122
|
position: relative;
|
|
@@ -176,7 +184,7 @@
|
|
|
176
184
|
color: var(--text);
|
|
177
185
|
}
|
|
178
186
|
.nav-item.active {
|
|
179
|
-
background: rgba(
|
|
187
|
+
background: rgba(0, 170, 255, 0.12);
|
|
180
188
|
color: var(--accent);
|
|
181
189
|
border-left: 2px solid var(--accent);
|
|
182
190
|
}
|
|
@@ -192,9 +200,9 @@
|
|
|
192
200
|
color: var(--text-dim);
|
|
193
201
|
}
|
|
194
202
|
.badge-v2 {
|
|
195
|
-
background: rgba(
|
|
203
|
+
background: rgba(0, 170, 255, 0.15);
|
|
196
204
|
color: var(--accent);
|
|
197
|
-
border: 1px solid rgba(
|
|
205
|
+
border: 1px solid rgba(0, 170, 255, 0.3);
|
|
198
206
|
padding: 3px 8px;
|
|
199
207
|
border-radius: 4px;
|
|
200
208
|
font-weight: 600;
|
|
@@ -218,8 +226,8 @@
|
|
|
218
226
|
display: flex;
|
|
219
227
|
align-items: center;
|
|
220
228
|
justify-content: space-between;
|
|
221
|
-
background: rgba(
|
|
222
|
-
backdrop-filter: blur(
|
|
229
|
+
background: rgba(12, 12, 14, 0.6);
|
|
230
|
+
backdrop-filter: blur(12px);
|
|
223
231
|
z-index: 5;
|
|
224
232
|
}
|
|
225
233
|
.page-title {
|
|
@@ -286,8 +294,10 @@
|
|
|
286
294
|
padding: 16px 32px;
|
|
287
295
|
background: var(--surface-glass);
|
|
288
296
|
border-bottom: 1px solid var(--border);
|
|
297
|
+
flex-wrap: wrap;
|
|
298
|
+
gap: 12px;
|
|
289
299
|
}
|
|
290
|
-
.filter-btns { display: flex; gap: 8px; }
|
|
300
|
+
.filter-btns { display: flex; gap: 8px; flex-wrap: wrap; }
|
|
291
301
|
.filter-btn {
|
|
292
302
|
background: var(--surface-card);
|
|
293
303
|
border: 1px solid var(--border);
|
|
@@ -324,7 +334,7 @@
|
|
|
324
334
|
display: flex;
|
|
325
335
|
flex-direction: column;
|
|
326
336
|
gap: 8px;
|
|
327
|
-
height: calc(100vh -
|
|
337
|
+
height: calc(100vh - 142px);
|
|
328
338
|
}
|
|
329
339
|
|
|
330
340
|
.log-empty {
|
|
@@ -350,6 +360,7 @@
|
|
|
350
360
|
font-size: 13px;
|
|
351
361
|
line-height: 1.5;
|
|
352
362
|
transition: transform 0.2s, background 0.2s;
|
|
363
|
+
align-items: flex-start;
|
|
353
364
|
}
|
|
354
365
|
.log-entry:hover {
|
|
355
366
|
background: rgba(255, 255, 255, 0.04);
|
|
@@ -360,26 +371,33 @@
|
|
|
360
371
|
font-size: 12px;
|
|
361
372
|
flex-shrink: 0;
|
|
362
373
|
white-space: nowrap;
|
|
374
|
+
margin-top: 1px;
|
|
363
375
|
}
|
|
364
376
|
.log-entry-badge {
|
|
365
377
|
font-size: 10px; font-weight: 700;
|
|
366
378
|
padding: 2px 8px; border-radius: 4px;
|
|
367
379
|
height: fit-content; flex-shrink: 0;
|
|
380
|
+
letter-spacing: 0.03em;
|
|
368
381
|
}
|
|
369
382
|
|
|
370
|
-
.badge-task { color: var(--task); background: rgba(
|
|
383
|
+
.badge-task { color: var(--task); background: rgba(168, 85, 247, 0.15); border: 1px solid rgba(168, 85, 247, 0.3); }
|
|
384
|
+
.badge-batch { color: var(--batch); background: rgba(0, 170, 255, 0.15); border: 1px solid rgba(0, 170, 255, 0.3); }
|
|
385
|
+
.badge-security { color: var(--security); background: rgba(245, 158, 11, 0.15); border: 1px solid rgba(245, 158, 11, 0.3); }
|
|
386
|
+
.badge-docs { color: var(--docs); background: rgba(16, 185, 129, 0.15); border: 1px solid rgba(16, 185, 129, 0.3); }
|
|
387
|
+
.badge-patch { color: #38bdf8; background: rgba(56, 189, 248, 0.15); border: 1px solid rgba(56, 189, 248, 0.3); }
|
|
388
|
+
.badge-luau { color: #c084fc; background: rgba(192, 132, 252, 0.15); border: 1px solid rgba(192, 132, 252, 0.3); }
|
|
371
389
|
.badge-response { color: var(--response); background: rgba(20, 184, 166, 0.15); border: 1px solid rgba(20, 184, 166, 0.3); }
|
|
372
390
|
.badge-error { color: var(--error); background: rgba(239, 68, 68, 0.15); border: 1px solid rgba(239, 68, 68, 0.3); }
|
|
373
391
|
.badge-system { color: var(--system); background: rgba(161, 161, 170, 0.15); border: 1px solid rgba(161, 161, 170, 0.3); }
|
|
374
392
|
|
|
375
|
-
.log-entry-msg { color: var(--text
|
|
393
|
+
.log-entry-msg { color: var(--text); word-break: break-word; flex: 1; }
|
|
376
394
|
|
|
377
|
-
/* ── STATS PAGE ── */
|
|
395
|
+
/* ── STATS & SESSIONS PAGE ── */
|
|
378
396
|
.stats-grid {
|
|
379
397
|
display: grid;
|
|
380
398
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
381
399
|
gap: 20px;
|
|
382
|
-
max-width:
|
|
400
|
+
max-width: 1040px;
|
|
383
401
|
}
|
|
384
402
|
.stat-card {
|
|
385
403
|
background: var(--surface-card);
|
|
@@ -410,11 +428,212 @@
|
|
|
410
428
|
.info-row:last-child { border-bottom: none; padding-bottom: 0; }
|
|
411
429
|
.info-row-label { font-size: 13px; color: var(--text-muted); }
|
|
412
430
|
.info-row-value { font-size: 14px; font-weight: 600; color: var(--text); font-family: 'JetBrains Mono', monospace; }
|
|
413
|
-
|
|
431
|
+
|
|
432
|
+
/* Studio Sessions Table */
|
|
433
|
+
.sessions-table {
|
|
434
|
+
width: 100%;
|
|
435
|
+
border-collapse: collapse;
|
|
436
|
+
margin-top: 16px;
|
|
437
|
+
font-size: 13px;
|
|
438
|
+
}
|
|
439
|
+
.sessions-table th {
|
|
440
|
+
text-align: left;
|
|
441
|
+
padding: 10px 14px;
|
|
442
|
+
color: var(--text-dim);
|
|
443
|
+
font-weight: 600;
|
|
444
|
+
border-bottom: 1px solid var(--border);
|
|
445
|
+
text-transform: uppercase;
|
|
446
|
+
font-size: 11px;
|
|
447
|
+
letter-spacing: 0.05em;
|
|
448
|
+
}
|
|
449
|
+
.sessions-table td {
|
|
450
|
+
padding: 12px 14px;
|
|
451
|
+
border-bottom: 1px solid rgba(255,255,255,0.04);
|
|
452
|
+
color: var(--text);
|
|
453
|
+
font-family: 'JetBrains Mono', monospace;
|
|
454
|
+
}
|
|
455
|
+
.sessions-table tr:hover td {
|
|
456
|
+
background: rgba(255,255,255,0.02);
|
|
457
|
+
}
|
|
458
|
+
.badge-pill {
|
|
459
|
+
display: inline-flex; align-items: center; gap: 6px;
|
|
460
|
+
font-size: 11px; padding: 2px 8px; border-radius: 12px; font-weight: 600;
|
|
461
|
+
}
|
|
462
|
+
.badge-ws { background: rgba(0, 170, 255, 0.15); color: var(--accent); border: 1px solid rgba(0, 170, 255, 0.3); }
|
|
463
|
+
.badge-poll { background: rgba(245, 158, 11, 0.15); color: var(--warning); border: 1px solid rgba(245, 158, 11, 0.3); }
|
|
464
|
+
|
|
465
|
+
/* ── DOCS EXPLORER PAGE ── */
|
|
466
|
+
.docs-container {
|
|
467
|
+
max-width: 900px;
|
|
468
|
+
display: flex;
|
|
469
|
+
flex-direction: column;
|
|
470
|
+
gap: 20px;
|
|
471
|
+
}
|
|
472
|
+
.docs-search-card {
|
|
473
|
+
background: var(--surface-card);
|
|
474
|
+
border: 1px solid var(--border);
|
|
475
|
+
border-radius: 12px;
|
|
476
|
+
padding: 24px;
|
|
477
|
+
display: flex;
|
|
478
|
+
flex-direction: column;
|
|
479
|
+
gap: 16px;
|
|
480
|
+
}
|
|
481
|
+
.docs-form {
|
|
482
|
+
display: flex;
|
|
483
|
+
gap: 12px;
|
|
484
|
+
flex-wrap: wrap;
|
|
485
|
+
}
|
|
486
|
+
.docs-input {
|
|
487
|
+
flex: 2;
|
|
488
|
+
min-width: 200px;
|
|
489
|
+
background: #0a0a0c;
|
|
490
|
+
border: 1px solid var(--border);
|
|
491
|
+
color: var(--text);
|
|
492
|
+
padding: 10px 14px;
|
|
493
|
+
border-radius: 8px;
|
|
494
|
+
font-family: inherit;
|
|
495
|
+
font-size: 14px;
|
|
496
|
+
outline: none;
|
|
497
|
+
transition: border-color 0.2s;
|
|
498
|
+
}
|
|
499
|
+
.docs-input:focus {
|
|
500
|
+
border-color: var(--accent);
|
|
501
|
+
box-shadow: 0 0 10px var(--accent-glow);
|
|
502
|
+
}
|
|
503
|
+
.docs-member-input {
|
|
504
|
+
flex: 1;
|
|
505
|
+
min-width: 140px;
|
|
506
|
+
background: #0a0a0c;
|
|
507
|
+
border: 1px solid var(--border);
|
|
508
|
+
color: var(--text);
|
|
509
|
+
padding: 10px 14px;
|
|
510
|
+
border-radius: 8px;
|
|
511
|
+
font-family: inherit;
|
|
512
|
+
font-size: 14px;
|
|
513
|
+
outline: none;
|
|
514
|
+
}
|
|
515
|
+
.docs-member-input:focus { border-color: var(--accent); }
|
|
516
|
+
.docs-btn {
|
|
517
|
+
background: linear-gradient(135deg, #0088cc 0%, #00aaff 100%);
|
|
518
|
+
color: #fff;
|
|
519
|
+
border: none;
|
|
520
|
+
padding: 10px 22px;
|
|
521
|
+
border-radius: 8px;
|
|
522
|
+
font-family: inherit;
|
|
523
|
+
font-size: 14px;
|
|
524
|
+
font-weight: 600;
|
|
525
|
+
cursor: pointer;
|
|
526
|
+
transition: transform 0.15s, box-shadow 0.2s;
|
|
527
|
+
display: flex; align-items: center; gap: 8px;
|
|
528
|
+
}
|
|
529
|
+
.docs-btn:hover {
|
|
530
|
+
transform: translateY(-1px);
|
|
531
|
+
box-shadow: 0 4px 14px var(--accent-glow);
|
|
532
|
+
}
|
|
533
|
+
.docs-chips {
|
|
534
|
+
display: flex;
|
|
535
|
+
gap: 8px;
|
|
536
|
+
flex-wrap: wrap;
|
|
537
|
+
align-items: center;
|
|
538
|
+
}
|
|
539
|
+
.chip-label { font-size: 12px; color: var(--text-dim); }
|
|
540
|
+
.doc-chip {
|
|
541
|
+
font-size: 12px;
|
|
542
|
+
background: rgba(255,255,255,0.04);
|
|
543
|
+
border: 1px solid var(--border);
|
|
544
|
+
color: var(--text-muted);
|
|
545
|
+
padding: 4px 10px;
|
|
546
|
+
border-radius: 6px;
|
|
547
|
+
cursor: pointer;
|
|
548
|
+
transition: all 0.2s;
|
|
549
|
+
font-family: 'JetBrains Mono', monospace;
|
|
550
|
+
}
|
|
551
|
+
.doc-chip:hover {
|
|
552
|
+
background: rgba(0, 170, 255, 0.15);
|
|
553
|
+
color: var(--accent);
|
|
554
|
+
border-color: var(--accent);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.docs-result-card {
|
|
558
|
+
background: var(--surface-card);
|
|
559
|
+
border: 1px solid var(--border);
|
|
560
|
+
border-radius: 12px;
|
|
561
|
+
padding: 28px;
|
|
562
|
+
min-height: 200px;
|
|
563
|
+
display: flex;
|
|
564
|
+
flex-direction: column;
|
|
565
|
+
position: relative;
|
|
566
|
+
}
|
|
567
|
+
.docs-result-header {
|
|
568
|
+
display: flex;
|
|
569
|
+
justify-content: space-between;
|
|
570
|
+
align-items: center;
|
|
571
|
+
margin-bottom: 16px;
|
|
572
|
+
padding-bottom: 12px;
|
|
573
|
+
border-bottom: 1px solid var(--border);
|
|
574
|
+
}
|
|
575
|
+
.docs-title {
|
|
576
|
+
font-size: 16px;
|
|
577
|
+
font-weight: 600;
|
|
578
|
+
color: var(--text);
|
|
579
|
+
display: flex; align-items: center; gap: 8px;
|
|
580
|
+
}
|
|
581
|
+
.docs-copy-btn {
|
|
582
|
+
background: transparent;
|
|
583
|
+
border: 1px solid var(--border);
|
|
584
|
+
color: var(--text-muted);
|
|
585
|
+
font-size: 12px;
|
|
586
|
+
padding: 4px 10px;
|
|
587
|
+
border-radius: 6px;
|
|
588
|
+
cursor: pointer;
|
|
589
|
+
transition: all 0.2s;
|
|
590
|
+
}
|
|
591
|
+
.docs-copy-btn:hover {
|
|
592
|
+
background: var(--surface-hover);
|
|
593
|
+
color: var(--text);
|
|
594
|
+
}
|
|
595
|
+
.docs-content {
|
|
596
|
+
font-family: 'Inter', sans-serif;
|
|
597
|
+
font-size: 14px;
|
|
598
|
+
line-height: 1.7;
|
|
599
|
+
color: var(--text);
|
|
600
|
+
overflow-x: auto;
|
|
601
|
+
}
|
|
602
|
+
.docs-content pre {
|
|
603
|
+
background: #09090b;
|
|
604
|
+
padding: 14px 18px;
|
|
605
|
+
border-radius: 8px;
|
|
606
|
+
border: 1px solid var(--border);
|
|
607
|
+
margin: 14px 0;
|
|
608
|
+
overflow-x: auto;
|
|
609
|
+
font-family: 'JetBrains Mono', monospace;
|
|
610
|
+
font-size: 13px;
|
|
611
|
+
color: #38bdf8;
|
|
612
|
+
}
|
|
613
|
+
.docs-content code {
|
|
614
|
+
font-family: 'JetBrains Mono', monospace;
|
|
615
|
+
background: rgba(255,255,255,0.06);
|
|
616
|
+
color: var(--accent);
|
|
617
|
+
padding: 2px 6px;
|
|
618
|
+
border-radius: 4px;
|
|
619
|
+
font-size: 12px;
|
|
620
|
+
}
|
|
621
|
+
.docs-content h1, .docs-content h2, .docs-content h3 {
|
|
622
|
+
margin: 18px 0 8px;
|
|
623
|
+
color: #fff;
|
|
624
|
+
}
|
|
625
|
+
.docs-content ul {
|
|
626
|
+
padding-left: 20px;
|
|
627
|
+
margin: 8px 0;
|
|
628
|
+
}
|
|
629
|
+
.docs-content li {
|
|
630
|
+
margin-bottom: 4px;
|
|
631
|
+
}
|
|
632
|
+
|
|
414
633
|
/* ── ABOUT PAGE ── */
|
|
415
|
-
.about-section { max-width:
|
|
634
|
+
.about-section { max-width: 720px; }
|
|
416
635
|
.about-hero {
|
|
417
|
-
background: linear-gradient(145deg, var(--surface-card) 0%, rgba(
|
|
636
|
+
background: linear-gradient(145deg, var(--surface-card) 0%, rgba(14,14,18,1) 100%);
|
|
418
637
|
border: 1px solid var(--border);
|
|
419
638
|
border-radius: 16px;
|
|
420
639
|
padding: 40px;
|
|
@@ -431,7 +650,7 @@
|
|
|
431
650
|
pointer-events: none;
|
|
432
651
|
}
|
|
433
652
|
.about-hero h2 { font-size: 28px; font-weight: 700; color: var(--text); letter-spacing: -0.02em; display: flex; align-items: center; gap: 12px;}
|
|
434
|
-
.v2-chip { font-size: 12px; background: var(--accent); color: white; padding: 2px 8px; border-radius: 12px; vertical-align: middle;}
|
|
653
|
+
.v2-chip { font-size: 12px; background: var(--accent); color: white; padding: 2px 8px; border-radius: 12px; vertical-align: middle; font-weight: 700;}
|
|
435
654
|
.about-hero p { font-size: 15px; color: var(--text-muted); line-height: 1.6; }
|
|
436
655
|
|
|
437
656
|
.about-links { display: flex; gap: 12px; margin-top: 8px; }
|
|
@@ -468,8 +687,8 @@
|
|
|
468
687
|
/* Scrollbar */
|
|
469
688
|
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
470
689
|
::-webkit-scrollbar-track { background: transparent; }
|
|
471
|
-
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.
|
|
472
|
-
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.
|
|
690
|
+
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 3px; }
|
|
691
|
+
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
|
|
473
692
|
|
|
474
693
|
/* Transitions */
|
|
475
694
|
.log-entry { animation: slideIn 0.3s cubic-bezier(0.16, 1, 0.3, 1); }
|
|
@@ -490,7 +709,7 @@
|
|
|
490
709
|
</div>
|
|
491
710
|
<div class="logo-text-wrapper">
|
|
492
711
|
<div class="logo-name">nvstudio-mcp</div>
|
|
493
|
-
<div class="logo-sub">Zero-Delay
|
|
712
|
+
<div class="logo-sub">Zero-Delay Bridge</div>
|
|
494
713
|
</div>
|
|
495
714
|
</div>
|
|
496
715
|
</div>
|
|
@@ -511,19 +730,25 @@
|
|
|
511
730
|
</div>
|
|
512
731
|
<div class="nav-item" data-page="stats">
|
|
513
732
|
<svg viewBox="0 0 24 24"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/></svg>
|
|
514
|
-
|
|
733
|
+
Diagnostics & Sessions
|
|
734
|
+
</div>
|
|
735
|
+
|
|
736
|
+
<div class="nav-group-label" style="margin-top:20px;">Engine Tools</div>
|
|
737
|
+
<div class="nav-item" data-page="docs">
|
|
738
|
+
<svg viewBox="0 0 24 24"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>
|
|
739
|
+
Docs Explorer
|
|
515
740
|
</div>
|
|
516
741
|
|
|
517
742
|
<div class="nav-group-label" style="margin-top:20px;">Info</div>
|
|
518
743
|
<div class="nav-item" data-page="about">
|
|
519
744
|
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>
|
|
520
|
-
About v2.
|
|
745
|
+
About v2.3
|
|
521
746
|
</div>
|
|
522
747
|
</nav>
|
|
523
748
|
|
|
524
749
|
<div class="sidebar-footer">
|
|
525
|
-
<span class="badge-v2" id="pkg-version">v2.0
|
|
526
|
-
<span style="font-size:10px; opacity:0.
|
|
750
|
+
<span class="badge-v2" id="pkg-version">v2.3.0</span>
|
|
751
|
+
<span style="font-size:10px; opacity:0.6;">Model Context Protocol</span>
|
|
527
752
|
</div>
|
|
528
753
|
</aside>
|
|
529
754
|
|
|
@@ -544,9 +769,11 @@
|
|
|
544
769
|
<div class="log-toolbar">
|
|
545
770
|
<div class="filter-btns">
|
|
546
771
|
<button class="filter-btn active" data-filter="all">All Logs</button>
|
|
547
|
-
<button class="filter-btn" data-filter="task">Task /
|
|
772
|
+
<button class="filter-btn" data-filter="task">Task / Command</button>
|
|
773
|
+
<button class="filter-btn" data-filter="batch">Batch Builder</button>
|
|
774
|
+
<button class="filter-btn" data-filter="security">Security / Scan</button>
|
|
548
775
|
<button class="filter-btn" data-filter="response">Response</button>
|
|
549
|
-
<button class="filter-btn" data-filter="error">
|
|
776
|
+
<button class="filter-btn" data-filter="error">Errors</button>
|
|
550
777
|
<button class="filter-btn" data-filter="system">System</button>
|
|
551
778
|
</div>
|
|
552
779
|
<button class="scroll-lock-btn locked" id="scroll-lock-btn">
|
|
@@ -559,12 +786,12 @@
|
|
|
559
786
|
<div class="log-list" id="log-list">
|
|
560
787
|
<div class="log-empty" id="log-empty">
|
|
561
788
|
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 8v4"/><path d="M12 16h.01"/></svg>
|
|
562
|
-
<p>Menunggu aktivitas dari Roblox Studio...</p>
|
|
789
|
+
<p>Menunggu aktivitas perintah dari AI / Roblox Studio...</p>
|
|
563
790
|
</div>
|
|
564
791
|
</div>
|
|
565
792
|
</div>
|
|
566
793
|
|
|
567
|
-
<!--
|
|
794
|
+
<!-- Diagnostics & Sessions Page -->
|
|
568
795
|
<div class="page" id="page-stats">
|
|
569
796
|
<div class="stats-grid">
|
|
570
797
|
<div class="stat-card">
|
|
@@ -592,23 +819,50 @@
|
|
|
592
819
|
<div class="progress-bar error" id="bar-error" style="width:0%"></div>
|
|
593
820
|
</div>
|
|
594
821
|
</div>
|
|
822
|
+
|
|
823
|
+
<!-- Active Connected Studios -->
|
|
824
|
+
<div class="stat-card full">
|
|
825
|
+
<div class="stat-card-label" style="display:flex; justify-content:space-between; align-items:center;">
|
|
826
|
+
<span>🎮 Active Studio Sessions</span>
|
|
827
|
+
<span id="studios-count-badge" class="badge-v2">0 Terhubung</span>
|
|
828
|
+
</div>
|
|
829
|
+
<div id="studios-container" style="margin-top:12px;">
|
|
830
|
+
<table class="sessions-table" id="sessions-table" style="display:none;">
|
|
831
|
+
<thead>
|
|
832
|
+
<tr>
|
|
833
|
+
<th>Studio ID</th>
|
|
834
|
+
<th>Place ID / Name</th>
|
|
835
|
+
<th>Transport</th>
|
|
836
|
+
<th>Status</th>
|
|
837
|
+
<th>Last Ping</th>
|
|
838
|
+
</tr>
|
|
839
|
+
</thead>
|
|
840
|
+
<tbody id="sessions-tbody"></tbody>
|
|
841
|
+
</table>
|
|
842
|
+
<div id="sessions-empty" style="padding:24px; text-align:center; color:var(--text-dim);">
|
|
843
|
+
Tidak ada sesi Studio aktif saat ini. Buka game di Roblox Studio dan pastikan plugin <code>nvstudio_mcp</code> aktif.
|
|
844
|
+
</div>
|
|
845
|
+
</div>
|
|
846
|
+
</div>
|
|
847
|
+
|
|
848
|
+
<!-- Diagnostics Summary -->
|
|
595
849
|
<div class="stat-card full">
|
|
596
850
|
<div class="stat-card-label">Session Diagnostics</div>
|
|
597
851
|
<div class="info-list">
|
|
598
852
|
<div class="info-row">
|
|
599
|
-
<span class="info-row-label">Mode
|
|
600
|
-
<span class="info-row-value" style="color:var(--accent);">
|
|
853
|
+
<span class="info-row-label">Transport Mode</span>
|
|
854
|
+
<span class="info-row-value" id="info-transport" style="color:var(--accent);">Dual-Mode (WebSocket ⚡ + Long-Poll)</span>
|
|
601
855
|
</div>
|
|
602
856
|
<div class="info-row">
|
|
603
857
|
<span class="info-row-label">Luau Pre-Validation</span>
|
|
604
|
-
<span class="info-row-value" style="color:var(--success);">Active ✓</span>
|
|
858
|
+
<span class="info-row-value" style="color:var(--success);">Active (AST-Protected) ✓</span>
|
|
605
859
|
</div>
|
|
606
860
|
<div class="info-row">
|
|
607
861
|
<span class="info-row-label">Port Server</span>
|
|
608
862
|
<span class="info-row-value">3055</span>
|
|
609
863
|
</div>
|
|
610
864
|
<div class="info-row">
|
|
611
|
-
<span class="info-row-label">Studio
|
|
865
|
+
<span class="info-row-label">Overall Studio Status</span>
|
|
612
866
|
<span class="info-row-value" id="info-studio">Disconnected</span>
|
|
613
867
|
</div>
|
|
614
868
|
<div class="info-row">
|
|
@@ -620,16 +874,59 @@
|
|
|
620
874
|
</div>
|
|
621
875
|
</div>
|
|
622
876
|
|
|
877
|
+
<!-- Docs Explorer Page -->
|
|
878
|
+
<div class="page" id="page-docs">
|
|
879
|
+
<div class="docs-container">
|
|
880
|
+
<div class="docs-search-card">
|
|
881
|
+
<div style="font-size:16px; font-weight:600; color:#fff; display:flex; align-items:center; gap:8px;">
|
|
882
|
+
<span>📚 Roblox Creator Docs Lookup</span>
|
|
883
|
+
<span class="badge-v2">Official Engine API</span>
|
|
884
|
+
</div>
|
|
885
|
+
<p style="font-size:13px; color:var(--text-muted);">
|
|
886
|
+
Cari dokumentasi resmi Class, Service, Datatype, Enum, atau Library Roblox langsung dari jembatan lokal (dilengkapi cache disk ~2 ms).
|
|
887
|
+
</p>
|
|
888
|
+
<form class="docs-form" id="docs-form">
|
|
889
|
+
<input type="text" class="docs-input" id="docs-query" placeholder="Nama Class / Service (contoh: TweenService, CanvasGroup, UDim2, task)" required />
|
|
890
|
+
<input type="text" class="docs-member-input" id="docs-member" placeholder="Member opsional (misal: Create)" />
|
|
891
|
+
<button type="submit" class="docs-btn" id="docs-btn">
|
|
892
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
893
|
+
Cari Docs
|
|
894
|
+
</button>
|
|
895
|
+
</form>
|
|
896
|
+
<div class="docs-chips">
|
|
897
|
+
<span class="chip-label">Pencarian Cepat:</span>
|
|
898
|
+
<span class="doc-chip" data-q="TweenService">TweenService</span>
|
|
899
|
+
<span class="doc-chip" data-q="CanvasGroup">CanvasGroup</span>
|
|
900
|
+
<span class="doc-chip" data-q="Frame">Frame</span>
|
|
901
|
+
<span class="doc-chip" data-q="UDim2">UDim2</span>
|
|
902
|
+
<span class="doc-chip" data-q="task">task</span>
|
|
903
|
+
<span class="doc-chip" data-q="UserInputService">UserInputService</span>
|
|
904
|
+
<span class="doc-chip" data-q="PathfindingService">PathfindingService</span>
|
|
905
|
+
</div>
|
|
906
|
+
</div>
|
|
907
|
+
|
|
908
|
+
<div class="docs-result-card" id="docs-result-card">
|
|
909
|
+
<div class="docs-result-header">
|
|
910
|
+
<span class="docs-title" id="docs-result-title">Hasil Dokumentasi</span>
|
|
911
|
+
<button class="docs-copy-btn" id="docs-copy-btn" style="display:none;">Salin Markdown</button>
|
|
912
|
+
</div>
|
|
913
|
+
<div class="docs-content" id="docs-content">
|
|
914
|
+
<span style="color:var(--text-dim); font-size:13px;">Ketik kata kunci class di atas atau klik salah satu chip pencarian cepat untuk menampilkan dokumentasi resmi Roblox Engine.</span>
|
|
915
|
+
</div>
|
|
916
|
+
</div>
|
|
917
|
+
</div>
|
|
918
|
+
</div>
|
|
919
|
+
|
|
623
920
|
<!-- About Page -->
|
|
624
921
|
<div class="page" id="page-about">
|
|
625
922
|
<div class="about-section">
|
|
626
923
|
<div class="about-hero">
|
|
627
|
-
<h2>nvstudio-mcp <span class="v2-chip">v2.
|
|
628
|
-
<p>Jembatan pintar berbasis <strong>Model Context Protocol (MCP)</strong> yang menghubungkan AI Agent (Antigravity IDE, Cursor, Claude) secara langsung ke dalam <strong>Roblox Studio</strong>.</p>
|
|
924
|
+
<h2>nvstudio-mcp <span class="v2-chip" id="about-version-chip">v2.3.0</span></h2>
|
|
925
|
+
<p>Jembatan pintar berbasis <strong>Model Context Protocol (MCP)</strong> yang menghubungkan AI Agent (Antigravity IDE, Cursor, Claude Desktop, Windsurf) secara langsung ke dalam <strong>Roblox Studio</strong>.</p>
|
|
629
926
|
<div class="about-links">
|
|
630
927
|
<a class="about-link" href="https://github.com/Adiholick/nvstudio-mcp" target="_blank">
|
|
631
928
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
|
|
632
|
-
GitHub
|
|
929
|
+
GitHub Repository
|
|
633
930
|
</a>
|
|
634
931
|
<a class="about-link" href="https://www.npmjs.com/package/@adiholick/nvstudio-mcp" target="_blank">
|
|
635
932
|
<svg width="15" height="15" viewBox="0 0 24 24" fill="currentColor"><path d="M0 0h24v24H0V0zm7.315 5.876H2.17v12.248H12v-9.6h-4.685V5.876zm9.515 0h-5.145v12.248h5.145V5.876zm-7.315 4.8H9.87v4.8H9.515v-4.8z"/></svg>
|
|
@@ -639,22 +936,26 @@
|
|
|
639
936
|
</div>
|
|
640
937
|
|
|
641
938
|
<div class="about-card">
|
|
642
|
-
<h3
|
|
939
|
+
<h3>🚀 Fitur Baru v2.3.0</h3>
|
|
643
940
|
<ul>
|
|
644
|
-
<li><strong
|
|
645
|
-
<li><strong
|
|
646
|
-
<li><strong
|
|
941
|
+
<li><strong>⚡ Batch Instance Builder:</strong> Membangun pohon hierarki GUI puluhan tingkat dalam 1 kali roundtrip via <code>batch_create_instances</code> dan update massal via <code>batch_set_properties</code>.</li>
|
|
942
|
+
<li><strong>🛡️ Asset Security Scanner:</strong> Memindai dan memusnahkan 10 jenis ancaman backdoor/malware pada aset sebelum dimasukkan ke game via <code>preview_asset</code> dan <code>scan_security</code>.</li>
|
|
943
|
+
<li><strong>📚 Live Creator Docs Lookup:</strong> Mengambil dokumentasi engine resmi dari Roblox Creator Docs secara offline/online dengan disk cache berkecepatan ~2 ms via <code>get_roblox_docs</code>.</li>
|
|
944
|
+
<li><strong>🎨 NV-Series GUI Standard Taste:</strong> Standardisasi tata letak Frame, LayoutOrder teratur, Spacer margin ScrollingFrame, dan deteksi perangkat responsif pintar (PC, Phone, Tablet, Console).</li>
|
|
647
945
|
</ul>
|
|
648
946
|
</div>
|
|
649
947
|
|
|
650
948
|
<div class="about-card">
|
|
651
|
-
<h3>🛠️
|
|
949
|
+
<h3>🛠️ Daftar Semantic MCP Tools Lengkap</h3>
|
|
652
950
|
<ul>
|
|
653
|
-
<li><code>
|
|
654
|
-
<li><code>
|
|
655
|
-
<li><code>
|
|
656
|
-
<li><code>
|
|
657
|
-
<li><code>
|
|
951
|
+
<li><code>batch_create_instances</code> — Pembuatan objek/hierarki massal pohon bersarang.</li>
|
|
952
|
+
<li><code>batch_set_properties</code> — Pembaruan properti multi-instance secara atomik.</li>
|
|
953
|
+
<li><code>get_roblox_docs</code> — Lookup dokumentasi resmi engine Roblox (Class, Datatype, Service, Enum).</li>
|
|
954
|
+
<li><code>preview_asset</code> / <code>scan_security</code> — Audit keamanan backdoor & sanitasi script model.</li>
|
|
955
|
+
<li><code>patch_script_source</code> — Mengedit bagian baris kode tertentu secara parsial.</li>
|
|
956
|
+
<li><code>execute_luau</code> — Eksekusi sembarang kode Luau dinamis di Studio.</li>
|
|
957
|
+
<li><code>get_full_tree</code> / <code>get_children</code> — Eksplorasi hierarki DataModel.</li>
|
|
958
|
+
<li><code>insert_asset</code> / <code>generate_terrain</code> — Memasukkan model atau membuat terrain voxel.</li>
|
|
658
959
|
</ul>
|
|
659
960
|
</div>
|
|
660
961
|
</div>
|
|
@@ -671,19 +972,27 @@
|
|
|
671
972
|
let activeFilter = 'all';
|
|
672
973
|
let autoScroll = true;
|
|
673
974
|
let sessionStartTime = null;
|
|
975
|
+
let latestDocsMarkdown = '';
|
|
674
976
|
|
|
675
977
|
const logList = document.getElementById('log-list');
|
|
676
978
|
const logEmpty = document.getElementById('log-empty');
|
|
677
979
|
const logCount = document.getElementById('log-count');
|
|
678
980
|
const statusDot = document.getElementById('status-dot');
|
|
679
981
|
const statusLabel = document.getElementById('status-label');
|
|
982
|
+
const pkgVersion = document.getElementById('pkg-version');
|
|
983
|
+
const aboutVersionChip = document.getElementById('about-version-chip');
|
|
680
984
|
|
|
681
985
|
/* ── Navigation ── */
|
|
682
986
|
const navItems = document.querySelectorAll('.nav-item');
|
|
683
987
|
const pages = document.querySelectorAll('.page');
|
|
684
988
|
const topbarTitle = document.getElementById('topbar-title');
|
|
685
989
|
const clearBtn = document.getElementById('clear-btn');
|
|
686
|
-
const pageTitles = {
|
|
990
|
+
const pageTitles = {
|
|
991
|
+
activity: 'Activity Log',
|
|
992
|
+
stats: 'Session Diagnostics & Studios',
|
|
993
|
+
docs: 'Roblox Docs Explorer',
|
|
994
|
+
about: 'About v2.3'
|
|
995
|
+
};
|
|
687
996
|
|
|
688
997
|
navItems.forEach(item => {
|
|
689
998
|
item.addEventListener('click', () => {
|
|
@@ -691,7 +1000,8 @@
|
|
|
691
1000
|
navItems.forEach(n => n.classList.remove('active'));
|
|
692
1001
|
pages.forEach(p => p.classList.remove('active'));
|
|
693
1002
|
item.classList.add('active');
|
|
694
|
-
document.getElementById('page-' + page)
|
|
1003
|
+
const targetPage = document.getElementById('page-' + page);
|
|
1004
|
+
if (targetPage) targetPage.classList.add('active');
|
|
695
1005
|
topbarTitle.textContent = pageTitles[page] || page;
|
|
696
1006
|
|
|
697
1007
|
const isActivity = page === 'activity';
|
|
@@ -700,7 +1010,7 @@
|
|
|
700
1010
|
});
|
|
701
1011
|
});
|
|
702
1012
|
|
|
703
|
-
/* ── Clear ── */
|
|
1013
|
+
/* ── Clear Logs ── */
|
|
704
1014
|
clearBtn.addEventListener('click', () => {
|
|
705
1015
|
allLogs = [];
|
|
706
1016
|
renderLogs();
|
|
@@ -723,47 +1033,50 @@
|
|
|
723
1033
|
scrollLockBtn.classList.toggle('locked', autoScroll);
|
|
724
1034
|
});
|
|
725
1035
|
|
|
726
|
-
/* ──
|
|
727
|
-
function
|
|
728
|
-
const
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
if (
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
}
|
|
736
|
-
logEmpty.style.display = 'none';
|
|
737
|
-
|
|
738
|
-
const frag = document.createDocumentFragment();
|
|
739
|
-
filtered.forEach(log => {
|
|
740
|
-
const el = createLogEl(log);
|
|
741
|
-
frag.appendChild(el);
|
|
742
|
-
});
|
|
743
|
-
logList.innerHTML = '';
|
|
744
|
-
logList.appendChild(logEmpty);
|
|
745
|
-
logEmpty.style.display = 'none';
|
|
746
|
-
logList.appendChild(frag);
|
|
747
|
-
|
|
748
|
-
if (autoScroll) logList.scrollTop = 0;
|
|
1036
|
+
/* ── Categorize Log ── */
|
|
1037
|
+
function getLogCategory(log) {
|
|
1038
|
+
const msg = log.message || '';
|
|
1039
|
+
if (msg.includes('batch_create') || msg.includes('batch_set')) return 'batch';
|
|
1040
|
+
if (msg.includes('preview_asset') || msg.includes('scan_security') || msg.includes('security') || msg.includes('strip_scripts')) return 'security';
|
|
1041
|
+
if (msg.includes('get_roblox_docs')) return 'docs';
|
|
1042
|
+
if (msg.includes('patch_script_source')) return 'patch';
|
|
1043
|
+
if (msg.includes('execute_luau')) return 'luau';
|
|
1044
|
+
return log.type;
|
|
749
1045
|
}
|
|
750
1046
|
|
|
1047
|
+
/* ── Create Log Element ── */
|
|
751
1048
|
function createLogEl(log) {
|
|
752
1049
|
const el = document.createElement('div');
|
|
753
1050
|
el.className = 'log-entry';
|
|
754
|
-
|
|
1051
|
+
const cat = getLogCategory(log);
|
|
1052
|
+
el.dataset.category = cat;
|
|
755
1053
|
|
|
756
1054
|
let displayType = log.type.toUpperCase();
|
|
757
|
-
|
|
758
|
-
|
|
1055
|
+
let badgeClass = 'badge-' + log.type;
|
|
1056
|
+
|
|
1057
|
+
if (cat === 'batch') {
|
|
1058
|
+
displayType = 'BATCH';
|
|
1059
|
+
badgeClass = 'badge-batch';
|
|
1060
|
+
} else if (cat === 'security') {
|
|
1061
|
+
displayType = 'SECURITY';
|
|
1062
|
+
badgeClass = 'badge-security';
|
|
1063
|
+
} else if (cat === 'docs') {
|
|
1064
|
+
displayType = 'DOCS';
|
|
1065
|
+
badgeClass = 'badge-docs';
|
|
1066
|
+
} else if (cat === 'patch') {
|
|
759
1067
|
displayType = 'PATCH';
|
|
1068
|
+
badgeClass = 'badge-patch';
|
|
1069
|
+
} else if (cat === 'luau') {
|
|
1070
|
+
displayType = 'LUAU';
|
|
1071
|
+
badgeClass = 'badge-luau';
|
|
760
1072
|
} else if (log.type === 'error' && log.message.includes('Syntax Error')) {
|
|
761
1073
|
displayType = 'SYNTAX';
|
|
1074
|
+
badgeClass = 'badge-error';
|
|
762
1075
|
}
|
|
763
1076
|
|
|
764
1077
|
el.innerHTML = `
|
|
765
1078
|
<span class="log-entry-time">${log.time}</span>
|
|
766
|
-
<span class="log-entry-badge
|
|
1079
|
+
<span class="log-entry-badge ${badgeClass}">${displayType}</span>
|
|
767
1080
|
<span class="log-entry-msg">${escapeHtml(log.message)}</span>
|
|
768
1081
|
`;
|
|
769
1082
|
return el;
|
|
@@ -773,12 +1086,49 @@
|
|
|
773
1086
|
return String(str).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
|
774
1087
|
}
|
|
775
1088
|
|
|
1089
|
+
function shouldShowLog(log) {
|
|
1090
|
+
if (activeFilter === 'all') return true;
|
|
1091
|
+
const cat = getLogCategory(log);
|
|
1092
|
+
if (activeFilter === 'batch') return cat === 'batch';
|
|
1093
|
+
if (activeFilter === 'security') return cat === 'security';
|
|
1094
|
+
if (activeFilter === 'task') return log.type === 'task';
|
|
1095
|
+
if (activeFilter === 'response') return log.type === 'response';
|
|
1096
|
+
if (activeFilter === 'error') return log.type === 'error';
|
|
1097
|
+
if (activeFilter === 'system') return log.type === 'system';
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/* ── Render Logs ── */
|
|
1102
|
+
function renderLogs() {
|
|
1103
|
+
const filtered = allLogs.filter(shouldShowLog);
|
|
1104
|
+
logCount.textContent = `${allLogs.length} event${allLogs.length !== 1 ? 's' : ''}`;
|
|
1105
|
+
|
|
1106
|
+
if (filtered.length === 0) {
|
|
1107
|
+
logEmpty.style.display = 'flex';
|
|
1108
|
+
[...logList.children].forEach(c => { if (c !== logEmpty) c.remove(); });
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
logEmpty.style.display = 'none';
|
|
1112
|
+
|
|
1113
|
+
const frag = document.createDocumentFragment();
|
|
1114
|
+
filtered.forEach(log => {
|
|
1115
|
+
const el = createLogEl(log);
|
|
1116
|
+
frag.appendChild(el);
|
|
1117
|
+
});
|
|
1118
|
+
logList.innerHTML = '';
|
|
1119
|
+
logList.appendChild(logEmpty);
|
|
1120
|
+
logEmpty.style.display = 'none';
|
|
1121
|
+
logList.appendChild(frag);
|
|
1122
|
+
|
|
1123
|
+
if (autoScroll) logList.scrollTop = 0;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
776
1126
|
function prependLog(log) {
|
|
777
1127
|
allLogs.unshift(log);
|
|
778
|
-
if (allLogs.length > 1000) allLogs.pop();
|
|
1128
|
+
if (allLogs.length > 1000) allLogs.pop();
|
|
779
1129
|
logCount.textContent = `${allLogs.length} event${allLogs.length !== 1 ? 's' : ''}`;
|
|
780
1130
|
|
|
781
|
-
if (
|
|
1131
|
+
if (!shouldShowLog(log)) return;
|
|
782
1132
|
|
|
783
1133
|
logEmpty.style.display = 'none';
|
|
784
1134
|
const el = createLogEl(log);
|
|
@@ -789,17 +1139,69 @@
|
|
|
789
1139
|
if (autoScroll) logList.scrollTop = 0;
|
|
790
1140
|
}
|
|
791
1141
|
|
|
792
|
-
/* ──
|
|
1142
|
+
/* ── Update Connected Studios Table ── */
|
|
1143
|
+
function updateStudiosTable(studios) {
|
|
1144
|
+
const tbody = document.getElementById('sessions-tbody');
|
|
1145
|
+
const table = document.getElementById('sessions-table');
|
|
1146
|
+
const empty = document.getElementById('sessions-empty');
|
|
1147
|
+
const badge = document.getElementById('studios-count-badge');
|
|
1148
|
+
|
|
1149
|
+
const list = Array.isArray(studios) ? studios : [];
|
|
1150
|
+
badge.textContent = `${list.length} Terhubung`;
|
|
1151
|
+
|
|
1152
|
+
if (list.length === 0) {
|
|
1153
|
+
table.style.display = 'none';
|
|
1154
|
+
empty.style.display = 'block';
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
empty.style.display = 'none';
|
|
1159
|
+
table.style.display = 'table';
|
|
1160
|
+
tbody.innerHTML = '';
|
|
1161
|
+
|
|
1162
|
+
const now = Date.now();
|
|
1163
|
+
list.forEach(s => {
|
|
1164
|
+
const tr = document.createElement('tr');
|
|
1165
|
+
const sidShort = s.studioId ? (s.studioId.length > 12 ? s.studioId.substring(0, 10) + '…' : s.studioId) : 'anon';
|
|
1166
|
+
const placeText = (s.placeId && s.placeId > 0) ? `Place #${s.placeId}` : 'Local Place (ID: 0)';
|
|
1167
|
+
const isWs = s.transport === 'websocket';
|
|
1168
|
+
const transportBadge = isWs
|
|
1169
|
+
? '<span class="badge-pill badge-ws">WebSocket ⚡</span>'
|
|
1170
|
+
: '<span class="badge-pill badge-poll">Long-Poll 🔄</span>';
|
|
1171
|
+
const statusBadge = s.isBusy
|
|
1172
|
+
? '<span style="color:var(--warning);">● Sibuk</span>'
|
|
1173
|
+
: '<span style="color:var(--success);">● Siap</span>';
|
|
1174
|
+
const elapsed = s.lastSeen ? Math.max(0, Math.round((now - s.lastSeen) / 1000)) + 'd lalu' : 'Baru saja';
|
|
1175
|
+
|
|
1176
|
+
tr.innerHTML = `
|
|
1177
|
+
<td title="${s.studioId || ''}"><code>${sidShort}</code></td>
|
|
1178
|
+
<td><strong>${placeText}</strong></td>
|
|
1179
|
+
<td>${transportBadge}</td>
|
|
1180
|
+
<td>${statusBadge}</td>
|
|
1181
|
+
<td style="color:var(--text-dim); font-size:12px;">${elapsed}</td>
|
|
1182
|
+
`;
|
|
1183
|
+
tbody.appendChild(tr);
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
/* ── Update Statistics ── */
|
|
793
1188
|
function updateStats(s) {
|
|
794
|
-
|
|
795
|
-
document.getElementById('stat-
|
|
796
|
-
document.getElementById('stat-
|
|
1189
|
+
if (!s) return;
|
|
1190
|
+
if (s.totalTasks !== undefined) document.getElementById('stat-total').textContent = s.totalTasks;
|
|
1191
|
+
if (s.successCount !== undefined) document.getElementById('stat-success').textContent = s.successCount;
|
|
1192
|
+
if (s.errorCount !== undefined) document.getElementById('stat-error').textContent = s.errorCount;
|
|
797
1193
|
|
|
798
|
-
|
|
1194
|
+
if (s.version) {
|
|
1195
|
+
if (pkgVersion) pkgVersion.textContent = 'v' + s.version;
|
|
1196
|
+
if (aboutVersionChip) aboutVersionChip.textContent = 'v' + s.version;
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
const total = s.totalTasks || 0;
|
|
1200
|
+
const rate = total > 0 ? Math.round(((s.successCount || 0) / total) * 100) : null;
|
|
799
1201
|
document.getElementById('stat-rate').textContent = rate !== null ? rate + '%' : '—';
|
|
800
1202
|
document.getElementById('bar-success').style.width = (rate || 0) + '%';
|
|
801
1203
|
|
|
802
|
-
const errRate =
|
|
1204
|
+
const errRate = total > 0 ? Math.round(((s.errorCount || 0) / total) * 100) : 0;
|
|
803
1205
|
document.getElementById('bar-error').style.width = errRate + '%';
|
|
804
1206
|
|
|
805
1207
|
if (s.sessionStart) {
|
|
@@ -807,16 +1209,34 @@
|
|
|
807
1209
|
}
|
|
808
1210
|
|
|
809
1211
|
const statusEl = document.getElementById('info-studio');
|
|
1212
|
+
const transportEl = document.getElementById('info-transport');
|
|
810
1213
|
if (s.studioConnected) {
|
|
811
|
-
statusEl.textContent =
|
|
1214
|
+
statusEl.textContent = `Connected ✓ (${s.studioCount || 1} Studio)`;
|
|
812
1215
|
statusEl.style.color = 'var(--success)';
|
|
1216
|
+
if (s.wsStudios && s.wsStudios > 0) {
|
|
1217
|
+
if (transportEl) {
|
|
1218
|
+
transportEl.textContent = `WebSocket Duplex ⚡ (${s.wsStudios} Active)`;
|
|
1219
|
+
transportEl.style.color = 'var(--success)';
|
|
1220
|
+
}
|
|
1221
|
+
} else if (transportEl) {
|
|
1222
|
+
transportEl.textContent = 'Long-Polling (Active Fallback)';
|
|
1223
|
+
transportEl.style.color = 'var(--accent)';
|
|
1224
|
+
}
|
|
813
1225
|
} else {
|
|
814
1226
|
statusEl.textContent = 'Disconnected';
|
|
815
1227
|
statusEl.style.color = 'var(--text-muted)';
|
|
1228
|
+
if (transportEl) {
|
|
1229
|
+
transportEl.textContent = 'Dual-Mode (WebSocket ⚡ + Long-Poll)';
|
|
1230
|
+
transportEl.style.color = 'var(--accent)';
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
if (s.connectedStudios) {
|
|
1235
|
+
updateStudiosTable(s.connectedStudios);
|
|
816
1236
|
}
|
|
817
1237
|
}
|
|
818
1238
|
|
|
819
|
-
/* ── Uptime ── */
|
|
1239
|
+
/* ── Uptime Timer ── */
|
|
820
1240
|
setInterval(() => {
|
|
821
1241
|
if (!sessionStartTime) return;
|
|
822
1242
|
const diff = Math.floor((Date.now() - sessionStartTime) / 1000);
|
|
@@ -826,14 +1246,15 @@
|
|
|
826
1246
|
document.getElementById('info-uptime').textContent = `${h}:${m}:${s}`;
|
|
827
1247
|
}, 1000);
|
|
828
1248
|
|
|
829
|
-
/* ── Studio Status ── */
|
|
830
|
-
function setStudioStatus(connected) {
|
|
1249
|
+
/* ── Studio Status Indicator ── */
|
|
1250
|
+
function setStudioStatus(connected, studioCount) {
|
|
831
1251
|
statusDot.className = 'status-dot ' + (connected ? 'connected' : 'waiting');
|
|
832
|
-
|
|
1252
|
+
const countText = studioCount && studioCount > 1 ? ` (${studioCount} Studios)` : '';
|
|
1253
|
+
statusLabel.textContent = connected ? `Studio Connected${countText}` : 'Menunggu Studio...';
|
|
833
1254
|
|
|
834
1255
|
const statusEl = document.getElementById('info-studio');
|
|
835
1256
|
if (connected) {
|
|
836
|
-
statusEl.textContent =
|
|
1257
|
+
statusEl.textContent = `Connected ✓${countText}`;
|
|
837
1258
|
statusEl.style.color = 'var(--success)';
|
|
838
1259
|
} else {
|
|
839
1260
|
statusEl.textContent = 'Disconnected';
|
|
@@ -841,7 +1262,109 @@
|
|
|
841
1262
|
}
|
|
842
1263
|
}
|
|
843
1264
|
|
|
844
|
-
/* ──
|
|
1265
|
+
/* ── Simple Markdown Formatter for Docs ── */
|
|
1266
|
+
function formatMarkdown(md) {
|
|
1267
|
+
if (!md) return '';
|
|
1268
|
+
let html = escapeHtml(md);
|
|
1269
|
+
|
|
1270
|
+
// Fenced code blocks ```
|
|
1271
|
+
html = html.replace(/```([a-zA-Z]*)\n([\s\S]*?)```/g, function(match, lang, code) {
|
|
1272
|
+
return '<pre><code>' + code + '</code></pre>';
|
|
1273
|
+
});
|
|
1274
|
+
|
|
1275
|
+
// Inline code `...`
|
|
1276
|
+
html = html.replace(/`([^`]+)`/g, '<code>$1</code>');
|
|
1277
|
+
|
|
1278
|
+
// Headers
|
|
1279
|
+
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>');
|
|
1280
|
+
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>');
|
|
1281
|
+
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>');
|
|
1282
|
+
|
|
1283
|
+
// Bold & Italic
|
|
1284
|
+
html = html.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
|
|
1285
|
+
html = html.replace(/\*([^*]+)\*/g, '<em>$1</em>');
|
|
1286
|
+
|
|
1287
|
+
// Unordered Lists
|
|
1288
|
+
html = html.replace(/^\- (.*$)/gim, '<li>$1</li>');
|
|
1289
|
+
html = html.replace(/(<li>.*<\/li>)/gim, '<ul>$1</ul>');
|
|
1290
|
+
|
|
1291
|
+
// Clean double <ul> tags
|
|
1292
|
+
html = html.replace(/<\/ul>\s*<ul>/g, '');
|
|
1293
|
+
|
|
1294
|
+
// Paragraph breaks
|
|
1295
|
+
html = html.replace(/\n\n/g, '<br><br>');
|
|
1296
|
+
|
|
1297
|
+
return html;
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
/* ── Docs Explorer Handler ── */
|
|
1301
|
+
const docsForm = document.getElementById('docs-form');
|
|
1302
|
+
const docsQuery = document.getElementById('docs-query');
|
|
1303
|
+
const docsMember = document.getElementById('docs-member');
|
|
1304
|
+
const docsBtn = document.getElementById('docs-btn');
|
|
1305
|
+
const docsContent = document.getElementById('docs-content');
|
|
1306
|
+
const docsResultTitle = document.getElementById('docs-result-title');
|
|
1307
|
+
const docsCopyBtn = document.getElementById('docs-copy-btn');
|
|
1308
|
+
|
|
1309
|
+
async function searchDocs(query, member) {
|
|
1310
|
+
if (!query) return;
|
|
1311
|
+
docsQuery.value = query;
|
|
1312
|
+
if (member) docsMember.value = member;
|
|
1313
|
+
|
|
1314
|
+
docsBtn.disabled = true;
|
|
1315
|
+
docsBtn.innerHTML = 'Mencari...';
|
|
1316
|
+
docsContent.innerHTML = '<span style="color:var(--accent);">Mengambil dokumentasi resmi Creator Docs...</span>';
|
|
1317
|
+
|
|
1318
|
+
try {
|
|
1319
|
+
const url = new URL('/api/docs', window.location.origin);
|
|
1320
|
+
url.searchParams.set('query', query);
|
|
1321
|
+
if (member) url.searchParams.set('member', member);
|
|
1322
|
+
|
|
1323
|
+
const res = await fetch(url.toString());
|
|
1324
|
+
const data = await res.json();
|
|
1325
|
+
|
|
1326
|
+
if (!res.ok || data.error) {
|
|
1327
|
+
docsContent.innerHTML = `<span style="color:var(--error);">Error: ${escapeHtml(data.error || 'Gagal mengambil dokumentasi')}</span>`;
|
|
1328
|
+
docsCopyBtn.style.display = 'none';
|
|
1329
|
+
} else {
|
|
1330
|
+
latestDocsMarkdown = data.result;
|
|
1331
|
+
docsResultTitle.innerHTML = `Dokumentasi: <code>${escapeHtml(query)}${member ? '.' + escapeHtml(member) : ''}</code>`;
|
|
1332
|
+
docsContent.innerHTML = formatMarkdown(data.result);
|
|
1333
|
+
docsCopyBtn.style.display = 'inline-block';
|
|
1334
|
+
}
|
|
1335
|
+
} catch (err) {
|
|
1336
|
+
docsContent.innerHTML = `<span style="color:var(--error);">Koneksi gagal: ${escapeHtml(err.message)}</span>`;
|
|
1337
|
+
docsCopyBtn.style.display = 'none';
|
|
1338
|
+
} finally {
|
|
1339
|
+
docsBtn.disabled = false;
|
|
1340
|
+
docsBtn.innerHTML = `
|
|
1341
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>
|
|
1342
|
+
Cari Docs
|
|
1343
|
+
`;
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
docsForm.addEventListener('submit', (e) => {
|
|
1348
|
+
e.preventDefault();
|
|
1349
|
+
searchDocs(docsQuery.value.trim(), docsMember.value.trim());
|
|
1350
|
+
});
|
|
1351
|
+
|
|
1352
|
+
document.querySelectorAll('.doc-chip').forEach(chip => {
|
|
1353
|
+
chip.addEventListener('click', () => {
|
|
1354
|
+
const q = chip.dataset.q;
|
|
1355
|
+
searchDocs(q, '');
|
|
1356
|
+
});
|
|
1357
|
+
});
|
|
1358
|
+
|
|
1359
|
+
docsCopyBtn.addEventListener('click', () => {
|
|
1360
|
+
if (latestDocsMarkdown) {
|
|
1361
|
+
navigator.clipboard.writeText(latestDocsMarkdown);
|
|
1362
|
+
docsCopyBtn.textContent = 'Tersalin ✓';
|
|
1363
|
+
setTimeout(() => { docsCopyBtn.textContent = 'Salin Markdown'; }, 2000);
|
|
1364
|
+
}
|
|
1365
|
+
});
|
|
1366
|
+
|
|
1367
|
+
/* ── Socket.IO Event Listeners ── */
|
|
845
1368
|
socket.on('connect', () => {});
|
|
846
1369
|
socket.on('disconnect', () => setStudioStatus(false));
|
|
847
1370
|
|
|
@@ -850,7 +1373,7 @@
|
|
|
850
1373
|
renderLogs();
|
|
851
1374
|
if (stats) {
|
|
852
1375
|
updateStats(stats);
|
|
853
|
-
if (stats.studioConnected) setStudioStatus(true);
|
|
1376
|
+
if (stats.studioConnected) setStudioStatus(true, stats.studioCount);
|
|
854
1377
|
}
|
|
855
1378
|
});
|
|
856
1379
|
|
|
@@ -862,9 +1385,11 @@
|
|
|
862
1385
|
updateStats(s);
|
|
863
1386
|
});
|
|
864
1387
|
|
|
865
|
-
socket.on('studio-status', ({ connected }) => {
|
|
866
|
-
setStudioStatus(connected);
|
|
1388
|
+
socket.on('studio-status', ({ connected, studioCount, connectedStudios, version }) => {
|
|
1389
|
+
setStudioStatus(connected, studioCount);
|
|
1390
|
+
if (connectedStudios) updateStudiosTable(connectedStudios);
|
|
1391
|
+
if (version && pkgVersion) pkgVersion.textContent = 'v' + version;
|
|
867
1392
|
});
|
|
868
1393
|
</script>
|
|
869
1394
|
</body>
|
|
870
|
-
</html>
|
|
1395
|
+
</html>
|