let-them-talk 5.3.0 → 5.4.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/CHANGELOG.md +3 -1
- package/README.md +158 -592
- package/SECURITY.md +3 -3
- package/USAGE.md +151 -0
- package/agent-contracts.js +447 -0
- package/api-agents.js +760 -0
- package/autonomy/decision-v2.js +380 -0
- package/autonomy/watchdog-policy.js +572 -0
- package/cli.js +454 -298
- package/conversation-templates/autonomous-feature.json +83 -22
- package/conversation-templates/code-review.json +69 -21
- package/conversation-templates/debug-squad.json +69 -21
- package/conversation-templates/feature-build.json +69 -21
- package/conversation-templates/research-write.json +69 -21
- package/dashboard.html +3148 -174
- package/dashboard.js +823 -786
- package/data-dir.js +58 -0
- package/docs/architecture/branch-semantics.md +157 -0
- package/docs/architecture/canonical-event-schema.md +88 -0
- package/docs/architecture/markdown-workspace.md +183 -0
- package/docs/architecture/runtime-contract.md +459 -0
- package/docs/architecture/runtime-migration-hardening.md +64 -0
- package/events/hooks.js +154 -0
- package/events/log.js +457 -0
- package/events/replay.js +33 -0
- package/events/schema.js +432 -0
- package/managed-team-integration.js +261 -0
- package/office/agents.js +704 -597
- package/office/animation.js +1 -1
- package/office/assets/arcade-cabinet.js +141 -0
- package/office/assets/archway.js +77 -0
- package/office/assets/bar-counter.js +91 -0
- package/office/assets/bar-stool.js +71 -0
- package/office/assets/beanbag.js +64 -0
- package/office/assets/bench.js +99 -0
- package/office/assets/bollard.js +87 -0
- package/office/assets/cactus.js +100 -0
- package/office/assets/carpet-tile.js +46 -0
- package/office/assets/chair.js +123 -0
- package/office/assets/chandelier.js +107 -0
- package/office/assets/coffee-machine.js +95 -0
- package/office/assets/coffee-table.js +81 -0
- package/office/assets/column.js +95 -0
- package/office/assets/desk-lamp.js +102 -0
- package/office/assets/desk.js +76 -0
- package/office/assets/dining-table.js +105 -0
- package/office/assets/door.js +70 -0
- package/office/assets/dual-monitor.js +72 -0
- package/office/assets/fence.js +76 -0
- package/office/assets/filing-cabinet.js +111 -0
- package/office/assets/floor-lamp.js +69 -0
- package/office/assets/floor-tile.js +54 -0
- package/office/assets/flower-pot.js +76 -0
- package/office/assets/foosball.js +95 -0
- package/office/assets/fridge.js +99 -0
- package/office/assets/gaming-chair.js +154 -0
- package/office/assets/gaming-desk.js +105 -0
- package/office/assets/glass-door.js +72 -0
- package/office/assets/glass-wall.js +64 -0
- package/office/assets/half-wall.js +49 -0
- package/office/assets/hanging-plant.js +112 -0
- package/office/assets/index.js +151 -0
- package/office/assets/indoor-tree.js +90 -0
- package/office/assets/l-sofa.js +153 -0
- package/office/assets/marble-floor.js +64 -0
- package/office/assets/materials.js +40 -0
- package/office/assets/meeting-table.js +88 -0
- package/office/assets/microwave.js +94 -0
- package/office/assets/monitor.js +67 -0
- package/office/assets/neon-strip.js +73 -0
- package/office/assets/painting.js +84 -0
- package/office/assets/palm-tree.js +108 -0
- package/office/assets/pc-tower.js +91 -0
- package/office/assets/pendant-light.js +67 -0
- package/office/assets/ping-pong.js +114 -0
- package/office/assets/plant.js +72 -0
- package/office/assets/planter-box.js +95 -0
- package/office/assets/pool-table.js +94 -0
- package/office/assets/printer.js +113 -0
- package/office/assets/reception-desk.js +133 -0
- package/office/assets/rug.js +78 -0
- package/office/assets/sculpture.js +85 -0
- package/office/assets/server-rack.js +98 -0
- package/office/assets/sink.js +109 -0
- package/office/assets/sofa.js +106 -0
- package/office/assets/speaker.js +83 -0
- package/office/assets/spotlight.js +83 -0
- package/office/assets/street-lamp.js +97 -0
- package/office/assets/trash-can.js +83 -0
- package/office/assets/treadmill.js +126 -0
- package/office/assets/trophy.js +89 -0
- package/office/assets/tv-screen.js +79 -0
- package/office/assets/vase.js +84 -0
- package/office/assets/wall-clock.js +84 -0
- package/office/assets/wall.js +53 -0
- package/office/assets/water-cooler.js +146 -0
- package/office/assets/whiteboard.js +115 -0
- package/office/assets.js +3 -431
- package/office/builder.js +791 -355
- package/office/campus-env.js +1012 -1119
- package/office/environment.js +2 -0
- package/office/gallery.js +997 -0
- package/office/index.js +165 -61
- package/office/navigation.js +173 -152
- package/office/player.js +178 -68
- package/office/robot-character.js +272 -0
- package/office/spectator-camera.js +33 -10
- package/office/state.js +2 -0
- package/office/world-save.js +35 -4
- package/package.json +57 -3
- package/providers/comfyui.js +383 -0
- package/providers/dalle.js +79 -0
- package/providers/gemini.js +181 -0
- package/providers/ollama.js +184 -0
- package/providers/replicate.js +115 -0
- package/providers/zai.js +183 -0
- package/runtime-descriptor.js +270 -0
- package/scripts/check-agent-contract-advisory.js +132 -0
- package/scripts/check-api-agent-parity.js +277 -0
- package/scripts/check-autonomy-v2-decision.js +207 -0
- package/scripts/check-autonomy-v2-execution.js +588 -0
- package/scripts/check-autonomy-v2-watchdog.js +224 -0
- package/scripts/check-branch-fork-snapshot.js +337 -0
- package/scripts/check-branch-isolation.js +787 -0
- package/scripts/check-branch-semantics.js +139 -0
- package/scripts/check-dashboard-control-plane.js +1304 -0
- package/scripts/check-docs-onboarding.js +490 -0
- package/scripts/check-event-schema.js +276 -0
- package/scripts/check-evidence-completion.js +239 -0
- package/scripts/check-invariants.js +992 -0
- package/scripts/check-lifecycle-hooks.js +525 -0
- package/scripts/check-managed-team-integration.js +166 -0
- package/scripts/check-markdown-workspace-export.js +548 -0
- package/scripts/check-markdown-workspace-safety.js +347 -0
- package/scripts/check-markdown-workspace.js +136 -0
- package/scripts/check-message-replay.js +429 -0
- package/scripts/check-migration-hardening.js +300 -0
- package/scripts/check-performance-indexing.js +272 -0
- package/scripts/check-provider-capabilities.js +316 -0
- package/scripts/check-runtime-contract.js +109 -0
- package/scripts/check-session-aware-context.js +172 -0
- package/scripts/check-session-lifecycle.js +210 -0
- package/scripts/export-markdown-workspace.js +84 -0
- package/scripts/fixtures/message-replay/clean.jsonl +2 -0
- package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
- package/scripts/migrate-legacy-to-canonical.js +201 -0
- package/scripts/run-verification-suite.js +242 -0
- package/scripts/sync-packaged-docs.js +69 -0
- package/server.js +9546 -7216
- package/state/agents.js +161 -0
- package/state/canonical.js +3068 -0
- package/state/dashboard-queries.js +441 -0
- package/state/evidence.js +56 -0
- package/state/io.js +69 -0
- package/state/markdown-workspace.js +951 -0
- package/state/messages.js +669 -0
- package/state/sessions.js +683 -0
- package/state/tasks-workflows.js +92 -0
- package/templates/debate.json +2 -2
- package/templates/managed.json +4 -4
- package/templates/pair.json +2 -2
- package/templates/review.json +2 -2
- package/templates/team.json +3 -3
package/dashboard.html
CHANGED
|
@@ -321,6 +321,7 @@
|
|
|
321
321
|
cursor: pointer;
|
|
322
322
|
max-width: 80px;
|
|
323
323
|
}
|
|
324
|
+
.agent-filter-bar select option { background: #1e2028; color: #e0e0e0; }
|
|
324
325
|
|
|
325
326
|
/* ===== ROLE GROUP ===== */
|
|
326
327
|
.role-group {
|
|
@@ -1144,6 +1145,7 @@
|
|
|
1144
1145
|
}
|
|
1145
1146
|
|
|
1146
1147
|
.input-target select:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
1148
|
+
.input-target select option { background: #1e2028; color: #e0e0e0; }
|
|
1147
1149
|
|
|
1148
1150
|
.input-msg {
|
|
1149
1151
|
flex: 1;
|
|
@@ -1276,6 +1278,7 @@
|
|
|
1276
1278
|
display: flex;
|
|
1277
1279
|
gap: 8px;
|
|
1278
1280
|
align-items: center;
|
|
1281
|
+
flex-wrap: wrap;
|
|
1279
1282
|
}
|
|
1280
1283
|
|
|
1281
1284
|
.search-input {
|
|
@@ -1294,6 +1297,12 @@
|
|
|
1294
1297
|
.search-input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-dim); }
|
|
1295
1298
|
.search-input::placeholder { color: var(--text-muted); }
|
|
1296
1299
|
|
|
1300
|
+
.search-input.omnibox-active {
|
|
1301
|
+
border-color: var(--accent);
|
|
1302
|
+
background: var(--accent-dim);
|
|
1303
|
+
box-shadow: 0 0 0 3px var(--accent-dim);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1297
1306
|
.compact-toggle {
|
|
1298
1307
|
background: var(--surface-2);
|
|
1299
1308
|
border: 1px solid var(--border);
|
|
@@ -1308,6 +1317,52 @@
|
|
|
1308
1317
|
.compact-toggle:hover { background: var(--surface-3); color: var(--text); }
|
|
1309
1318
|
.compact-toggle.active { background: var(--accent-dim); color: var(--accent); border-color: var(--accent); }
|
|
1310
1319
|
|
|
1320
|
+
.workspace-layout-controls {
|
|
1321
|
+
display: flex;
|
|
1322
|
+
align-items: center;
|
|
1323
|
+
gap: 8px;
|
|
1324
|
+
margin-left: auto;
|
|
1325
|
+
flex-wrap: wrap;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
.workspace-layout-select,
|
|
1329
|
+
.workspace-layout-btn {
|
|
1330
|
+
background: var(--surface-2);
|
|
1331
|
+
border: 1px solid var(--border);
|
|
1332
|
+
border-radius: 6px;
|
|
1333
|
+
color: var(--text-dim);
|
|
1334
|
+
font-size: 11px;
|
|
1335
|
+
padding: 4px 8px;
|
|
1336
|
+
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.workspace-layout-select {
|
|
1340
|
+
min-width: 150px;
|
|
1341
|
+
cursor: pointer;
|
|
1342
|
+
outline: none;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
.workspace-layout-select:focus {
|
|
1346
|
+
border-color: var(--accent);
|
|
1347
|
+
box-shadow: 0 0 0 3px var(--accent-dim);
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
.workspace-layout-btn {
|
|
1351
|
+
cursor: pointer;
|
|
1352
|
+
white-space: nowrap;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
.workspace-layout-btn:hover {
|
|
1356
|
+
background: var(--surface-3);
|
|
1357
|
+
color: var(--text);
|
|
1358
|
+
border-color: var(--border-light);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
.workspace-layout-btn:disabled {
|
|
1362
|
+
opacity: 0.5;
|
|
1363
|
+
cursor: not-allowed;
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1311
1366
|
/* Compact mode styles */
|
|
1312
1367
|
.messages-area.compact-mode { gap: 0; padding: 8px 12px; }
|
|
1313
1368
|
.messages-area.compact-mode .message { padding: 3px 8px; gap: 6px; }
|
|
@@ -1326,6 +1381,129 @@
|
|
|
1326
1381
|
white-space: nowrap;
|
|
1327
1382
|
}
|
|
1328
1383
|
|
|
1384
|
+
.omnibox-panel {
|
|
1385
|
+
display: none;
|
|
1386
|
+
flex: 1 0 100%;
|
|
1387
|
+
background: var(--surface-2);
|
|
1388
|
+
background-image: var(--gradient-surface);
|
|
1389
|
+
border: 1px solid var(--border-light);
|
|
1390
|
+
border-radius: 12px;
|
|
1391
|
+
box-shadow: var(--shadow-lg);
|
|
1392
|
+
overflow: hidden;
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
.omnibox-panel.open {
|
|
1396
|
+
display: block;
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
.omnibox-header {
|
|
1400
|
+
display: flex;
|
|
1401
|
+
align-items: center;
|
|
1402
|
+
justify-content: space-between;
|
|
1403
|
+
gap: 12px;
|
|
1404
|
+
padding: 10px 12px;
|
|
1405
|
+
border-bottom: 1px solid var(--border);
|
|
1406
|
+
font-size: 10px;
|
|
1407
|
+
color: var(--text-muted);
|
|
1408
|
+
text-transform: uppercase;
|
|
1409
|
+
letter-spacing: 0.08em;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
.omnibox-header strong {
|
|
1413
|
+
color: var(--accent);
|
|
1414
|
+
font-size: 10px;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
.omnibox-hint {
|
|
1418
|
+
color: var(--text-dim);
|
|
1419
|
+
text-transform: none;
|
|
1420
|
+
letter-spacing: 0;
|
|
1421
|
+
font-size: 11px;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
.omnibox-list {
|
|
1425
|
+
display: flex;
|
|
1426
|
+
flex-direction: column;
|
|
1427
|
+
max-height: 320px;
|
|
1428
|
+
overflow-y: auto;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
.omnibox-item {
|
|
1432
|
+
width: 100%;
|
|
1433
|
+
background: transparent;
|
|
1434
|
+
border: none;
|
|
1435
|
+
border-bottom: 1px solid var(--border);
|
|
1436
|
+
color: var(--text);
|
|
1437
|
+
display: flex;
|
|
1438
|
+
align-items: center;
|
|
1439
|
+
gap: 10px;
|
|
1440
|
+
padding: 12px;
|
|
1441
|
+
text-align: left;
|
|
1442
|
+
cursor: pointer;
|
|
1443
|
+
transition: background 0.15s ease, border-color 0.15s ease;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
.omnibox-item:last-child {
|
|
1447
|
+
border-bottom: none;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
.omnibox-item:hover,
|
|
1451
|
+
.omnibox-item.active {
|
|
1452
|
+
background: var(--accent-dim);
|
|
1453
|
+
border-color: var(--accent-glow);
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
.omnibox-item-kind {
|
|
1457
|
+
flex-shrink: 0;
|
|
1458
|
+
min-width: 68px;
|
|
1459
|
+
font-size: 9px;
|
|
1460
|
+
font-weight: 700;
|
|
1461
|
+
text-transform: uppercase;
|
|
1462
|
+
letter-spacing: 0.08em;
|
|
1463
|
+
color: var(--accent);
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
.omnibox-item-main {
|
|
1467
|
+
flex: 1;
|
|
1468
|
+
min-width: 0;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
.omnibox-item-label {
|
|
1472
|
+
font-size: 13px;
|
|
1473
|
+
font-weight: 600;
|
|
1474
|
+
color: var(--text);
|
|
1475
|
+
white-space: nowrap;
|
|
1476
|
+
overflow: hidden;
|
|
1477
|
+
text-overflow: ellipsis;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
.omnibox-item-sub {
|
|
1481
|
+
margin-top: 2px;
|
|
1482
|
+
font-size: 11px;
|
|
1483
|
+
color: var(--text-dim);
|
|
1484
|
+
white-space: nowrap;
|
|
1485
|
+
overflow: hidden;
|
|
1486
|
+
text-overflow: ellipsis;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
.omnibox-item-badge {
|
|
1490
|
+
flex-shrink: 0;
|
|
1491
|
+
font-size: 10px;
|
|
1492
|
+
font-weight: 700;
|
|
1493
|
+
color: var(--text-dim);
|
|
1494
|
+
background: var(--surface-3);
|
|
1495
|
+
border: 1px solid var(--border);
|
|
1496
|
+
border-radius: 999px;
|
|
1497
|
+
padding: 3px 8px;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
.omnibox-empty {
|
|
1501
|
+
padding: 16px 14px;
|
|
1502
|
+
font-size: 12px;
|
|
1503
|
+
color: var(--text-muted);
|
|
1504
|
+
text-align: center;
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1329
1507
|
/* ===== EMPTY STATE ONBOARDING ===== */
|
|
1330
1508
|
.onboard-steps {
|
|
1331
1509
|
text-align: left;
|
|
@@ -1848,6 +2026,7 @@
|
|
|
1848
2026
|
|
|
1849
2027
|
@media (max-width: 768px) {
|
|
1850
2028
|
.kanban { grid-template-columns: 1fr; }
|
|
2029
|
+
.graph-surface-wrap { height: 560px; }
|
|
1851
2030
|
}
|
|
1852
2031
|
|
|
1853
2032
|
/* ===== REPLAY MODE ===== */
|
|
@@ -2261,6 +2440,10 @@
|
|
|
2261
2440
|
|
|
2262
2441
|
/* View tabs */
|
|
2263
2442
|
.view-tab { padding: 7px 12px; font-size: 11px; }
|
|
2443
|
+
|
|
2444
|
+
.graph-area { padding: 12px; }
|
|
2445
|
+
.graph-shell { padding: 12px; }
|
|
2446
|
+
.graph-surface-wrap { height: 460px; }
|
|
2264
2447
|
}
|
|
2265
2448
|
|
|
2266
2449
|
/* ===== MOBILE: VERY SMALL PHONE (360px) ===== */
|
|
@@ -2365,6 +2548,268 @@
|
|
|
2365
2548
|
color: var(--text);
|
|
2366
2549
|
}
|
|
2367
2550
|
|
|
2551
|
+
.profile-popup-actions {
|
|
2552
|
+
display: flex;
|
|
2553
|
+
gap: 8px;
|
|
2554
|
+
margin-top: 12px;
|
|
2555
|
+
}
|
|
2556
|
+
|
|
2557
|
+
.profile-popup-actions .btn {
|
|
2558
|
+
flex: 1;
|
|
2559
|
+
margin-top: 0 !important;
|
|
2560
|
+
}
|
|
2561
|
+
|
|
2562
|
+
.agent-drawer-backdrop {
|
|
2563
|
+
position: fixed;
|
|
2564
|
+
inset: 0;
|
|
2565
|
+
background: rgba(0, 0, 0, 0.55);
|
|
2566
|
+
backdrop-filter: blur(4px);
|
|
2567
|
+
-webkit-backdrop-filter: blur(4px);
|
|
2568
|
+
opacity: 0;
|
|
2569
|
+
pointer-events: none;
|
|
2570
|
+
transition: opacity 0.2s ease;
|
|
2571
|
+
z-index: 320;
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
.agent-drawer-backdrop.open {
|
|
2575
|
+
opacity: 1;
|
|
2576
|
+
pointer-events: auto;
|
|
2577
|
+
}
|
|
2578
|
+
|
|
2579
|
+
.agent-drawer {
|
|
2580
|
+
position: fixed;
|
|
2581
|
+
top: 0;
|
|
2582
|
+
right: 0;
|
|
2583
|
+
bottom: 0;
|
|
2584
|
+
width: min(420px, 100vw);
|
|
2585
|
+
background: var(--surface);
|
|
2586
|
+
background-image: var(--gradient-surface);
|
|
2587
|
+
border-left: 1px solid var(--border-light);
|
|
2588
|
+
box-shadow: var(--shadow-lg);
|
|
2589
|
+
transform: translateX(100%);
|
|
2590
|
+
transition: transform 0.22s ease;
|
|
2591
|
+
z-index: 321;
|
|
2592
|
+
display: flex;
|
|
2593
|
+
flex-direction: column;
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2596
|
+
.agent-drawer.open {
|
|
2597
|
+
transform: translateX(0);
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
.agent-drawer-header {
|
|
2601
|
+
display: flex;
|
|
2602
|
+
align-items: flex-start;
|
|
2603
|
+
justify-content: space-between;
|
|
2604
|
+
gap: 12px;
|
|
2605
|
+
padding: 20px 20px 14px;
|
|
2606
|
+
border-bottom: 1px solid var(--border);
|
|
2607
|
+
}
|
|
2608
|
+
|
|
2609
|
+
.agent-drawer-header-main {
|
|
2610
|
+
display: flex;
|
|
2611
|
+
align-items: center;
|
|
2612
|
+
gap: 12px;
|
|
2613
|
+
min-width: 0;
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
.agent-drawer-avatar,
|
|
2617
|
+
.agent-drawer-avatar-img {
|
|
2618
|
+
width: 52px;
|
|
2619
|
+
height: 52px;
|
|
2620
|
+
border-radius: 16px;
|
|
2621
|
+
flex-shrink: 0;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
.agent-drawer-avatar {
|
|
2625
|
+
display: flex;
|
|
2626
|
+
align-items: center;
|
|
2627
|
+
justify-content: center;
|
|
2628
|
+
color: #fff;
|
|
2629
|
+
font-size: 18px;
|
|
2630
|
+
font-weight: 800;
|
|
2631
|
+
}
|
|
2632
|
+
|
|
2633
|
+
.agent-drawer-avatar-img {
|
|
2634
|
+
object-fit: cover;
|
|
2635
|
+
}
|
|
2636
|
+
|
|
2637
|
+
.agent-drawer-title-wrap {
|
|
2638
|
+
min-width: 0;
|
|
2639
|
+
}
|
|
2640
|
+
|
|
2641
|
+
.agent-drawer-title {
|
|
2642
|
+
font-size: 18px;
|
|
2643
|
+
font-weight: 700;
|
|
2644
|
+
color: var(--text);
|
|
2645
|
+
white-space: nowrap;
|
|
2646
|
+
overflow: hidden;
|
|
2647
|
+
text-overflow: ellipsis;
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
.agent-drawer-subtitle {
|
|
2651
|
+
margin-top: 4px;
|
|
2652
|
+
font-size: 12px;
|
|
2653
|
+
color: var(--text-dim);
|
|
2654
|
+
display: flex;
|
|
2655
|
+
align-items: center;
|
|
2656
|
+
gap: 8px;
|
|
2657
|
+
flex-wrap: wrap;
|
|
2658
|
+
}
|
|
2659
|
+
|
|
2660
|
+
.agent-drawer-close {
|
|
2661
|
+
background: var(--surface-2);
|
|
2662
|
+
border: 1px solid var(--border);
|
|
2663
|
+
color: var(--text-dim);
|
|
2664
|
+
width: 32px;
|
|
2665
|
+
height: 32px;
|
|
2666
|
+
border-radius: 10px;
|
|
2667
|
+
cursor: pointer;
|
|
2668
|
+
font-size: 18px;
|
|
2669
|
+
line-height: 1;
|
|
2670
|
+
transition: all 0.15s ease;
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
.agent-drawer-close:hover {
|
|
2674
|
+
color: var(--text);
|
|
2675
|
+
border-color: var(--border-light);
|
|
2676
|
+
background: var(--surface-3);
|
|
2677
|
+
}
|
|
2678
|
+
|
|
2679
|
+
.agent-drawer-body {
|
|
2680
|
+
flex: 1;
|
|
2681
|
+
overflow-y: auto;
|
|
2682
|
+
padding: 18px 20px 24px;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
.agent-drawer-status-row {
|
|
2686
|
+
display: flex;
|
|
2687
|
+
flex-wrap: wrap;
|
|
2688
|
+
gap: 8px;
|
|
2689
|
+
margin-bottom: 16px;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
.agent-detail-pill {
|
|
2693
|
+
display: inline-flex;
|
|
2694
|
+
align-items: center;
|
|
2695
|
+
gap: 6px;
|
|
2696
|
+
background: var(--surface-2);
|
|
2697
|
+
border: 1px solid var(--border);
|
|
2698
|
+
border-radius: 999px;
|
|
2699
|
+
padding: 5px 10px;
|
|
2700
|
+
font-size: 10px;
|
|
2701
|
+
font-weight: 700;
|
|
2702
|
+
text-transform: uppercase;
|
|
2703
|
+
letter-spacing: 0.06em;
|
|
2704
|
+
color: var(--text-dim);
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
.agent-detail-pill strong {
|
|
2708
|
+
color: var(--text);
|
|
2709
|
+
}
|
|
2710
|
+
|
|
2711
|
+
.agent-detail-note {
|
|
2712
|
+
background: var(--surface-2);
|
|
2713
|
+
border: 1px solid var(--border);
|
|
2714
|
+
border-radius: 12px;
|
|
2715
|
+
padding: 12px 14px;
|
|
2716
|
+
margin-bottom: 16px;
|
|
2717
|
+
}
|
|
2718
|
+
|
|
2719
|
+
.agent-detail-note-label {
|
|
2720
|
+
font-size: 10px;
|
|
2721
|
+
font-weight: 700;
|
|
2722
|
+
color: var(--text-muted);
|
|
2723
|
+
text-transform: uppercase;
|
|
2724
|
+
letter-spacing: 0.08em;
|
|
2725
|
+
margin-bottom: 6px;
|
|
2726
|
+
}
|
|
2727
|
+
|
|
2728
|
+
.agent-detail-note-value {
|
|
2729
|
+
font-size: 12px;
|
|
2730
|
+
line-height: 1.6;
|
|
2731
|
+
color: var(--text);
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
.agent-drawer-section {
|
|
2735
|
+
margin-bottom: 18px;
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2738
|
+
.agent-drawer-section-title {
|
|
2739
|
+
font-size: 11px;
|
|
2740
|
+
font-weight: 700;
|
|
2741
|
+
color: var(--text-muted);
|
|
2742
|
+
text-transform: uppercase;
|
|
2743
|
+
letter-spacing: 0.08em;
|
|
2744
|
+
margin-bottom: 10px;
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
.agent-detail-grid {
|
|
2748
|
+
display: grid;
|
|
2749
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
2750
|
+
gap: 10px;
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
.agent-detail-card {
|
|
2754
|
+
background: var(--surface-2);
|
|
2755
|
+
border: 1px solid var(--border);
|
|
2756
|
+
border-radius: 12px;
|
|
2757
|
+
padding: 12px;
|
|
2758
|
+
min-width: 0;
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
.agent-detail-label {
|
|
2762
|
+
font-size: 10px;
|
|
2763
|
+
font-weight: 700;
|
|
2764
|
+
color: var(--text-muted);
|
|
2765
|
+
text-transform: uppercase;
|
|
2766
|
+
letter-spacing: 0.06em;
|
|
2767
|
+
margin-bottom: 6px;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
.agent-detail-value {
|
|
2771
|
+
font-size: 13px;
|
|
2772
|
+
font-weight: 600;
|
|
2773
|
+
color: var(--text);
|
|
2774
|
+
line-height: 1.45;
|
|
2775
|
+
word-break: break-word;
|
|
2776
|
+
}
|
|
2777
|
+
|
|
2778
|
+
.agent-detail-value.muted {
|
|
2779
|
+
font-size: 12px;
|
|
2780
|
+
font-weight: 500;
|
|
2781
|
+
color: var(--text-dim);
|
|
2782
|
+
}
|
|
2783
|
+
|
|
2784
|
+
.agent-detail-empty {
|
|
2785
|
+
color: var(--text-muted);
|
|
2786
|
+
}
|
|
2787
|
+
|
|
2788
|
+
.agent-drawer-actions {
|
|
2789
|
+
display: flex;
|
|
2790
|
+
gap: 8px;
|
|
2791
|
+
margin-top: 6px;
|
|
2792
|
+
flex-wrap: wrap;
|
|
2793
|
+
}
|
|
2794
|
+
|
|
2795
|
+
.agent-drawer-actions .btn {
|
|
2796
|
+
flex: 1 1 140px;
|
|
2797
|
+
}
|
|
2798
|
+
|
|
2799
|
+
@media (max-width: 768px) {
|
|
2800
|
+
.agent-drawer {
|
|
2801
|
+
width: 100vw;
|
|
2802
|
+
}
|
|
2803
|
+
|
|
2804
|
+
.agent-detail-grid {
|
|
2805
|
+
grid-template-columns: 1fr;
|
|
2806
|
+
}
|
|
2807
|
+
|
|
2808
|
+
.profile-popup-actions {
|
|
2809
|
+
flex-direction: column;
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
|
|
2368
2813
|
/* ===== v3.0: WORKSPACES TAB ===== */
|
|
2369
2814
|
.workspaces-area {
|
|
2370
2815
|
flex: 1;
|
|
@@ -2459,6 +2904,91 @@
|
|
|
2459
2904
|
|
|
2460
2905
|
.workflows-area.visible { display: block; }
|
|
2461
2906
|
|
|
2907
|
+
.graph-area {
|
|
2908
|
+
flex: 1;
|
|
2909
|
+
overflow-y: auto;
|
|
2910
|
+
padding: 16px 20px;
|
|
2911
|
+
display: none;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
.graph-area.visible { display: block; }
|
|
2915
|
+
|
|
2916
|
+
.graph-shell {
|
|
2917
|
+
background: var(--surface);
|
|
2918
|
+
background-image: var(--gradient-surface);
|
|
2919
|
+
border: 1px solid var(--border);
|
|
2920
|
+
border-radius: 16px;
|
|
2921
|
+
padding: 16px;
|
|
2922
|
+
box-shadow: var(--shadow-sm);
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
.graph-header {
|
|
2926
|
+
display: flex;
|
|
2927
|
+
align-items: flex-start;
|
|
2928
|
+
justify-content: space-between;
|
|
2929
|
+
gap: 16px;
|
|
2930
|
+
flex-wrap: wrap;
|
|
2931
|
+
margin-bottom: 14px;
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
.graph-title {
|
|
2935
|
+
font-size: 16px;
|
|
2936
|
+
font-weight: 700;
|
|
2937
|
+
color: var(--text);
|
|
2938
|
+
}
|
|
2939
|
+
|
|
2940
|
+
.graph-subtitle {
|
|
2941
|
+
margin-top: 4px;
|
|
2942
|
+
font-size: 12px;
|
|
2943
|
+
line-height: 1.6;
|
|
2944
|
+
color: var(--text-dim);
|
|
2945
|
+
max-width: 760px;
|
|
2946
|
+
}
|
|
2947
|
+
|
|
2948
|
+
.graph-meta {
|
|
2949
|
+
display: flex;
|
|
2950
|
+
align-items: center;
|
|
2951
|
+
gap: 8px;
|
|
2952
|
+
flex-wrap: wrap;
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
.graph-pill {
|
|
2956
|
+
display: inline-flex;
|
|
2957
|
+
align-items: center;
|
|
2958
|
+
gap: 6px;
|
|
2959
|
+
padding: 6px 10px;
|
|
2960
|
+
border-radius: 999px;
|
|
2961
|
+
background: var(--surface-2);
|
|
2962
|
+
border: 1px solid var(--border);
|
|
2963
|
+
font-size: 10px;
|
|
2964
|
+
font-weight: 700;
|
|
2965
|
+
text-transform: uppercase;
|
|
2966
|
+
letter-spacing: 0.06em;
|
|
2967
|
+
color: var(--text-dim);
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
.graph-pill strong {
|
|
2971
|
+
color: var(--text);
|
|
2972
|
+
font-size: 11px;
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
.graph-surface-wrap {
|
|
2976
|
+
height: 680px;
|
|
2977
|
+
border-radius: 14px;
|
|
2978
|
+
overflow: hidden;
|
|
2979
|
+
border: 1px solid var(--border);
|
|
2980
|
+
background:
|
|
2981
|
+
radial-gradient(circle at top left, var(--accent-dim), transparent 42%),
|
|
2982
|
+
radial-gradient(circle at bottom right, var(--purple-dim), transparent 36%),
|
|
2983
|
+
var(--bg);
|
|
2984
|
+
}
|
|
2985
|
+
|
|
2986
|
+
.graph-surface {
|
|
2987
|
+
display: block;
|
|
2988
|
+
width: 100%;
|
|
2989
|
+
height: 100%;
|
|
2990
|
+
}
|
|
2991
|
+
|
|
2462
2992
|
.plan-area {
|
|
2463
2993
|
flex: 1;
|
|
2464
2994
|
overflow-y: auto;
|
|
@@ -2683,6 +3213,107 @@
|
|
|
2683
3213
|
.stats-area.visible { display: block; }
|
|
2684
3214
|
|
|
2685
3215
|
/* ===== DOCS VIEW ===== */
|
|
3216
|
+
/* ===== SERVICES VIEW ===== */
|
|
3217
|
+
.services-area {
|
|
3218
|
+
flex: 1;
|
|
3219
|
+
overflow-y: auto;
|
|
3220
|
+
padding: 20px;
|
|
3221
|
+
display: none;
|
|
3222
|
+
}
|
|
3223
|
+
.services-area.visible { display: block; }
|
|
3224
|
+
.services-container { max-width: 900px; margin: 0 auto; }
|
|
3225
|
+
.services-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
|
|
3226
|
+
.services-header h2 { font-size: 20px; font-weight: 700; color: var(--text); margin: 0; }
|
|
3227
|
+
.services-form {
|
|
3228
|
+
background: var(--surface-2);
|
|
3229
|
+
border: 1px solid var(--border);
|
|
3230
|
+
border-radius: 10px;
|
|
3231
|
+
padding: 16px;
|
|
3232
|
+
margin-bottom: 20px;
|
|
3233
|
+
}
|
|
3234
|
+
.services-form label { display: block; font-size: 11px; color: var(--text-dim); margin-bottom: 4px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
3235
|
+
.services-form input, .services-form select {
|
|
3236
|
+
width: 100%;
|
|
3237
|
+
background: var(--surface-1);
|
|
3238
|
+
border: 1px solid var(--border);
|
|
3239
|
+
border-radius: 6px;
|
|
3240
|
+
padding: 8px 10px;
|
|
3241
|
+
font-size: 13px;
|
|
3242
|
+
color: var(--text);
|
|
3243
|
+
margin-bottom: 12px;
|
|
3244
|
+
box-sizing: border-box;
|
|
3245
|
+
}
|
|
3246
|
+
.services-form select option {
|
|
3247
|
+
background: #1e2028;
|
|
3248
|
+
color: #e0e0e0;
|
|
3249
|
+
}
|
|
3250
|
+
.services-form .form-row { display: flex; gap: 12px; }
|
|
3251
|
+
.services-form .form-row > div { flex: 1; }
|
|
3252
|
+
.services-form button {
|
|
3253
|
+
background: var(--accent);
|
|
3254
|
+
color: #fff;
|
|
3255
|
+
border: none;
|
|
3256
|
+
border-radius: 6px;
|
|
3257
|
+
padding: 8px 20px;
|
|
3258
|
+
font-size: 13px;
|
|
3259
|
+
font-weight: 600;
|
|
3260
|
+
cursor: pointer;
|
|
3261
|
+
transition: opacity 0.2s;
|
|
3262
|
+
}
|
|
3263
|
+
.services-form button:hover { opacity: 0.85; }
|
|
3264
|
+
.services-cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 12px; margin-bottom: 24px; }
|
|
3265
|
+
.service-card {
|
|
3266
|
+
background: var(--surface-2);
|
|
3267
|
+
border: 1px solid var(--border);
|
|
3268
|
+
border-radius: 10px;
|
|
3269
|
+
padding: 14px;
|
|
3270
|
+
position: relative;
|
|
3271
|
+
}
|
|
3272
|
+
.service-card .sc-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
|
|
3273
|
+
.service-card .sc-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
3274
|
+
.service-card .sc-name { font-weight: 600; font-size: 14px; color: var(--text); }
|
|
3275
|
+
.service-card .sc-provider { font-size: 10px; padding: 2px 6px; border-radius: 4px; color: #fff; font-weight: 600; text-transform: uppercase; }
|
|
3276
|
+
.service-card .sc-stats { font-size: 11px; color: var(--text-dim); margin-bottom: 8px; line-height: 1.6; }
|
|
3277
|
+
.service-card .sc-controls { display: flex; gap: 6px; }
|
|
3278
|
+
.service-card .sc-controls button {
|
|
3279
|
+
flex: 1;
|
|
3280
|
+
padding: 5px 8px;
|
|
3281
|
+
border: 1px solid var(--border);
|
|
3282
|
+
border-radius: 5px;
|
|
3283
|
+
background: var(--surface-1);
|
|
3284
|
+
color: var(--text-muted);
|
|
3285
|
+
font-size: 11px;
|
|
3286
|
+
cursor: pointer;
|
|
3287
|
+
transition: all 0.2s;
|
|
3288
|
+
}
|
|
3289
|
+
.service-card .sc-controls button:hover { background: var(--surface-2); color: var(--text); }
|
|
3290
|
+
.service-card .sc-controls .sc-start { border-color: #22c55e44; color: #4ade80; }
|
|
3291
|
+
.service-card .sc-controls .sc-stop { border-color: #ef444444; color: #f87171; }
|
|
3292
|
+
.service-card .sc-controls .sc-delete { border-color: #ef444444; color: #f87171; }
|
|
3293
|
+
.media-browser { margin-top: 16px; }
|
|
3294
|
+
.media-browser h3 { font-size: 15px; font-weight: 600; color: var(--text); margin-bottom: 12px; }
|
|
3295
|
+
.media-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; }
|
|
3296
|
+
.media-item {
|
|
3297
|
+
background: var(--surface-2);
|
|
3298
|
+
border: 1px solid var(--border);
|
|
3299
|
+
border-radius: 8px;
|
|
3300
|
+
overflow: hidden;
|
|
3301
|
+
cursor: pointer;
|
|
3302
|
+
transition: border-color 0.2s;
|
|
3303
|
+
}
|
|
3304
|
+
.media-item:hover { border-color: var(--accent); }
|
|
3305
|
+
.media-item img { width: 100%; aspect-ratio: 1; object-fit: cover; display: block; }
|
|
3306
|
+
.media-item .mi-info { padding: 6px 8px; font-size: 10px; color: var(--text-dim); }
|
|
3307
|
+
.media-item .mi-prompt { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
3308
|
+
.media-lightbox {
|
|
3309
|
+
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
|
|
3310
|
+
background: rgba(0,0,0,0.9); z-index: 10000;
|
|
3311
|
+
display: flex; align-items: center; justify-content: center;
|
|
3312
|
+
cursor: pointer;
|
|
3313
|
+
}
|
|
3314
|
+
.media-lightbox img { max-width: 90%; max-height: 90%; object-fit: contain; border-radius: 8px; }
|
|
3315
|
+
.media-lightbox .lb-info { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); color: #aaa; font-size: 12px; text-align: center; max-width: 600px; }
|
|
3316
|
+
|
|
2686
3317
|
.docs-area {
|
|
2687
3318
|
flex: 1;
|
|
2688
3319
|
overflow-y: auto;
|
|
@@ -3715,11 +4346,15 @@
|
|
|
3715
4346
|
<select class="project-select" id="project-select" onchange="switchProject()">
|
|
3716
4347
|
<option value="">Default (local)</option>
|
|
3717
4348
|
</select>
|
|
3718
|
-
<
|
|
3719
|
-
|
|
4349
|
+
<div class="project-input-row" id="project-input-row" style="display:none;gap:6px;align-items:center;margin-top:4px">
|
|
4350
|
+
<input class="project-input" id="project-path-input" placeholder="Enter project folder path..." style="flex:1;display:block"
|
|
4351
|
+
onkeydown="if(event.key==='Enter'){addProject();}">
|
|
4352
|
+
<button class="btn btn-primary" id="project-submit-btn" onclick="addProject()" title="Add the pasted path as a project">Submit</button>
|
|
4353
|
+
</div>
|
|
3720
4354
|
<div class="project-actions">
|
|
3721
4355
|
<button class="btn btn-primary" onclick="showAddProject()">+ Add</button>
|
|
3722
4356
|
<button class="btn" onclick="discoverProjects()">Discover</button>
|
|
4357
|
+
<button class="btn" onclick="reinstallProviders()" id="reinstall-providers-btn" style="display:none" title="Re-run init for Claude/Gemini/Codex configs in this project (merge-safe — preserves your other MCP servers)">Reinstall Providers</button>
|
|
3723
4358
|
<button class="btn btn-danger" onclick="removeProject()" id="remove-project-btn" style="display:none">Remove</button>
|
|
3724
4359
|
</div>
|
|
3725
4360
|
<div id="discover-results" style="display:none"></div>
|
|
@@ -3741,6 +4376,7 @@
|
|
|
3741
4376
|
<option value="quality">Quality</option>
|
|
3742
4377
|
<option value="monitor">Monitor</option>
|
|
3743
4378
|
<option value="advisor">Advisor</option>
|
|
4379
|
+
<option value="api-agent">API Agent</option>
|
|
3744
4380
|
</select>
|
|
3745
4381
|
<select id="agent-status-filter" onchange="filterAgents()">
|
|
3746
4382
|
<option value="">Status</option>
|
|
@@ -3803,10 +4439,12 @@
|
|
|
3803
4439
|
<div class="view-tab" id="tab-tasks" onclick="switchView('tasks')">Tasks</div>
|
|
3804
4440
|
<div class="view-tab" id="tab-workspaces" onclick="switchView('workspaces')">Workspaces</div>
|
|
3805
4441
|
<div class="view-tab" id="tab-workflows" onclick="switchView('workflows')">Workflows</div>
|
|
4442
|
+
<div class="view-tab" id="tab-graph" onclick="switchView('graph')">Graph</div>
|
|
3806
4443
|
<div class="view-tab" id="tab-plan" onclick="switchView('plan')">Plan</div>
|
|
3807
4444
|
<div class="view-tab" id="tab-launch" onclick="switchView('launch')">Launch</div>
|
|
3808
4445
|
<div class="view-tab" id="tab-rules" onclick="switchView('rules')">Rules</div>
|
|
3809
4446
|
<div class="view-tab" id="tab-stats" onclick="switchView('stats')">Stats</div>
|
|
4447
|
+
<div class="view-tab" id="tab-services" onclick="switchView('services')">Services</div>
|
|
3810
4448
|
<div class="view-tab" id="tab-docs" onclick="switchView('docs')">Docs</div>
|
|
3811
4449
|
</div>
|
|
3812
4450
|
<div class="branch-tabs" id="branch-tabs"></div>
|
|
@@ -3819,6 +4457,14 @@
|
|
|
3819
4457
|
<select id="conversation-select" onchange="loadConversation()" title="Load saved conversation" style="background:var(--surface-2);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:10px;cursor:pointer;color:var(--text-muted)"><option value="">Current</option></select>
|
|
3820
4458
|
<button onclick="newConversation()" title="Archive current and start fresh" style="background:var(--surface-2);border:1px solid var(--border);border-radius:6px;padding:4px 8px;font-size:10px;cursor:pointer;color:var(--text-muted);white-space:nowrap;transition:all 0.2s">New Conversation</button>
|
|
3821
4459
|
<button onclick="clearMessages()" title="Clear all messages for this project" style="background:#4a2020;border:1px solid #6a3030;border-radius:6px;padding:4px 8px;font-size:10px;cursor:pointer;color:#ff6b6b;white-space:nowrap;transition:all 0.2s">Clear Messages</button>
|
|
4460
|
+
<div class="workspace-layout-controls">
|
|
4461
|
+
<select id="dashboard-workspace-select" class="workspace-layout-select" onchange="onDashboardWorkspaceSelection()" title="Saved dashboard layouts">
|
|
4462
|
+
<option value="">Current layout</option>
|
|
4463
|
+
</select>
|
|
4464
|
+
<button id="dashboard-workspace-save" class="workspace-layout-btn" onclick="saveNamedDashboardWorkspace()" title="Save the current dashboard layout as a named workspace">Save As</button>
|
|
4465
|
+
<button id="dashboard-workspace-load" class="workspace-layout-btn" onclick="loadNamedDashboardWorkspace()" title="Restore the selected dashboard layout" disabled>Load</button>
|
|
4466
|
+
</div>
|
|
4467
|
+
<div class="omnibox-panel" id="omnibox-panel" aria-hidden="true"></div>
|
|
3822
4468
|
</div>
|
|
3823
4469
|
<div class="channel-filter-bar" id="channel-filter-bar" style="display:none"></div>
|
|
3824
4470
|
<div class="pinned-section" id="pinned-section">
|
|
@@ -3829,6 +4475,7 @@
|
|
|
3829
4475
|
<div class="tasks-area" id="tasks-area"></div>
|
|
3830
4476
|
<div class="workspaces-area" id="workspaces-area"></div>
|
|
3831
4477
|
<div class="workflows-area" id="workflows-area"></div>
|
|
4478
|
+
<div class="graph-area" id="graph-area"></div>
|
|
3832
4479
|
<div class="plan-area" id="plan-area"></div>
|
|
3833
4480
|
<div class="plan-area" id="monitor-panel" style="display:none"></div>
|
|
3834
4481
|
<div class="office-area" id="office-area">
|
|
@@ -3860,6 +4507,7 @@
|
|
|
3860
4507
|
<div class="launch-area" id="launch-area"></div>
|
|
3861
4508
|
<div class="rules-area" id="rules-area"></div>
|
|
3862
4509
|
<div class="stats-area" id="stats-area"></div>
|
|
4510
|
+
<div class="services-area" id="services-area"></div>
|
|
3863
4511
|
<div class="docs-area" id="docs-area"><div id="docs-content"></div></div>
|
|
3864
4512
|
<button class="scroll-bottom" id="scroll-bottom" onclick="scrollToBottom()">↓<span class="new-count" id="new-msg-count" style="display:none">0</span></button>
|
|
3865
4513
|
<div class="typing-bar" id="typing-bar"></div>
|
|
@@ -3874,8 +4522,23 @@
|
|
|
3874
4522
|
</div>
|
|
3875
4523
|
<div class="input-msg">
|
|
3876
4524
|
<label>Message</label>
|
|
3877
|
-
<
|
|
3878
|
-
|
|
4525
|
+
<div style="position:relative;display:flex;align-items:flex-end;gap:6px;">
|
|
4526
|
+
<textarea id="inject-content" placeholder="Type a message to inject..." rows="1"
|
|
4527
|
+
onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();doInject();}" style="flex:1"></textarea>
|
|
4528
|
+
<input type="file" id="inject-file" accept="image/*" style="display:none" onchange="onFileSelected(this)">
|
|
4529
|
+
<button onclick="document.getElementById('inject-file').click()" title="Attach image" style="background:none;border:1px solid var(--border);border-radius:6px;color:var(--text-dim);cursor:pointer;padding:6px 8px;font-size:14px;line-height:1;flex-shrink:0;">📷</button>
|
|
4530
|
+
</div>
|
|
4531
|
+
<div id="inject-file-preview" style="display:none;margin-top:6px;position:relative;">
|
|
4532
|
+
<img id="inject-file-thumb" style="max-height:80px;max-width:200px;border-radius:6px;border:1px solid var(--border);">
|
|
4533
|
+
<button onclick="clearAttachment()" style="position:absolute;top:-6px;right:-6px;background:#ef4444;color:#fff;border:none;border-radius:50%;width:18px;height:18px;cursor:pointer;font-size:11px;line-height:18px;padding:0;">X</button>
|
|
4534
|
+
<div id="inject-file-name" style="font-size:9px;color:var(--text-dim);margin-top:2px;"></div>
|
|
4535
|
+
</div>
|
|
4536
|
+
<div id="assistant-private-row" style="display:none;align-items:center;gap:6px;margin-top:6px;font-size:10px;color:var(--text-muted);">
|
|
4537
|
+
<label style="display:flex;align-items:center;gap:6px;cursor:pointer;">
|
|
4538
|
+
<input type="checkbox" id="assistant-private-optin" style="accent-color:var(--accent)">
|
|
4539
|
+
Send privately to Assistant only
|
|
4540
|
+
</label>
|
|
4541
|
+
</div>
|
|
3879
4542
|
</div>
|
|
3880
4543
|
<button class="send-btn" onclick="doInject()" id="inject-btn" disabled>Send</button>
|
|
3881
4544
|
</div>
|
|
@@ -3895,9 +4558,28 @@
|
|
|
3895
4558
|
<div id="pp-role"></div>
|
|
3896
4559
|
<div class="profile-popup-bio" id="pp-bio"></div>
|
|
3897
4560
|
<div class="profile-popup-stats" id="pp-stats"></div>
|
|
3898
|
-
<
|
|
4561
|
+
<div class="profile-popup-actions">
|
|
4562
|
+
<button class="btn" id="pp-details-btn" onclick="openAgentMetadataDrawerFromPopup()">Details</button>
|
|
4563
|
+
<button class="btn btn-primary" id="pp-edit-btn" onclick="openProfileEditor()">Edit Profile</button>
|
|
4564
|
+
</div>
|
|
3899
4565
|
</div>
|
|
3900
4566
|
|
|
4567
|
+
<div class="agent-drawer-backdrop" id="agent-drawer-backdrop" onclick="closeAgentMetadataDrawer()"></div>
|
|
4568
|
+
<aside class="agent-drawer" id="agent-drawer" aria-hidden="true">
|
|
4569
|
+
<div class="agent-drawer-header">
|
|
4570
|
+
<div class="agent-drawer-header-main">
|
|
4571
|
+
<div class="agent-drawer-avatar" id="agent-drawer-avatar">?</div>
|
|
4572
|
+
<img class="agent-drawer-avatar-img" id="agent-drawer-avatar-img" src="data:," style="display:none">
|
|
4573
|
+
<div class="agent-drawer-title-wrap">
|
|
4574
|
+
<div class="agent-drawer-title" id="agent-drawer-title">Agent details</div>
|
|
4575
|
+
<div class="agent-drawer-subtitle" id="agent-drawer-subtitle"></div>
|
|
4576
|
+
</div>
|
|
4577
|
+
</div>
|
|
4578
|
+
<button class="agent-drawer-close" type="button" onclick="closeAgentMetadataDrawer()">×</button>
|
|
4579
|
+
</div>
|
|
4580
|
+
<div class="agent-drawer-body" id="agent-drawer-body"></div>
|
|
4581
|
+
</aside>
|
|
4582
|
+
|
|
3901
4583
|
<!-- Character Designer Panel -->
|
|
3902
4584
|
<div class="char-designer-backdrop" id="cd-backdrop" onclick="closeProfileEditor()"></div>
|
|
3903
4585
|
<div class="char-designer" id="char-designer">
|
|
@@ -4278,23 +4960,448 @@ var _lttToken = (function() {
|
|
|
4278
4960
|
try { var p = new URLSearchParams(window.location.search); var t = p.get('token'); if (t) sessionStorage.setItem('ltt-token', t); return sessionStorage.getItem('ltt-token') || ''; } catch(e) { return ''; }
|
|
4279
4961
|
})();
|
|
4280
4962
|
|
|
4281
|
-
function lttFetch(url, opts) {
|
|
4282
|
-
opts = opts || {};
|
|
4283
|
-
// Append LAN token to URL if present (needed for phone/LAN access)
|
|
4284
|
-
if (_lttToken) {
|
|
4285
|
-
var sep = url.indexOf('?') >= 0 ? '&' : '?';
|
|
4286
|
-
url = url + sep + 'token=' + encodeURIComponent(_lttToken);
|
|
4963
|
+
function lttFetch(url, opts) {
|
|
4964
|
+
opts = opts || {};
|
|
4965
|
+
// Append LAN token to URL if present (needed for phone/LAN access)
|
|
4966
|
+
if (_lttToken) {
|
|
4967
|
+
var sep = url.indexOf('?') >= 0 ? '&' : '?';
|
|
4968
|
+
url = url + sep + 'token=' + encodeURIComponent(_lttToken);
|
|
4969
|
+
}
|
|
4970
|
+
if (opts.method && opts.method !== 'GET') {
|
|
4971
|
+
if (!opts.headers) opts.headers = {};
|
|
4972
|
+
opts.headers['X-LTT-Request'] = '1';
|
|
4973
|
+
}
|
|
4974
|
+
return fetch(url, opts);
|
|
4975
|
+
}
|
|
4976
|
+
|
|
4977
|
+
var DASHBOARD_WORKSPACE_STATE_KEY = 'ltt-dashboard-workspace-state';
|
|
4978
|
+
var DASHBOARD_WORKSPACE_STATE_VERSION = 1;
|
|
4979
|
+
|
|
4980
|
+
function cloneJsonValue(value, fallback) {
|
|
4981
|
+
try {
|
|
4982
|
+
return JSON.parse(JSON.stringify(value));
|
|
4983
|
+
} catch (e) {
|
|
4984
|
+
return fallback;
|
|
4985
|
+
}
|
|
4986
|
+
}
|
|
4987
|
+
|
|
4988
|
+
function clonePlainObject(value) {
|
|
4989
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return {};
|
|
4990
|
+
return cloneJsonValue(value, {});
|
|
4991
|
+
}
|
|
4992
|
+
|
|
4993
|
+
function cleanString(value, fallback) {
|
|
4994
|
+
return typeof value === 'string' ? value : fallback;
|
|
4995
|
+
}
|
|
4996
|
+
|
|
4997
|
+
function buildDefaultDashboardLayoutSnapshot() {
|
|
4998
|
+
return {
|
|
4999
|
+
view: 'office',
|
|
5000
|
+
theme: 'dark',
|
|
5001
|
+
compactMode: false,
|
|
5002
|
+
project: '',
|
|
5003
|
+
branch: 'main',
|
|
5004
|
+
pinnedExpanded: true,
|
|
5005
|
+
agentFilters: {
|
|
5006
|
+
search: '',
|
|
5007
|
+
role: '',
|
|
5008
|
+
status: '',
|
|
5009
|
+
},
|
|
5010
|
+
collapsedRoleGroups: {},
|
|
5011
|
+
};
|
|
5012
|
+
}
|
|
5013
|
+
|
|
5014
|
+
function buildDefaultDashboardWorkspaceState() {
|
|
5015
|
+
return {
|
|
5016
|
+
version: DASHBOARD_WORKSPACE_STATE_VERSION,
|
|
5017
|
+
liveWorkspace: {
|
|
5018
|
+
name: 'Current browser layout',
|
|
5019
|
+
snapshot: buildDefaultDashboardLayoutSnapshot(),
|
|
5020
|
+
updatedAt: new Date().toISOString(),
|
|
5021
|
+
},
|
|
5022
|
+
savedWorkspaces: [],
|
|
5023
|
+
selectedWorkspaceId: '',
|
|
5024
|
+
preferences: {
|
|
5025
|
+
notificationsEnabled: false,
|
|
5026
|
+
bookmarks: {},
|
|
5027
|
+
pins: {},
|
|
5028
|
+
reactions: {},
|
|
5029
|
+
},
|
|
5030
|
+
};
|
|
5031
|
+
}
|
|
5032
|
+
|
|
5033
|
+
function normalizeDashboardLayoutSnapshot(rawSnapshot) {
|
|
5034
|
+
var defaults = buildDefaultDashboardLayoutSnapshot();
|
|
5035
|
+
var snapshot = rawSnapshot && typeof rawSnapshot === 'object' ? rawSnapshot : {};
|
|
5036
|
+
var agentFilters = snapshot.agentFilters && typeof snapshot.agentFilters === 'object' ? snapshot.agentFilters : {};
|
|
5037
|
+
var collapsedRoleGroups = snapshot.collapsedRoleGroups && typeof snapshot.collapsedRoleGroups === 'object' ? snapshot.collapsedRoleGroups : {};
|
|
5038
|
+
var allowedViews = {
|
|
5039
|
+
office: true,
|
|
5040
|
+
messages: true,
|
|
5041
|
+
tasks: true,
|
|
5042
|
+
workspaces: true,
|
|
5043
|
+
workflows: true,
|
|
5044
|
+
graph: true,
|
|
5045
|
+
plan: true,
|
|
5046
|
+
launch: true,
|
|
5047
|
+
rules: true,
|
|
5048
|
+
stats: true,
|
|
5049
|
+
services: true,
|
|
5050
|
+
docs: true,
|
|
5051
|
+
};
|
|
5052
|
+
var normalizedCollapsedGroups = {};
|
|
5053
|
+
for (var key in collapsedRoleGroups) {
|
|
5054
|
+
if (!Object.prototype.hasOwnProperty.call(collapsedRoleGroups, key)) continue;
|
|
5055
|
+
normalizedCollapsedGroups[key] = !!collapsedRoleGroups[key];
|
|
5056
|
+
}
|
|
5057
|
+
return {
|
|
5058
|
+
view: allowedViews[snapshot.view] ? snapshot.view : defaults.view,
|
|
5059
|
+
theme: snapshot.theme === 'light' ? 'light' : defaults.theme,
|
|
5060
|
+
compactMode: !!snapshot.compactMode,
|
|
5061
|
+
project: cleanString(snapshot.project, defaults.project),
|
|
5062
|
+
branch: cleanString(snapshot.branch, defaults.branch) || defaults.branch,
|
|
5063
|
+
pinnedExpanded: snapshot.pinnedExpanded !== false,
|
|
5064
|
+
agentFilters: {
|
|
5065
|
+
search: cleanString(agentFilters.search, defaults.agentFilters.search),
|
|
5066
|
+
role: cleanString(agentFilters.role, defaults.agentFilters.role),
|
|
5067
|
+
status: cleanString(agentFilters.status, defaults.agentFilters.status),
|
|
5068
|
+
},
|
|
5069
|
+
collapsedRoleGroups: normalizedCollapsedGroups,
|
|
5070
|
+
};
|
|
5071
|
+
}
|
|
5072
|
+
|
|
5073
|
+
function createDashboardWorkspaceId(name) {
|
|
5074
|
+
var base = (name || 'workspace').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '') || 'workspace';
|
|
5075
|
+
return base + '-' + String(Date.now());
|
|
5076
|
+
}
|
|
5077
|
+
|
|
5078
|
+
function normalizeSavedDashboardWorkspace(entry, index) {
|
|
5079
|
+
var workspace = entry && typeof entry === 'object' ? entry : {};
|
|
5080
|
+
var workspaceName = cleanString(workspace.name, '').trim() || ('Workspace ' + (index + 1));
|
|
5081
|
+
return {
|
|
5082
|
+
id: cleanString(workspace.id, '').trim() || createDashboardWorkspaceId(workspaceName),
|
|
5083
|
+
name: workspaceName,
|
|
5084
|
+
snapshot: normalizeDashboardLayoutSnapshot(workspace.snapshot),
|
|
5085
|
+
updatedAt: cleanString(workspace.updatedAt, new Date().toISOString()),
|
|
5086
|
+
};
|
|
5087
|
+
}
|
|
5088
|
+
|
|
5089
|
+
function readLegacyDashboardMap(key) {
|
|
5090
|
+
try {
|
|
5091
|
+
var raw = localStorage.getItem(key);
|
|
5092
|
+
if (!raw) return {};
|
|
5093
|
+
var parsed = JSON.parse(raw);
|
|
5094
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
|
|
5095
|
+
} catch (e) {
|
|
5096
|
+
return {};
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
|
|
5100
|
+
function migrateLegacyDashboardWorkspaceState() {
|
|
5101
|
+
var state = buildDefaultDashboardWorkspaceState();
|
|
5102
|
+
try {
|
|
5103
|
+
var legacyTheme = localStorage.getItem('ltt-theme');
|
|
5104
|
+
if (legacyTheme === 'light' || legacyTheme === 'dark') state.liveWorkspace.snapshot.theme = legacyTheme;
|
|
5105
|
+
state.liveWorkspace.snapshot.compactMode = localStorage.getItem('compactMode') === 'true';
|
|
5106
|
+
state.liveWorkspace.snapshot.project = localStorage.getItem('ltt_last_project') || '';
|
|
5107
|
+
state.preferences.notificationsEnabled = localStorage.getItem('ltt-notif') === 'true';
|
|
5108
|
+
state.preferences.bookmarks = readLegacyDashboardMap('ltt-bookmarks');
|
|
5109
|
+
state.preferences.pins = readLegacyDashboardMap('ltt-pins');
|
|
5110
|
+
state.preferences.reactions = readLegacyDashboardMap('ltt-reactions');
|
|
5111
|
+
} catch (e) {}
|
|
5112
|
+
return state;
|
|
5113
|
+
}
|
|
5114
|
+
|
|
5115
|
+
function normalizeDashboardWorkspaceState(rawState) {
|
|
5116
|
+
var defaults = buildDefaultDashboardWorkspaceState();
|
|
5117
|
+
var state = rawState && typeof rawState === 'object' ? rawState : {};
|
|
5118
|
+
var liveWorkspace = state.liveWorkspace && typeof state.liveWorkspace === 'object' ? state.liveWorkspace : {};
|
|
5119
|
+
var preferences = state.preferences && typeof state.preferences === 'object' ? state.preferences : {};
|
|
5120
|
+
var savedRaw = Array.isArray(state.savedWorkspaces) ? state.savedWorkspaces : [];
|
|
5121
|
+
var normalizedSavedWorkspaces = [];
|
|
5122
|
+
var seenIds = {};
|
|
5123
|
+
for (var i = 0; i < savedRaw.length; i++) {
|
|
5124
|
+
var normalizedWorkspace = normalizeSavedDashboardWorkspace(savedRaw[i], i);
|
|
5125
|
+
while (seenIds[normalizedWorkspace.id]) {
|
|
5126
|
+
normalizedWorkspace.id = createDashboardWorkspaceId(normalizedWorkspace.name);
|
|
5127
|
+
}
|
|
5128
|
+
seenIds[normalizedWorkspace.id] = true;
|
|
5129
|
+
normalizedSavedWorkspaces.push(normalizedWorkspace);
|
|
5130
|
+
}
|
|
5131
|
+
var selectedWorkspaceId = cleanString(state.selectedWorkspaceId, '');
|
|
5132
|
+
if (selectedWorkspaceId && !seenIds[selectedWorkspaceId]) selectedWorkspaceId = '';
|
|
5133
|
+
return {
|
|
5134
|
+
version: DASHBOARD_WORKSPACE_STATE_VERSION,
|
|
5135
|
+
liveWorkspace: {
|
|
5136
|
+
name: cleanString(liveWorkspace.name, defaults.liveWorkspace.name),
|
|
5137
|
+
snapshot: normalizeDashboardLayoutSnapshot(liveWorkspace.snapshot),
|
|
5138
|
+
updatedAt: cleanString(liveWorkspace.updatedAt, defaults.liveWorkspace.updatedAt),
|
|
5139
|
+
},
|
|
5140
|
+
savedWorkspaces: normalizedSavedWorkspaces,
|
|
5141
|
+
selectedWorkspaceId: selectedWorkspaceId,
|
|
5142
|
+
preferences: {
|
|
5143
|
+
notificationsEnabled: !!preferences.notificationsEnabled,
|
|
5144
|
+
bookmarks: clonePlainObject(preferences.bookmarks),
|
|
5145
|
+
pins: clonePlainObject(preferences.pins),
|
|
5146
|
+
reactions: clonePlainObject(preferences.reactions),
|
|
5147
|
+
},
|
|
5148
|
+
};
|
|
5149
|
+
}
|
|
5150
|
+
|
|
5151
|
+
function loadDashboardWorkspaceState() {
|
|
5152
|
+
try {
|
|
5153
|
+
var raw = localStorage.getItem(DASHBOARD_WORKSPACE_STATE_KEY);
|
|
5154
|
+
if (!raw) return normalizeDashboardWorkspaceState(migrateLegacyDashboardWorkspaceState());
|
|
5155
|
+
return normalizeDashboardWorkspaceState(JSON.parse(raw));
|
|
5156
|
+
} catch (e) {
|
|
5157
|
+
return normalizeDashboardWorkspaceState(migrateLegacyDashboardWorkspaceState());
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
|
|
5161
|
+
function saveDashboardWorkspaceState() {
|
|
5162
|
+
try {
|
|
5163
|
+
localStorage.setItem(DASHBOARD_WORKSPACE_STATE_KEY, JSON.stringify(dashboardWorkspaceState));
|
|
5164
|
+
} catch (e) {}
|
|
5165
|
+
}
|
|
5166
|
+
|
|
5167
|
+
var dashboardWorkspaceState = loadDashboardWorkspaceState();
|
|
5168
|
+
|
|
5169
|
+
function getActiveDashboardBranchName() {
|
|
5170
|
+
return activeBranch && activeBranch !== 'main' ? activeBranch : 'main';
|
|
5171
|
+
}
|
|
5172
|
+
|
|
5173
|
+
function captureCurrentDashboardLayoutSnapshot() {
|
|
5174
|
+
return normalizeDashboardLayoutSnapshot({
|
|
5175
|
+
view: activeView,
|
|
5176
|
+
theme: currentTheme,
|
|
5177
|
+
compactMode: compactMode,
|
|
5178
|
+
project: activeProject || '',
|
|
5179
|
+
branch: getActiveDashboardBranchName(),
|
|
5180
|
+
pinnedExpanded: pinnedExpanded,
|
|
5181
|
+
agentFilters: {
|
|
5182
|
+
search: agentFilterSearch || '',
|
|
5183
|
+
role: agentFilterRole || '',
|
|
5184
|
+
status: agentFilterStatus || '',
|
|
5185
|
+
},
|
|
5186
|
+
collapsedRoleGroups: clonePlainObject(collapsedRoleGroups),
|
|
5187
|
+
});
|
|
5188
|
+
}
|
|
5189
|
+
|
|
5190
|
+
function syncDashboardWorkspacePreferences() {
|
|
5191
|
+
dashboardWorkspaceState.preferences.notificationsEnabled = !!notifEnabled;
|
|
5192
|
+
dashboardWorkspaceState.preferences.bookmarks = clonePlainObject(bookmarks);
|
|
5193
|
+
dashboardWorkspaceState.preferences.pins = clonePlainObject(pins);
|
|
5194
|
+
dashboardWorkspaceState.preferences.reactions = clonePlainObject(reactions);
|
|
5195
|
+
}
|
|
5196
|
+
|
|
5197
|
+
function persistLiveDashboardLayout() {
|
|
5198
|
+
dashboardWorkspaceState.liveWorkspace.snapshot = captureCurrentDashboardLayoutSnapshot();
|
|
5199
|
+
dashboardWorkspaceState.liveWorkspace.updatedAt = new Date().toISOString();
|
|
5200
|
+
saveDashboardWorkspaceState();
|
|
5201
|
+
refreshDashboardWorkspaceControls();
|
|
5202
|
+
}
|
|
5203
|
+
|
|
5204
|
+
function persistDashboardPreferences() {
|
|
5205
|
+
syncDashboardWorkspacePreferences();
|
|
5206
|
+
saveDashboardWorkspaceState();
|
|
5207
|
+
}
|
|
5208
|
+
|
|
5209
|
+
function findSavedDashboardWorkspaceById(workspaceId) {
|
|
5210
|
+
if (!workspaceId) return null;
|
|
5211
|
+
for (var i = 0; i < dashboardWorkspaceState.savedWorkspaces.length; i++) {
|
|
5212
|
+
if (dashboardWorkspaceState.savedWorkspaces[i].id === workspaceId) return dashboardWorkspaceState.savedWorkspaces[i];
|
|
5213
|
+
}
|
|
5214
|
+
return null;
|
|
5215
|
+
}
|
|
5216
|
+
|
|
5217
|
+
function applyAgentFilterInputs() {
|
|
5218
|
+
var searchEl = document.getElementById('agent-search');
|
|
5219
|
+
var roleEl = document.getElementById('agent-role-filter');
|
|
5220
|
+
var statusEl = document.getElementById('agent-status-filter');
|
|
5221
|
+
if (searchEl) searchEl.value = agentFilterSearch || '';
|
|
5222
|
+
if (roleEl) roleEl.value = agentFilterRole || '';
|
|
5223
|
+
if (statusEl) statusEl.value = agentFilterStatus || '';
|
|
5224
|
+
}
|
|
5225
|
+
|
|
5226
|
+
function syncProjectSelectionUI() {
|
|
5227
|
+
var sidebarSel = document.getElementById('project-select');
|
|
5228
|
+
var mobileSel = document.getElementById('mobile-project-select');
|
|
5229
|
+
var removeBtn = document.getElementById('remove-project-btn');
|
|
5230
|
+
var indicator = document.getElementById('mobile-project-name');
|
|
5231
|
+
var activeLabel = '';
|
|
5232
|
+
|
|
5233
|
+
if (sidebarSel) {
|
|
5234
|
+
sidebarSel.value = activeProject || '';
|
|
5235
|
+
if (sidebarSel.value !== (activeProject || '')) activeProject = sidebarSel.value || '';
|
|
5236
|
+
if (sidebarSel.selectedIndex >= 0 && sidebarSel.options[sidebarSel.selectedIndex]) {
|
|
5237
|
+
activeLabel = sidebarSel.options[sidebarSel.selectedIndex].textContent;
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
|
|
5241
|
+
if (mobileSel) mobileSel.value = activeProject || '';
|
|
5242
|
+
if (removeBtn) removeBtn.style.display = activeProject ? '' : 'none';
|
|
5243
|
+
var reinstallBtn = document.getElementById('reinstall-providers-btn');
|
|
5244
|
+
if (reinstallBtn) reinstallBtn.style.display = activeProject ? '' : 'none';
|
|
5245
|
+
|
|
5246
|
+
if (indicator) {
|
|
5247
|
+
indicator.textContent = activeProject ? activeLabel : '';
|
|
5248
|
+
indicator.style.display = activeProject ? '' : 'none';
|
|
5249
|
+
}
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5252
|
+
function applyPinnedSectionState() {
|
|
5253
|
+
var list = document.getElementById('pinned-list');
|
|
5254
|
+
var toggle = document.getElementById('pinned-toggle');
|
|
5255
|
+
if (list) list.style.display = pinnedExpanded ? '' : 'none';
|
|
5256
|
+
if (toggle) toggle.textContent = pinnedExpanded ? 'Hide' : 'Show';
|
|
5257
|
+
}
|
|
5258
|
+
|
|
5259
|
+
function refreshDashboardWorkspaceControls() {
|
|
5260
|
+
var select = document.getElementById('dashboard-workspace-select');
|
|
5261
|
+
var loadBtn = document.getElementById('dashboard-workspace-load');
|
|
5262
|
+
if (!select) return;
|
|
5263
|
+
|
|
5264
|
+
var options = ['<option value="">Current layout</option>'];
|
|
5265
|
+
for (var i = 0; i < dashboardWorkspaceState.savedWorkspaces.length; i++) {
|
|
5266
|
+
var workspace = dashboardWorkspaceState.savedWorkspaces[i];
|
|
5267
|
+
options.push('<option value="' + escapeHtml(workspace.id) + '">' + escapeHtml(workspace.name) + '</option>');
|
|
5268
|
+
}
|
|
5269
|
+
select.innerHTML = options.join('');
|
|
5270
|
+
select.value = dashboardWorkspaceState.selectedWorkspaceId || '';
|
|
5271
|
+
if (loadBtn) loadBtn.disabled = !dashboardWorkspaceState.selectedWorkspaceId;
|
|
5272
|
+
}
|
|
5273
|
+
|
|
5274
|
+
function onDashboardWorkspaceSelection() {
|
|
5275
|
+
var select = document.getElementById('dashboard-workspace-select');
|
|
5276
|
+
dashboardWorkspaceState.selectedWorkspaceId = select ? select.value : '';
|
|
5277
|
+
persistLiveDashboardLayout();
|
|
5278
|
+
}
|
|
5279
|
+
|
|
5280
|
+
function buildSuggestedDashboardWorkspaceName() {
|
|
5281
|
+
var projectName = activeProject ? activeProject.split(/[/\\]/).pop() : 'dashboard';
|
|
5282
|
+
var viewName = activeView === 'office' ? '3d-hub' : activeView;
|
|
5283
|
+
return (projectName + ' ' + viewName).replace(/\s+/g, ' ').trim();
|
|
5284
|
+
}
|
|
5285
|
+
|
|
5286
|
+
function saveNamedDashboardWorkspace() {
|
|
5287
|
+
var selectedWorkspace = findSavedDashboardWorkspaceById(dashboardWorkspaceState.selectedWorkspaceId);
|
|
5288
|
+
var suggestedName = selectedWorkspace ? selectedWorkspace.name : buildSuggestedDashboardWorkspaceName();
|
|
5289
|
+
var providedName = window.prompt('Save current dashboard layout as:', suggestedName);
|
|
5290
|
+
if (providedName === null) return;
|
|
5291
|
+
|
|
5292
|
+
var workspaceName = providedName.trim();
|
|
5293
|
+
if (!workspaceName) {
|
|
5294
|
+
showToast('Workspace name cannot be empty.');
|
|
5295
|
+
return;
|
|
5296
|
+
}
|
|
5297
|
+
|
|
5298
|
+
var normalizedName = workspaceName.toLowerCase();
|
|
5299
|
+
var snapshot = captureCurrentDashboardLayoutSnapshot();
|
|
5300
|
+
var overwriteWorkspace = null;
|
|
5301
|
+
for (var i = 0; i < dashboardWorkspaceState.savedWorkspaces.length; i++) {
|
|
5302
|
+
var existingWorkspace = dashboardWorkspaceState.savedWorkspaces[i];
|
|
5303
|
+
if (existingWorkspace.name.toLowerCase() !== normalizedName) continue;
|
|
5304
|
+
overwriteWorkspace = existingWorkspace;
|
|
5305
|
+
break;
|
|
5306
|
+
}
|
|
5307
|
+
|
|
5308
|
+
if (overwriteWorkspace && (!selectedWorkspace || overwriteWorkspace.id !== selectedWorkspace.id)) {
|
|
5309
|
+
if (!confirm('Overwrite the saved workspace "' + overwriteWorkspace.name + '"?')) return;
|
|
5310
|
+
}
|
|
5311
|
+
|
|
5312
|
+
var targetId = '';
|
|
5313
|
+
if (overwriteWorkspace) targetId = overwriteWorkspace.id;
|
|
5314
|
+
else if (selectedWorkspace && selectedWorkspace.name.toLowerCase() === normalizedName) targetId = selectedWorkspace.id;
|
|
5315
|
+
else targetId = createDashboardWorkspaceId(workspaceName);
|
|
5316
|
+
|
|
5317
|
+
var savedWorkspace = normalizeSavedDashboardWorkspace({
|
|
5318
|
+
id: targetId,
|
|
5319
|
+
name: workspaceName,
|
|
5320
|
+
snapshot: snapshot,
|
|
5321
|
+
updatedAt: new Date().toISOString(),
|
|
5322
|
+
}, dashboardWorkspaceState.savedWorkspaces.length);
|
|
5323
|
+
|
|
5324
|
+
var didUpdate = false;
|
|
5325
|
+
var nextSavedWorkspaces = [];
|
|
5326
|
+
for (var j = 0; j < dashboardWorkspaceState.savedWorkspaces.length; j++) {
|
|
5327
|
+
var candidate = dashboardWorkspaceState.savedWorkspaces[j];
|
|
5328
|
+
if (candidate.id === savedWorkspace.id) {
|
|
5329
|
+
nextSavedWorkspaces.push(savedWorkspace);
|
|
5330
|
+
didUpdate = true;
|
|
5331
|
+
continue;
|
|
5332
|
+
}
|
|
5333
|
+
nextSavedWorkspaces.push(candidate);
|
|
5334
|
+
}
|
|
5335
|
+
if (!didUpdate) nextSavedWorkspaces.push(savedWorkspace);
|
|
5336
|
+
|
|
5337
|
+
dashboardWorkspaceState.savedWorkspaces = nextSavedWorkspaces;
|
|
5338
|
+
dashboardWorkspaceState.selectedWorkspaceId = savedWorkspace.id;
|
|
5339
|
+
dashboardWorkspaceState.liveWorkspace.snapshot = snapshot;
|
|
5340
|
+
dashboardWorkspaceState.liveWorkspace.updatedAt = new Date().toISOString();
|
|
5341
|
+
saveDashboardWorkspaceState();
|
|
5342
|
+
refreshDashboardWorkspaceControls();
|
|
5343
|
+
showToast((didUpdate ? 'Updated' : 'Saved') + ' workspace "' + savedWorkspace.name + '"');
|
|
5344
|
+
}
|
|
5345
|
+
|
|
5346
|
+
function applyDashboardLayoutSnapshot(rawSnapshot, options) {
|
|
5347
|
+
var opts = options || {};
|
|
5348
|
+
var snapshot = normalizeDashboardLayoutSnapshot(rawSnapshot);
|
|
5349
|
+
|
|
5350
|
+
activeProject = snapshot.project || '';
|
|
5351
|
+
activeBranch = snapshot.branch || 'main';
|
|
5352
|
+
agentFilterSearch = snapshot.agentFilters.search;
|
|
5353
|
+
agentFilterRole = snapshot.agentFilters.role;
|
|
5354
|
+
agentFilterStatus = snapshot.agentFilters.status;
|
|
5355
|
+
collapsedRoleGroups = clonePlainObject(snapshot.collapsedRoleGroups);
|
|
5356
|
+
pinnedExpanded = snapshot.pinnedExpanded !== false;
|
|
5357
|
+
|
|
5358
|
+
applyTheme(snapshot.theme, { skipPersist: true });
|
|
5359
|
+
applyCompactMode(snapshot.compactMode);
|
|
5360
|
+
applyAgentFilterInputs();
|
|
5361
|
+
applyPinnedSectionState();
|
|
5362
|
+
syncProjectSelectionUI();
|
|
5363
|
+
|
|
5364
|
+
if (Object.prototype.hasOwnProperty.call(opts, 'selectedWorkspaceId')) {
|
|
5365
|
+
dashboardWorkspaceState.selectedWorkspaceId = opts.selectedWorkspaceId || '';
|
|
5366
|
+
}
|
|
5367
|
+
|
|
5368
|
+
if (cachedAgents && Object.keys(cachedAgents).length) renderAgents(cachedAgents);
|
|
5369
|
+
renderPinnedMessages();
|
|
5370
|
+
switchView(snapshot.view, { skipPersist: true });
|
|
5371
|
+
|
|
5372
|
+
dashboardWorkspaceState.liveWorkspace.snapshot = captureCurrentDashboardLayoutSnapshot();
|
|
5373
|
+
dashboardWorkspaceState.liveWorkspace.updatedAt = new Date().toISOString();
|
|
5374
|
+
|
|
5375
|
+
lastMessageCount = 0;
|
|
5376
|
+
lastRenderedIds = [];
|
|
5377
|
+
refreshDashboardWorkspaceControls();
|
|
5378
|
+
|
|
5379
|
+
if (!opts.skipPersist) saveDashboardWorkspaceState();
|
|
5380
|
+
if (!opts.skipDataRefresh) {
|
|
5381
|
+
loadConversationList();
|
|
5382
|
+
poll();
|
|
4287
5383
|
}
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
5384
|
+
}
|
|
5385
|
+
|
|
5386
|
+
function loadNamedDashboardWorkspace() {
|
|
5387
|
+
var selectedWorkspace = findSavedDashboardWorkspaceById(dashboardWorkspaceState.selectedWorkspaceId);
|
|
5388
|
+
if (!selectedWorkspace) {
|
|
5389
|
+
dashboardWorkspaceState.selectedWorkspaceId = '';
|
|
5390
|
+
saveDashboardWorkspaceState();
|
|
5391
|
+
refreshDashboardWorkspaceControls();
|
|
5392
|
+
showToast('Select a saved workspace first.');
|
|
5393
|
+
return;
|
|
4291
5394
|
}
|
|
4292
|
-
|
|
5395
|
+
|
|
5396
|
+
applyDashboardLayoutSnapshot(selectedWorkspace.snapshot, {
|
|
5397
|
+
selectedWorkspaceId: selectedWorkspace.id,
|
|
5398
|
+
});
|
|
5399
|
+
showToast('Loaded workspace "' + selectedWorkspace.name + '"');
|
|
4293
5400
|
}
|
|
4294
5401
|
|
|
4295
5402
|
var activeThread = null;
|
|
4296
5403
|
var activeChannel = null; // null = all channels
|
|
4297
|
-
var activeProject = ''; // empty = default/local
|
|
5404
|
+
var activeProject = dashboardWorkspaceState.liveWorkspace.snapshot.project || ''; // empty = default/local
|
|
4298
5405
|
var cachedHistory = [];
|
|
4299
5406
|
var cachedAgents = {};
|
|
4300
5407
|
var msgPage = 0; // 0 = latest (default poll), 1+ = paginated older messages
|
|
@@ -4533,21 +5640,23 @@ function getProviderBadge(provider) {
|
|
|
4533
5640
|
return '<span style="color:' + c + ';font-size:9px;font-weight:600;margin-right:4px">' + escapeHtml(provider) + '</span>';
|
|
4534
5641
|
}
|
|
4535
5642
|
|
|
4536
|
-
var agentFilterSearch = '';
|
|
4537
|
-
var agentFilterRole = '';
|
|
4538
|
-
var agentFilterStatus = '';
|
|
4539
|
-
var collapsedRoleGroups =
|
|
5643
|
+
var agentFilterSearch = dashboardWorkspaceState.liveWorkspace.snapshot.agentFilters.search || '';
|
|
5644
|
+
var agentFilterRole = dashboardWorkspaceState.liveWorkspace.snapshot.agentFilters.role || '';
|
|
5645
|
+
var agentFilterStatus = dashboardWorkspaceState.liveWorkspace.snapshot.agentFilters.status || '';
|
|
5646
|
+
var collapsedRoleGroups = clonePlainObject(dashboardWorkspaceState.liveWorkspace.snapshot.collapsedRoleGroups);
|
|
4540
5647
|
|
|
4541
5648
|
function filterAgents() {
|
|
4542
5649
|
agentFilterSearch = (document.getElementById('agent-search').value || '').toLowerCase();
|
|
4543
5650
|
agentFilterRole = document.getElementById('agent-role-filter').value;
|
|
4544
5651
|
agentFilterStatus = document.getElementById('agent-status-filter').value;
|
|
4545
5652
|
renderAgents(cachedAgents);
|
|
5653
|
+
persistLiveDashboardLayout();
|
|
4546
5654
|
}
|
|
4547
5655
|
|
|
4548
5656
|
function toggleRoleGroup(role) {
|
|
4549
5657
|
collapsedRoleGroups[role] = !collapsedRoleGroups[role];
|
|
4550
5658
|
renderAgents(cachedAgents);
|
|
5659
|
+
persistLiveDashboardLayout();
|
|
4551
5660
|
}
|
|
4552
5661
|
|
|
4553
5662
|
function renderAgents(agents) {
|
|
@@ -4564,6 +5673,8 @@ function renderAgents(agents) {
|
|
|
4564
5673
|
var fname = keys[f];
|
|
4565
5674
|
var finfo = agents[fname];
|
|
4566
5675
|
var fstate = finfo.status || (finfo.alive ? 'active' : 'dead');
|
|
5676
|
+
// Hide API agents unless they are actively running (user clicked Start)
|
|
5677
|
+
if (finfo.is_api_agent && fstate !== 'active') continue;
|
|
4567
5678
|
if (agentFilterSearch && fname.toLowerCase().indexOf(agentFilterSearch) === -1 &&
|
|
4568
5679
|
(!finfo.display_name || finfo.display_name.toLowerCase().indexOf(agentFilterSearch) === -1)) continue;
|
|
4569
5680
|
if (agentFilterRole && finfo.role !== agentFilterRole) continue;
|
|
@@ -4577,9 +5688,9 @@ function renderAgents(agents) {
|
|
|
4577
5688
|
}
|
|
4578
5689
|
|
|
4579
5690
|
// Group by role
|
|
4580
|
-
var ROLE_ORDER = ['lead', 'backend', 'frontend', 'quality', 'monitor', 'advisor', ''];
|
|
4581
|
-
var ROLE_LABELS = { lead: 'Lead', backend: 'Backend', frontend: 'Frontend', quality: 'Quality', monitor: 'Monitor', advisor: 'Advisor', '': 'Unassigned' };
|
|
4582
|
-
var ROLE_COLORS = { lead: 'var(--accent)', backend: '#f59e0b', frontend: '#8b5cf6', quality: 'var(--green)', monitor: 'var(--red,#ef4444)', advisor: 'var(--purple,#a855f7)', '': 'var(--text-muted)' };
|
|
5691
|
+
var ROLE_ORDER = ['lead', 'backend', 'frontend', 'quality', 'monitor', 'advisor', 'api-agent', ''];
|
|
5692
|
+
var ROLE_LABELS = { lead: 'Lead', backend: 'Backend', frontend: 'Frontend', quality: 'Quality', monitor: 'Monitor', advisor: 'Advisor', 'api-agent': 'API Agents', '': 'Unassigned' };
|
|
5693
|
+
var ROLE_COLORS = { lead: 'var(--accent)', backend: '#f59e0b', frontend: '#8b5cf6', quality: 'var(--green)', monitor: 'var(--red,#ef4444)', advisor: 'var(--purple,#a855f7)', 'api-agent': '#0ea5e9', '': 'var(--text-muted)' };
|
|
4583
5694
|
var roleGroups = {};
|
|
4584
5695
|
for (var g = 0; g < filtered.length; g++) {
|
|
4585
5696
|
var gname = filtered[g];
|
|
@@ -4649,7 +5760,10 @@ function renderAgents(agents) {
|
|
|
4649
5760
|
: '<div class="agent-avatar" style="background:' + color + '">' + initial(name) + '</div>';
|
|
4650
5761
|
var displayName = info.display_name || name;
|
|
4651
5762
|
var roleHtml = '';
|
|
4652
|
-
if (info.
|
|
5763
|
+
if (info.is_api_agent) {
|
|
5764
|
+
var botColor = info.provider_color || '#0ea5e9';
|
|
5765
|
+
roleHtml = '<span class="role-badge" style="background:' + botColor + '22;color:' + botColor + '">BOT</span>';
|
|
5766
|
+
} else if (info.role) {
|
|
4653
5767
|
var roleBg = info.role === 'quality' ? 'background:rgba(34,197,94,0.15);color:var(--green)' :
|
|
4654
5768
|
info.role === 'monitor' ? 'background:rgba(239,68,68,0.15);color:var(--red,#ef4444)' :
|
|
4655
5769
|
info.role === 'advisor' ? 'background:rgba(168,85,247,0.15);color:var(--purple,#a855f7)' :
|
|
@@ -4720,8 +5834,127 @@ function renderAgents(agents) {
|
|
|
4720
5834
|
alertEl.style.display = 'none';
|
|
4721
5835
|
}
|
|
4722
5836
|
|
|
4723
|
-
// Update inject target dropdown
|
|
4724
|
-
updateInjectTargets(
|
|
5837
|
+
// Update inject target dropdown from the visible, live branch-local agent set
|
|
5838
|
+
updateInjectTargets(getEligibleInjectTargets(filtered, agents));
|
|
5839
|
+
}
|
|
5840
|
+
|
|
5841
|
+
function getServiceAgentEntries() {
|
|
5842
|
+
var branchName = getActiveDashboardBranchName();
|
|
5843
|
+
var keys = Object.keys(cachedAgents || {}).sort();
|
|
5844
|
+
var entries = [];
|
|
5845
|
+
for (var i = 0; i < keys.length; i++) {
|
|
5846
|
+
var name = keys[i];
|
|
5847
|
+
var info = cachedAgents[name] || {};
|
|
5848
|
+
if (!info.is_api_agent) continue;
|
|
5849
|
+
var agentBranch = cleanString(info.branch, 'main') || 'main';
|
|
5850
|
+
if (agentBranch !== branchName) continue;
|
|
5851
|
+
entries.push({ name: name, info: info });
|
|
5852
|
+
}
|
|
5853
|
+
return entries;
|
|
5854
|
+
}
|
|
5855
|
+
|
|
5856
|
+
function renderServices() {
|
|
5857
|
+
var el = document.getElementById('services-area');
|
|
5858
|
+
if (!el) return;
|
|
5859
|
+
|
|
5860
|
+
var branchName = getActiveDashboardBranchName();
|
|
5861
|
+
var projectLabel = activeProject ? activeProject.split(/[/\\]/).pop() : 'default project';
|
|
5862
|
+
var entries = getServiceAgentEntries();
|
|
5863
|
+
var activeCount = 0;
|
|
5864
|
+
var sleepingCount = 0;
|
|
5865
|
+
var offlineCount = 0;
|
|
5866
|
+
|
|
5867
|
+
for (var i = 0; i < entries.length; i++) {
|
|
5868
|
+
var state = entries[i].info.status || (entries[i].info.alive ? 'active' : 'dead');
|
|
5869
|
+
if (state === 'active') activeCount++;
|
|
5870
|
+
else if (state === 'sleeping') sleepingCount++;
|
|
5871
|
+
else offlineCount++;
|
|
5872
|
+
}
|
|
5873
|
+
|
|
5874
|
+
var html = '<div style="padding:20px">' +
|
|
5875
|
+
'<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:18px">' +
|
|
5876
|
+
'<div>' +
|
|
5877
|
+
'<div style="font-size:20px;font-weight:700;color:var(--text);margin-bottom:4px">Services</div>' +
|
|
5878
|
+
'<div style="font-size:12px;color:var(--text-dim)">API-backed agents for <strong style="color:var(--text)">' + escapeHtml(projectLabel) + '</strong> on branch <code style="background:var(--surface-2);padding:2px 6px;border-radius:4px;font-size:11px;color:var(--orange)">' + escapeHtml(branchName) + '</code>.</div>' +
|
|
5879
|
+
'</div>' +
|
|
5880
|
+
'<div style="display:flex;gap:8px;flex-wrap:wrap">' +
|
|
5881
|
+
'<span class="agent-detail-pill">Services <strong>' + entries.length + '</strong></span>' +
|
|
5882
|
+
'<span class="agent-detail-pill">Active <strong>' + activeCount + '</strong></span>' +
|
|
5883
|
+
'<span class="agent-detail-pill">Sleeping <strong>' + sleepingCount + '</strong></span>' +
|
|
5884
|
+
'<span class="agent-detail-pill">Offline <strong>' + offlineCount + '</strong></span>' +
|
|
5885
|
+
'</div>' +
|
|
5886
|
+
'</div>';
|
|
5887
|
+
|
|
5888
|
+
if (!entries.length) {
|
|
5889
|
+
html += '<div class="tasks-empty" style="padding:48px 24px">No API service agents are registered for this branch yet.<br><span style="display:block;margin-top:8px;font-size:11px">Use Launch to start one in the current project or switch branches to inspect another runtime slice.</span><button class="btn" type="button" style="margin-top:16px" onclick="switchView(\'launch\')">Open Launch</button></div>';
|
|
5890
|
+
html += '</div>';
|
|
5891
|
+
el.innerHTML = html;
|
|
5892
|
+
return;
|
|
5893
|
+
}
|
|
5894
|
+
|
|
5895
|
+
html += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:12px">';
|
|
5896
|
+
for (var j = 0; j < entries.length; j++) {
|
|
5897
|
+
var name = entries[j].name;
|
|
5898
|
+
var info = entries[j].info;
|
|
5899
|
+
var color = info.provider_color || getColor(name);
|
|
5900
|
+
var state = info.status || (info.alive ? 'active' : 'dead');
|
|
5901
|
+
var stateLabel = state.charAt(0).toUpperCase() + state.slice(1);
|
|
5902
|
+
var displayName = info.display_name || name;
|
|
5903
|
+
var capabilities = Array.isArray(info.capabilities) ? info.capabilities : [];
|
|
5904
|
+
var runtimeLabel = info.runtime_type || 'api-agent';
|
|
5905
|
+
var modelLabel = info.model_id || info.provider_id || info.provider || 'unknown';
|
|
5906
|
+
var lastSeenLabel = info.last_activity ? timeAgo(info.last_activity) : 'No heartbeat';
|
|
5907
|
+
var capHtml = '';
|
|
5908
|
+
if (capabilities.length) {
|
|
5909
|
+
for (var c = 0; c < capabilities.length; c++) {
|
|
5910
|
+
capHtml += '<span class="agent-detail-pill">' + escapeHtml(capabilities[c].replace(/_/g, ' ')) + '</span>';
|
|
5911
|
+
}
|
|
5912
|
+
} else {
|
|
5913
|
+
capHtml = '<span class="agent-detail-pill">No capabilities declared</span>';
|
|
5914
|
+
}
|
|
5915
|
+
|
|
5916
|
+
html += '<div class="agent-card' + (state !== 'active' ? ' ' + state : '') + '">';
|
|
5917
|
+
html += '<div class="agent-top" style="cursor:pointer" onclick="openAgentMetadataDrawer(\'' + name + '\')">';
|
|
5918
|
+
if (info.avatar) {
|
|
5919
|
+
html += '<img class="agent-avatar-img" src="' + escapeHtml(info.avatar) + '" alt="' + escapeHtml(name) + '" onerror="this.style.display=\'none\'">';
|
|
5920
|
+
} else {
|
|
5921
|
+
html += '<div class="agent-avatar" style="background:' + color + '">' + initial(name) + '</div>';
|
|
5922
|
+
}
|
|
5923
|
+
html += '<div class="agent-info">' +
|
|
5924
|
+
'<div class="agent-name" style="color:' + color + '">' + escapeHtml(displayName) + ' <span class="agent-badge ' + state + '">' + escapeHtml(stateLabel) + '</span></div>' +
|
|
5925
|
+
'<div class="agent-meta"><span>' + getProviderBadge(info.provider) + escapeHtml(runtimeLabel) + '</span><span style="margin-left:8px">' + escapeHtml(modelLabel) + '</span></div>' +
|
|
5926
|
+
'</div>' +
|
|
5927
|
+
'</div>';
|
|
5928
|
+
html += '<div class="agent-activity"><span class="agent-activity-icon ' + state + '"></span>' + escapeHtml(lastSeenLabel) + '</div>';
|
|
5929
|
+
if (info.current_status) {
|
|
5930
|
+
html += '<div class="agent-status-intent" title="' + escapeHtml(info.current_status) + '">' + escapeHtml(info.current_status) + '</div>';
|
|
5931
|
+
}
|
|
5932
|
+
html += '<div style="display:flex;flex-wrap:wrap;gap:6px;margin:10px 0">' + capHtml + '</div>';
|
|
5933
|
+
html += '<div style="display:flex;gap:8px;flex-wrap:wrap">';
|
|
5934
|
+
if (state === 'active') {
|
|
5935
|
+
html += '<button class="btn btn-primary" type="button" onclick="focusInjectForAgent(\'' + name + '\')">Message</button>';
|
|
5936
|
+
} else {
|
|
5937
|
+
html += '<button class="btn" type="button" onclick="switchView(\'launch\')">Open Launch</button>';
|
|
5938
|
+
}
|
|
5939
|
+
html += '<button class="btn" type="button" onclick="openAgentMetadataDrawer(\'' + name + '\')">Details</button>' +
|
|
5940
|
+
'</div>' +
|
|
5941
|
+
'</div>';
|
|
5942
|
+
}
|
|
5943
|
+
html += '</div></div>';
|
|
5944
|
+
el.innerHTML = html;
|
|
5945
|
+
}
|
|
5946
|
+
|
|
5947
|
+
function fetchApiAgents() {
|
|
5948
|
+
return lttFetch(scopedApiUrl('/api/agents', null, { includeBranch: false })).then(function(r) {
|
|
5949
|
+
return r.json();
|
|
5950
|
+
}).then(function(data) {
|
|
5951
|
+
cachedAgents = data && typeof data === 'object' ? data : {};
|
|
5952
|
+
renderServices();
|
|
5953
|
+
refreshAgentMetadataDrawer();
|
|
5954
|
+
}).catch(function(e) {
|
|
5955
|
+
console.error('Fetch API agents failed:', e);
|
|
5956
|
+
renderServices();
|
|
5957
|
+
});
|
|
4725
5958
|
}
|
|
4726
5959
|
|
|
4727
5960
|
function sendNudge(agentName) {
|
|
@@ -4753,9 +5986,7 @@ function removeAgent(agentName) {
|
|
|
4753
5986
|
// ==================== RESPAWN AGENT ====================
|
|
4754
5987
|
|
|
4755
5988
|
function respawnAgent(agentName) {
|
|
4756
|
-
|
|
4757
|
-
var sep = pq ? '&' : '?';
|
|
4758
|
-
lttFetch('/api/agents/' + encodeURIComponent(agentName) + '/respawn-prompt' + pq, {
|
|
5989
|
+
lttFetch(scopedApiUrl('/api/agents/' + encodeURIComponent(agentName) + '/respawn-prompt'), {
|
|
4759
5990
|
method: 'GET'
|
|
4760
5991
|
}).then(function(r) {
|
|
4761
5992
|
if (!r.ok) throw new Error('API returned ' + r.status);
|
|
@@ -4831,61 +6062,183 @@ function copyRespawnPrompt() {
|
|
|
4831
6062
|
|
|
4832
6063
|
var lastAgentKeys = '';
|
|
4833
6064
|
|
|
6065
|
+
function getEligibleInjectTargets(agentNames, agents) {
|
|
6066
|
+
var branchName = (typeof getActiveDashboardBranchName === 'function' ? getActiveDashboardBranchName() : (activeBranch || 'main')) || 'main';
|
|
6067
|
+
var eligible = [];
|
|
6068
|
+
for (var i = 0; i < agentNames.length; i++) {
|
|
6069
|
+
var name = agentNames[i];
|
|
6070
|
+
var info = agents[name] || {};
|
|
6071
|
+
var state = info.status || (info.alive ? 'active' : 'dead');
|
|
6072
|
+
var agentBranch = info.branch || 'main';
|
|
6073
|
+
if (agentBranch !== branchName) continue;
|
|
6074
|
+
if (state === 'dead') continue;
|
|
6075
|
+
eligible.push(name);
|
|
6076
|
+
}
|
|
6077
|
+
return eligible;
|
|
6078
|
+
}
|
|
6079
|
+
|
|
4834
6080
|
function updateInjectTargets(keys) {
|
|
4835
|
-
var
|
|
6081
|
+
var sel = document.getElementById('inject-target');
|
|
6082
|
+
if (!sel) return;
|
|
6083
|
+
|
|
6084
|
+
var normalizedKeys = [];
|
|
6085
|
+
var seen = {};
|
|
6086
|
+
for (var i = 0; i < keys.length; i++) {
|
|
6087
|
+
var key = keys[i];
|
|
6088
|
+
if (!key || seen[key]) continue;
|
|
6089
|
+
seen[key] = true;
|
|
6090
|
+
normalizedKeys.push(key);
|
|
6091
|
+
}
|
|
6092
|
+
|
|
6093
|
+
var joined = normalizedKeys.join(',');
|
|
4836
6094
|
if (joined === lastAgentKeys) return;
|
|
4837
6095
|
lastAgentKeys = joined;
|
|
4838
6096
|
|
|
4839
|
-
var sel = document.getElementById('inject-target');
|
|
4840
6097
|
var current = sel.value;
|
|
4841
6098
|
sel.innerHTML = '<option value="">Select agent...</option>';
|
|
4842
|
-
for (var i = 0; i <
|
|
6099
|
+
for (var i = 0; i < normalizedKeys.length; i++) {
|
|
4843
6100
|
var opt = document.createElement('option');
|
|
4844
|
-
opt.value =
|
|
4845
|
-
opt.textContent =
|
|
6101
|
+
opt.value = normalizedKeys[i];
|
|
6102
|
+
opt.textContent = normalizedKeys[i];
|
|
4846
6103
|
sel.appendChild(opt);
|
|
4847
6104
|
}
|
|
4848
6105
|
// Broadcast option
|
|
4849
|
-
if (
|
|
6106
|
+
if (normalizedKeys.length > 1) {
|
|
4850
6107
|
var allOpt = document.createElement('option');
|
|
4851
6108
|
allOpt.value = '__all__';
|
|
4852
6109
|
allOpt.textContent = 'All agents';
|
|
4853
6110
|
sel.appendChild(allOpt);
|
|
4854
6111
|
}
|
|
4855
6112
|
|
|
4856
|
-
if (current) sel.value =
|
|
6113
|
+
if (current === '__all__' && normalizedKeys.length > 1) sel.value = '__all__';
|
|
6114
|
+
else if (normalizedKeys.indexOf(current) !== -1) sel.value = current;
|
|
6115
|
+
else sel.value = '';
|
|
4857
6116
|
updateSendBtn();
|
|
4858
6117
|
}
|
|
4859
6118
|
|
|
6119
|
+
function buildDashboardQuery(extraParams, options) {
|
|
6120
|
+
var opts = options || {};
|
|
6121
|
+
var params = new URLSearchParams();
|
|
6122
|
+
var includeProject = opts.includeProject !== false;
|
|
6123
|
+
var includeBranch = opts.includeBranch !== false;
|
|
6124
|
+
var branch = Object.prototype.hasOwnProperty.call(opts, 'branch') ? opts.branch : activeBranch;
|
|
6125
|
+
|
|
6126
|
+
if (includeProject && activeProject) params.set('project', activeProject);
|
|
6127
|
+
if (includeBranch && branch && branch !== 'main') params.set('branch', branch);
|
|
6128
|
+
if (opts.includeToken && _lttToken) params.set('token', _lttToken);
|
|
6129
|
+
|
|
6130
|
+
if (extraParams) {
|
|
6131
|
+
for (var key in extraParams) {
|
|
6132
|
+
if (!Object.prototype.hasOwnProperty.call(extraParams, key)) continue;
|
|
6133
|
+
var value = extraParams[key];
|
|
6134
|
+
if (value === undefined || value === null || value === '') continue;
|
|
6135
|
+
params.set(key, String(value));
|
|
6136
|
+
}
|
|
6137
|
+
}
|
|
6138
|
+
|
|
6139
|
+
var query = params.toString();
|
|
6140
|
+
return query ? '?' + query : '';
|
|
6141
|
+
}
|
|
6142
|
+
|
|
6143
|
+
function scopedApiUrl(path, extraParams, options) {
|
|
6144
|
+
return path + buildDashboardQuery(extraParams, options);
|
|
6145
|
+
}
|
|
6146
|
+
|
|
6147
|
+
window.scopedApiUrl = scopedApiUrl;
|
|
6148
|
+
|
|
4860
6149
|
function updateSendBtn() {
|
|
4861
6150
|
var target = document.getElementById('inject-target').value;
|
|
4862
6151
|
var content = document.getElementById('inject-content').value.trim();
|
|
4863
|
-
|
|
6152
|
+
updateAssistantPrivateVisibility();
|
|
6153
|
+
document.getElementById('inject-btn').disabled = !target || (!content && !_attachedFile);
|
|
4864
6154
|
}
|
|
4865
6155
|
|
|
4866
6156
|
document.getElementById('inject-target').addEventListener('change', updateSendBtn);
|
|
4867
6157
|
document.getElementById('inject-content').addEventListener('input', updateSendBtn);
|
|
4868
6158
|
|
|
4869
6159
|
function projectParam() {
|
|
4870
|
-
return
|
|
6160
|
+
return buildDashboardQuery(null, { includeBranch: false });
|
|
6161
|
+
}
|
|
6162
|
+
|
|
6163
|
+
function isMainBranchSelected() {
|
|
6164
|
+
return !activeBranch || activeBranch === 'main';
|
|
6165
|
+
}
|
|
6166
|
+
|
|
6167
|
+
function mainBranchOnlyViewHtml(surface) {
|
|
6168
|
+
return '<div class="tasks-empty">' + surface + ' only supports the <code style="background:var(--surface-2);padding:2px 6px;border-radius:4px;font-size:12px;color:var(--orange)">main</code> branch while canonical storage is still globally scoped.</div>';
|
|
6169
|
+
}
|
|
6170
|
+
|
|
6171
|
+
function renderMainBranchOnlyView(elementId, surface) {
|
|
6172
|
+
var el = document.getElementById(elementId);
|
|
6173
|
+
if (el) el.innerHTML = mainBranchOnlyViewHtml(surface);
|
|
6174
|
+
}
|
|
6175
|
+
|
|
6176
|
+
function updateAssistantPrivateVisibility() {
|
|
6177
|
+
var row = document.getElementById('assistant-private-row');
|
|
6178
|
+
var checkbox = document.getElementById('assistant-private-optin');
|
|
6179
|
+
var isAssistantTarget = document.getElementById('inject-target').value === 'Assistant';
|
|
6180
|
+
if (!row || !checkbox) return;
|
|
6181
|
+
row.style.display = isAssistantTarget ? 'flex' : 'none';
|
|
6182
|
+
if (!isAssistantTarget) checkbox.checked = false;
|
|
6183
|
+
}
|
|
6184
|
+
|
|
6185
|
+
// ==================== FILE ATTACHMENT ====================
|
|
6186
|
+
var _attachedFile = null; // { name, mimeType, base64 }
|
|
6187
|
+
|
|
6188
|
+
function onFileSelected(input) {
|
|
6189
|
+
if (!input.files || !input.files[0]) return;
|
|
6190
|
+
var file = input.files[0];
|
|
6191
|
+
if (file.size > 10 * 1024 * 1024) { alert('File too large (max 10MB)'); input.value = ''; return; }
|
|
6192
|
+
var reader = new FileReader();
|
|
6193
|
+
reader.onload = function(e) {
|
|
6194
|
+
var dataUrl = e.target.result;
|
|
6195
|
+
var base64 = dataUrl.split(',')[1];
|
|
6196
|
+
_attachedFile = { name: file.name, mimeType: file.type, base64: base64 };
|
|
6197
|
+
document.getElementById('inject-file-preview').style.display = 'block';
|
|
6198
|
+
document.getElementById('inject-file-thumb').src = dataUrl;
|
|
6199
|
+
document.getElementById('inject-file-name').textContent = file.name + ' (' + Math.round(file.size / 1024) + 'KB)';
|
|
6200
|
+
updateSendBtn();
|
|
6201
|
+
};
|
|
6202
|
+
reader.readAsDataURL(file);
|
|
6203
|
+
}
|
|
6204
|
+
|
|
6205
|
+
function clearAttachment() {
|
|
6206
|
+
_attachedFile = null;
|
|
6207
|
+
document.getElementById('inject-file').value = '';
|
|
6208
|
+
document.getElementById('inject-file-preview').style.display = 'none';
|
|
6209
|
+
updateSendBtn();
|
|
4871
6210
|
}
|
|
4872
6211
|
|
|
4873
6212
|
function doInject() {
|
|
4874
6213
|
var target = document.getElementById('inject-target').value;
|
|
4875
6214
|
var content = document.getElementById('inject-content').value.trim();
|
|
4876
|
-
if (!target || !content) return;
|
|
6215
|
+
if (!target || (!content && !_attachedFile)) return;
|
|
4877
6216
|
|
|
4878
|
-
//
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
var
|
|
4882
|
-
|
|
6217
|
+
// If no text but has image, default prompt
|
|
6218
|
+
if (!content && _attachedFile) content = 'Analyze this image';
|
|
6219
|
+
|
|
6220
|
+
var payload = { to: target, content: content };
|
|
6221
|
+
if (target === 'Assistant') {
|
|
6222
|
+
payload.assistant_private = !!document.getElementById('assistant-private-optin').checked;
|
|
6223
|
+
}
|
|
6224
|
+
|
|
6225
|
+
// Include attachment if present
|
|
6226
|
+
if (_attachedFile) {
|
|
6227
|
+
payload.attachments = [{
|
|
6228
|
+
name: _attachedFile.name,
|
|
6229
|
+
mimeType: _attachedFile.mimeType,
|
|
6230
|
+
base64: _attachedFile.base64,
|
|
6231
|
+
}];
|
|
6232
|
+
}
|
|
6233
|
+
|
|
6234
|
+
lttFetch(scopedApiUrl('/api/inject'), {
|
|
4883
6235
|
method: 'POST',
|
|
4884
6236
|
headers: { 'Content-Type': 'application/json' },
|
|
4885
|
-
body:
|
|
6237
|
+
body: JSON.stringify(payload)
|
|
4886
6238
|
}).then(function(r) { return r.json(); }).then(function(res) {
|
|
4887
6239
|
if (res.success) {
|
|
4888
6240
|
document.getElementById('inject-content').value = '';
|
|
6241
|
+
clearAttachment();
|
|
4889
6242
|
updateSendBtn();
|
|
4890
6243
|
poll();
|
|
4891
6244
|
}
|
|
@@ -5120,6 +6473,18 @@ function renderMessages(messages) {
|
|
|
5120
6473
|
lastFrom = m.from;
|
|
5121
6474
|
lastTo = m.to;
|
|
5122
6475
|
} else {
|
|
6476
|
+
// Build attachment thumbnails if present
|
|
6477
|
+
var attachHtml = '';
|
|
6478
|
+
if (m.attachments && m.attachments.length > 0) {
|
|
6479
|
+
attachHtml = '<div style="margin:6px 0;display:flex;gap:6px;flex-wrap:wrap;">';
|
|
6480
|
+
for (var ati = 0; ati < m.attachments.length; ati++) {
|
|
6481
|
+
var att = m.attachments[ati];
|
|
6482
|
+
if (att.base64 && att.mimeType && att.mimeType.indexOf('image') === 0) {
|
|
6483
|
+
attachHtml += '<img src="data:' + att.mimeType + ';base64,' + att.base64 + '" style="max-height:120px;max-width:200px;border-radius:6px;border:1px solid var(--border);cursor:pointer;" onclick="window.open(this.src)" title="' + escapeHtml(att.name || 'image') + '">';
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
attachHtml += '</div>';
|
|
6487
|
+
}
|
|
5123
6488
|
html += '<div class="message' + newClass + groupClass + '">' +
|
|
5124
6489
|
buildMsgActions(m.id) +
|
|
5125
6490
|
msgAvatarHtml +
|
|
@@ -5131,6 +6496,7 @@ function renderMessages(messages) {
|
|
|
5131
6496
|
'<span class="msg-time" title="' + formatTime(m.timestamp) + '">' + relativeTime(m.timestamp) + '</span>' +
|
|
5132
6497
|
'<div class="msg-badges">' + badges + '</div>' +
|
|
5133
6498
|
'</div>' +
|
|
6499
|
+
attachHtml +
|
|
5134
6500
|
'<div class="msg-content">' + renderMarkdown(m.content) + '</div>' +
|
|
5135
6501
|
buildReactionsHtml(m.id) +
|
|
5136
6502
|
buildReadReceipts(m.id) +
|
|
@@ -5220,11 +6586,9 @@ function loadMoreMessages() {
|
|
|
5220
6586
|
if (msgLoadingMore) return;
|
|
5221
6587
|
msgLoadingMore = true;
|
|
5222
6588
|
renderMessages(cachedHistory); // Re-render to show loading state
|
|
5223
|
-
var pp = activeProject ? '&project=' + encodeURIComponent(activeProject) : '';
|
|
5224
|
-
var bp = activeBranch && activeBranch !== 'main' ? '&branch=' + encodeURIComponent(activeBranch) : '';
|
|
5225
6589
|
// Calculate next page: we want older messages
|
|
5226
6590
|
var nextPage = Math.max(1, msgTotalPages - Math.floor(cachedHistory.length / 50));
|
|
5227
|
-
lttFetch('/api/history
|
|
6591
|
+
lttFetch(scopedApiUrl('/api/history', { page: nextPage, limit: 50 })).then(function(r) {
|
|
5228
6592
|
return r.json();
|
|
5229
6593
|
}).then(function(data) {
|
|
5230
6594
|
msgLoadingMore = false;
|
|
@@ -5334,38 +6698,53 @@ function scrollToBottom() {
|
|
|
5334
6698
|
|
|
5335
6699
|
// ==================== COMPACT MODE ====================
|
|
5336
6700
|
|
|
5337
|
-
var compactMode =
|
|
6701
|
+
var compactMode = !!dashboardWorkspaceState.liveWorkspace.snapshot.compactMode;
|
|
5338
6702
|
|
|
5339
|
-
function
|
|
6703
|
+
function applyCompactMode(enabled) {
|
|
6704
|
+
compactMode = !!enabled;
|
|
5340
6705
|
var area = document.getElementById('messages');
|
|
5341
6706
|
var btn = document.getElementById('compact-toggle');
|
|
5342
|
-
if (compactMode)
|
|
5343
|
-
|
|
5344
|
-
|
|
5345
|
-
|
|
6707
|
+
if (area) area.classList.toggle('compact-mode', compactMode);
|
|
6708
|
+
if (btn) btn.classList.toggle('active', compactMode);
|
|
6709
|
+
}
|
|
6710
|
+
|
|
6711
|
+
function initCompactMode() {
|
|
6712
|
+
applyCompactMode(compactMode);
|
|
5346
6713
|
}
|
|
5347
6714
|
|
|
5348
6715
|
function toggleCompactMode() {
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
var area = document.getElementById('messages');
|
|
5352
|
-
var btn = document.getElementById('compact-toggle');
|
|
5353
|
-
area.classList.toggle('compact-mode', compactMode);
|
|
5354
|
-
btn.classList.toggle('active', compactMode);
|
|
6716
|
+
applyCompactMode(!compactMode);
|
|
6717
|
+
persistLiveDashboardLayout();
|
|
5355
6718
|
}
|
|
5356
6719
|
|
|
5357
6720
|
// ==================== CONVERSATION MANAGEMENT ====================
|
|
5358
6721
|
|
|
5359
6722
|
function clearMessages() {
|
|
5360
|
-
if (!confirm('Clear all messages for
|
|
5361
|
-
|
|
5362
|
-
lttFetch('/api/clear-messages?project=' + encodeURIComponent(project), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ confirm: true }) })
|
|
6723
|
+
if (!confirm('Clear all messages for the current branch? This cannot be undone.')) return;
|
|
6724
|
+
lttFetch(scopedApiUrl('/api/clear-messages'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ confirm: true }) })
|
|
5363
6725
|
.then(function(r) { return r.json(); })
|
|
5364
6726
|
.then(function(data) {
|
|
6727
|
+
if (data && data.error) {
|
|
6728
|
+
throw new Error(data.error);
|
|
6729
|
+
}
|
|
5365
6730
|
if (data.success) {
|
|
5366
|
-
|
|
5367
|
-
|
|
6731
|
+
cachedHistory = [];
|
|
6732
|
+
lastRenderedIds = [];
|
|
6733
|
+
lastMessageCount = 0;
|
|
6734
|
+
msgTotalPages = 0;
|
|
6735
|
+
activeThread = null;
|
|
6736
|
+
activeChannel = null;
|
|
6737
|
+
renderThreads(cachedHistory);
|
|
6738
|
+
renderChannelBar(cachedHistory);
|
|
6739
|
+
renderMessages(cachedHistory);
|
|
6740
|
+
renderPinnedMessages();
|
|
6741
|
+
renderBookmarksSidebar();
|
|
6742
|
+
showToast('Cleared ' + (data.cleared_messages || 0) + ' message' + ((data.cleared_messages || 0) === 1 ? '' : 's') + ' from ' + ((data.branch || 'main') === 'main' ? 'main' : data.branch));
|
|
6743
|
+
poll();
|
|
5368
6744
|
}
|
|
6745
|
+
}).catch(function(error) {
|
|
6746
|
+
console.error('Clear messages failed:', error);
|
|
6747
|
+
showToast('Clear messages failed: ' + error.message);
|
|
5369
6748
|
});
|
|
5370
6749
|
}
|
|
5371
6750
|
|
|
@@ -5436,8 +6815,281 @@ function showToast(msg) {
|
|
|
5436
6815
|
// ==================== SEARCH ====================
|
|
5437
6816
|
|
|
5438
6817
|
var searchQuery = '';
|
|
6818
|
+
var omniboxForcedOpen = false;
|
|
6819
|
+
var omniboxResults = [];
|
|
6820
|
+
var omniboxSelectedIndex = 0;
|
|
6821
|
+
var omniboxSnapshotValue = '';
|
|
6822
|
+
|
|
6823
|
+
function isOmniboxActive() {
|
|
6824
|
+
var input = document.getElementById('search-input');
|
|
6825
|
+
var raw = input ? String(input.value || '') : '';
|
|
6826
|
+
return !!omniboxForcedOpen || raw.trim().charAt(0) === '>';
|
|
6827
|
+
}
|
|
6828
|
+
|
|
6829
|
+
function getOmniboxQuery() {
|
|
6830
|
+
var input = document.getElementById('search-input');
|
|
6831
|
+
var raw = input ? String(input.value || '') : '';
|
|
6832
|
+
var trimmed = raw.trim();
|
|
6833
|
+
if (trimmed.charAt(0) === '>') trimmed = trimmed.slice(1);
|
|
6834
|
+
return trimmed.trim().toLowerCase();
|
|
6835
|
+
}
|
|
6836
|
+
|
|
6837
|
+
function closeOmniboxPanel() {
|
|
6838
|
+
var panel = document.getElementById('omnibox-panel');
|
|
6839
|
+
if (!panel) return;
|
|
6840
|
+
panel.classList.remove('open');
|
|
6841
|
+
panel.setAttribute('aria-hidden', 'true');
|
|
6842
|
+
panel.innerHTML = '';
|
|
6843
|
+
omniboxResults = [];
|
|
6844
|
+
omniboxSelectedIndex = 0;
|
|
6845
|
+
}
|
|
6846
|
+
|
|
6847
|
+
function syncSearchBarVisibility() {
|
|
6848
|
+
var bar = document.getElementById('search-bar');
|
|
6849
|
+
if (!bar) return;
|
|
6850
|
+
bar.style.display = (!replayActive && (activeView === 'messages' || isOmniboxActive())) ? 'flex' : 'none';
|
|
6851
|
+
}
|
|
6852
|
+
|
|
6853
|
+
function updateSearchInputMode() {
|
|
6854
|
+
var input = document.getElementById('search-input');
|
|
6855
|
+
if (!input) return;
|
|
6856
|
+
var omniboxActive = isOmniboxActive();
|
|
6857
|
+
input.classList.toggle('omnibox-active', omniboxActive);
|
|
6858
|
+
if (omniboxActive) input.placeholder = 'Command palette — switch views, branches, projects, or open an agent';
|
|
6859
|
+
else if (searchAllMode) input.placeholder = 'Search ALL projects...';
|
|
6860
|
+
else input.placeholder = 'Search messages... ( / )';
|
|
6861
|
+
if (!omniboxActive) closeOmniboxPanel();
|
|
6862
|
+
}
|
|
6863
|
+
|
|
6864
|
+
function openOmnibox(prefillQuery) {
|
|
6865
|
+
if (replayActive) return;
|
|
6866
|
+
var input = document.getElementById('search-input');
|
|
6867
|
+
if (!input) return;
|
|
6868
|
+
if (!isOmniboxActive()) omniboxSnapshotValue = input.value || '';
|
|
6869
|
+
omniboxForcedOpen = true;
|
|
6870
|
+
if (typeof prefillQuery === 'string') input.value = prefillQuery;
|
|
6871
|
+
syncSearchBarVisibility();
|
|
6872
|
+
updateSearchInputMode();
|
|
6873
|
+
renderOmnibox();
|
|
6874
|
+
input.focus();
|
|
6875
|
+
if (typeof input.setSelectionRange === 'function') input.setSelectionRange(input.value.length, input.value.length);
|
|
6876
|
+
}
|
|
6877
|
+
|
|
6878
|
+
function closeOmnibox(options) {
|
|
6879
|
+
var opts = options || {};
|
|
6880
|
+
var input = document.getElementById('search-input');
|
|
6881
|
+
omniboxForcedOpen = false;
|
|
6882
|
+
if (input) {
|
|
6883
|
+
if (opts.restoreSnapshot) input.value = omniboxSnapshotValue || '';
|
|
6884
|
+
else if (!opts.preserveInput) input.value = '';
|
|
6885
|
+
}
|
|
6886
|
+
updateSearchInputMode();
|
|
6887
|
+
if (activeView === 'messages' && !isOmniboxActive()) onSearch();
|
|
6888
|
+
else searchQuery = '';
|
|
6889
|
+
syncSearchBarVisibility();
|
|
6890
|
+
if (!opts.keepFocus && input) input.blur();
|
|
6891
|
+
}
|
|
6892
|
+
|
|
6893
|
+
function ensureLocalMessageSearchMode() {
|
|
6894
|
+
if (searchAllMode) toggleSearchAll();
|
|
6895
|
+
}
|
|
6896
|
+
|
|
6897
|
+
function applyOmniboxMessageSearch(query) {
|
|
6898
|
+
var input = document.getElementById('search-input');
|
|
6899
|
+
if (!input) return;
|
|
6900
|
+
omniboxForcedOpen = false;
|
|
6901
|
+
ensureLocalMessageSearchMode();
|
|
6902
|
+
input.value = query;
|
|
6903
|
+
updateSearchInputMode();
|
|
6904
|
+
switchView('messages');
|
|
6905
|
+
onSearch();
|
|
6906
|
+
input.focus();
|
|
6907
|
+
if (typeof input.setSelectionRange === 'function') input.setSelectionRange(input.value.length, input.value.length);
|
|
6908
|
+
}
|
|
6909
|
+
|
|
6910
|
+
function applyOmniboxDeepSearch(query) {
|
|
6911
|
+
var input = document.getElementById('search-input');
|
|
6912
|
+
if (!input) return;
|
|
6913
|
+
omniboxForcedOpen = false;
|
|
6914
|
+
ensureLocalMessageSearchMode();
|
|
6915
|
+
input.value = query;
|
|
6916
|
+
updateSearchInputMode();
|
|
6917
|
+
switchView('messages');
|
|
6918
|
+
deepSearch();
|
|
6919
|
+
input.focus();
|
|
6920
|
+
if (typeof input.setSelectionRange === 'function') input.setSelectionRange(input.value.length, input.value.length);
|
|
6921
|
+
}
|
|
6922
|
+
|
|
6923
|
+
function buildOmniboxCommand(kind, label, subtitle, badge, keywords, run) {
|
|
6924
|
+
return {
|
|
6925
|
+
kind: kind,
|
|
6926
|
+
label: label,
|
|
6927
|
+
subtitle: subtitle,
|
|
6928
|
+
badge: badge || '',
|
|
6929
|
+
keywords: keywords || '',
|
|
6930
|
+
run: run,
|
|
6931
|
+
};
|
|
6932
|
+
}
|
|
6933
|
+
|
|
6934
|
+
function collectOmniboxCommands() {
|
|
6935
|
+
var commands = [];
|
|
6936
|
+
var query = getOmniboxQuery();
|
|
6937
|
+
var queryLabel = query ? '“' + query + '”' : '';
|
|
6938
|
+
var viewCommands = [
|
|
6939
|
+
{ view: 'office', label: 'Open 3D Hub', subtitle: 'Switch to the office world view', keywords: 'office 3d hub world' },
|
|
6940
|
+
{ view: 'messages', label: 'Open Messages', subtitle: 'Switch to the live conversation feed', keywords: 'messages inbox chat conversation' },
|
|
6941
|
+
{ view: 'tasks', label: 'Open Tasks', subtitle: 'Switch to the task board', keywords: 'tasks kanban board' },
|
|
6942
|
+
{ view: 'workspaces', label: 'Open Workspaces', subtitle: 'Switch to per-agent workspaces', keywords: 'workspaces notes storage' },
|
|
6943
|
+
{ view: 'workflows', label: 'Open Workflows', subtitle: 'Switch to workflow pipelines', keywords: 'workflows pipelines steps' },
|
|
6944
|
+
{ view: 'graph', label: 'Open Graph', subtitle: 'Switch to the operator flow graph', keywords: 'graph operator flow channels svg' },
|
|
6945
|
+
{ view: 'plan', label: 'Open Plan', subtitle: 'Switch to the autonomous plan view', keywords: 'plan monitor autonomous execution' },
|
|
6946
|
+
{ view: 'launch', label: 'Open Launch', subtitle: 'Switch to the launcher panel', keywords: 'launch terminals templates' },
|
|
6947
|
+
{ view: 'rules', label: 'Open Rules', subtitle: 'Switch to project rules', keywords: 'rules governance' },
|
|
6948
|
+
{ view: 'stats', label: 'Open Stats', subtitle: 'Switch to team statistics', keywords: 'stats analytics leaderboard' },
|
|
6949
|
+
{ view: 'services', label: 'Open Services', subtitle: 'Switch to API service agents', keywords: 'services api agents bots' },
|
|
6950
|
+
{ view: 'docs', label: 'Open Docs', subtitle: 'Switch to in-dashboard documentation', keywords: 'docs help manual' },
|
|
6951
|
+
];
|
|
6952
|
+
|
|
6953
|
+
if (query.length >= 2) {
|
|
6954
|
+
commands.push(buildOmniboxCommand('Search', 'Search loaded messages for ' + queryLabel, 'Filter the current conversation in the Messages view', 'Enter', 'search messages filter conversation', function() {
|
|
6955
|
+
applyOmniboxMessageSearch(query);
|
|
6956
|
+
}));
|
|
6957
|
+
commands.push(buildOmniboxCommand('Deep Search', 'Deep search history for ' + queryLabel, 'Run the existing server-side history search', 'Enter', 'deep search history full text server', function() {
|
|
6958
|
+
applyOmniboxDeepSearch(query);
|
|
6959
|
+
}));
|
|
6960
|
+
}
|
|
6961
|
+
|
|
6962
|
+
for (var i = 0; i < viewCommands.length; i++) {
|
|
6963
|
+
(function(command) {
|
|
6964
|
+
commands.push(buildOmniboxCommand('View', command.label, command.subtitle, activeView === command.view ? 'Current' : '', command.keywords, function() {
|
|
6965
|
+
closeOmnibox();
|
|
6966
|
+
switchView(command.view);
|
|
6967
|
+
}));
|
|
6968
|
+
})(viewCommands[i]);
|
|
6969
|
+
}
|
|
6970
|
+
|
|
6971
|
+
var projectSelect = document.getElementById('project-select');
|
|
6972
|
+
if (projectSelect) {
|
|
6973
|
+
for (var p = 0; p < projectSelect.options.length; p++) {
|
|
6974
|
+
var option = projectSelect.options[p];
|
|
6975
|
+
if (!option.value) continue;
|
|
6976
|
+
(function(projectValue, projectLabel) {
|
|
6977
|
+
commands.push(buildOmniboxCommand('Project', 'Switch project to ' + projectLabel, projectValue, activeProject === projectValue ? 'Current' : '', 'project workspace repo folder', function() {
|
|
6978
|
+
closeOmnibox();
|
|
6979
|
+
projectSelect.value = projectValue;
|
|
6980
|
+
switchProject();
|
|
6981
|
+
}));
|
|
6982
|
+
})(option.value, option.textContent || option.value);
|
|
6983
|
+
}
|
|
6984
|
+
}
|
|
6985
|
+
|
|
6986
|
+
var branchMap = (cachedBranchInfo && typeof cachedBranchInfo === 'object') ? cachedBranchInfo : {};
|
|
6987
|
+
if (!branchMap.main) branchMap.main = { message_count: 0 };
|
|
6988
|
+
var branchNames = Object.keys(branchMap);
|
|
6989
|
+
for (var b = 0; b < branchNames.length; b++) {
|
|
6990
|
+
(function(branchName, branchInfo) {
|
|
6991
|
+
commands.push(buildOmniboxCommand('Branch', 'Switch branch to ' + branchName, (branchInfo.message_count || 0) + ' messages', activeBranch === branchName ? 'Current' : '', 'branch conversation history', function() {
|
|
6992
|
+
closeOmnibox();
|
|
6993
|
+
switchBranch(branchName);
|
|
6994
|
+
}));
|
|
6995
|
+
})(branchNames[b], branchMap[branchNames[b]] || {});
|
|
6996
|
+
}
|
|
6997
|
+
|
|
6998
|
+
var agentNames = Object.keys(cachedAgents || {}).sort();
|
|
6999
|
+
for (var a = 0; a < agentNames.length; a++) {
|
|
7000
|
+
(function(agentName) {
|
|
7001
|
+
var agent = cachedAgents[agentName] || {};
|
|
7002
|
+
var state = agent.status || (agent.alive ? 'active' : 'dead');
|
|
7003
|
+
var parts = [];
|
|
7004
|
+
if (agent.role) parts.push(agent.role);
|
|
7005
|
+
parts.push(state);
|
|
7006
|
+
if (agent.provider) parts.push(agent.provider);
|
|
7007
|
+
commands.push(buildOmniboxCommand('Agent', 'Open details for ' + (agent.display_name || agentName), parts.join(' • '), state.charAt(0).toUpperCase() + state.slice(1), 'agent profile metadata drawer details ' + agentName + ' ' + (agent.display_name || '') + ' ' + (agent.role || '') + ' ' + (agent.provider || ''), function() {
|
|
7008
|
+
closeOmnibox();
|
|
7009
|
+
openAgentMetadataDrawer(agentName);
|
|
7010
|
+
}));
|
|
7011
|
+
})(agentNames[a]);
|
|
7012
|
+
}
|
|
7013
|
+
|
|
7014
|
+
commands.push(buildOmniboxCommand('Display', currentTheme === 'dark' ? 'Switch to light theme' : 'Switch to dark theme', 'Use the existing dashboard theme toggle', currentTheme === 'dark' ? 'Dark' : 'Light', 'theme appearance display light dark', function() {
|
|
7015
|
+
closeOmnibox();
|
|
7016
|
+
toggleTheme();
|
|
7017
|
+
}));
|
|
7018
|
+
commands.push(buildOmniboxCommand('Display', compactMode ? 'Disable compact mode' : 'Enable compact mode', 'Use the existing compact message layout toggle', compactMode ? 'On' : 'Off', 'compact mode density messages layout', function() {
|
|
7019
|
+
closeOmnibox();
|
|
7020
|
+
toggleCompactMode();
|
|
7021
|
+
}));
|
|
7022
|
+
commands.push(buildOmniboxCommand('Refresh', 'Refresh dashboard data', 'Run the existing dashboard poll immediately', '', 'refresh reload sync poll', function() {
|
|
7023
|
+
closeOmnibox();
|
|
7024
|
+
poll();
|
|
7025
|
+
}));
|
|
7026
|
+
commands.push(buildOmniboxCommand('Help', 'Show keyboard shortcuts', 'Open the current shortcuts overlay', '', 'help shortcuts keyboard overlay', function() {
|
|
7027
|
+
closeOmnibox();
|
|
7028
|
+
toggleShortcutsOverlay();
|
|
7029
|
+
}));
|
|
7030
|
+
|
|
7031
|
+
if (!query) return commands.slice(0, 14);
|
|
7032
|
+
|
|
7033
|
+
return commands.filter(function(command) {
|
|
7034
|
+
var haystack = (command.kind + ' ' + command.label + ' ' + command.subtitle + ' ' + command.keywords).toLowerCase();
|
|
7035
|
+
return haystack.indexOf(query) !== -1;
|
|
7036
|
+
}).slice(0, 14);
|
|
7037
|
+
}
|
|
7038
|
+
|
|
7039
|
+
function renderOmnibox() {
|
|
7040
|
+
if (!isOmniboxActive()) {
|
|
7041
|
+
updateSearchInputMode();
|
|
7042
|
+
return;
|
|
7043
|
+
}
|
|
7044
|
+
|
|
7045
|
+
var panel = document.getElementById('omnibox-panel');
|
|
7046
|
+
if (!panel) return;
|
|
7047
|
+
omniboxResults = collectOmniboxCommands();
|
|
7048
|
+
if (omniboxSelectedIndex >= omniboxResults.length) omniboxSelectedIndex = omniboxResults.length ? 0 : 0;
|
|
7049
|
+
if (omniboxSelectedIndex < 0) omniboxSelectedIndex = 0;
|
|
7050
|
+
|
|
7051
|
+
var html = '<div class="omnibox-header"><strong>Command palette</strong><span class="omnibox-hint">↑ ↓ navigate • Enter run • Esc close</span></div>';
|
|
7052
|
+
if (!omniboxResults.length) {
|
|
7053
|
+
html += '<div class="omnibox-empty">No matching commands yet. Try a view, project, branch, agent, or a deep-search phrase.</div>';
|
|
7054
|
+
} else {
|
|
7055
|
+
html += '<div class="omnibox-list">';
|
|
7056
|
+
for (var i = 0; i < omniboxResults.length; i++) {
|
|
7057
|
+
var result = omniboxResults[i];
|
|
7058
|
+
html += '<button type="button" class="omnibox-item' + (i === omniboxSelectedIndex ? ' active' : '') + '" onclick="executeOmniboxResult(' + i + ')">' +
|
|
7059
|
+
'<span class="omnibox-item-kind">' + escapeHtml(result.kind) + '</span>' +
|
|
7060
|
+
'<span class="omnibox-item-main">' +
|
|
7061
|
+
'<span class="omnibox-item-label">' + escapeHtml(result.label) + '</span>' +
|
|
7062
|
+
'<span class="omnibox-item-sub">' + escapeHtml(result.subtitle) + '</span>' +
|
|
7063
|
+
'</span>' +
|
|
7064
|
+
(result.badge ? '<span class="omnibox-item-badge">' + escapeHtml(result.badge) + '</span>' : '') +
|
|
7065
|
+
'</button>';
|
|
7066
|
+
}
|
|
7067
|
+
html += '</div>';
|
|
7068
|
+
}
|
|
7069
|
+
panel.innerHTML = html;
|
|
7070
|
+
panel.classList.add('open');
|
|
7071
|
+
panel.setAttribute('aria-hidden', 'false');
|
|
7072
|
+
}
|
|
7073
|
+
|
|
7074
|
+
function moveOmniboxSelection(delta) {
|
|
7075
|
+
if (!omniboxResults.length) return;
|
|
7076
|
+
omniboxSelectedIndex = (omniboxSelectedIndex + delta + omniboxResults.length) % omniboxResults.length;
|
|
7077
|
+
renderOmnibox();
|
|
7078
|
+
}
|
|
7079
|
+
|
|
7080
|
+
function executeOmniboxResult(index) {
|
|
7081
|
+
var command = omniboxResults[index];
|
|
7082
|
+
if (!command || typeof command.run !== 'function') return;
|
|
7083
|
+
command.run();
|
|
7084
|
+
}
|
|
5439
7085
|
|
|
5440
7086
|
function onSearch() {
|
|
7087
|
+
updateSearchInputMode();
|
|
7088
|
+
if (isOmniboxActive()) {
|
|
7089
|
+
renderOmnibox();
|
|
7090
|
+
return;
|
|
7091
|
+
}
|
|
7092
|
+
omniboxSnapshotValue = document.getElementById('search-input').value || '';
|
|
5441
7093
|
searchQuery = document.getElementById('search-input').value.toLowerCase().trim();
|
|
5442
7094
|
if (searchAllMode && searchQuery.length >= 2) {
|
|
5443
7095
|
searchAllProjects(searchQuery);
|
|
@@ -5450,10 +7102,9 @@ function onSearch() {
|
|
|
5450
7102
|
function deepSearch() {
|
|
5451
7103
|
var query = document.getElementById('search-input').value.trim();
|
|
5452
7104
|
if (query.length < 2) return;
|
|
5453
|
-
var pq = activeProject ? '&project=' + encodeURIComponent(activeProject) : '';
|
|
5454
7105
|
var countEl = document.getElementById('search-count');
|
|
5455
7106
|
countEl.textContent = 'Searching...';
|
|
5456
|
-
lttFetch('/api/search
|
|
7107
|
+
lttFetch(scopedApiUrl('/api/search', { q: query, limit: 100 })).then(function(r) { return r.json(); }).then(function(data) {
|
|
5457
7108
|
if (data.error) { countEl.textContent = data.error; return; }
|
|
5458
7109
|
countEl.textContent = data.results_count + ' deep result' + (data.results_count !== 1 ? 's' : '');
|
|
5459
7110
|
// Convert search results to message format for renderMessages
|
|
@@ -5508,16 +7159,16 @@ var reactions = {};
|
|
|
5508
7159
|
var pins = {};
|
|
5509
7160
|
|
|
5510
7161
|
function loadReactions() {
|
|
5511
|
-
|
|
5512
|
-
|
|
7162
|
+
reactions = clonePlainObject(dashboardWorkspaceState.preferences.reactions);
|
|
7163
|
+
pins = clonePlainObject(dashboardWorkspaceState.preferences.pins);
|
|
5513
7164
|
}
|
|
5514
7165
|
|
|
5515
7166
|
function saveReactions() {
|
|
5516
|
-
|
|
7167
|
+
persistDashboardPreferences();
|
|
5517
7168
|
}
|
|
5518
7169
|
|
|
5519
7170
|
function savePins() {
|
|
5520
|
-
|
|
7171
|
+
persistDashboardPreferences();
|
|
5521
7172
|
}
|
|
5522
7173
|
|
|
5523
7174
|
function toggleReactPicker(msgId) {
|
|
@@ -5553,6 +7204,7 @@ function renderPinnedMessages() {
|
|
|
5553
7204
|
var pinIds = Object.keys(pins);
|
|
5554
7205
|
if (!pinIds.length) {
|
|
5555
7206
|
section.classList.remove('visible');
|
|
7207
|
+
list.innerHTML = '';
|
|
5556
7208
|
return;
|
|
5557
7209
|
}
|
|
5558
7210
|
section.classList.add('visible');
|
|
@@ -5569,13 +7221,14 @@ function renderPinnedMessages() {
|
|
|
5569
7221
|
'</div></div>';
|
|
5570
7222
|
}
|
|
5571
7223
|
list.innerHTML = html;
|
|
7224
|
+
applyPinnedSectionState();
|
|
5572
7225
|
}
|
|
5573
7226
|
|
|
5574
|
-
var pinnedExpanded =
|
|
7227
|
+
var pinnedExpanded = dashboardWorkspaceState.liveWorkspace.snapshot.pinnedExpanded !== false;
|
|
5575
7228
|
function togglePinnedSection() {
|
|
5576
7229
|
pinnedExpanded = !pinnedExpanded;
|
|
5577
|
-
|
|
5578
|
-
|
|
7230
|
+
applyPinnedSectionState();
|
|
7231
|
+
persistLiveDashboardLayout();
|
|
5579
7232
|
}
|
|
5580
7233
|
|
|
5581
7234
|
function buildMsgActions(msgId) {
|
|
@@ -5702,10 +7355,22 @@ loadReactions();
|
|
|
5702
7355
|
|
|
5703
7356
|
// ==================== THEME ====================
|
|
5704
7357
|
|
|
5705
|
-
var currentTheme =
|
|
7358
|
+
var currentTheme = dashboardWorkspaceState.liveWorkspace.snapshot.theme || 'dark';
|
|
7359
|
+
|
|
7360
|
+
function refreshThemeSensitiveDashboardViews() {
|
|
7361
|
+
renderAgents(cachedAgents);
|
|
7362
|
+
refreshAgentMetadataDrawer();
|
|
7363
|
+
renderAgentStats();
|
|
7364
|
+
renderThreads(cachedHistory);
|
|
7365
|
+
renderPinnedMessages();
|
|
7366
|
+
renderBookmarksSidebar();
|
|
7367
|
+
if (!replayActive) renderMessages(cachedHistory);
|
|
7368
|
+
if (activeView === 'graph') renderGraphView();
|
|
7369
|
+
}
|
|
5706
7370
|
|
|
5707
|
-
function applyTheme(theme) {
|
|
5708
|
-
|
|
7371
|
+
function applyTheme(theme, options) {
|
|
7372
|
+
var opts = options || {};
|
|
7373
|
+
currentTheme = theme === 'light' ? 'light' : 'dark';
|
|
5709
7374
|
if (theme === 'light') {
|
|
5710
7375
|
document.documentElement.setAttribute('data-theme', 'light');
|
|
5711
7376
|
document.getElementById('theme-toggle').innerHTML = '☀️';
|
|
@@ -5713,25 +7378,55 @@ function applyTheme(theme) {
|
|
|
5713
7378
|
document.documentElement.removeAttribute('data-theme');
|
|
5714
7379
|
document.getElementById('theme-toggle').innerHTML = '🌙';
|
|
5715
7380
|
}
|
|
5716
|
-
|
|
7381
|
+
if (!opts.skipRefresh) refreshThemeSensitiveDashboardViews();
|
|
7382
|
+
if (!opts.skipPersist) persistLiveDashboardLayout();
|
|
5717
7383
|
}
|
|
5718
7384
|
|
|
5719
7385
|
function toggleTheme() {
|
|
5720
7386
|
applyTheme(currentTheme === 'dark' ? 'light' : 'dark');
|
|
5721
7387
|
}
|
|
5722
7388
|
|
|
5723
|
-
applyTheme(currentTheme);
|
|
7389
|
+
applyTheme(currentTheme, { skipPersist: true, skipRefresh: true });
|
|
5724
7390
|
|
|
5725
7391
|
// ==================== KEYBOARD SHORTCUTS ====================
|
|
5726
7392
|
|
|
7393
|
+
document.getElementById('search-input').addEventListener('focus', function() {
|
|
7394
|
+
if (isOmniboxActive()) renderOmnibox();
|
|
7395
|
+
});
|
|
7396
|
+
|
|
7397
|
+
document.getElementById('search-input').addEventListener('keydown', function(e) {
|
|
7398
|
+
if (!isOmniboxActive()) return;
|
|
7399
|
+
if (e.key === 'ArrowDown') {
|
|
7400
|
+
e.preventDefault();
|
|
7401
|
+
moveOmniboxSelection(1);
|
|
7402
|
+
return;
|
|
7403
|
+
}
|
|
7404
|
+
if (e.key === 'ArrowUp') {
|
|
7405
|
+
e.preventDefault();
|
|
7406
|
+
moveOmniboxSelection(-1);
|
|
7407
|
+
return;
|
|
7408
|
+
}
|
|
7409
|
+
if ((e.key === 'Enter' || e.key === 'Tab') && omniboxResults.length) {
|
|
7410
|
+
e.preventDefault();
|
|
7411
|
+
executeOmniboxResult(omniboxSelectedIndex);
|
|
7412
|
+
}
|
|
7413
|
+
});
|
|
7414
|
+
|
|
5727
7415
|
document.addEventListener('keydown', function(e) {
|
|
5728
7416
|
// Don't intercept when typing in inputs
|
|
5729
7417
|
var tag = document.activeElement.tagName;
|
|
5730
7418
|
var inInput = tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT';
|
|
7419
|
+
var isPaletteShortcut = (e.ctrlKey || e.metaKey) && !e.shiftKey && String(e.key || '').toLowerCase() === 'k';
|
|
5731
7420
|
|
|
5732
7421
|
// Escape — exit replay or clear search/filters
|
|
5733
7422
|
if (e.key === 'Escape') {
|
|
5734
7423
|
if (replayActive) { exitReplay(); return; }
|
|
7424
|
+
if (isAgentMetadataDrawerOpen()) { closeAgentMetadataDrawer(); return; }
|
|
7425
|
+
if (isOmniboxActive()) {
|
|
7426
|
+
e.preventDefault();
|
|
7427
|
+
closeOmnibox({ restoreSnapshot: activeView === 'messages' });
|
|
7428
|
+
return;
|
|
7429
|
+
}
|
|
5735
7430
|
document.getElementById('search-input').value = '';
|
|
5736
7431
|
searchQuery = '';
|
|
5737
7432
|
bookmarkFilter = false;
|
|
@@ -5744,11 +7439,18 @@ document.addEventListener('keydown', function(e) {
|
|
|
5744
7439
|
return;
|
|
5745
7440
|
}
|
|
5746
7441
|
|
|
7442
|
+
if (isPaletteShortcut) {
|
|
7443
|
+
e.preventDefault();
|
|
7444
|
+
openOmnibox('');
|
|
7445
|
+
return;
|
|
7446
|
+
}
|
|
7447
|
+
|
|
5747
7448
|
if (inInput) return;
|
|
5748
7449
|
|
|
5749
|
-
// /
|
|
5750
|
-
if (e.key === '/'
|
|
7450
|
+
// / — focus search
|
|
7451
|
+
if (e.key === '/') {
|
|
5751
7452
|
e.preventDefault();
|
|
7453
|
+
if (activeView !== 'messages') switchView('messages');
|
|
5752
7454
|
document.getElementById('search-input').focus();
|
|
5753
7455
|
return;
|
|
5754
7456
|
}
|
|
@@ -5786,7 +7488,9 @@ function toggleShortcutsOverlay() {
|
|
|
5786
7488
|
'<h3 style="font-size:16px;font-weight:700;margin-bottom:16px;color:var(--text)">Keyboard Shortcuts</h3>' +
|
|
5787
7489
|
'<div style="display:grid;grid-template-columns:80px 1fr;gap:8px 16px;font-size:13px">' +
|
|
5788
7490
|
'<kbd style="background:var(--surface-2);padding:2px 8px;border-radius:4px;text-align:center;font-family:monospace">?</kbd><span>Show this help</span>' +
|
|
5789
|
-
'<kbd style="background:var(--surface-2);padding:2px 8px;border-radius:4px;text-align:center;font-family:monospace">/</kbd><span>Focus search</span>' +
|
|
7491
|
+
'<kbd style="background:var(--surface-2);padding:2px 8px;border-radius:4px;text-align:center;font-family:monospace">/</kbd><span>Focus message search</span>' +
|
|
7492
|
+
'<kbd style="background:var(--surface-2);padding:2px 8px;border-radius:4px;text-align:center;font-family:monospace">Ctrl/⌘ K</kbd><span>Open command palette</span>' +
|
|
7493
|
+
'<kbd style="background:var(--surface-2);padding:2px 8px;border-radius:4px;text-align:center;font-family:monospace">></kbd><span>Use command mode inside search</span>' +
|
|
5790
7494
|
'<kbd style="background:var(--surface-2);padding:2px 8px;border-radius:4px;text-align:center;font-family:monospace">Esc</kbd><span>Clear search / exit</span>' +
|
|
5791
7495
|
'<kbd style="background:var(--surface-2);padding:2px 8px;border-radius:4px;text-align:center;font-family:monospace">1</kbd><span>3D Hub</span>' +
|
|
5792
7496
|
'<kbd style="background:var(--surface-2);padding:2px 8px;border-radius:4px;text-align:center;font-family:monospace">2</kbd><span>Messages</span>' +
|
|
@@ -5940,14 +7644,11 @@ var bookmarks = {};
|
|
|
5940
7644
|
var bookmarkFilter = false;
|
|
5941
7645
|
|
|
5942
7646
|
function loadBookmarks() {
|
|
5943
|
-
|
|
5944
|
-
var stored = localStorage.getItem('ltt-bookmarks');
|
|
5945
|
-
if (stored) bookmarks = JSON.parse(stored);
|
|
5946
|
-
} catch (e) {}
|
|
7647
|
+
bookmarks = clonePlainObject(dashboardWorkspaceState.preferences.bookmarks);
|
|
5947
7648
|
}
|
|
5948
7649
|
|
|
5949
7650
|
function saveBookmarks() {
|
|
5950
|
-
|
|
7651
|
+
persistDashboardPreferences();
|
|
5951
7652
|
}
|
|
5952
7653
|
|
|
5953
7654
|
function toggleBookmark(msgId) {
|
|
@@ -6031,47 +7732,53 @@ function exportConversation() {
|
|
|
6031
7732
|
|
|
6032
7733
|
function exportJSON() {
|
|
6033
7734
|
if (!cachedHistory.length) return;
|
|
6034
|
-
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
6035
7735
|
var a = document.createElement('a');
|
|
6036
|
-
a.href = '/api/export-json'
|
|
7736
|
+
a.href = scopedApiUrl('/api/export-json', null, { includeToken: true });
|
|
6037
7737
|
a.download = 'conversation-' + new Date().toISOString().slice(0, 10) + '-full.json';
|
|
6038
7738
|
a.click();
|
|
6039
7739
|
}
|
|
6040
7740
|
|
|
6041
7741
|
// ==================== VIEW SWITCHING ====================
|
|
6042
7742
|
|
|
6043
|
-
var activeView = 'office';
|
|
7743
|
+
var activeView = dashboardWorkspaceState.liveWorkspace.snapshot.view || 'office';
|
|
6044
7744
|
|
|
6045
|
-
function switchView(view) {
|
|
7745
|
+
function switchView(view, options) {
|
|
7746
|
+
var opts = options || {};
|
|
6046
7747
|
activeView = view;
|
|
6047
7748
|
document.getElementById('tab-messages').classList.toggle('active', view === 'messages');
|
|
6048
7749
|
document.getElementById('tab-tasks').classList.toggle('active', view === 'tasks');
|
|
6049
7750
|
document.getElementById('tab-workspaces').classList.toggle('active', view === 'workspaces');
|
|
6050
7751
|
document.getElementById('tab-workflows').classList.toggle('active', view === 'workflows');
|
|
7752
|
+
document.getElementById('tab-graph').classList.toggle('active', view === 'graph');
|
|
6051
7753
|
document.getElementById('tab-plan').classList.toggle('active', view === 'plan');
|
|
6052
7754
|
document.getElementById('tab-office').classList.toggle('active', view === 'office');
|
|
6053
7755
|
document.getElementById('tab-launch').classList.toggle('active', view === 'launch');
|
|
6054
7756
|
document.getElementById('tab-rules').classList.toggle('active', view === 'rules');
|
|
6055
7757
|
document.getElementById('tab-stats').classList.toggle('active', view === 'stats');
|
|
7758
|
+
document.getElementById('tab-services').classList.toggle('active', view === 'services');
|
|
6056
7759
|
document.getElementById('tab-docs').classList.toggle('active', view === 'docs');
|
|
6057
7760
|
document.getElementById('messages').style.display = view === 'messages' ? 'flex' : 'none';
|
|
6058
7761
|
document.getElementById('tasks-area').classList.toggle('visible', view === 'tasks');
|
|
6059
7762
|
document.getElementById('workspaces-area').classList.toggle('visible', view === 'workspaces');
|
|
6060
7763
|
document.getElementById('workflows-area').classList.toggle('visible', view === 'workflows');
|
|
7764
|
+
document.getElementById('graph-area').classList.toggle('visible', view === 'graph');
|
|
6061
7765
|
document.getElementById('plan-area').classList.toggle('visible', view === 'plan');
|
|
6062
7766
|
document.getElementById('monitor-panel').style.display = view === 'plan' ? 'block' : 'none';
|
|
6063
7767
|
document.getElementById('office-area').classList.toggle('visible', view === 'office');
|
|
6064
7768
|
document.getElementById('launch-area').classList.toggle('visible', view === 'launch');
|
|
6065
7769
|
document.getElementById('rules-area').classList.toggle('visible', view === 'rules');
|
|
6066
7770
|
document.getElementById('stats-area').classList.toggle('visible', view === 'stats');
|
|
7771
|
+
document.getElementById('services-area').classList.toggle('visible', view === 'services');
|
|
6067
7772
|
document.getElementById('docs-area').classList.toggle('visible', view === 'docs');
|
|
6068
|
-
|
|
7773
|
+
syncSearchBarVisibility();
|
|
6069
7774
|
document.getElementById('channel-filter-bar').style.display = view === 'messages' && activeChannel !== undefined ? '' : 'none';
|
|
6070
7775
|
if (view === 'messages') renderChannelBar(cachedHistory);
|
|
6071
7776
|
if (view === 'tasks') fetchTasks();
|
|
6072
7777
|
if (view === 'workspaces') fetchWorkspaces();
|
|
6073
7778
|
if (view === 'workflows') fetchWorkflows();
|
|
7779
|
+
if (view === 'graph') { renderGraphView(); ensureGraphWorkflowData(); }
|
|
6074
7780
|
if (view === 'plan') { fetchPlanStatus(); fetchMonitorHealth(); }
|
|
7781
|
+
if (view === 'services') renderServices();
|
|
6075
7782
|
if (view === 'docs') renderDocs();
|
|
6076
7783
|
if (view === 'office') {
|
|
6077
7784
|
if (window.office3dStart) {
|
|
@@ -6103,6 +7810,7 @@ function switchView(view) {
|
|
|
6103
7810
|
}
|
|
6104
7811
|
// Auto-close sidebar on mobile after view switch
|
|
6105
7812
|
if (isMobile) closeSidebar();
|
|
7813
|
+
if (!opts.skipPersist) persistLiveDashboardLayout();
|
|
6106
7814
|
}
|
|
6107
7815
|
|
|
6108
7816
|
// ==================== TASKS ====================
|
|
@@ -6110,9 +7818,13 @@ function switchView(view) {
|
|
|
6110
7818
|
var cachedTasks = [];
|
|
6111
7819
|
|
|
6112
7820
|
function fetchTasks() {
|
|
6113
|
-
var pq = projectParam();
|
|
6114
7821
|
document.getElementById('tasks-area').innerHTML = '<div class="loading-spinner">Loading tasks...</div>';
|
|
6115
|
-
lttFetch('/api/tasks'
|
|
7822
|
+
lttFetch(scopedApiUrl('/api/tasks')).then(function(r) { return r.json(); }).then(function(tasks) {
|
|
7823
|
+
if (tasks && tasks.error) {
|
|
7824
|
+
cachedTasks = [];
|
|
7825
|
+
document.getElementById('tasks-area').innerHTML = '<div class="tasks-empty">' + escapeHtml(tasks.error) + '</div>';
|
|
7826
|
+
return;
|
|
7827
|
+
}
|
|
6116
7828
|
cachedTasks = Array.isArray(tasks) ? tasks : [];
|
|
6117
7829
|
renderTasks();
|
|
6118
7830
|
}).catch(function() {
|
|
@@ -6123,8 +7835,15 @@ function fetchTasks() {
|
|
|
6123
7835
|
|
|
6124
7836
|
function renderTasks() {
|
|
6125
7837
|
var el = document.getElementById('tasks-area');
|
|
7838
|
+
var toolbar = '<div class="tasks-toolbar" style="display:flex;justify-content:flex-end;gap:8px;padding:8px 4px;align-items:center">' +
|
|
7839
|
+
'<span style="font-size:11px;color:var(--text-muted);margin-right:auto">' + cachedTasks.length + ' task' + (cachedTasks.length === 1 ? '' : 's') + '</span>' +
|
|
7840
|
+
'<button onclick="clearTasks()" title="Clear all tasks for this branch" ' +
|
|
7841
|
+
'style="background:#4a2020;border:1px solid #6a3030;border-radius:6px;padding:4px 10px;font-size:11px;cursor:pointer;color:#ff6b6b;white-space:nowrap;transition:all 0.2s"' +
|
|
7842
|
+
(cachedTasks.length ? '' : ' disabled') + '>Clear All Tasks</button>' +
|
|
7843
|
+
'</div>';
|
|
7844
|
+
|
|
6126
7845
|
if (!cachedTasks.length) {
|
|
6127
|
-
el.innerHTML = '<div class="kanban">' +
|
|
7846
|
+
el.innerHTML = toolbar + '<div class="kanban">' +
|
|
6128
7847
|
'<div class="kanban-col">' +
|
|
6129
7848
|
'<div class="kanban-title pending">Pending<span class="kanban-count">0</span></div>' +
|
|
6130
7849
|
'<div class="task-card" style="opacity:0.3;border-style:dashed">' +
|
|
@@ -6162,7 +7881,7 @@ function renderTasks() {
|
|
|
6162
7881
|
{ key: 'blocked', label: 'Blocked' }
|
|
6163
7882
|
];
|
|
6164
7883
|
|
|
6165
|
-
var html = '<div class="kanban">';
|
|
7884
|
+
var html = toolbar + '<div class="kanban">';
|
|
6166
7885
|
for (var c = 0; c < cols.length; c++) {
|
|
6167
7886
|
var col = cols[c];
|
|
6168
7887
|
var tasks = groups[col.key] || [];
|
|
@@ -6177,6 +7896,22 @@ function renderTasks() {
|
|
|
6177
7896
|
el.innerHTML = html;
|
|
6178
7897
|
}
|
|
6179
7898
|
|
|
7899
|
+
function clearTasks() {
|
|
7900
|
+
if (!cachedTasks.length) return;
|
|
7901
|
+
if (!confirm('Clear all ' + cachedTasks.length + ' task' + (cachedTasks.length === 1 ? '' : 's') + ' on this branch? This cannot be undone.')) return;
|
|
7902
|
+
lttFetch(scopedApiUrl('/api/clear-tasks'), { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ confirm: true }) })
|
|
7903
|
+
.then(function(r) { return r.json(); })
|
|
7904
|
+
.then(function(data) {
|
|
7905
|
+
if (data && data.error) {
|
|
7906
|
+
showToast('Clear tasks failed: ' + data.error);
|
|
7907
|
+
return;
|
|
7908
|
+
}
|
|
7909
|
+
showToast('Cleared ' + (data.cleared_tasks || 0) + ' task' + ((data.cleared_tasks || 0) === 1 ? '' : 's'));
|
|
7910
|
+
fetchTasks();
|
|
7911
|
+
})
|
|
7912
|
+
.catch(function(e) { showToast('Clear tasks failed: ' + (e && e.message ? e.message : e)); });
|
|
7913
|
+
}
|
|
7914
|
+
|
|
6180
7915
|
function buildTaskCard(t) {
|
|
6181
7916
|
var assigneeHtml = '';
|
|
6182
7917
|
if (t.assignee) {
|
|
@@ -6216,11 +7951,16 @@ function buildTaskCard(t) {
|
|
|
6216
7951
|
}
|
|
6217
7952
|
|
|
6218
7953
|
function updateTaskStatus(taskId, newStatus) {
|
|
6219
|
-
lttFetch('/api/tasks'
|
|
7954
|
+
lttFetch(scopedApiUrl('/api/tasks'), {
|
|
6220
7955
|
method: 'POST',
|
|
6221
7956
|
headers: { 'Content-Type': 'application/json' },
|
|
6222
7957
|
body: JSON.stringify({ task_id: taskId, status: newStatus })
|
|
6223
|
-
}).then(function(r) { return r.json(); }).then(function() {
|
|
7958
|
+
}).then(function(r) { return r.json(); }).then(function(data) {
|
|
7959
|
+
if (data && data.error) {
|
|
7960
|
+
showToast('Task update failed: ' + data.error);
|
|
7961
|
+
fetchTasks();
|
|
7962
|
+
return;
|
|
7963
|
+
}
|
|
6224
7964
|
fetchTasks();
|
|
6225
7965
|
}).catch(function(e) { console.error('Task update failed:', e); });
|
|
6226
7966
|
}
|
|
@@ -6576,8 +8316,7 @@ document.addEventListener('click', function(e) {
|
|
|
6576
8316
|
|
|
6577
8317
|
function exportShareableHTML() {
|
|
6578
8318
|
if (!cachedHistory.length) return;
|
|
6579
|
-
|
|
6580
|
-
window.open('/api/export' + pp, '_blank');
|
|
8319
|
+
window.open(scopedApiUrl('/api/export', null, { includeToken: true }), '_blank');
|
|
6581
8320
|
}
|
|
6582
8321
|
|
|
6583
8322
|
// ==================== AUTO-GROW TEXTAREA ====================
|
|
@@ -6826,6 +8565,7 @@ function openProfileEditor() {
|
|
|
6826
8565
|
if (!editingAgent || !agent) return;
|
|
6827
8566
|
|
|
6828
8567
|
document.getElementById('profile-popup').classList.remove('open');
|
|
8568
|
+
if (isAgentMetadataDrawerOpen()) closeAgentMetadataDrawer();
|
|
6829
8569
|
|
|
6830
8570
|
// Populate basic fields
|
|
6831
8571
|
document.getElementById('pe-display-name').value = agent.display_name || editingAgent;
|
|
@@ -6972,16 +8712,238 @@ function saveProfile() {
|
|
|
6972
8712
|
};
|
|
6973
8713
|
if (avatar) body.avatar = avatar;
|
|
6974
8714
|
|
|
6975
|
-
lttFetch('/api/profiles' + projectParam(), {
|
|
6976
|
-
method: 'POST',
|
|
6977
|
-
headers: { 'Content-Type': 'application/json' },
|
|
6978
|
-
body: JSON.stringify(body)
|
|
6979
|
-
}).then(function(r) { return r.json(); }).then(function(res) {
|
|
6980
|
-
if (res.success) {
|
|
6981
|
-
closeProfileEditor();
|
|
6982
|
-
poll();
|
|
6983
|
-
}
|
|
6984
|
-
}).catch(function(e) { console.error('Save profile failed:', e); });
|
|
8715
|
+
lttFetch('/api/profiles' + projectParam(), {
|
|
8716
|
+
method: 'POST',
|
|
8717
|
+
headers: { 'Content-Type': 'application/json' },
|
|
8718
|
+
body: JSON.stringify(body)
|
|
8719
|
+
}).then(function(r) { return r.json(); }).then(function(res) {
|
|
8720
|
+
if (res.success) {
|
|
8721
|
+
closeProfileEditor();
|
|
8722
|
+
poll();
|
|
8723
|
+
}
|
|
8724
|
+
}).catch(function(e) { console.error('Save profile failed:', e); });
|
|
8725
|
+
}
|
|
8726
|
+
|
|
8727
|
+
// ==================== AGENT METADATA DRAWER ====================
|
|
8728
|
+
|
|
8729
|
+
var activeAgentDrawer = '';
|
|
8730
|
+
|
|
8731
|
+
function isAgentMetadataDrawerOpen() {
|
|
8732
|
+
var drawer = document.getElementById('agent-drawer');
|
|
8733
|
+
return !!(drawer && drawer.classList.contains('open'));
|
|
8734
|
+
}
|
|
8735
|
+
|
|
8736
|
+
function formatAgentDrawerValue(value) {
|
|
8737
|
+
if (value === null || value === undefined || value === '') return '<span class="agent-detail-empty">—</span>';
|
|
8738
|
+
return escapeHtml(String(value));
|
|
8739
|
+
}
|
|
8740
|
+
|
|
8741
|
+
function formatAgentDrawerTimestamp(timestamp) {
|
|
8742
|
+
if (!timestamp) return '<span class="agent-detail-empty">—</span>';
|
|
8743
|
+
return escapeHtml(timeAgo(timestamp) + ' • ' + new Date(timestamp).toLocaleString());
|
|
8744
|
+
}
|
|
8745
|
+
|
|
8746
|
+
function formatAgentDrawerIdle(seconds) {
|
|
8747
|
+
if (seconds === null || seconds === undefined || seconds === '') return '<span class="agent-detail-empty">—</span>';
|
|
8748
|
+
var total = Math.max(0, parseInt(seconds, 10) || 0);
|
|
8749
|
+
if (total < 60) return escapeHtml(total + 's');
|
|
8750
|
+
var mins = Math.floor(total / 60);
|
|
8751
|
+
var secs = total % 60;
|
|
8752
|
+
if (mins < 60) return escapeHtml(mins + 'm ' + secs + 's');
|
|
8753
|
+
var hours = Math.floor(mins / 60);
|
|
8754
|
+
return escapeHtml(hours + 'h ' + (mins % 60) + 'm');
|
|
8755
|
+
}
|
|
8756
|
+
|
|
8757
|
+
function buildAgentDetailCard(label, valueHtml, muted) {
|
|
8758
|
+
return '<div class="agent-detail-card">' +
|
|
8759
|
+
'<div class="agent-detail-label">' + escapeHtml(label) + '</div>' +
|
|
8760
|
+
'<div class="agent-detail-value' + (muted ? ' muted' : '') + '">' + valueHtml + '</div>' +
|
|
8761
|
+
'</div>';
|
|
8762
|
+
}
|
|
8763
|
+
|
|
8764
|
+
function buildAgentMetadataSummary(agentName) {
|
|
8765
|
+
var summary = {
|
|
8766
|
+
sent: 0,
|
|
8767
|
+
received: 0,
|
|
8768
|
+
handoffsOut: 0,
|
|
8769
|
+
handoffsIn: 0,
|
|
8770
|
+
threads: {},
|
|
8771
|
+
channels: {},
|
|
8772
|
+
lastSent: null,
|
|
8773
|
+
lastReceived: null,
|
|
8774
|
+
};
|
|
8775
|
+
|
|
8776
|
+
for (var i = 0; i < cachedHistory.length; i++) {
|
|
8777
|
+
var message = cachedHistory[i];
|
|
8778
|
+
if (message.from === agentName) {
|
|
8779
|
+
summary.sent++;
|
|
8780
|
+
if (message.type === 'handoff') summary.handoffsOut++;
|
|
8781
|
+
if (message.thread_id) summary.threads[message.thread_id] = true;
|
|
8782
|
+
summary.channels[message.channel || 'general'] = true;
|
|
8783
|
+
summary.lastSent = message;
|
|
8784
|
+
}
|
|
8785
|
+
if (message.to === agentName) {
|
|
8786
|
+
summary.received++;
|
|
8787
|
+
if (message.type === 'handoff') summary.handoffsIn++;
|
|
8788
|
+
if (message.thread_id) summary.threads[message.thread_id] = true;
|
|
8789
|
+
summary.channels[message.channel || 'general'] = true;
|
|
8790
|
+
summary.lastReceived = message;
|
|
8791
|
+
}
|
|
8792
|
+
}
|
|
8793
|
+
|
|
8794
|
+
return summary;
|
|
8795
|
+
}
|
|
8796
|
+
|
|
8797
|
+
function openAgentMetadataDrawerFromPopup() {
|
|
8798
|
+
if (!editingAgent) return;
|
|
8799
|
+
openAgentMetadataDrawer(editingAgent);
|
|
8800
|
+
}
|
|
8801
|
+
|
|
8802
|
+
function focusInjectForAgent(agentName) {
|
|
8803
|
+
closeAgentMetadataDrawer();
|
|
8804
|
+
switchView('messages');
|
|
8805
|
+
var target = document.getElementById('inject-target');
|
|
8806
|
+
var content = document.getElementById('inject-content');
|
|
8807
|
+
if (target) target.value = agentName;
|
|
8808
|
+
updateSendBtn();
|
|
8809
|
+
if (content) content.focus();
|
|
8810
|
+
}
|
|
8811
|
+
|
|
8812
|
+
function renderAgentMetadataDrawer() {
|
|
8813
|
+
var agent = cachedAgents[activeAgentDrawer];
|
|
8814
|
+
var body = document.getElementById('agent-drawer-body');
|
|
8815
|
+
var title = document.getElementById('agent-drawer-title');
|
|
8816
|
+
var subtitle = document.getElementById('agent-drawer-subtitle');
|
|
8817
|
+
var avatar = document.getElementById('agent-drawer-avatar');
|
|
8818
|
+
var avatarImg = document.getElementById('agent-drawer-avatar-img');
|
|
8819
|
+
if (!body || !title || !subtitle || !avatar || !avatarImg) return;
|
|
8820
|
+
|
|
8821
|
+
if (!activeAgentDrawer || !agent) {
|
|
8822
|
+
title.textContent = 'Agent details unavailable';
|
|
8823
|
+
subtitle.textContent = '';
|
|
8824
|
+
avatar.style.display = 'flex';
|
|
8825
|
+
avatarImg.style.display = 'none';
|
|
8826
|
+
avatar.textContent = '?';
|
|
8827
|
+
avatar.style.background = 'var(--surface-3)';
|
|
8828
|
+
body.innerHTML = '<div class="agent-detail-note"><div class="agent-detail-note-label">Unavailable</div><div class="agent-detail-note-value">This agent is not currently present in the cached dashboard data.</div></div>';
|
|
8829
|
+
return;
|
|
8830
|
+
}
|
|
8831
|
+
|
|
8832
|
+
var color = getColor(activeAgentDrawer);
|
|
8833
|
+
var displayName = agent.display_name || activeAgentDrawer;
|
|
8834
|
+
var state = agent.status || (agent.alive ? 'active' : 'dead');
|
|
8835
|
+
var stateLabel = state.charAt(0).toUpperCase() + state.slice(1);
|
|
8836
|
+
var listeningLabel = state === 'dead' ? 'Offline' : (agent.is_listening ? 'Listening' : 'Busy');
|
|
8837
|
+
var hasContract = !!(agent.has_explicit_contract || agent.archetype || (agent.skills && agent.skills.length) || (agent.contract && agent.contract.archetype) || (agent.contract_mode && agent.contract_mode !== 'advisory'));
|
|
8838
|
+
var summary = buildAgentMetadataSummary(activeAgentDrawer);
|
|
8839
|
+
var channelNames = Object.keys(summary.channels).sort();
|
|
8840
|
+
var channelSummary = channelNames.length ? channelNames.join(', ') : '—';
|
|
8841
|
+
|
|
8842
|
+
title.textContent = displayName;
|
|
8843
|
+
title.style.color = color;
|
|
8844
|
+
subtitle.textContent = '@' + activeAgentDrawer + ' • ' + (agent.provider || 'unknown') + ' • branch ' + (agent.branch || 'main');
|
|
8845
|
+
|
|
8846
|
+
if (agent.avatar && (agent.avatar.startsWith('data:image/') || agent.avatar.startsWith('/'))) {
|
|
8847
|
+
avatarImg.setAttribute('src', agent.avatar);
|
|
8848
|
+
avatarImg.style.display = '';
|
|
8849
|
+
avatar.style.display = 'none';
|
|
8850
|
+
} else {
|
|
8851
|
+
avatarImg.style.display = 'none';
|
|
8852
|
+
avatar.style.display = 'flex';
|
|
8853
|
+
avatar.textContent = initial(activeAgentDrawer);
|
|
8854
|
+
avatar.style.background = color;
|
|
8855
|
+
}
|
|
8856
|
+
|
|
8857
|
+
var html = '';
|
|
8858
|
+
html += '<div class="agent-drawer-status-row">';
|
|
8859
|
+
html += '<span class="agent-detail-pill"><strong>' + escapeHtml(stateLabel) + '</strong></span>';
|
|
8860
|
+
html += '<span class="agent-detail-pill">Listening <strong>' + escapeHtml(listeningLabel) + '</strong></span>';
|
|
8861
|
+
if (agent.role) html += '<span class="agent-detail-pill">Role <strong>' + escapeHtml(agent.role) + '</strong></span>';
|
|
8862
|
+
if (hasContract && agent.contract && agent.contract.archetype) html += '<span class="agent-detail-pill">Archetype <strong>' + escapeHtml(agent.contract.archetype) + '</strong></span>';
|
|
8863
|
+
if (hasContract) html += '<span class="agent-detail-pill">Contract <strong>' + escapeHtml(agent.contract_mode || 'advisory') + '</strong></span>';
|
|
8864
|
+
html += '<span class="agent-detail-pill">Branch <strong>' + escapeHtml(agent.branch || 'main') + '</strong></span>';
|
|
8865
|
+
html += '</div>';
|
|
8866
|
+
|
|
8867
|
+
if (agent.current_status) {
|
|
8868
|
+
html += '<div class="agent-detail-note">' +
|
|
8869
|
+
'<div class="agent-detail-note-label">Current status</div>' +
|
|
8870
|
+
'<div class="agent-detail-note-value">' + escapeHtml(agent.current_status) + '</div>' +
|
|
8871
|
+
'</div>';
|
|
8872
|
+
}
|
|
8873
|
+
|
|
8874
|
+
if (agent.bio) {
|
|
8875
|
+
html += '<div class="agent-detail-note">' +
|
|
8876
|
+
'<div class="agent-detail-note-label">Bio</div>' +
|
|
8877
|
+
'<div class="agent-detail-note-value">' + escapeHtml(agent.bio) + '</div>' +
|
|
8878
|
+
'</div>';
|
|
8879
|
+
}
|
|
8880
|
+
|
|
8881
|
+
html += '<div class="agent-drawer-section">';
|
|
8882
|
+
html += '<div class="agent-drawer-section-title">Runtime</div>';
|
|
8883
|
+
html += '<div class="agent-detail-grid">';
|
|
8884
|
+
html += buildAgentDetailCard('Provider', formatAgentDrawerValue(agent.provider || 'unknown'));
|
|
8885
|
+
html += buildAgentDetailCard('PID', formatAgentDrawerValue(agent.pid));
|
|
8886
|
+
html += buildAgentDetailCard('Status', formatAgentDrawerValue(stateLabel));
|
|
8887
|
+
html += buildAgentDetailCard('Idle', formatAgentDrawerIdle(agent.idle_seconds));
|
|
8888
|
+
html += buildAgentDetailCard('Registered', formatAgentDrawerTimestamp(agent.registered_at), true);
|
|
8889
|
+
html += buildAgentDetailCard('Last heartbeat', formatAgentDrawerTimestamp(agent.last_activity), true);
|
|
8890
|
+
html += buildAgentDetailCard('Last message', formatAgentDrawerTimestamp(agent.last_message), true);
|
|
8891
|
+
html += buildAgentDetailCard('Role', formatAgentDrawerValue(agent.role), true);
|
|
8892
|
+
if (hasContract) html += buildAgentDetailCard('Archetype', formatAgentDrawerValue(agent.archetype || (agent.contract && agent.contract.archetype)), true);
|
|
8893
|
+
if (hasContract) html += buildAgentDetailCard('Contract mode', formatAgentDrawerValue(agent.contract_mode || 'advisory'), true);
|
|
8894
|
+
if (hasContract) html += buildAgentDetailCard('Skills', formatAgentDrawerValue((agent.skills || []).join(', ')), true);
|
|
8895
|
+
if (hasContract) html += buildAgentDetailCard('Effective skills', formatAgentDrawerValue(agent.contract && agent.contract.effective_skills ? agent.contract.effective_skills.join(', ') : ''), true);
|
|
8896
|
+
if (hasContract) html += buildAgentDetailCard('Role alignment', formatAgentDrawerValue(agent.contract && agent.contract.role_alignment ? String(agent.contract.role_alignment).replace(/_/g, ' ') : ''), true);
|
|
8897
|
+
html += '</div>';
|
|
8898
|
+
html += '</div>';
|
|
8899
|
+
|
|
8900
|
+
html += '<div class="agent-drawer-section">';
|
|
8901
|
+
html += '<div class="agent-drawer-section-title">Conversation</div>';
|
|
8902
|
+
html += '<div class="agent-detail-grid">';
|
|
8903
|
+
html += buildAgentDetailCard('Sent', formatAgentDrawerValue(summary.sent));
|
|
8904
|
+
html += buildAgentDetailCard('Received', formatAgentDrawerValue(summary.received));
|
|
8905
|
+
html += buildAgentDetailCard('Handoffs out', formatAgentDrawerValue(summary.handoffsOut));
|
|
8906
|
+
html += buildAgentDetailCard('Handoffs in', formatAgentDrawerValue(summary.handoffsIn));
|
|
8907
|
+
html += buildAgentDetailCard('Threads', formatAgentDrawerValue(Object.keys(summary.threads).length));
|
|
8908
|
+
html += buildAgentDetailCard('Channels', formatAgentDrawerValue(channelSummary), true);
|
|
8909
|
+
html += buildAgentDetailCard('Last sent', formatAgentDrawerTimestamp(summary.lastSent && summary.lastSent.timestamp), true);
|
|
8910
|
+
html += buildAgentDetailCard('Last received', formatAgentDrawerTimestamp(summary.lastReceived && summary.lastReceived.timestamp), true);
|
|
8911
|
+
html += '</div>';
|
|
8912
|
+
html += '</div>';
|
|
8913
|
+
|
|
8914
|
+
html += '<div class="agent-drawer-actions">';
|
|
8915
|
+
html += '<button class="btn btn-primary" type="button" onclick="focusInjectForAgent(\'' + escapeHtml(activeAgentDrawer) + '\')">Message Agent</button>';
|
|
8916
|
+
html += '<button class="btn" type="button" onclick="openProfileEditor()">Edit Profile</button>';
|
|
8917
|
+
html += '</div>';
|
|
8918
|
+
|
|
8919
|
+
body.innerHTML = html;
|
|
8920
|
+
}
|
|
8921
|
+
|
|
8922
|
+
function openAgentMetadataDrawer(agentName) {
|
|
8923
|
+
if (!agentName) return;
|
|
8924
|
+
editingAgent = agentName;
|
|
8925
|
+
activeAgentDrawer = agentName;
|
|
8926
|
+
document.getElementById('profile-popup').classList.remove('open');
|
|
8927
|
+
renderAgentMetadataDrawer();
|
|
8928
|
+
document.getElementById('agent-drawer-backdrop').classList.add('open');
|
|
8929
|
+
document.getElementById('agent-drawer').classList.add('open');
|
|
8930
|
+
document.getElementById('agent-drawer').setAttribute('aria-hidden', 'false');
|
|
8931
|
+
}
|
|
8932
|
+
|
|
8933
|
+
function closeAgentMetadataDrawer() {
|
|
8934
|
+
activeAgentDrawer = '';
|
|
8935
|
+
document.getElementById('agent-drawer-backdrop').classList.remove('open');
|
|
8936
|
+
document.getElementById('agent-drawer').classList.remove('open');
|
|
8937
|
+
document.getElementById('agent-drawer').setAttribute('aria-hidden', 'true');
|
|
8938
|
+
}
|
|
8939
|
+
|
|
8940
|
+
function refreshAgentMetadataDrawer() {
|
|
8941
|
+
if (!isAgentMetadataDrawerOpen()) return;
|
|
8942
|
+
if (!activeAgentDrawer || !cachedAgents[activeAgentDrawer]) {
|
|
8943
|
+
closeAgentMetadataDrawer();
|
|
8944
|
+
return;
|
|
8945
|
+
}
|
|
8946
|
+
renderAgentMetadataDrawer();
|
|
6985
8947
|
}
|
|
6986
8948
|
|
|
6987
8949
|
// ==================== v3.0: WORKSPACES ====================
|
|
@@ -7034,15 +8996,26 @@ function renderWorkspaces(data) {
|
|
|
7034
8996
|
// ==================== v3.0: WORKFLOWS ====================
|
|
7035
8997
|
|
|
7036
8998
|
function fetchWorkflows() {
|
|
7037
|
-
|
|
8999
|
+
if (!isMainBranchSelected()) {
|
|
9000
|
+
renderMainBranchOnlyView('workflows-area', 'Workflows');
|
|
9001
|
+
return;
|
|
9002
|
+
}
|
|
7038
9003
|
document.getElementById('workflows-area').innerHTML = '<div class="loading-spinner">Loading workflows...</div>';
|
|
7039
|
-
lttFetch('/api/workflows'
|
|
9004
|
+
lttFetch(scopedApiUrl('/api/workflows')).then(function(r) { return r.json(); }).then(function(data) {
|
|
9005
|
+
if (data && data.code === 'main_branch_only') {
|
|
9006
|
+
renderMainBranchOnlyView('workflows-area', 'Workflows');
|
|
9007
|
+
return;
|
|
9008
|
+
}
|
|
7040
9009
|
renderWorkflows(Array.isArray(data) ? data : []);
|
|
7041
9010
|
}).catch(function() {});
|
|
7042
9011
|
}
|
|
7043
9012
|
|
|
7044
9013
|
function renderWorkflows(workflows) {
|
|
7045
9014
|
var el = document.getElementById('workflows-area');
|
|
9015
|
+
if (!isMainBranchSelected()) {
|
|
9016
|
+
el.innerHTML = mainBranchOnlyViewHtml('Workflows');
|
|
9017
|
+
return;
|
|
9018
|
+
}
|
|
7046
9019
|
if (!workflows.length) {
|
|
7047
9020
|
el.innerHTML = '<div class="tasks-empty">No workflows yet. Agents create workflows with <code style="background:var(--surface-2);padding:2px 6px;border-radius:4px;font-size:12px;color:var(--orange)">create_workflow(name, steps)</code></div>';
|
|
7048
9021
|
return;
|
|
@@ -7106,24 +9079,687 @@ function renderWorkflows(workflows) {
|
|
|
7106
9079
|
}
|
|
7107
9080
|
|
|
7108
9081
|
function dashAdvanceWorkflow(wfId) {
|
|
7109
|
-
|
|
9082
|
+
if (!isMainBranchSelected()) {
|
|
9083
|
+
showToast('Workflows only support the main branch right now.');
|
|
9084
|
+
return;
|
|
9085
|
+
}
|
|
9086
|
+
lttFetch(scopedApiUrl('/api/workflows'), {
|
|
7110
9087
|
method: 'POST',
|
|
7111
9088
|
headers: { 'Content-Type': 'application/json' },
|
|
7112
9089
|
body: JSON.stringify({ action: 'advance', workflow_id: wfId })
|
|
7113
9090
|
}).then(function() { fetchWorkflows(); }).catch(function() {});
|
|
7114
9091
|
}
|
|
7115
9092
|
|
|
9093
|
+
// ==================== TASK 8C: GRAPH VIEW ====================
|
|
9094
|
+
|
|
9095
|
+
var graphWorkflowState = {
|
|
9096
|
+
cacheKey: '',
|
|
9097
|
+
status: 'idle',
|
|
9098
|
+
data: [],
|
|
9099
|
+
error: '',
|
|
9100
|
+
lastFetchedAt: 0,
|
|
9101
|
+
};
|
|
9102
|
+
|
|
9103
|
+
var GRAPH_CHANNEL_PALETTE = [
|
|
9104
|
+
{ accent: 'var(--accent)', dim: 'var(--accent-dim)' },
|
|
9105
|
+
{ accent: 'var(--purple)', dim: 'var(--purple-dim)' },
|
|
9106
|
+
{ accent: 'var(--green)', dim: 'var(--green-dim)' },
|
|
9107
|
+
{ accent: 'var(--orange)', dim: 'var(--orange-dim)' },
|
|
9108
|
+
{ accent: 'var(--red)', dim: 'var(--red-dim)' },
|
|
9109
|
+
];
|
|
9110
|
+
|
|
9111
|
+
function getGraphWorkflowCacheKey() {
|
|
9112
|
+
return (activeProject || '__default__') + '::main';
|
|
9113
|
+
}
|
|
9114
|
+
|
|
9115
|
+
function shouldRefreshGraphWorkflowData() {
|
|
9116
|
+
if (graphWorkflowState.cacheKey !== getGraphWorkflowCacheKey()) return true;
|
|
9117
|
+
if (graphWorkflowState.status === 'idle') return true;
|
|
9118
|
+
return (Date.now() - graphWorkflowState.lastFetchedAt) > 15000;
|
|
9119
|
+
}
|
|
9120
|
+
|
|
9121
|
+
function ensureGraphWorkflowData(options) {
|
|
9122
|
+
var opts = options || {};
|
|
9123
|
+
if (!opts.force && !shouldRefreshGraphWorkflowData()) return;
|
|
9124
|
+
if (graphWorkflowState.status === 'loading') return;
|
|
9125
|
+
fetchGraphWorkflowData();
|
|
9126
|
+
}
|
|
9127
|
+
|
|
9128
|
+
function normalizeGraphWorkflowList(rawWorkflows) {
|
|
9129
|
+
if (!Array.isArray(rawWorkflows)) return [];
|
|
9130
|
+
var workflows = [];
|
|
9131
|
+
for (var i = 0; i < rawWorkflows.length; i++) {
|
|
9132
|
+
var workflow = rawWorkflows[i];
|
|
9133
|
+
if (!workflow || typeof workflow !== 'object' || !Array.isArray(workflow.steps)) continue;
|
|
9134
|
+
var steps = [];
|
|
9135
|
+
for (var j = 0; j < workflow.steps.length; j++) {
|
|
9136
|
+
var step = workflow.steps[j];
|
|
9137
|
+
if (!step || typeof step !== 'object') continue;
|
|
9138
|
+
steps.push({
|
|
9139
|
+
id: step.id,
|
|
9140
|
+
description: cleanString(step.description, 'Step ' + (j + 1)),
|
|
9141
|
+
status: cleanString(step.status, 'pending') || 'pending',
|
|
9142
|
+
assignee: cleanString(step.assignee, ''),
|
|
9143
|
+
});
|
|
9144
|
+
}
|
|
9145
|
+
workflows.push({
|
|
9146
|
+
id: cleanString(workflow.id, 'workflow-' + (i + 1)),
|
|
9147
|
+
name: cleanString(workflow.name, 'Workflow ' + (i + 1)),
|
|
9148
|
+
status: cleanString(workflow.status, 'active') || 'active',
|
|
9149
|
+
steps: steps,
|
|
9150
|
+
autonomous: !!workflow.autonomous,
|
|
9151
|
+
parallel: !!workflow.parallel,
|
|
9152
|
+
});
|
|
9153
|
+
}
|
|
9154
|
+
return workflows;
|
|
9155
|
+
}
|
|
9156
|
+
|
|
9157
|
+
function fetchGraphWorkflowData() {
|
|
9158
|
+
var requestCacheKey = getGraphWorkflowCacheKey();
|
|
9159
|
+
var requestUrl = scopedApiUrl('/api/workflows', null, { branch: 'main' });
|
|
9160
|
+
graphWorkflowState.status = 'loading';
|
|
9161
|
+
graphWorkflowState.cacheKey = requestCacheKey;
|
|
9162
|
+
graphWorkflowState.error = '';
|
|
9163
|
+
graphWorkflowState.lastFetchedAt = Date.now();
|
|
9164
|
+
if (activeView === 'graph') renderGraphView();
|
|
9165
|
+
lttFetch(requestUrl).then(function(r) { return r.json(); }).then(function(data) {
|
|
9166
|
+
if (requestCacheKey !== getGraphWorkflowCacheKey()) {
|
|
9167
|
+
graphWorkflowState.status = 'idle';
|
|
9168
|
+
if (activeView === 'graph') ensureGraphWorkflowData({ force: true });
|
|
9169
|
+
return;
|
|
9170
|
+
}
|
|
9171
|
+
graphWorkflowState.cacheKey = requestCacheKey;
|
|
9172
|
+
graphWorkflowState.status = 'ready';
|
|
9173
|
+
graphWorkflowState.data = normalizeGraphWorkflowList(Array.isArray(data) ? data : []);
|
|
9174
|
+
graphWorkflowState.error = '';
|
|
9175
|
+
graphWorkflowState.lastFetchedAt = Date.now();
|
|
9176
|
+
if (activeView === 'graph') renderGraphView();
|
|
9177
|
+
}).catch(function(error) {
|
|
9178
|
+
if (requestCacheKey !== getGraphWorkflowCacheKey()) {
|
|
9179
|
+
graphWorkflowState.status = 'idle';
|
|
9180
|
+
if (activeView === 'graph') ensureGraphWorkflowData({ force: true });
|
|
9181
|
+
return;
|
|
9182
|
+
}
|
|
9183
|
+
graphWorkflowState.cacheKey = requestCacheKey;
|
|
9184
|
+
graphWorkflowState.status = 'error';
|
|
9185
|
+
graphWorkflowState.data = [];
|
|
9186
|
+
graphWorkflowState.error = error && error.message ? error.message : 'Workflow data unavailable';
|
|
9187
|
+
graphWorkflowState.lastFetchedAt = Date.now();
|
|
9188
|
+
if (activeView === 'graph') renderGraphView();
|
|
9189
|
+
});
|
|
9190
|
+
}
|
|
9191
|
+
|
|
9192
|
+
function normalizeGraphEndpoint(name) {
|
|
9193
|
+
var raw = cleanString(name, '').trim();
|
|
9194
|
+
if (!raw) return null;
|
|
9195
|
+
var lower = raw.toLowerCase();
|
|
9196
|
+
if (lower === '__all__' || lower === '__group__' || lower === 'all' || lower === '*' || lower === 'broadcast') {
|
|
9197
|
+
return { key: '__broadcast__', label: 'Broadcast', synthetic: true };
|
|
9198
|
+
}
|
|
9199
|
+
if (lower === 'system' || lower === 'dashboard' || lower === 'server') {
|
|
9200
|
+
return { key: '__system__', label: raw.charAt(0).toUpperCase() + raw.slice(1), synthetic: true };
|
|
9201
|
+
}
|
|
9202
|
+
return { key: raw, label: raw, synthetic: false };
|
|
9203
|
+
}
|
|
9204
|
+
|
|
9205
|
+
function ensureGraphNode(nodeMap, endpoint, agentsSource, branchName) {
|
|
9206
|
+
if (!endpoint) return null;
|
|
9207
|
+
if (!nodeMap[endpoint.key]) {
|
|
9208
|
+
var agent = !endpoint.synthetic && agentsSource && agentsSource[endpoint.key] ? agentsSource[endpoint.key] : null;
|
|
9209
|
+
var agentStatus = agent && cleanString(agent.status, '') ? cleanString(agent.status, '') : (agent && agent.alive ? 'active' : 'unknown');
|
|
9210
|
+
nodeMap[endpoint.key] = {
|
|
9211
|
+
key: endpoint.key,
|
|
9212
|
+
label: agent && cleanString(agent.display_name, '').trim() ? cleanString(agent.display_name, '').trim() : endpoint.label,
|
|
9213
|
+
agentName: endpoint.synthetic ? '' : endpoint.key,
|
|
9214
|
+
synthetic: !!endpoint.synthetic,
|
|
9215
|
+
color: endpoint.synthetic ? 'var(--surface-3)' : getColor(endpoint.key),
|
|
9216
|
+
status: endpoint.synthetic ? 'synthetic' : (agentStatus || 'unknown'),
|
|
9217
|
+
provider: agent ? cleanString(agent.provider, '') : '',
|
|
9218
|
+
role: agent ? cleanString(agent.role, '') : '',
|
|
9219
|
+
branch: agent ? (cleanString(agent.branch, 'main') || 'main') : branchName,
|
|
9220
|
+
sendCount: 0,
|
|
9221
|
+
receiveCount: 0,
|
|
9222
|
+
channelCounts: {},
|
|
9223
|
+
neighbors: {},
|
|
9224
|
+
x: 0,
|
|
9225
|
+
y: 0,
|
|
9226
|
+
laneKey: '',
|
|
9227
|
+
dominantChannel: '',
|
|
9228
|
+
};
|
|
9229
|
+
}
|
|
9230
|
+
return nodeMap[endpoint.key];
|
|
9231
|
+
}
|
|
9232
|
+
|
|
9233
|
+
function getGraphDominantChannel(channelCounts) {
|
|
9234
|
+
var keys = Object.keys(channelCounts || {});
|
|
9235
|
+
if (!keys.length) return '';
|
|
9236
|
+
keys.sort(function(a, b) {
|
|
9237
|
+
return (channelCounts[b] || 0) - (channelCounts[a] || 0) || a.localeCompare(b);
|
|
9238
|
+
});
|
|
9239
|
+
return keys[0] || '';
|
|
9240
|
+
}
|
|
9241
|
+
|
|
9242
|
+
function getGraphLanePalette(index) {
|
|
9243
|
+
return GRAPH_CHANNEL_PALETTE[index % GRAPH_CHANNEL_PALETTE.length];
|
|
9244
|
+
}
|
|
9245
|
+
|
|
9246
|
+
function getGraphStatusColor(status) {
|
|
9247
|
+
if (status === 'active') return 'var(--green)';
|
|
9248
|
+
if (status === 'sleeping' || status === 'idle') return 'var(--orange)';
|
|
9249
|
+
if (status === 'dead') return 'var(--red)';
|
|
9250
|
+
if (status === 'synthetic') return 'var(--border-light)';
|
|
9251
|
+
return 'var(--text-muted)';
|
|
9252
|
+
}
|
|
9253
|
+
|
|
9254
|
+
function truncateGraphText(text, maxLength) {
|
|
9255
|
+
var value = cleanString(text, '');
|
|
9256
|
+
if (value.length <= maxLength) return value;
|
|
9257
|
+
return value.slice(0, Math.max(1, maxLength - 1)) + '…';
|
|
9258
|
+
}
|
|
9259
|
+
|
|
9260
|
+
function buildGraphModel() {
|
|
9261
|
+
var branchName = getActiveDashboardBranchName();
|
|
9262
|
+
var agentsSource = cachedAgents && typeof cachedAgents === 'object' ? cachedAgents : {};
|
|
9263
|
+
var historySource = Array.isArray(cachedHistory) ? cachedHistory : [];
|
|
9264
|
+
var nodeMap = {};
|
|
9265
|
+
var edgeMap = {};
|
|
9266
|
+
var channelMap = {};
|
|
9267
|
+
var agentNames = Object.keys(agentsSource || {});
|
|
9268
|
+
|
|
9269
|
+
for (var a = 0; a < agentNames.length; a++) {
|
|
9270
|
+
var agentName = agentNames[a];
|
|
9271
|
+
var agent = agentsSource[agentName];
|
|
9272
|
+
var agentBranch = agent ? (cleanString(agent.branch, 'main') || 'main') : 'main';
|
|
9273
|
+
if (agentBranch !== branchName) continue;
|
|
9274
|
+
ensureGraphNode(nodeMap, { key: agentName, label: agentName, synthetic: false }, agentsSource, branchName);
|
|
9275
|
+
}
|
|
9276
|
+
|
|
9277
|
+
for (var i = 0; i < historySource.length; i++) {
|
|
9278
|
+
var message = historySource[i];
|
|
9279
|
+
if (!message || typeof message !== 'object') continue;
|
|
9280
|
+
var fromEndpoint = normalizeGraphEndpoint(message.from);
|
|
9281
|
+
var toEndpoint = normalizeGraphEndpoint(message.to);
|
|
9282
|
+
if (!fromEndpoint || !toEndpoint) continue;
|
|
9283
|
+
var fromNode = ensureGraphNode(nodeMap, fromEndpoint, agentsSource, branchName);
|
|
9284
|
+
var toNode = ensureGraphNode(nodeMap, toEndpoint, agentsSource, branchName);
|
|
9285
|
+
if (!fromNode || !toNode) continue;
|
|
9286
|
+
|
|
9287
|
+
var channelName = cleanString(message.channel, '').trim() || 'general';
|
|
9288
|
+
if (!channelMap[channelName]) {
|
|
9289
|
+
channelMap[channelName] = {
|
|
9290
|
+
name: channelName,
|
|
9291
|
+
count: 0,
|
|
9292
|
+
participants: {},
|
|
9293
|
+
};
|
|
9294
|
+
}
|
|
9295
|
+
channelMap[channelName].count += 1;
|
|
9296
|
+
channelMap[channelName].participants[fromNode.key] = true;
|
|
9297
|
+
channelMap[channelName].participants[toNode.key] = true;
|
|
9298
|
+
|
|
9299
|
+
fromNode.sendCount += 1;
|
|
9300
|
+
toNode.receiveCount += 1;
|
|
9301
|
+
fromNode.channelCounts[channelName] = (fromNode.channelCounts[channelName] || 0) + 1;
|
|
9302
|
+
toNode.channelCounts[channelName] = (toNode.channelCounts[channelName] || 0) + 1;
|
|
9303
|
+
fromNode.neighbors[toNode.key] = true;
|
|
9304
|
+
toNode.neighbors[fromNode.key] = true;
|
|
9305
|
+
|
|
9306
|
+
if (fromNode.key === toNode.key) continue;
|
|
9307
|
+
var edgeKey = fromNode.key + '→' + toNode.key;
|
|
9308
|
+
if (!edgeMap[edgeKey]) {
|
|
9309
|
+
edgeMap[edgeKey] = {
|
|
9310
|
+
key: edgeKey,
|
|
9311
|
+
from: fromNode.key,
|
|
9312
|
+
to: toNode.key,
|
|
9313
|
+
count: 0,
|
|
9314
|
+
handoffs: 0,
|
|
9315
|
+
channelCounts: {},
|
|
9316
|
+
lastTimestamp: '',
|
|
9317
|
+
};
|
|
9318
|
+
}
|
|
9319
|
+
edgeMap[edgeKey].count += 1;
|
|
9320
|
+
if (message.type === 'handoff') edgeMap[edgeKey].handoffs += 1;
|
|
9321
|
+
edgeMap[edgeKey].channelCounts[channelName] = (edgeMap[edgeKey].channelCounts[channelName] || 0) + 1;
|
|
9322
|
+
if (message.timestamp) edgeMap[edgeKey].lastTimestamp = message.timestamp;
|
|
9323
|
+
}
|
|
9324
|
+
|
|
9325
|
+
var channels = [];
|
|
9326
|
+
var channelKeys = Object.keys(channelMap);
|
|
9327
|
+
for (var c = 0; c < channelKeys.length; c++) {
|
|
9328
|
+
var key = channelKeys[c];
|
|
9329
|
+
channels.push({
|
|
9330
|
+
name: key,
|
|
9331
|
+
count: channelMap[key].count,
|
|
9332
|
+
participantCount: Object.keys(channelMap[key].participants).length,
|
|
9333
|
+
});
|
|
9334
|
+
}
|
|
9335
|
+
channels.sort(function(a, b) {
|
|
9336
|
+
return b.count - a.count || a.name.localeCompare(b.name);
|
|
9337
|
+
});
|
|
9338
|
+
|
|
9339
|
+
var displayedChannels = channels.slice(0, 5);
|
|
9340
|
+
var displayedChannelNames = {};
|
|
9341
|
+
var lanes = [];
|
|
9342
|
+
var laneLookup = {};
|
|
9343
|
+
|
|
9344
|
+
for (var dc = 0; dc < displayedChannels.length; dc++) {
|
|
9345
|
+
var channel = displayedChannels[dc];
|
|
9346
|
+
var palette = getGraphLanePalette(dc);
|
|
9347
|
+
var laneKey = 'channel:' + channel.name;
|
|
9348
|
+
displayedChannelNames[channel.name] = true;
|
|
9349
|
+
laneLookup[laneKey] = {
|
|
9350
|
+
key: laneKey,
|
|
9351
|
+
label: '#' + channel.name,
|
|
9352
|
+
type: 'channel',
|
|
9353
|
+
accent: palette.accent,
|
|
9354
|
+
dim: palette.dim,
|
|
9355
|
+
count: channel.count,
|
|
9356
|
+
participantCount: channel.participantCount,
|
|
9357
|
+
nodes: [],
|
|
9358
|
+
y: 0,
|
|
9359
|
+
height: 0,
|
|
9360
|
+
cy: 0,
|
|
9361
|
+
};
|
|
9362
|
+
lanes.push(laneLookup[laneKey]);
|
|
9363
|
+
}
|
|
9364
|
+
|
|
9365
|
+
var hiddenChannelCount = 0;
|
|
9366
|
+
var hiddenChannelMessages = 0;
|
|
9367
|
+
for (var hc = 5; hc < channels.length; hc++) {
|
|
9368
|
+
hiddenChannelCount += 1;
|
|
9369
|
+
hiddenChannelMessages += channels[hc].count;
|
|
9370
|
+
}
|
|
9371
|
+
if (hiddenChannelCount > 0) {
|
|
9372
|
+
laneLookup.__other__ = {
|
|
9373
|
+
key: '__other__',
|
|
9374
|
+
label: 'Other channels',
|
|
9375
|
+
type: 'other',
|
|
9376
|
+
accent: 'var(--text-dim)',
|
|
9377
|
+
dim: 'var(--surface-2)',
|
|
9378
|
+
count: hiddenChannelMessages,
|
|
9379
|
+
participantCount: 0,
|
|
9380
|
+
nodes: [],
|
|
9381
|
+
y: 0,
|
|
9382
|
+
height: 0,
|
|
9383
|
+
cy: 0,
|
|
9384
|
+
};
|
|
9385
|
+
lanes.push(laneLookup.__other__);
|
|
9386
|
+
}
|
|
9387
|
+
|
|
9388
|
+
var nodes = [];
|
|
9389
|
+
var nodeKeys = Object.keys(nodeMap);
|
|
9390
|
+
var hasIdleNodes = false;
|
|
9391
|
+
var hasSyntheticNodes = false;
|
|
9392
|
+
for (var nk = 0; nk < nodeKeys.length; nk++) {
|
|
9393
|
+
var node = nodeMap[nodeKeys[nk]];
|
|
9394
|
+
node.dominantChannel = getGraphDominantChannel(node.channelCounts);
|
|
9395
|
+
if (node.synthetic) {
|
|
9396
|
+
node.laneKey = '__system__';
|
|
9397
|
+
hasSyntheticNodes = true;
|
|
9398
|
+
} else if (!node.dominantChannel) {
|
|
9399
|
+
node.laneKey = '__idle__';
|
|
9400
|
+
hasIdleNodes = true;
|
|
9401
|
+
} else if (displayedChannelNames[node.dominantChannel]) {
|
|
9402
|
+
node.laneKey = 'channel:' + node.dominantChannel;
|
|
9403
|
+
} else {
|
|
9404
|
+
node.laneKey = hiddenChannelCount > 0 ? '__other__' : 'channel:' + node.dominantChannel;
|
|
9405
|
+
if (!laneLookup[node.laneKey]) {
|
|
9406
|
+
laneLookup[node.laneKey] = {
|
|
9407
|
+
key: node.laneKey,
|
|
9408
|
+
label: '#' + node.dominantChannel,
|
|
9409
|
+
type: 'channel',
|
|
9410
|
+
accent: 'var(--accent)',
|
|
9411
|
+
dim: 'var(--accent-dim)',
|
|
9412
|
+
count: node.channelCounts[node.dominantChannel] || 0,
|
|
9413
|
+
participantCount: 1,
|
|
9414
|
+
nodes: [],
|
|
9415
|
+
y: 0,
|
|
9416
|
+
height: 0,
|
|
9417
|
+
cy: 0,
|
|
9418
|
+
};
|
|
9419
|
+
lanes.push(laneLookup[node.laneKey]);
|
|
9420
|
+
}
|
|
9421
|
+
}
|
|
9422
|
+
if (laneLookup[node.laneKey]) laneLookup[node.laneKey].nodes.push(node.key);
|
|
9423
|
+
nodes.push(node);
|
|
9424
|
+
}
|
|
9425
|
+
|
|
9426
|
+
if (hasIdleNodes) {
|
|
9427
|
+
laneLookup.__idle__ = {
|
|
9428
|
+
key: '__idle__',
|
|
9429
|
+
label: 'Idle agents',
|
|
9430
|
+
type: 'idle',
|
|
9431
|
+
accent: 'var(--text-muted)',
|
|
9432
|
+
dim: 'var(--surface-2)',
|
|
9433
|
+
count: 0,
|
|
9434
|
+
participantCount: laneLookup.__idle__ && laneLookup.__idle__.nodes ? laneLookup.__idle__.nodes.length : 0,
|
|
9435
|
+
nodes: laneLookup.__idle__ && laneLookup.__idle__.nodes ? laneLookup.__idle__.nodes : [],
|
|
9436
|
+
y: 0,
|
|
9437
|
+
height: 0,
|
|
9438
|
+
cy: 0,
|
|
9439
|
+
};
|
|
9440
|
+
if (!laneLookup.__idle__.nodes.length) {
|
|
9441
|
+
for (var idleIndex = 0; idleIndex < nodes.length; idleIndex++) {
|
|
9442
|
+
if (nodes[idleIndex].laneKey === '__idle__') laneLookup.__idle__.nodes.push(nodes[idleIndex].key);
|
|
9443
|
+
}
|
|
9444
|
+
}
|
|
9445
|
+
lanes.push(laneLookup.__idle__);
|
|
9446
|
+
}
|
|
9447
|
+
|
|
9448
|
+
if (hasSyntheticNodes) {
|
|
9449
|
+
laneLookup.__system__ = {
|
|
9450
|
+
key: '__system__',
|
|
9451
|
+
label: 'System endpoints',
|
|
9452
|
+
type: 'system',
|
|
9453
|
+
accent: 'var(--border-light)',
|
|
9454
|
+
dim: 'var(--surface-2)',
|
|
9455
|
+
count: 0,
|
|
9456
|
+
participantCount: laneLookup.__system__ && laneLookup.__system__.nodes ? laneLookup.__system__.nodes.length : 0,
|
|
9457
|
+
nodes: laneLookup.__system__ && laneLookup.__system__.nodes ? laneLookup.__system__.nodes : [],
|
|
9458
|
+
y: 0,
|
|
9459
|
+
height: 0,
|
|
9460
|
+
cy: 0,
|
|
9461
|
+
};
|
|
9462
|
+
if (!laneLookup.__system__.nodes.length) {
|
|
9463
|
+
for (var systemIndex = 0; systemIndex < nodes.length; systemIndex++) {
|
|
9464
|
+
if (nodes[systemIndex].laneKey === '__system__') laneLookup.__system__.nodes.push(nodes[systemIndex].key);
|
|
9465
|
+
}
|
|
9466
|
+
}
|
|
9467
|
+
lanes.push(laneLookup.__system__);
|
|
9468
|
+
}
|
|
9469
|
+
|
|
9470
|
+
var uniqueLaneMap = {};
|
|
9471
|
+
var orderedLanes = [];
|
|
9472
|
+
for (var l = 0; l < lanes.length; l++) {
|
|
9473
|
+
if (uniqueLaneMap[lanes[l].key]) continue;
|
|
9474
|
+
uniqueLaneMap[lanes[l].key] = true;
|
|
9475
|
+
orderedLanes.push(lanes[l]);
|
|
9476
|
+
}
|
|
9477
|
+
lanes = orderedLanes;
|
|
9478
|
+
|
|
9479
|
+
var networkPanel = { x: 24, y: 78, w: 808, h: 718 };
|
|
9480
|
+
var laneAreaTop = networkPanel.y + 56;
|
|
9481
|
+
var laneAreaBottom = networkPanel.y + networkPanel.h - 24;
|
|
9482
|
+
var laneAreaHeight = laneAreaBottom - laneAreaTop;
|
|
9483
|
+
var laneHeight = lanes.length ? Math.max(68, Math.floor(laneAreaHeight / lanes.length)) : 0;
|
|
9484
|
+
var nodeXStart = networkPanel.x + 172;
|
|
9485
|
+
var nodeXEnd = networkPanel.x + networkPanel.w - 70;
|
|
9486
|
+
|
|
9487
|
+
for (var laneIndex = 0; laneIndex < lanes.length; laneIndex++) {
|
|
9488
|
+
var lane = lanes[laneIndex];
|
|
9489
|
+
lane.y = laneAreaTop + (laneIndex * laneHeight) + 6;
|
|
9490
|
+
lane.height = Math.max(58, laneHeight - 12);
|
|
9491
|
+
lane.cy = lane.y + (lane.height / 2);
|
|
9492
|
+
lane.participantCount = lane.nodes.length;
|
|
9493
|
+
var laneNodes = [];
|
|
9494
|
+
for (var ln = 0; ln < lane.nodes.length; ln++) {
|
|
9495
|
+
if (nodeMap[lane.nodes[ln]]) laneNodes.push(nodeMap[lane.nodes[ln]]);
|
|
9496
|
+
}
|
|
9497
|
+
laneNodes.sort(function(a, b) {
|
|
9498
|
+
var aCount = a.sendCount + a.receiveCount;
|
|
9499
|
+
var bCount = b.sendCount + b.receiveCount;
|
|
9500
|
+
return bCount - aCount || a.label.localeCompare(b.label);
|
|
9501
|
+
});
|
|
9502
|
+
var useTwoRows = laneNodes.length > 4;
|
|
9503
|
+
var columns = Math.max(1, useTwoRows ? Math.ceil(laneNodes.length / 2) : laneNodes.length);
|
|
9504
|
+
var stepX = columns === 1 ? 0 : (nodeXEnd - nodeXStart) / (columns - 1);
|
|
9505
|
+
for (var nodeIndex = 0; nodeIndex < laneNodes.length; nodeIndex++) {
|
|
9506
|
+
var laneNode = laneNodes[nodeIndex];
|
|
9507
|
+
var row = useTwoRows ? (nodeIndex % 2) : 0;
|
|
9508
|
+
var column = useTwoRows ? Math.floor(nodeIndex / 2) : nodeIndex;
|
|
9509
|
+
laneNode.x = columns === 1 ? (nodeXStart + nodeXEnd) / 2 : (nodeXStart + (column * stepX));
|
|
9510
|
+
laneNode.y = lane.cy + (useTwoRows ? (row === 0 ? -24 : 24) : 0);
|
|
9511
|
+
}
|
|
9512
|
+
}
|
|
9513
|
+
|
|
9514
|
+
var edges = [];
|
|
9515
|
+
var edgeKeys = Object.keys(edgeMap);
|
|
9516
|
+
for (var ek = 0; ek < edgeKeys.length; ek++) {
|
|
9517
|
+
var edge = edgeMap[edgeKeys[ek]];
|
|
9518
|
+
edge.dominantChannel = getGraphDominantChannel(edge.channelCounts);
|
|
9519
|
+
if (edge.dominantChannel && displayedChannelNames[edge.dominantChannel]) edge.laneKey = 'channel:' + edge.dominantChannel;
|
|
9520
|
+
else if (edge.dominantChannel && hiddenChannelCount > 0) edge.laneKey = '__other__';
|
|
9521
|
+
else edge.laneKey = '';
|
|
9522
|
+
edges.push(edge);
|
|
9523
|
+
}
|
|
9524
|
+
edges.sort(function(a, b) {
|
|
9525
|
+
return b.count - a.count || b.handoffs - a.handoffs || String(b.lastTimestamp || '').localeCompare(String(a.lastTimestamp || ''));
|
|
9526
|
+
});
|
|
9527
|
+
|
|
9528
|
+
return {
|
|
9529
|
+
branchName: branchName,
|
|
9530
|
+
nodes: nodes,
|
|
9531
|
+
nodeMap: nodeMap,
|
|
9532
|
+
lanes: lanes,
|
|
9533
|
+
channels: channels,
|
|
9534
|
+
edges: edges,
|
|
9535
|
+
visibleEdges: edges.slice(0, 18),
|
|
9536
|
+
};
|
|
9537
|
+
}
|
|
9538
|
+
|
|
9539
|
+
function buildGraphWorkflowPanelSvg(workflowState) {
|
|
9540
|
+
var panelX = 852;
|
|
9541
|
+
var panelY = 78;
|
|
9542
|
+
var panelW = 324;
|
|
9543
|
+
var panelH = 718;
|
|
9544
|
+
var parts = [];
|
|
9545
|
+
parts.push('<rect x="' + panelX + '" y="' + panelY + '" width="' + panelW + '" height="' + panelH + '" rx="22" style="fill:var(--surface);stroke:var(--border-light);"></rect>');
|
|
9546
|
+
parts.push('<text x="' + (panelX + 18) + '" y="' + (panelY + 28) + '" style="fill:var(--text);font-size:16px;font-weight:700;">Main branch workflows</text>');
|
|
9547
|
+
parts.push('<text x="' + (panelX + 18) + '" y="' + (panelY + 48) + '" style="fill:var(--text-dim);font-size:11px;">Read-only structure from the existing workflow surface</text>');
|
|
9548
|
+
|
|
9549
|
+
if (workflowState.status === 'loading' && !workflowState.data.length) {
|
|
9550
|
+
parts.push('<rect x="' + (panelX + 18) + '" y="' + (panelY + 72) + '" width="' + (panelW - 36) + '" height="92" rx="16" style="fill:var(--surface-2);stroke:var(--border);"></rect>');
|
|
9551
|
+
parts.push('<text x="' + (panelX + 34) + '" y="' + (panelY + 112) + '" style="fill:var(--text);font-size:13px;font-weight:600;">Loading workflows…</text>');
|
|
9552
|
+
parts.push('<text x="' + (panelX + 34) + '" y="' + (panelY + 132) + '" style="fill:var(--text-dim);font-size:11px;">The graph will add the main pipeline rail when the data returns.</text>');
|
|
9553
|
+
return parts.join('');
|
|
9554
|
+
}
|
|
9555
|
+
|
|
9556
|
+
if (workflowState.status === 'error') {
|
|
9557
|
+
parts.push('<rect x="' + (panelX + 18) + '" y="' + (panelY + 72) + '" width="' + (panelW - 36) + '" height="112" rx="16" style="fill:var(--surface-2);stroke:var(--red);"></rect>');
|
|
9558
|
+
parts.push('<text x="' + (panelX + 34) + '" y="' + (panelY + 108) + '" style="fill:var(--text);font-size:13px;font-weight:600;">Workflow data unavailable</text>');
|
|
9559
|
+
parts.push('<text x="' + (panelX + 34) + '" y="' + (panelY + 128) + '" style="fill:var(--text-dim);font-size:11px;">' + escapeHtml(truncateGraphText(workflowState.error || 'The existing workflow view still works.', 42)) + '</text>');
|
|
9560
|
+
parts.push('<text x="' + (panelX + 34) + '" y="' + (panelY + 148) + '" style="fill:var(--text-dim);font-size:11px;">This optional graph surface degrades safely instead of breaking the dashboard.</text>');
|
|
9561
|
+
return parts.join('');
|
|
9562
|
+
}
|
|
9563
|
+
|
|
9564
|
+
var workflows = Array.isArray(workflowState.data) ? workflowState.data.slice(0, 4) : [];
|
|
9565
|
+
if (!workflows.length) {
|
|
9566
|
+
parts.push('<rect x="' + (panelX + 18) + '" y="' + (panelY + 72) + '" width="' + (panelW - 36) + '" height="112" rx="16" style="fill:var(--surface-2);stroke:var(--border);"></rect>');
|
|
9567
|
+
parts.push('<text x="' + (panelX + 34) + '" y="' + (panelY + 110) + '" style="fill:var(--text);font-size:13px;font-weight:600;">No main-branch workflows yet</text>');
|
|
9568
|
+
parts.push('<text x="' + (panelX + 34) + '" y="' + (panelY + 130) + '" style="fill:var(--text-dim);font-size:11px;">The graph still renders agents, channels, and message flow from the current branch.</text>');
|
|
9569
|
+
return parts.join('');
|
|
9570
|
+
}
|
|
9571
|
+
|
|
9572
|
+
var cardHeight = 152;
|
|
9573
|
+
var cardGap = 12;
|
|
9574
|
+
for (var i = 0; i < workflows.length; i++) {
|
|
9575
|
+
var workflow = workflows[i];
|
|
9576
|
+
var steps = Array.isArray(workflow.steps) ? workflow.steps : [];
|
|
9577
|
+
var doneCount = 0;
|
|
9578
|
+
var activeStep = null;
|
|
9579
|
+
for (var s = 0; s < steps.length; s++) {
|
|
9580
|
+
if (steps[s].status === 'done') doneCount += 1;
|
|
9581
|
+
if (!activeStep && steps[s].status === 'in_progress') activeStep = steps[s];
|
|
9582
|
+
}
|
|
9583
|
+
if (!activeStep && steps.length) activeStep = steps[0];
|
|
9584
|
+
var pct = steps.length ? Math.round((doneCount / steps.length) * 100) : 0;
|
|
9585
|
+
var cardX = panelX + 16;
|
|
9586
|
+
var cardY = panelY + 68 + (i * (cardHeight + cardGap));
|
|
9587
|
+
var cardW = panelW - 32;
|
|
9588
|
+
var statusColor = workflow.status === 'completed' ? 'var(--green)' : workflow.status === 'paused' ? 'var(--orange)' : 'var(--accent)';
|
|
9589
|
+
var statusLabel = truncateGraphText(String(workflow.status || 'active').replace(/_/g, ' '), 12).toUpperCase();
|
|
9590
|
+
var summaryLabel = activeStep ? truncateGraphText(activeStep.description || 'Waiting for next step', 42) : 'Waiting for steps';
|
|
9591
|
+
parts.push('<rect x="' + cardX + '" y="' + cardY + '" width="' + cardW + '" height="' + cardHeight + '" rx="18" style="fill:var(--surface-2);stroke:var(--border);"></rect>');
|
|
9592
|
+
parts.push('<text x="' + (cardX + 16) + '" y="' + (cardY + 24) + '" style="fill:var(--text);font-size:13px;font-weight:700;">' + escapeHtml(truncateGraphText(workflow.name, 28)) + '</text>');
|
|
9593
|
+
parts.push('<rect x="' + (cardX + cardW - 94) + '" y="' + (cardY + 12) + '" width="78" height="20" rx="10" style="fill:' + statusColor + ';fill-opacity:0.14;stroke:' + statusColor + ';"></rect>');
|
|
9594
|
+
parts.push('<text x="' + (cardX + cardW - 55) + '" y="' + (cardY + 26) + '" text-anchor="middle" style="fill:' + statusColor + ';font-size:9px;font-weight:700;letter-spacing:0.08em;">' + escapeHtml(statusLabel) + '</text>');
|
|
9595
|
+
parts.push('<text x="' + (cardX + 16) + '" y="' + (cardY + 46) + '" style="fill:var(--text-dim);font-size:10px;">' + doneCount + '/' + steps.length + ' steps complete • ' + pct + '%</text>');
|
|
9596
|
+
parts.push('<rect x="' + (cardX + 16) + '" y="' + (cardY + 56) + '" width="' + (cardW - 32) + '" height="6" rx="3" style="fill:var(--surface-3);"></rect>');
|
|
9597
|
+
parts.push('<rect x="' + (cardX + 16) + '" y="' + (cardY + 56) + '" width="' + Math.max(0, Math.round((cardW - 32) * (pct / 100))) + '" height="6" rx="3" style="fill:' + (workflow.status === 'completed' ? 'var(--green)' : 'var(--accent)') + ';"></rect>');
|
|
9598
|
+
|
|
9599
|
+
var visibleSteps = steps.slice(0, 6);
|
|
9600
|
+
var stepStartX = cardX + 22;
|
|
9601
|
+
var stepEndX = cardX + cardW - 22;
|
|
9602
|
+
var stepGap = visibleSteps.length > 1 ? (stepEndX - stepStartX) / (visibleSteps.length - 1) : 0;
|
|
9603
|
+
var stepY = cardY + 88;
|
|
9604
|
+
if (visibleSteps.length > 1) {
|
|
9605
|
+
parts.push('<line x1="' + stepStartX + '" y1="' + stepY + '" x2="' + stepEndX + '" y2="' + stepY + '" style="stroke:var(--border-light);stroke-width:2;"></line>');
|
|
9606
|
+
}
|
|
9607
|
+
for (var stepIndex = 0; stepIndex < visibleSteps.length; stepIndex++) {
|
|
9608
|
+
var visibleStep = visibleSteps[stepIndex];
|
|
9609
|
+
var dotX = visibleSteps.length === 1 ? ((stepStartX + stepEndX) / 2) : (stepStartX + (stepIndex * stepGap));
|
|
9610
|
+
var dotColor = visibleStep.status === 'done' ? 'var(--green)' : visibleStep.status === 'in_progress' ? 'var(--accent)' : 'var(--text-muted)';
|
|
9611
|
+
parts.push('<circle cx="' + dotX + '" cy="' + stepY + '" r="7" style="fill:var(--surface);stroke:' + dotColor + ';stroke-width:3;"></circle>');
|
|
9612
|
+
parts.push('<text x="' + dotX + '" y="' + (stepY + 22) + '" text-anchor="middle" style="fill:var(--text-dim);font-size:9px;font-weight:700;">' + escapeHtml(String(visibleStep.id || (stepIndex + 1))) + '</text>');
|
|
9613
|
+
}
|
|
9614
|
+
if (steps.length > visibleSteps.length) {
|
|
9615
|
+
parts.push('<text x="' + (cardX + cardW - 18) + '" y="' + (stepY + 24) + '" text-anchor="end" style="fill:var(--text-muted);font-size:9px;font-weight:700;">+' + (steps.length - visibleSteps.length) + '</text>');
|
|
9616
|
+
}
|
|
9617
|
+
|
|
9618
|
+
parts.push('<text x="' + (cardX + 16) + '" y="' + (cardY + 124) + '" style="fill:var(--text);font-size:11px;font-weight:600;">' + escapeHtml(summaryLabel) + '</text>');
|
|
9619
|
+
parts.push('<text x="' + (cardX + 16) + '" y="' + (cardY + 142) + '" style="fill:var(--text-dim);font-size:10px;">' + escapeHtml(activeStep && activeStep.assignee ? ('Owner: ' + activeStep.assignee) : 'Owner pending') + (workflow.autonomous ? ' • autonomous' : '') + (workflow.parallel ? ' • parallel' : '') + '</text>');
|
|
9620
|
+
}
|
|
9621
|
+
|
|
9622
|
+
if (workflowState.data.length > workflows.length) {
|
|
9623
|
+
parts.push('<text x="' + (panelX + 18) + '" y="' + (panelY + panelH - 20) + '" style="fill:var(--text-dim);font-size:10px;">+' + (workflowState.data.length - workflows.length) + ' more workflows continue in the primary Workflows view.</text>');
|
|
9624
|
+
}
|
|
9625
|
+
|
|
9626
|
+
return parts.join('');
|
|
9627
|
+
}
|
|
9628
|
+
|
|
9629
|
+
function buildGraphSvg(model) {
|
|
9630
|
+
var networkPanel = { x: 24, y: 78, w: 808, h: 718 };
|
|
9631
|
+
var parts = [];
|
|
9632
|
+
parts.push('<svg class="graph-surface" viewBox="0 0 1200 820" role="img" aria-label="Operator graph view showing agents, message flow, channels, and main branch workflows">');
|
|
9633
|
+
parts.push('<defs>');
|
|
9634
|
+
parts.push('<marker id="graph-arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto" markerUnits="strokeWidth">');
|
|
9635
|
+
parts.push('<path d="M0,0 L10,5 L0,10 z" style="fill:var(--text-muted);"></path>');
|
|
9636
|
+
parts.push('</marker>');
|
|
9637
|
+
parts.push('</defs>');
|
|
9638
|
+
parts.push('<rect x="0" y="0" width="1200" height="820" fill="transparent"></rect>');
|
|
9639
|
+
parts.push('<rect x="' + networkPanel.x + '" y="' + networkPanel.y + '" width="' + networkPanel.w + '" height="' + networkPanel.h + '" rx="22" style="fill:var(--surface);stroke:var(--border-light);"></rect>');
|
|
9640
|
+
parts.push('<text x="' + (networkPanel.x + 18) + '" y="' + (networkPanel.y + 28) + '" style="fill:var(--text);font-size:16px;font-weight:700;">Conversation flow</text>');
|
|
9641
|
+
parts.push('<text x="' + (networkPanel.x + 18) + '" y="' + (networkPanel.y + 48) + '" style="fill:var(--text-dim);font-size:11px;">Branch ' + escapeHtml(model.branchName) + ' • grouped by dominant channel • top 18 directed edges</text>');
|
|
9642
|
+
|
|
9643
|
+
if (!model.nodes.length) {
|
|
9644
|
+
parts.push('<rect x="' + (networkPanel.x + 18) + '" y="' + (networkPanel.y + 72) + '" width="' + (networkPanel.w - 36) + '" height="120" rx="18" style="fill:var(--surface-2);stroke:var(--border);"></rect>');
|
|
9645
|
+
parts.push('<text x="' + (networkPanel.x + 40) + '" y="' + (networkPanel.y + 120) + '" style="fill:var(--text);font-size:14px;font-weight:600;">No graphable operator data yet</text>');
|
|
9646
|
+
parts.push('<text x="' + (networkPanel.x + 40) + '" y="' + (networkPanel.y + 144) + '" style="fill:var(--text-dim);font-size:11px;">Once agents register and exchange messages, this view will render channel lanes and conversation edges.</text>');
|
|
9647
|
+
} else {
|
|
9648
|
+
for (var laneIndex = 0; laneIndex < model.lanes.length; laneIndex++) {
|
|
9649
|
+
var lane = model.lanes[laneIndex];
|
|
9650
|
+
var laneWidth = networkPanel.w - 24;
|
|
9651
|
+
var laneX = networkPanel.x + 12;
|
|
9652
|
+
parts.push('<rect x="' + laneX + '" y="' + lane.y + '" width="' + laneWidth + '" height="' + lane.height + '" rx="18" style="fill:' + lane.dim + ';fill-opacity:0.55;stroke:' + lane.accent + ';stroke-opacity:0.32;"></rect>');
|
|
9653
|
+
parts.push('<text x="' + (laneX + 16) + '" y="' + (lane.y + 24) + '" style="fill:' + lane.accent + ';font-size:11px;font-weight:700;letter-spacing:0.05em;">' + escapeHtml(lane.label.toUpperCase()) + '</text>');
|
|
9654
|
+
parts.push('<text x="' + (laneX + 16) + '" y="' + (lane.y + 42) + '" style="fill:var(--text-dim);font-size:10px;">' + escapeHtml(String(lane.count || 0)) + ' msgs • ' + escapeHtml(String(lane.participantCount || 0)) + ' participants</text>');
|
|
9655
|
+
}
|
|
9656
|
+
|
|
9657
|
+
for (var edgeIndex = 0; edgeIndex < model.visibleEdges.length; edgeIndex++) {
|
|
9658
|
+
var edge = model.visibleEdges[edgeIndex];
|
|
9659
|
+
var fromNode = model.nodeMap[edge.from];
|
|
9660
|
+
var toNode = model.nodeMap[edge.to];
|
|
9661
|
+
if (!fromNode || !toNode || !fromNode.x || !toNode.x) continue;
|
|
9662
|
+
var stroke = edge.laneKey && model.lanes.filter(function(lane) { return lane.key === edge.laneKey; }).length ? model.lanes.filter(function(lane) { return lane.key === edge.laneKey; })[0].accent : 'var(--text-muted)';
|
|
9663
|
+
var dx = toNode.x - fromNode.x;
|
|
9664
|
+
var dy = toNode.y - fromNode.y;
|
|
9665
|
+
var sameLane = Math.abs(dy) < 8;
|
|
9666
|
+
var midX = (fromNode.x + toNode.x) / 2;
|
|
9667
|
+
var controlY = sameLane ? (fromNode.y - (44 + ((edgeIndex % 3) * 16))) : ((fromNode.y + toNode.y) / 2) - (dy > 0 ? 20 : -20);
|
|
9668
|
+
var path = 'M' + fromNode.x + ' ' + fromNode.y + ' Q ' + midX + ' ' + controlY + ' ' + toNode.x + ' ' + toNode.y;
|
|
9669
|
+
var labelX = midX;
|
|
9670
|
+
var labelY = sameLane ? (controlY - 10) : (controlY - 8);
|
|
9671
|
+
var labelText = String(edge.count) + (edge.handoffs ? '↗' : '');
|
|
9672
|
+
var labelWidth = 12 + (labelText.length * 6);
|
|
9673
|
+
parts.push('<g>');
|
|
9674
|
+
parts.push('<path d="' + path + '" fill="none" stroke="' + stroke + '" stroke-opacity="0.7" stroke-width="' + (1.5 + Math.min(5, edge.count * 0.8)) + '" marker-end="url(#graph-arrow)"></path>');
|
|
9675
|
+
parts.push('<title>' + escapeHtml(edge.from + ' → ' + edge.to + ' • ' + edge.count + ' messages' + (edge.dominantChannel ? ' • #' + edge.dominantChannel : '')) + '</title>');
|
|
9676
|
+
parts.push('<rect x="' + Math.round(labelX - (labelWidth / 2)) + '" y="' + Math.round(labelY - 10) + '" width="' + labelWidth + '" height="16" rx="8" style="fill:var(--surface);stroke:var(--border-light);"></rect>');
|
|
9677
|
+
parts.push('<text x="' + labelX + '" y="' + (labelY + 2) + '" text-anchor="middle" style="fill:var(--text);font-size:9px;font-weight:700;">' + escapeHtml(labelText) + '</text>');
|
|
9678
|
+
parts.push('</g>');
|
|
9679
|
+
}
|
|
9680
|
+
|
|
9681
|
+
for (var nodeIndex = 0; nodeIndex < model.nodes.length; nodeIndex++) {
|
|
9682
|
+
var node = model.nodes[nodeIndex];
|
|
9683
|
+
if (!node.x || !node.y) continue;
|
|
9684
|
+
var nodeStatusColor = getGraphStatusColor(node.status);
|
|
9685
|
+
var nodeInitialFill = node.synthetic ? 'var(--text)' : 'var(--surface)';
|
|
9686
|
+
var label = truncateGraphText(node.label, 16);
|
|
9687
|
+
var summary = (node.sendCount + node.receiveCount) ? (node.sendCount + '↑ ' + node.receiveCount + '↓') : (node.synthetic ? 'system' : 'idle');
|
|
9688
|
+
var clickAttr = node.synthetic ? '' : ' style="cursor:pointer" onclick="openAgentMetadataDrawer(' + escapeHtml(JSON.stringify(node.agentName)) + ')"';
|
|
9689
|
+
parts.push('<g' + clickAttr + '>');
|
|
9690
|
+
parts.push('<title>' + escapeHtml(node.label + ' • sent ' + node.sendCount + ' • received ' + node.receiveCount + (node.role ? ' • ' + node.role : '')) + '</title>');
|
|
9691
|
+
parts.push('<circle cx="' + node.x + '" cy="' + node.y + '" r="26" style="fill:var(--bg);opacity:0.48;"></circle>');
|
|
9692
|
+
parts.push('<circle cx="' + node.x + '" cy="' + node.y + '" r="21" style="fill:' + node.color + ';stroke:' + nodeStatusColor + ';stroke-width:2.5;opacity:' + (node.status === 'dead' ? '0.58' : '0.94') + ';"></circle>');
|
|
9693
|
+
parts.push('<text x="' + node.x + '" y="' + (node.y + 5) + '" text-anchor="middle" style="fill:' + nodeInitialFill + ';font-size:12px;font-weight:800;">' + escapeHtml(initial(node.label)) + '</text>');
|
|
9694
|
+
parts.push('<text x="' + node.x + '" y="' + (node.y + 38) + '" text-anchor="middle" style="fill:var(--text);font-size:11px;font-weight:700;">' + escapeHtml(label) + '</text>');
|
|
9695
|
+
parts.push('<text x="' + node.x + '" y="' + (node.y + 54) + '" text-anchor="middle" style="fill:var(--text-dim);font-size:9px;">' + escapeHtml(summary) + '</text>');
|
|
9696
|
+
parts.push('</g>');
|
|
9697
|
+
}
|
|
9698
|
+
}
|
|
9699
|
+
|
|
9700
|
+
parts.push('<text x="40" y="784" style="fill:var(--text-dim);font-size:10px;">Tip: click an agent node to open the existing metadata drawer. The primary Messages and Workflows views remain the main control surfaces.</text>');
|
|
9701
|
+
parts.push(buildGraphWorkflowPanelSvg(graphWorkflowState));
|
|
9702
|
+
parts.push('</svg>');
|
|
9703
|
+
return parts.join('');
|
|
9704
|
+
}
|
|
9705
|
+
|
|
9706
|
+
function renderGraphView() {
|
|
9707
|
+
var el = document.getElementById('graph-area');
|
|
9708
|
+
if (!el) return;
|
|
9709
|
+
var model = buildGraphModel();
|
|
9710
|
+
var branchLabel = activeBranch && activeBranch !== 'main' ? activeBranch : 'main';
|
|
9711
|
+
var workflowBadge = graphWorkflowState.status === 'loading'
|
|
9712
|
+
? 'Loading…'
|
|
9713
|
+
: graphWorkflowState.status === 'error'
|
|
9714
|
+
? 'Unavailable'
|
|
9715
|
+
: String(Array.isArray(graphWorkflowState.data) ? graphWorkflowState.data.length : 0);
|
|
9716
|
+
var subtitle = branchLabel === 'main'
|
|
9717
|
+
? 'Secondary operator slice only: this graph reuses the current dashboard data already in memory, while Messages and Workflows remain the primary navigation and control views.'
|
|
9718
|
+
: 'Secondary operator slice only: conversation flow is scoped to branch ' + branchLabel + ', while the workflow rail intentionally stays pinned to main-branch structure.';
|
|
9719
|
+
var html = '';
|
|
9720
|
+
html += '<div class="graph-shell">';
|
|
9721
|
+
html += '<div class="graph-header">';
|
|
9722
|
+
html += '<div>';
|
|
9723
|
+
html += '<div class="graph-title">Graph operator view</div>';
|
|
9724
|
+
html += '<div class="graph-subtitle">' + escapeHtml(subtitle) + '</div>';
|
|
9725
|
+
html += '</div>';
|
|
9726
|
+
html += '<div class="graph-meta">';
|
|
9727
|
+
html += '<div class="graph-pill">Flow branch <strong>' + escapeHtml(branchLabel) + '</strong></div>';
|
|
9728
|
+
html += '<div class="graph-pill">Agents <strong>' + escapeHtml(String(model.nodes.length)) + '</strong></div>';
|
|
9729
|
+
html += '<div class="graph-pill">Edges <strong>' + escapeHtml(String(model.edges.length)) + '</strong></div>';
|
|
9730
|
+
html += '<div class="graph-pill">Channels <strong>' + escapeHtml(String(model.channels.length)) + '</strong></div>';
|
|
9731
|
+
html += '<div class="graph-pill">Main workflows <strong>' + escapeHtml(workflowBadge) + '</strong></div>';
|
|
9732
|
+
html += '</div>';
|
|
9733
|
+
html += '</div>';
|
|
9734
|
+
html += '<div class="graph-surface-wrap">' + buildGraphSvg(model) + '</div>';
|
|
9735
|
+
html += '</div>';
|
|
9736
|
+
el.innerHTML = html;
|
|
9737
|
+
}
|
|
9738
|
+
|
|
7116
9739
|
// ==================== v5.0: PLAN EXECUTION VIEW ====================
|
|
7117
9740
|
|
|
7118
9741
|
var planRefreshInterval = null;
|
|
7119
9742
|
|
|
7120
9743
|
function fetchPlanStatus() {
|
|
7121
|
-
|
|
7122
|
-
|
|
9744
|
+
if (!isMainBranchSelected()) {
|
|
9745
|
+
if (planRefreshInterval) { clearInterval(planRefreshInterval); planRefreshInterval = null; }
|
|
9746
|
+
document.getElementById('monitor-panel').innerHTML = '';
|
|
9747
|
+
renderMainBranchOnlyView('plan-area', 'Plan view');
|
|
9748
|
+
return;
|
|
9749
|
+
}
|
|
9750
|
+
lttFetch(scopedApiUrl('/api/plan/status')).then(function(r) { return r.json(); }).then(function(data) {
|
|
9751
|
+
if (data && data.code === 'main_branch_only') {
|
|
9752
|
+
renderMainBranchOnlyView('plan-area', 'Plan view');
|
|
9753
|
+
return;
|
|
9754
|
+
}
|
|
7123
9755
|
renderPlanView(data);
|
|
7124
9756
|
}).catch(function() {
|
|
7125
9757
|
// Fallback: use workflows API if plan API not yet available
|
|
7126
|
-
lttFetch('/api/workflows'
|
|
9758
|
+
lttFetch(scopedApiUrl('/api/workflows')).then(function(r) { return r.json(); }).then(function(wfs) {
|
|
9759
|
+
if (wfs && wfs.code === 'main_branch_only') {
|
|
9760
|
+
renderMainBranchOnlyView('plan-area', 'Plan view');
|
|
9761
|
+
return;
|
|
9762
|
+
}
|
|
7127
9763
|
var active = (Array.isArray(wfs) ? wfs : []).filter(function(w) { return w.status === 'active'; });
|
|
7128
9764
|
renderPlanView({ workflows: active, fallback: true });
|
|
7129
9765
|
}).catch(function() {
|
|
@@ -7140,6 +9776,10 @@ function fetchPlanStatus() {
|
|
|
7140
9776
|
|
|
7141
9777
|
function renderPlanView(data) {
|
|
7142
9778
|
var el = document.getElementById('plan-area');
|
|
9779
|
+
if (!isMainBranchSelected()) {
|
|
9780
|
+
el.innerHTML = mainBranchOnlyViewHtml('Plan view');
|
|
9781
|
+
return;
|
|
9782
|
+
}
|
|
7143
9783
|
var workflows = data.workflows || (data.fallback ? data.workflows : [data]);
|
|
7144
9784
|
|
|
7145
9785
|
if (!workflows || !workflows.length) {
|
|
@@ -7296,8 +9936,11 @@ function renderPlanView(data) {
|
|
|
7296
9936
|
}
|
|
7297
9937
|
|
|
7298
9938
|
function planAction(action, stepId, wfId) {
|
|
7299
|
-
|
|
7300
|
-
|
|
9939
|
+
if (!isMainBranchSelected()) {
|
|
9940
|
+
showToast('Plan controls only support the main branch right now.');
|
|
9941
|
+
return;
|
|
9942
|
+
}
|
|
9943
|
+
var url = scopedApiUrl('/api/plan/' + action + (stepId ? '/' + stepId : ''));
|
|
7301
9944
|
lttFetch(url, {
|
|
7302
9945
|
method: 'POST',
|
|
7303
9946
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -7306,10 +9949,13 @@ function planAction(action, stepId, wfId) {
|
|
|
7306
9949
|
}
|
|
7307
9950
|
|
|
7308
9951
|
function planReassign(stepId, wfId) {
|
|
9952
|
+
if (!isMainBranchSelected()) {
|
|
9953
|
+
showToast('Plan controls only support the main branch right now.');
|
|
9954
|
+
return;
|
|
9955
|
+
}
|
|
7309
9956
|
var newAgent = prompt('Reassign step ' + stepId + ' to which agent?');
|
|
7310
9957
|
if (!newAgent) return;
|
|
7311
|
-
|
|
7312
|
-
lttFetch('/api/plan/reassign/' + stepId + pq, {
|
|
9958
|
+
lttFetch(scopedApiUrl('/api/plan/reassign/' + stepId), {
|
|
7313
9959
|
method: 'POST',
|
|
7314
9960
|
headers: { 'Content-Type': 'application/json' },
|
|
7315
9961
|
body: JSON.stringify({ workflow_id: wfId, new_assignee: newAgent })
|
|
@@ -7317,10 +9963,13 @@ function planReassign(stepId, wfId) {
|
|
|
7317
9963
|
}
|
|
7318
9964
|
|
|
7319
9965
|
function planInject(wfId) {
|
|
9966
|
+
if (!isMainBranchSelected()) {
|
|
9967
|
+
showToast('Plan controls only support the main branch right now.');
|
|
9968
|
+
return;
|
|
9969
|
+
}
|
|
7320
9970
|
var msg = prompt('Inject message to all agents:');
|
|
7321
9971
|
if (!msg) return;
|
|
7322
|
-
|
|
7323
|
-
lttFetch('/api/plan/inject' + pq, {
|
|
9972
|
+
lttFetch(scopedApiUrl('/api/plan/inject'), {
|
|
7324
9973
|
method: 'POST',
|
|
7325
9974
|
headers: { 'Content-Type': 'application/json' },
|
|
7326
9975
|
body: JSON.stringify({ workflow_id: wfId, message: msg })
|
|
@@ -7332,8 +9981,8 @@ function planInject(wfId) {
|
|
|
7332
9981
|
var _lastKnownWorkflowStatuses = {};
|
|
7333
9982
|
|
|
7334
9983
|
function checkPlanCompletion() {
|
|
7335
|
-
|
|
7336
|
-
lttFetch('/api/workflows'
|
|
9984
|
+
if (!isMainBranchSelected()) return;
|
|
9985
|
+
lttFetch(scopedApiUrl('/api/workflows')).then(function(r) { return r.json(); }).then(function(wfs) {
|
|
7337
9986
|
if (!Array.isArray(wfs)) return;
|
|
7338
9987
|
for (var i = 0; i < wfs.length; i++) {
|
|
7339
9988
|
var wf = wfs[i];
|
|
@@ -7359,6 +10008,11 @@ function checkPlanCompletion() {
|
|
|
7359
10008
|
// ==================== v5.0: MONITOR HEALTH PANEL ====================
|
|
7360
10009
|
|
|
7361
10010
|
function fetchMonitorHealth() {
|
|
10011
|
+
if (!isMainBranchSelected()) {
|
|
10012
|
+
var hiddenEl = document.getElementById('monitor-panel');
|
|
10013
|
+
if (hiddenEl) hiddenEl.innerHTML = '';
|
|
10014
|
+
return;
|
|
10015
|
+
}
|
|
7362
10016
|
var pq = projectParam();
|
|
7363
10017
|
lttFetch('/api/monitor/health' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
7364
10018
|
renderMonitorPanel(data);
|
|
@@ -7419,12 +10073,15 @@ function renderMonitorPanel(data) {
|
|
|
7419
10073
|
|
|
7420
10074
|
// ==================== v3.0: BRANCHES ====================
|
|
7421
10075
|
|
|
7422
|
-
var activeBranch = '';
|
|
10076
|
+
var activeBranch = dashboardWorkspaceState.liveWorkspace.snapshot.branch || 'main';
|
|
10077
|
+
var cachedBranchInfo = { main: { message_count: 0 } };
|
|
7423
10078
|
|
|
7424
10079
|
function fetchBranches() {
|
|
7425
10080
|
var pq = projectParam();
|
|
7426
10081
|
lttFetch('/api/branches' + pq).then(function(r) { return r.json(); }).then(function(data) {
|
|
7427
|
-
|
|
10082
|
+
cachedBranchInfo = data && typeof data === 'object' ? data : { main: { message_count: 0 } };
|
|
10083
|
+
if (!cachedBranchInfo.main) cachedBranchInfo.main = { message_count: 0 };
|
|
10084
|
+
renderBranchTabs(cachedBranchInfo);
|
|
7428
10085
|
}).catch(function() {});
|
|
7429
10086
|
}
|
|
7430
10087
|
|
|
@@ -7452,6 +10109,8 @@ function renderBranchTabs(branches) {
|
|
|
7452
10109
|
function switchBranch(name) {
|
|
7453
10110
|
activeBranch = name;
|
|
7454
10111
|
lastMessageCount = 0;
|
|
10112
|
+
lastRenderedIds = [];
|
|
10113
|
+
persistLiveDashboardLayout();
|
|
7455
10114
|
poll();
|
|
7456
10115
|
}
|
|
7457
10116
|
|
|
@@ -7529,11 +10188,7 @@ function toggleSearchAll() {
|
|
|
7529
10188
|
btn.style.background = searchAllMode ? 'var(--accent-dim)' : 'var(--surface-2)';
|
|
7530
10189
|
btn.style.color = searchAllMode ? 'var(--accent)' : 'var(--text-muted)';
|
|
7531
10190
|
btn.style.borderColor = searchAllMode ? 'var(--accent)' : 'var(--border)';
|
|
7532
|
-
|
|
7533
|
-
document.getElementById('search-input').placeholder = 'Search ALL projects...';
|
|
7534
|
-
} else {
|
|
7535
|
-
document.getElementById('search-input').placeholder = 'Search messages... ( / )';
|
|
7536
|
-
}
|
|
10191
|
+
updateSearchInputMode();
|
|
7537
10192
|
onSearch();
|
|
7538
10193
|
}
|
|
7539
10194
|
|
|
@@ -7568,8 +10223,7 @@ function searchAllProjects(query) {
|
|
|
7568
10223
|
// ==================== v3.5: REPLAY EXPORT ====================
|
|
7569
10224
|
|
|
7570
10225
|
function exportReplay() {
|
|
7571
|
-
|
|
7572
|
-
window.location.href = '/api/export-replay' + (pq || '?') + (_lttToken ? '&token=' + encodeURIComponent(_lttToken) : '');
|
|
10226
|
+
window.location.href = scopedApiUrl('/api/export-replay', null, { includeToken: true });
|
|
7573
10227
|
}
|
|
7574
10228
|
|
|
7575
10229
|
// ==================== v3.5: PERFORMANCE SCORES ====================
|
|
@@ -7627,12 +10281,10 @@ function renderScores(data, repData) {
|
|
|
7627
10281
|
// ==================== POLLING ====================
|
|
7628
10282
|
|
|
7629
10283
|
function poll() {
|
|
7630
|
-
var pp = activeProject ? '&project=' + encodeURIComponent(activeProject) : '';
|
|
7631
10284
|
var pq = activeProject ? '?project=' + encodeURIComponent(activeProject) : '';
|
|
7632
|
-
var bp = activeBranch && activeBranch !== 'main' ? '&branch=' + encodeURIComponent(activeBranch) : '';
|
|
7633
10285
|
var pollStart = Date.now();
|
|
7634
10286
|
Promise.all([
|
|
7635
|
-
lttFetch('/api/history
|
|
10287
|
+
lttFetch(scopedApiUrl('/api/history', { limit: 500 })).then(function(r) { return r.json(); }),
|
|
7636
10288
|
lttFetch('/api/agents' + pq).then(function(r) { return r.json(); }),
|
|
7637
10289
|
lttFetch('/api/status' + pq).then(function(r) { return r.json(); }),
|
|
7638
10290
|
]).then(function(results) {
|
|
@@ -7683,6 +10335,7 @@ function poll() {
|
|
|
7683
10335
|
: 'Let Them Talk';
|
|
7684
10336
|
|
|
7685
10337
|
renderAgents(cachedAgents);
|
|
10338
|
+
refreshAgentMetadataDrawer();
|
|
7686
10339
|
renderAgentStats();
|
|
7687
10340
|
renderThreads(cachedHistory);
|
|
7688
10341
|
if (!replayActive) renderMessages(cachedHistory);
|
|
@@ -7693,9 +10346,11 @@ function poll() {
|
|
|
7693
10346
|
fetchBranches();
|
|
7694
10347
|
fetchNotifications();
|
|
7695
10348
|
updateTypingIndicator(cachedAgents);
|
|
10349
|
+
if (activeView === 'services') renderServices();
|
|
7696
10350
|
if (activeView === 'tasks') fetchTasks();
|
|
7697
10351
|
if (activeView === 'workspaces') fetchWorkspaces();
|
|
7698
10352
|
if (activeView === 'workflows') fetchWorkflows();
|
|
10353
|
+
if (activeView === 'graph') { renderGraphView(); ensureGraphWorkflowData(); }
|
|
7699
10354
|
if (activeView === 'plan') fetchPlanStatus();
|
|
7700
10355
|
if (activeView === 'stats') fetchStats();
|
|
7701
10356
|
fetchReadReceipts();
|
|
@@ -7732,10 +10387,8 @@ function loadProjects() {
|
|
|
7732
10387
|
sel.appendChild(opt);
|
|
7733
10388
|
}
|
|
7734
10389
|
|
|
7735
|
-
// Auto-select first project if none selected
|
|
7736
10390
|
if (!activeProject && projects.length > 0) {
|
|
7737
10391
|
activeProject = projects[0].path;
|
|
7738
|
-
console.log('[LTT] auto-selected first project:', activeProject);
|
|
7739
10392
|
}
|
|
7740
10393
|
|
|
7741
10394
|
// If project came from URL param, try fuzzy match (handles path separator differences)
|
|
@@ -7749,22 +10402,26 @@ function loadProjects() {
|
|
|
7749
10402
|
}
|
|
7750
10403
|
}
|
|
7751
10404
|
|
|
10405
|
+
var hasMatchingProject = !activeProject;
|
|
7752
10406
|
if (activeProject) {
|
|
7753
|
-
|
|
7754
|
-
|
|
7755
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
var proj = projects.find(function(p) { return p.path === activeProject; });
|
|
7759
|
-
indicator.textContent = proj ? proj.name : '';
|
|
7760
|
-
indicator.style.display = proj ? '' : 'none';
|
|
10407
|
+
for (var k = 0; k < projects.length; k++) {
|
|
10408
|
+
if (projects[k].path === activeProject) {
|
|
10409
|
+
hasMatchingProject = true;
|
|
10410
|
+
break;
|
|
10411
|
+
}
|
|
7761
10412
|
}
|
|
7762
10413
|
}
|
|
10414
|
+
if (!hasMatchingProject) {
|
|
10415
|
+
activeProject = projects.length > 0 ? projects[0].path : '';
|
|
10416
|
+
}
|
|
10417
|
+
|
|
10418
|
+
if (activeProject) {
|
|
10419
|
+
sel.value = activeProject;
|
|
10420
|
+
}
|
|
7763
10421
|
|
|
7764
|
-
// Show/hide remove button
|
|
7765
|
-
document.getElementById('remove-project-btn').style.display = activeProject ? '' : 'none';
|
|
7766
|
-
// Sync mobile header project select
|
|
7767
10422
|
syncMobileProjectSelect();
|
|
10423
|
+
syncProjectSelectionUI();
|
|
10424
|
+
persistLiveDashboardLayout();
|
|
7768
10425
|
}).catch(function(e) {
|
|
7769
10426
|
console.error('[LTT] loadProjects failed:', e);
|
|
7770
10427
|
});
|
|
@@ -7773,8 +10430,10 @@ function loadProjects() {
|
|
|
7773
10430
|
function switchProject() {
|
|
7774
10431
|
activeProject = document.getElementById('project-select').value;
|
|
7775
10432
|
lastMessageCount = 0;
|
|
7776
|
-
|
|
10433
|
+
lastRenderedIds = [];
|
|
7777
10434
|
syncMobileProjectSelect();
|
|
10435
|
+
syncProjectSelectionUI();
|
|
10436
|
+
persistLiveDashboardLayout();
|
|
7778
10437
|
loadConversationList();
|
|
7779
10438
|
poll();
|
|
7780
10439
|
if (isMobile) closeSidebar();
|
|
@@ -7783,9 +10442,12 @@ function switchProject() {
|
|
|
7783
10442
|
function mobileSelectProject(val) {
|
|
7784
10443
|
activeProject = val;
|
|
7785
10444
|
lastMessageCount = 0;
|
|
10445
|
+
lastRenderedIds = [];
|
|
7786
10446
|
// Sync sidebar select
|
|
7787
10447
|
document.getElementById('project-select').value = val;
|
|
7788
|
-
|
|
10448
|
+
syncProjectSelectionUI();
|
|
10449
|
+
persistLiveDashboardLayout();
|
|
10450
|
+
loadConversationList();
|
|
7789
10451
|
poll();
|
|
7790
10452
|
}
|
|
7791
10453
|
|
|
@@ -7805,15 +10467,29 @@ function syncMobileProjectSelect() {
|
|
|
7805
10467
|
}
|
|
7806
10468
|
|
|
7807
10469
|
function showAddProject() {
|
|
10470
|
+
var row = document.getElementById('project-input-row');
|
|
7808
10471
|
var input = document.getElementById('project-path-input');
|
|
7809
|
-
if (
|
|
7810
|
-
|
|
10472
|
+
if (row.style.display === 'flex') {
|
|
10473
|
+
row.style.display = 'none';
|
|
7811
10474
|
} else {
|
|
7812
|
-
|
|
10475
|
+
row.style.display = 'flex';
|
|
7813
10476
|
input.focus();
|
|
7814
10477
|
}
|
|
7815
10478
|
}
|
|
7816
10479
|
|
|
10480
|
+
function projectInitSuccessMessage(res) {
|
|
10481
|
+
if (!res || !res.initialization) return 'Project added.';
|
|
10482
|
+
if (res.initialization.mode === '--all') {
|
|
10483
|
+
return 'Project added and initialized for Claude, Gemini, and Codex.';
|
|
10484
|
+
}
|
|
10485
|
+
return 'Project added and initialized.';
|
|
10486
|
+
}
|
|
10487
|
+
|
|
10488
|
+
function reportProjectAddFailure(res, fallbackMessage) {
|
|
10489
|
+
var message = (res && res.error) ? res.error : fallbackMessage;
|
|
10490
|
+
alert(message);
|
|
10491
|
+
}
|
|
10492
|
+
|
|
7817
10493
|
function addProject() {
|
|
7818
10494
|
var input = document.getElementById('project-path-input');
|
|
7819
10495
|
var projectPath = input.value.trim();
|
|
@@ -7826,18 +10502,21 @@ function addProject() {
|
|
|
7826
10502
|
}).then(function(r) { return r.json(); }).then(function(res) {
|
|
7827
10503
|
if (res.success) {
|
|
7828
10504
|
input.value = '';
|
|
7829
|
-
|
|
10505
|
+
var row = document.getElementById('project-input-row');
|
|
10506
|
+
if (row) row.style.display = 'none';
|
|
7830
10507
|
// Auto-switch to the newly added project
|
|
7831
10508
|
activeProject = res.project.path;
|
|
7832
|
-
loadProjects()
|
|
7833
|
-
setTimeout(function() {
|
|
10509
|
+
return loadProjects().then(function() {
|
|
7834
10510
|
document.getElementById('project-select').value = activeProject;
|
|
7835
10511
|
switchProject();
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
alert(res.error || 'Failed to add project');
|
|
10512
|
+
showToast(projectInitSuccessMessage(res));
|
|
10513
|
+
});
|
|
7839
10514
|
}
|
|
7840
|
-
|
|
10515
|
+
reportProjectAddFailure(res, 'Failed to add project');
|
|
10516
|
+
}).catch(function(e) {
|
|
10517
|
+
console.error('Add project failed:', e);
|
|
10518
|
+
reportProjectAddFailure(null, 'Add project failed: ' + e.message);
|
|
10519
|
+
});
|
|
7841
10520
|
}
|
|
7842
10521
|
|
|
7843
10522
|
function discoverProjects() {
|
|
@@ -7878,9 +10557,38 @@ function addDiscovered(projectPath, name) {
|
|
|
7878
10557
|
}).then(function(r) { return r.json(); }).then(function(res) {
|
|
7879
10558
|
if (res.success) {
|
|
7880
10559
|
document.getElementById('discover-results').style.display = 'none';
|
|
7881
|
-
loadProjects()
|
|
10560
|
+
return loadProjects().then(function() {
|
|
10561
|
+
showToast(projectInitSuccessMessage(res));
|
|
10562
|
+
});
|
|
7882
10563
|
}
|
|
7883
|
-
|
|
10564
|
+
reportProjectAddFailure(res, 'Failed to add discovered project');
|
|
10565
|
+
}).catch(function(e) {
|
|
10566
|
+
console.error('Add discovered project failed:', e);
|
|
10567
|
+
reportProjectAddFailure(null, 'Add discovered project failed: ' + e.message);
|
|
10568
|
+
});
|
|
10569
|
+
}
|
|
10570
|
+
|
|
10571
|
+
function reinstallProviders() {
|
|
10572
|
+
if (!activeProject) return;
|
|
10573
|
+
if (!confirm('Re-run init for Claude, Gemini, and Codex configs in this project?\n\nMerge-safe — preserves your other MCP servers and creates .backup files.')) return;
|
|
10574
|
+
var btn = document.getElementById('reinstall-providers-btn');
|
|
10575
|
+
if (btn) { btn.disabled = true; btn.textContent = 'Reinstalling...'; }
|
|
10576
|
+
lttFetch('/api/projects/reinit', {
|
|
10577
|
+
method: 'POST',
|
|
10578
|
+
headers: { 'Content-Type': 'application/json' },
|
|
10579
|
+
body: JSON.stringify({ path: activeProject })
|
|
10580
|
+
}).then(function(r) { return r.json(); }).then(function(res) {
|
|
10581
|
+
if (res && res.success) {
|
|
10582
|
+
var targets = (res.initialization && res.initialization.targets) ? res.initialization.targets.join(', ') : 'all providers';
|
|
10583
|
+
showToast('Reinstalled providers: ' + targets + '. Restart your CLI terminals to pick it up.');
|
|
10584
|
+
} else {
|
|
10585
|
+
showToast('Reinstall failed: ' + ((res && res.error) || 'unknown error'));
|
|
10586
|
+
}
|
|
10587
|
+
}).catch(function(e) {
|
|
10588
|
+
showToast('Reinstall failed: ' + (e && e.message ? e.message : e));
|
|
10589
|
+
}).finally(function() {
|
|
10590
|
+
if (btn) { btn.disabled = false; btn.textContent = 'Reinstall Providers'; }
|
|
10591
|
+
});
|
|
7884
10592
|
}
|
|
7885
10593
|
|
|
7886
10594
|
function removeProject() {
|
|
@@ -7895,6 +10603,8 @@ function removeProject() {
|
|
|
7895
10603
|
if (res.success) {
|
|
7896
10604
|
activeProject = '';
|
|
7897
10605
|
document.getElementById('project-select').value = '';
|
|
10606
|
+
syncProjectSelectionUI();
|
|
10607
|
+
persistLiveDashboardLayout();
|
|
7898
10608
|
loadProjects();
|
|
7899
10609
|
poll();
|
|
7900
10610
|
}
|
|
@@ -8003,7 +10713,7 @@ function exitReplay() {
|
|
|
8003
10713
|
if (replayTimer) { clearTimeout(replayTimer); replayTimer = null; }
|
|
8004
10714
|
document.getElementById('replay-bar').classList.remove('visible');
|
|
8005
10715
|
document.getElementById('view-tabs').style.display = '';
|
|
8006
|
-
|
|
10716
|
+
syncSearchBarVisibility();
|
|
8007
10717
|
document.getElementById('replay-header-btn').style.display = '';
|
|
8008
10718
|
lastMessageCount = 0;
|
|
8009
10719
|
renderMessages(cachedHistory);
|
|
@@ -8094,7 +10804,7 @@ function renderReplayMessages() {
|
|
|
8094
10804
|
|
|
8095
10805
|
// ==================== BROWSER NOTIFICATIONS ====================
|
|
8096
10806
|
|
|
8097
|
-
var notifEnabled =
|
|
10807
|
+
var notifEnabled = !!dashboardWorkspaceState.preferences.notificationsEnabled;
|
|
8098
10808
|
var notifPermission = (typeof Notification !== 'undefined') ? Notification.permission : 'denied';
|
|
8099
10809
|
|
|
8100
10810
|
function toggleNotifications() {
|
|
@@ -8105,17 +10815,17 @@ function toggleNotifications() {
|
|
|
8105
10815
|
notifPermission = perm;
|
|
8106
10816
|
if (perm === 'granted') {
|
|
8107
10817
|
notifEnabled = true;
|
|
8108
|
-
|
|
10818
|
+
persistDashboardPreferences();
|
|
8109
10819
|
updateNotifBtn();
|
|
8110
10820
|
}
|
|
8111
10821
|
});
|
|
8112
10822
|
} else {
|
|
8113
10823
|
notifEnabled = true;
|
|
8114
|
-
|
|
10824
|
+
persistDashboardPreferences();
|
|
8115
10825
|
}
|
|
8116
10826
|
} else {
|
|
8117
10827
|
notifEnabled = false;
|
|
8118
|
-
|
|
10828
|
+
persistDashboardPreferences();
|
|
8119
10829
|
}
|
|
8120
10830
|
updateNotifBtn();
|
|
8121
10831
|
}
|
|
@@ -8676,6 +11386,263 @@ function copyWatcherPrompt(idx) {
|
|
|
8676
11386
|
}).catch(function() {});
|
|
8677
11387
|
}
|
|
8678
11388
|
|
|
11389
|
+
// ==================== SERVICES VIEW ====================
|
|
11390
|
+
|
|
11391
|
+
var cachedApiAgents = [];
|
|
11392
|
+
var cachedMediaItems = [];
|
|
11393
|
+
|
|
11394
|
+
function renderServices() {
|
|
11395
|
+
var el = document.getElementById('services-area');
|
|
11396
|
+
fetchApiAgents();
|
|
11397
|
+
fetchMediaItems();
|
|
11398
|
+
}
|
|
11399
|
+
|
|
11400
|
+
function fetchApiAgents() {
|
|
11401
|
+
var pq = projectParam();
|
|
11402
|
+
lttFetch('/api/api-agents' + pq).then(function(r) { return r.json(); }).then(function(agents) {
|
|
11403
|
+
cachedApiAgents = Array.isArray(agents) ? agents : [];
|
|
11404
|
+
renderServicesUI();
|
|
11405
|
+
}).catch(function() { cachedApiAgents = []; renderServicesUI(); });
|
|
11406
|
+
}
|
|
11407
|
+
|
|
11408
|
+
function fetchMediaItems() {
|
|
11409
|
+
var pq = projectParam();
|
|
11410
|
+
lttFetch('/api/media' + pq + (pq ? '&' : '?') + 'limit=50').then(function(r) { return r.json(); }).then(function(media) {
|
|
11411
|
+
cachedMediaItems = Array.isArray(media) ? media : [];
|
|
11412
|
+
renderMediaBrowser();
|
|
11413
|
+
}).catch(function() { cachedMediaItems = []; renderMediaBrowser(); });
|
|
11414
|
+
}
|
|
11415
|
+
|
|
11416
|
+
function renderServicesUI() {
|
|
11417
|
+
var el = document.getElementById('services-area');
|
|
11418
|
+
var html = '<div class="services-container">';
|
|
11419
|
+
html += '<div class="services-header"><h2>API Service Agents</h2></div>';
|
|
11420
|
+
|
|
11421
|
+
// Create form
|
|
11422
|
+
html += '<div class="services-form">';
|
|
11423
|
+
html += '<div class="form-row"><div>';
|
|
11424
|
+
html += '<label>Agent Name</label>';
|
|
11425
|
+
html += '<input type="text" id="svc-name" placeholder="e.g. ImageGen" maxlength="20">';
|
|
11426
|
+
html += '</div><div>';
|
|
11427
|
+
html += '<label>Provider</label>';
|
|
11428
|
+
html += '<select id="svc-provider" onchange="onProviderChange()">';
|
|
11429
|
+
html += '<option value="gemini">Gemini (Google)</option>';
|
|
11430
|
+
html += '<option value="zai">Z.AI / GLM (Cloud)</option>';
|
|
11431
|
+
html += '<option value="comfyui">ComfyUI (Local)</option>';
|
|
11432
|
+
html += '<option value="ollama">Ollama (Local)</option>';
|
|
11433
|
+
html += '<option value="dalle">DALL-E 3 (OpenAI)</option>';
|
|
11434
|
+
html += '<option value="replicate">Replicate (Cloud)</option>';
|
|
11435
|
+
html += '</select>';
|
|
11436
|
+
html += '</div></div>';
|
|
11437
|
+
|
|
11438
|
+
html += '<div class="form-row"><div>';
|
|
11439
|
+
html += '<label>Model</label>';
|
|
11440
|
+
html += '<input type="text" id="svc-model" placeholder="e.g. sdxl, flux, dall-e-3">';
|
|
11441
|
+
html += '</div><div>';
|
|
11442
|
+
html += '<label id="svc-endpoint-label">Endpoint URL</label>';
|
|
11443
|
+
html += '<input type="text" id="svc-endpoint" placeholder="http://localhost:11434">';
|
|
11444
|
+
html += '</div></div>';
|
|
11445
|
+
|
|
11446
|
+
html += '<div id="svc-apikey-row" style="display:none">';
|
|
11447
|
+
html += '<label>API Key</label>';
|
|
11448
|
+
html += '<input type="password" id="svc-apikey" placeholder="sk-...">';
|
|
11449
|
+
html += '</div>';
|
|
11450
|
+
|
|
11451
|
+
html += '<button onclick="createApiAgent()">Create Agent</button>';
|
|
11452
|
+
html += '</div>';
|
|
11453
|
+
|
|
11454
|
+
// Agent cards
|
|
11455
|
+
if (cachedApiAgents.length > 0) {
|
|
11456
|
+
html += '<div class="services-cards">';
|
|
11457
|
+
cachedApiAgents.forEach(function(agent) {
|
|
11458
|
+
var dotColor = agent.running ? '#4ade80' : '#666';
|
|
11459
|
+
var statusText = agent.running ? 'Running' : 'Stopped';
|
|
11460
|
+
html += '<div class="service-card">';
|
|
11461
|
+
html += '<div class="sc-header">';
|
|
11462
|
+
html += '<div class="sc-dot" style="background:' + dotColor + '"></div>';
|
|
11463
|
+
html += '<span class="sc-name">' + escapeHtml(agent.name) + '</span>';
|
|
11464
|
+
html += '<span class="sc-provider" style="background:' + (agent.color || '#666') + '">' + escapeHtml(agent.provider) + '</span>';
|
|
11465
|
+
html += '</div>';
|
|
11466
|
+
html += '<div class="sc-stats">';
|
|
11467
|
+
html += 'Model: ' + escapeHtml(agent.model) + '<br>';
|
|
11468
|
+
html += 'Status: ' + statusText + '<br>';
|
|
11469
|
+
html += 'Requests: ' + (agent.stats ? agent.stats.requests : 0) + ' | Completed: ' + (agent.stats ? agent.stats.completed : 0) + ' | Errors: ' + (agent.stats ? agent.stats.errors : 0);
|
|
11470
|
+
if (agent.stats && agent.stats.lastActivity) {
|
|
11471
|
+
html += '<br>Last: ' + new Date(agent.stats.lastActivity).toLocaleTimeString();
|
|
11472
|
+
}
|
|
11473
|
+
html += '</div>';
|
|
11474
|
+
html += '<div class="sc-controls">';
|
|
11475
|
+
if (agent.running) {
|
|
11476
|
+
html += '<button class="sc-stop" onclick="stopApiAgent(\'' + escapeHtml(agent.name) + '\')">Stop</button>';
|
|
11477
|
+
} else {
|
|
11478
|
+
html += '<button class="sc-start" onclick="startApiAgent(\'' + escapeHtml(agent.name) + '\')">Start</button>';
|
|
11479
|
+
}
|
|
11480
|
+
html += '<button class="sc-delete" onclick="deleteApiAgent(\'' + escapeHtml(agent.name) + '\')">Delete</button>';
|
|
11481
|
+
html += '</div>';
|
|
11482
|
+
html += '</div>';
|
|
11483
|
+
});
|
|
11484
|
+
html += '</div>';
|
|
11485
|
+
} else {
|
|
11486
|
+
html += '<div style="text-align:center;padding:24px;color:var(--text-dim);font-size:13px">No API agents configured. Create one above to get started.</div>';
|
|
11487
|
+
}
|
|
11488
|
+
|
|
11489
|
+
// Media browser placeholder
|
|
11490
|
+
html += '<div class="media-browser"><h3>Generated Media</h3><div id="media-grid-container" class="media-grid"></div></div>';
|
|
11491
|
+
|
|
11492
|
+
html += '</div>';
|
|
11493
|
+
el.innerHTML = html;
|
|
11494
|
+
|
|
11495
|
+
// Initialize provider form fields (show/hide API key, set defaults)
|
|
11496
|
+
if (document.getElementById('svc-provider')) onProviderChange();
|
|
11497
|
+
|
|
11498
|
+
// Render media separately (may load async)
|
|
11499
|
+
renderMediaBrowser();
|
|
11500
|
+
}
|
|
11501
|
+
|
|
11502
|
+
function renderMediaBrowser() {
|
|
11503
|
+
var container = document.getElementById('media-grid-container');
|
|
11504
|
+
if (!container) return;
|
|
11505
|
+
|
|
11506
|
+
if (cachedMediaItems.length === 0) {
|
|
11507
|
+
container.innerHTML = '<div style="grid-column:1/-1;text-align:center;padding:20px;color:var(--text-dim);font-size:12px">No media generated yet. Send a message to an API agent to get started.</div>';
|
|
11508
|
+
return;
|
|
11509
|
+
}
|
|
11510
|
+
|
|
11511
|
+
var pq = projectParam();
|
|
11512
|
+
var html = '';
|
|
11513
|
+
cachedMediaItems.forEach(function(item) {
|
|
11514
|
+
var imgSrc = '/api/media/' + item.id + '/file' + pq;
|
|
11515
|
+
html += '<div class="media-item" onclick="showMediaLightbox(\'' + item.id + '\')">';
|
|
11516
|
+
if (item.type === 'image') {
|
|
11517
|
+
html += '<img src="' + imgSrc + '" alt="' + escapeHtml(item.prompt || '') + '" loading="lazy">';
|
|
11518
|
+
} else {
|
|
11519
|
+
html += '<div style="width:100%;aspect-ratio:1;background:var(--surface-1);display:flex;align-items:center;justify-content:center;color:var(--text-dim);font-size:24px">' + (item.type === 'video' ? '▶' : '🖼') + '</div>';
|
|
11520
|
+
}
|
|
11521
|
+
html += '<div class="mi-info">';
|
|
11522
|
+
html += '<div class="mi-prompt">' + escapeHtml(item.prompt || 'Untitled') + '</div>';
|
|
11523
|
+
html += '<div style="display:flex;justify-content:space-between;margin-top:2px"><span>' + escapeHtml(item.agent || '') + '</span><span>' + new Date(item.timestamp).toLocaleTimeString() + '</span></div>';
|
|
11524
|
+
html += '</div></div>';
|
|
11525
|
+
});
|
|
11526
|
+
container.innerHTML = html;
|
|
11527
|
+
}
|
|
11528
|
+
|
|
11529
|
+
function showMediaLightbox(id) {
|
|
11530
|
+
var pq = projectParam();
|
|
11531
|
+
var item = cachedMediaItems.find(function(m) { return m.id === id; });
|
|
11532
|
+
if (!item) return;
|
|
11533
|
+
|
|
11534
|
+
var overlay = document.createElement('div');
|
|
11535
|
+
overlay.className = 'media-lightbox';
|
|
11536
|
+
overlay.onclick = function() { overlay.remove(); };
|
|
11537
|
+
|
|
11538
|
+
var imgSrc = '/api/media/' + id + '/file' + pq;
|
|
11539
|
+
overlay.innerHTML = '<img src="' + imgSrc + '"><div class="lb-info">' + escapeHtml(item.prompt || '') + '<br><small>' + escapeHtml(item.agent || '') + ' · ' + escapeHtml(item.model || '') + ' · ' + new Date(item.timestamp).toLocaleString() + '</small></div>';
|
|
11540
|
+
document.body.appendChild(overlay);
|
|
11541
|
+
}
|
|
11542
|
+
|
|
11543
|
+
function onProviderChange() {
|
|
11544
|
+
var provider = document.getElementById('svc-provider').value;
|
|
11545
|
+
var keyRow = document.getElementById('svc-apikey-row');
|
|
11546
|
+
var endpointLabel = document.getElementById('svc-endpoint-label');
|
|
11547
|
+
var endpointInput = document.getElementById('svc-endpoint');
|
|
11548
|
+
|
|
11549
|
+
if (provider === 'zai') {
|
|
11550
|
+
keyRow.style.display = 'block';
|
|
11551
|
+
endpointLabel.textContent = 'Endpoint (leave default)';
|
|
11552
|
+
endpointInput.placeholder = 'https://api.z.ai';
|
|
11553
|
+
endpointInput.value = '';
|
|
11554
|
+
document.getElementById('svc-model').placeholder = 'glm-5';
|
|
11555
|
+
document.getElementById('svc-model').value = 'glm-5';
|
|
11556
|
+
document.getElementById('svc-apikey').placeholder = 'your-zai-api-key';
|
|
11557
|
+
} else if (provider === 'comfyui') {
|
|
11558
|
+
keyRow.style.display = 'none';
|
|
11559
|
+
endpointLabel.textContent = 'ComfyUI URL';
|
|
11560
|
+
endpointInput.placeholder = 'http://127.0.0.1:8188';
|
|
11561
|
+
endpointInput.value = 'http://127.0.0.1:8188';
|
|
11562
|
+
document.getElementById('svc-model').placeholder = 'flux_text_to_image';
|
|
11563
|
+
document.getElementById('svc-model').value = 'flux_text_to_image';
|
|
11564
|
+
} else if (provider === 'gemini') {
|
|
11565
|
+
keyRow.style.display = 'block';
|
|
11566
|
+
endpointLabel.textContent = 'Endpoint (leave default)';
|
|
11567
|
+
endpointInput.placeholder = 'https://generativelanguage.googleapis.com';
|
|
11568
|
+
endpointInput.value = '';
|
|
11569
|
+
document.getElementById('svc-model').placeholder = 'gemini-3-pro-image-preview';
|
|
11570
|
+
document.getElementById('svc-model').value = 'gemini-3-pro-image-preview';
|
|
11571
|
+
document.getElementById('svc-apikey').placeholder = 'AIza...';
|
|
11572
|
+
} else if (provider === 'ollama') {
|
|
11573
|
+
keyRow.style.display = 'none';
|
|
11574
|
+
endpointLabel.textContent = 'Endpoint URL';
|
|
11575
|
+
endpointInput.placeholder = 'http://localhost:11434';
|
|
11576
|
+
endpointInput.value = 'http://localhost:11434';
|
|
11577
|
+
document.getElementById('svc-model').placeholder = 'e.g. qwen3.5, llama3.2-vision';
|
|
11578
|
+
document.getElementById('svc-model').value = '';
|
|
11579
|
+
document.getElementById('svc-apikey').placeholder = 'sk-...';
|
|
11580
|
+
} else if (provider === 'dalle') {
|
|
11581
|
+
keyRow.style.display = 'block';
|
|
11582
|
+
endpointLabel.textContent = 'Endpoint (leave default)';
|
|
11583
|
+
endpointInput.placeholder = 'https://api.openai.com';
|
|
11584
|
+
endpointInput.value = '';
|
|
11585
|
+
document.getElementById('svc-model').placeholder = 'dall-e-3';
|
|
11586
|
+
document.getElementById('svc-model').value = 'dall-e-3';
|
|
11587
|
+
document.getElementById('svc-apikey').placeholder = 'sk-...';
|
|
11588
|
+
} else if (provider === 'replicate') {
|
|
11589
|
+
keyRow.style.display = 'block';
|
|
11590
|
+
endpointLabel.textContent = 'Endpoint (leave default)';
|
|
11591
|
+
endpointInput.placeholder = 'https://api.replicate.com';
|
|
11592
|
+
endpointInput.value = '';
|
|
11593
|
+
document.getElementById('svc-model').placeholder = 'stability-ai/sdxl';
|
|
11594
|
+
document.getElementById('svc-model').value = '';
|
|
11595
|
+
document.getElementById('svc-apikey').placeholder = 'r8_...';
|
|
11596
|
+
}
|
|
11597
|
+
}
|
|
11598
|
+
|
|
11599
|
+
function createApiAgent() {
|
|
11600
|
+
var name = document.getElementById('svc-name').value.trim();
|
|
11601
|
+
var provider = document.getElementById('svc-provider').value;
|
|
11602
|
+
var model = document.getElementById('svc-model').value.trim() || 'sdxl';
|
|
11603
|
+
var endpoint = document.getElementById('svc-endpoint').value.trim() || 'http://localhost:11434';
|
|
11604
|
+
var apiKey = (document.getElementById('svc-apikey') || {}).value || '';
|
|
11605
|
+
|
|
11606
|
+
if (!name) { alert('Enter an agent name'); return; }
|
|
11607
|
+
|
|
11608
|
+
var pq = projectParam();
|
|
11609
|
+
lttFetch('/api/api-agents' + pq, {
|
|
11610
|
+
method: 'POST',
|
|
11611
|
+
headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
|
|
11612
|
+
body: JSON.stringify({ name: name, provider: provider, model: model, endpoint: endpoint, apiKey: apiKey })
|
|
11613
|
+
}).then(function(r) { return r.json(); }).then(function(result) {
|
|
11614
|
+
if (result.error) { alert(result.error); return; }
|
|
11615
|
+
document.getElementById('svc-name').value = '';
|
|
11616
|
+
// Auto-start the bot immediately after creation
|
|
11617
|
+
startApiAgent(name);
|
|
11618
|
+
}).catch(function(e) { alert('Failed: ' + e.message); });
|
|
11619
|
+
}
|
|
11620
|
+
|
|
11621
|
+
function startApiAgent(name) {
|
|
11622
|
+
var pq = projectParam();
|
|
11623
|
+
lttFetch('/api/api-agents/' + encodeURIComponent(name) + '/start' + pq, {
|
|
11624
|
+
method: 'POST',
|
|
11625
|
+
headers: { 'X-LTT-Request': '1' }
|
|
11626
|
+
}).then(function() { fetchApiAgents(); });
|
|
11627
|
+
}
|
|
11628
|
+
|
|
11629
|
+
function stopApiAgent(name) {
|
|
11630
|
+
var pq = projectParam();
|
|
11631
|
+
lttFetch('/api/api-agents/' + encodeURIComponent(name) + '/stop' + pq, {
|
|
11632
|
+
method: 'POST',
|
|
11633
|
+
headers: { 'X-LTT-Request': '1' }
|
|
11634
|
+
}).then(function() { fetchApiAgents(); });
|
|
11635
|
+
}
|
|
11636
|
+
|
|
11637
|
+
function deleteApiAgent(name) {
|
|
11638
|
+
if (!confirm('Delete API agent "' + name + '"?')) return;
|
|
11639
|
+
var pq = projectParam();
|
|
11640
|
+
lttFetch('/api/api-agents/' + encodeURIComponent(name) + pq, {
|
|
11641
|
+
method: 'DELETE',
|
|
11642
|
+
headers: { 'X-LTT-Request': '1' }
|
|
11643
|
+
}).then(function() { fetchApiAgents(); });
|
|
11644
|
+
}
|
|
11645
|
+
|
|
8679
11646
|
// ==================== v3.6: DOCS VIEW ====================
|
|
8680
11647
|
|
|
8681
11648
|
function renderDocs() {
|
|
@@ -8694,7 +11661,7 @@ function renderDocs() {
|
|
|
8694
11661
|
'<h3>Quick Start \u2014 One Command</h3>' +
|
|
8695
11662
|
'<pre><code>npx let-them-talk run "build a REST API with auth" --agents 4</code></pre>' +
|
|
8696
11663
|
'<p>Spawns 4 agents, auto-assigns roles (Lead, Implementer, Quality, etc.), creates an autonomous workflow, and starts execution. Walk away, come back to finished work.</p>' +
|
|
8697
|
-
'<p>Monitor progress: <code>
|
|
11664
|
+
'<p>Monitor progress: <code>node .agent-bridge/launch.js</code> | Check status: <code>node .agent-bridge/launch.js status</code> | Diagnose issues: <code>npx let-them-talk doctor</code></p>' +
|
|
8698
11665
|
'</div>' +
|
|
8699
11666
|
|
|
8700
11667
|
// v5.0 Autonomy Engine
|
|
@@ -8720,7 +11687,7 @@ function renderDocs() {
|
|
|
8720
11687
|
'<p>This auto-detects your CLI (Claude Code, Gemini CLI, or Codex CLI) and adds the agent-bridge MCP server to its config. To set up all CLIs at once:</p>' +
|
|
8721
11688
|
'<pre><code>npx let-them-talk init --all</code></pre>' +
|
|
8722
11689
|
'<h4>2. Open the Dashboard</h4>' +
|
|
8723
|
-
'<pre><code>
|
|
11690
|
+
'<pre><code>node .agent-bridge/launch.js</code></pre>' +
|
|
8724
11691
|
'<p>Opens this web dashboard at <code>http://localhost:3777</code>. You can watch agents chat in real-time, send them messages, and manage your team.</p>' +
|
|
8725
11692
|
'<h4>3. Start Your Agents</h4>' +
|
|
8726
11693
|
'<p>Open two or more terminal windows in your project folder. In each one, start your AI CLI (e.g. type <code>claude</code>) and tell it to register:</p>' +
|
|
@@ -8799,9 +11766,10 @@ function renderDocs() {
|
|
|
8799
11766
|
'<div class="docs-tool-item"><code>npx let-them-talk init</code><div class="desc">Set up MCP server config for your CLI</div></div>' +
|
|
8800
11767
|
'<div class="docs-tool-item"><code>npx let-them-talk init --all</code><div class="desc">Set up for all detected CLIs at once</div></div>' +
|
|
8801
11768
|
'<div class="docs-tool-item"><code>npx let-them-talk init --template <name></code><div class="desc">Start with a team template</div></div>' +
|
|
8802
|
-
'<div class="docs-tool-item"><code>
|
|
11769
|
+
'<div class="docs-tool-item"><code>node .agent-bridge/launch.js</code><div class="desc">Open the web dashboard (no re-download)</div></div>' +
|
|
11770
|
+
'<div class="docs-tool-item"><code>node .agent-bridge/launch.js status</code><div class="desc">Show active agents and tasks</div></div>' +
|
|
11771
|
+
'<div class="docs-tool-item"><code>node .agent-bridge/launch.js reset</code><div class="desc">Clear conversation data (auto-archives first)</div></div>' +
|
|
8803
11772
|
'<div class="docs-tool-item"><code>npx let-them-talk templates</code><div class="desc">List available templates</div></div>' +
|
|
8804
|
-
'<div class="docs-tool-item"><code>npx let-them-talk reset --force</code><div class="desc">Clear conversation data (auto-archives first)</div></div>' +
|
|
8805
11773
|
'</div>' +
|
|
8806
11774
|
'</div>' +
|
|
8807
11775
|
|
|
@@ -9013,6 +11981,7 @@ function initSSE() {
|
|
|
9013
11981
|
// Targeted fetch for tasks/workflows only — no full poll needed
|
|
9014
11982
|
if (needTasks && activeView === 'tasks') fetchTasks();
|
|
9015
11983
|
if (needWorkflows && activeView === 'workflows') fetchWorkflows();
|
|
11984
|
+
if (needAgents && activeView === 'services') fetchApiAgents();
|
|
9016
11985
|
}
|
|
9017
11986
|
};
|
|
9018
11987
|
eventSource.onopen = function() {
|
|
@@ -9057,6 +12026,11 @@ function initSSE() {
|
|
|
9057
12026
|
|
|
9058
12027
|
// Init UI preferences
|
|
9059
12028
|
initCompactMode();
|
|
12029
|
+
applyAgentFilterInputs();
|
|
12030
|
+
applyPinnedSectionState();
|
|
12031
|
+
refreshDashboardWorkspaceControls();
|
|
12032
|
+
updateSearchInputMode();
|
|
12033
|
+
syncSearchBarVisibility();
|
|
9060
12034
|
|
|
9061
12035
|
// Load projects first, then poll (so auto-select works before first data fetch)
|
|
9062
12036
|
loadProjects().then(function() {
|
|
@@ -9064,12 +12038,12 @@ loadProjects().then(function() {
|
|
|
9064
12038
|
loadConversationList();
|
|
9065
12039
|
poll();
|
|
9066
12040
|
initSSE();
|
|
9067
|
-
switchView(
|
|
12041
|
+
switchView(activeView, { skipPersist: true });
|
|
9068
12042
|
}).catch(function(e) {
|
|
9069
12043
|
console.error('[LTT] init: loadProjects failed, polling anyway:', e);
|
|
9070
12044
|
poll();
|
|
9071
12045
|
initSSE();
|
|
9072
|
-
switchView(
|
|
12046
|
+
switchView(activeView, { skipPersist: true });
|
|
9073
12047
|
});
|
|
9074
12048
|
// Safety-net poll at 10s (SSE handles real-time, this catches any missed updates)
|
|
9075
12049
|
setInterval(poll, 10000);
|