@jungjaehoon/mama-os 0.32.3 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -17
- package/dist/agent/os-agent-capabilities.md +2 -2
- package/dist/api/graph-api.d.ts +4 -4
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +47 -78
- package/dist/api/graph-api.js.map +1 -1
- package/dist/api/index.d.ts +10 -0
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +9 -2
- package/dist/api/index.js.map +1 -1
- package/dist/api/operator-handler.d.ts +1 -1
- package/dist/api/operator-handler.js +1 -1
- package/dist/api/runtime-status-handler.d.ts +41 -0
- package/dist/api/runtime-status-handler.d.ts.map +1 -0
- package/dist/api/runtime-status-handler.js +55 -0
- package/dist/api/runtime-status-handler.js.map +1 -0
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +58 -23
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/cli/runtime/api-routes-init.d.ts +2 -0
- package/dist/cli/runtime/api-routes-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-routes-init.js +62 -1
- package/dist/cli/runtime/api-routes-init.js.map +1 -1
- package/dist/cli/runtime/api-server-init.d.ts +12 -1
- package/dist/cli/runtime/api-server-init.d.ts.map +1 -1
- package/dist/cli/runtime/api-server-init.js +54 -0
- package/dist/cli/runtime/api-server-init.js.map +1 -1
- package/dist/cli/runtime/utilities.d.ts +1 -1
- package/dist/cli/runtime/utilities.d.ts.map +1 -1
- package/dist/cli/runtime/utilities.js +3 -1
- package/dist/cli/runtime/utilities.js.map +1 -1
- package/dist/gateways/message-router.d.ts +4 -0
- package/dist/gateways/message-router.d.ts.map +1 -1
- package/dist/gateways/message-router.js +62 -13
- package/dist/gateways/message-router.js.map +1 -1
- package/dist/gateways/owner-report-inbox.d.ts +40 -0
- package/dist/gateways/owner-report-inbox.d.ts.map +1 -0
- package/dist/gateways/owner-report-inbox.js +128 -0
- package/dist/gateways/owner-report-inbox.js.map +1 -0
- package/dist/gateways/report-projection-v1.d.ts +41 -0
- package/dist/gateways/report-projection-v1.d.ts.map +1 -0
- package/dist/gateways/report-projection-v1.js +160 -0
- package/dist/gateways/report-projection-v1.js.map +1 -0
- package/dist/gateways/session-store.d.ts +22 -0
- package/dist/gateways/session-store.d.ts.map +1 -1
- package/dist/gateways/session-store.js +76 -3
- package/dist/gateways/session-store.js.map +1 -1
- package/dist/gateways/telegram-message-ledger.d.ts +10 -0
- package/dist/gateways/telegram-message-ledger.d.ts.map +1 -1
- package/dist/gateways/telegram-message-ledger.js +23 -2
- package/dist/gateways/telegram-message-ledger.js.map +1 -1
- package/dist/gateways/telegram-report-context-store.d.ts +193 -0
- package/dist/gateways/telegram-report-context-store.d.ts.map +1 -0
- package/dist/gateways/telegram-report-context-store.js +612 -0
- package/dist/gateways/telegram-report-context-store.js.map +1 -0
- package/dist/gateways/telegram.d.ts +9 -0
- package/dist/gateways/telegram.d.ts.map +1 -1
- package/dist/gateways/telegram.js +100 -0
- package/dist/gateways/telegram.js.map +1 -1
- package/dist/gateways/types.d.ts +12 -0
- package/dist/gateways/types.d.ts.map +1 -1
- package/dist/multi-agent/dashboard-agent-persona.js +2 -2
- package/dist/operator/board-slot-instructions.d.ts +1 -1
- package/dist/operator/board-slot-instructions.js +1 -1
- package/dist/operator/operator-trigger-loop.d.ts +17 -1
- package/dist/operator/operator-trigger-loop.d.ts.map +1 -1
- package/dist/operator/operator-trigger-loop.js +87 -23
- package/dist/operator/operator-trigger-loop.js.map +1 -1
- package/dist/operator/report-delivery-coordinator.d.ts +82 -0
- package/dist/operator/report-delivery-coordinator.d.ts.map +1 -0
- package/dist/operator/report-delivery-coordinator.js +123 -0
- package/dist/operator/report-delivery-coordinator.js.map +1 -0
- package/dist/operator/situation-report.d.ts +7 -0
- package/dist/operator/situation-report.d.ts.map +1 -1
- package/dist/operator/situation-report.js +12 -0
- package/dist/operator/situation-report.js.map +1 -1
- package/package.json +1 -2
- package/public/viewer/js/modules/system.js +183 -0
- package/public/viewer/js/utils/ui-commands.js +4 -9
- package/public/viewer/log-viewer.html +2 -192
- package/public/viewer/operator/operator.css +1 -0
- package/public/viewer/operator/operator.js +14830 -0
- package/public/viewer/src/modules/system.ts +242 -0
- package/public/viewer/src/utils/ui-commands.ts +4 -8
- package/public/viewer/sw.js +44 -10
- package/public/viewer/viewer.css +338 -331
- package/public/viewer/viewer.html +388 -1472
- package/public/ui/assets/index-BDXDGEV6.js +0 -62
- package/public/ui/assets/index-po2tN0xq.css +0 -1
- package/public/ui/index.html +0 -14
- package/public/ui/theme-init.js +0 -20
- package/public/viewer/js/modules/agents.js +0 -1153
- package/public/viewer/js/modules/chat.js +0 -1993
- package/public/viewer/js/modules/connector-feed.js +0 -352
- package/public/viewer/js/modules/dashboard.js +0 -456
- package/public/viewer/js/modules/entity-audit.js +0 -106
- package/public/viewer/js/modules/entity-review.js +0 -66
- package/public/viewer/js/modules/settings.js +0 -1257
- package/public/viewer/src/modules/agents.ts +0 -1302
- package/public/viewer/src/modules/chat.ts +0 -2426
- package/public/viewer/src/modules/connector-feed.ts +0 -391
- package/public/viewer/src/modules/dashboard.ts +0 -498
- package/public/viewer/src/modules/entity-audit.ts +0 -154
- package/public/viewer/src/modules/entity-review.ts +0 -175
- package/public/viewer/src/modules/settings.ts +0 -1547
|
@@ -255,56 +255,28 @@
|
|
|
255
255
|
background: #ffce00;
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
/* ──
|
|
259
|
-
|
|
260
|
-
display: none;
|
|
261
|
-
position: fixed;
|
|
262
|
-
bottom: 0;
|
|
263
|
-
left: 0;
|
|
264
|
-
right: 0;
|
|
265
|
-
height: 64px;
|
|
266
|
-
background: #ffffff;
|
|
267
|
-
border-top: 1px solid #ede9e1;
|
|
268
|
-
z-index: 100;
|
|
269
|
-
padding: 0 8px;
|
|
270
|
-
align-items: center;
|
|
271
|
-
justify-content: space-around;
|
|
272
|
-
}
|
|
273
|
-
.mama-mobile-tab {
|
|
258
|
+
/* ── Group headings inside the rail ── */
|
|
259
|
+
.mama-nav-group {
|
|
274
260
|
display: flex;
|
|
275
261
|
flex-direction: column;
|
|
276
|
-
align-items: center;
|
|
277
262
|
gap: 2px;
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
background: transparent;
|
|
281
|
-
color: #6b6560;
|
|
282
|
-
font-size: 10px;
|
|
283
|
-
font-weight: 600;
|
|
284
|
-
text-decoration: none;
|
|
285
|
-
font-family: 'Inter', 'Noto Sans KR', sans-serif;
|
|
286
|
-
cursor: pointer;
|
|
287
|
-
transition: color 0.15s ease;
|
|
288
|
-
}
|
|
289
|
-
.mama-mobile-tab i,
|
|
290
|
-
.mama-mobile-tab svg {
|
|
291
|
-
width: 20px;
|
|
292
|
-
height: 20px;
|
|
263
|
+
width: 100%;
|
|
264
|
+
margin-bottom: 10px;
|
|
293
265
|
}
|
|
294
|
-
.mama-
|
|
295
|
-
|
|
296
|
-
|
|
266
|
+
.mama-nav-group-label {
|
|
267
|
+
padding: 0 12px;
|
|
268
|
+
font-family: 'Inter', 'Noto Sans KR', sans-serif;
|
|
269
|
+
font-size: 10px;
|
|
270
|
+
font-weight: 700;
|
|
271
|
+
letter-spacing: 0.06em;
|
|
272
|
+
text-transform: uppercase;
|
|
273
|
+
color: #9b9389;
|
|
274
|
+
white-space: nowrap;
|
|
275
|
+
opacity: 0;
|
|
276
|
+
transition: opacity 0.15s ease;
|
|
297
277
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
position: absolute;
|
|
301
|
-
top: 0;
|
|
302
|
-
left: 50%;
|
|
303
|
-
transform: translateX(-50%);
|
|
304
|
-
width: 24px;
|
|
305
|
-
height: 2px;
|
|
306
|
-
border-radius: 1px;
|
|
307
|
-
background: #ffce00;
|
|
278
|
+
#mama-sidebar:hover .mama-nav-group-label {
|
|
279
|
+
opacity: 1;
|
|
308
280
|
}
|
|
309
281
|
|
|
310
282
|
/* ── Slot container ── */
|
|
@@ -315,101 +287,14 @@
|
|
|
315
287
|
min-height: 200px;
|
|
316
288
|
}
|
|
317
289
|
|
|
318
|
-
/* ── Settings Sections ── */
|
|
319
|
-
.mama-settings-section {
|
|
320
|
-
border-bottom: 1px solid #ede9e1;
|
|
321
|
-
padding: 16px 0;
|
|
322
|
-
border-radius: 4px;
|
|
323
|
-
}
|
|
324
|
-
.mama-settings-section:last-child {
|
|
325
|
-
border-bottom: none;
|
|
326
|
-
}
|
|
327
|
-
.mama-settings-heading {
|
|
328
|
-
font-family: 'Inter', 'Noto Sans KR', sans-serif;
|
|
329
|
-
font-size: 16px;
|
|
330
|
-
font-weight: 600;
|
|
331
|
-
color: #1a1a1a;
|
|
332
|
-
cursor: pointer;
|
|
333
|
-
display: flex;
|
|
334
|
-
align-items: center;
|
|
335
|
-
justify-content: space-between;
|
|
336
|
-
user-select: none;
|
|
337
|
-
padding: 0 0 8px 0;
|
|
338
|
-
margin: 0;
|
|
339
|
-
border: none;
|
|
340
|
-
background: none;
|
|
341
|
-
width: 100%;
|
|
342
|
-
text-align: left;
|
|
343
|
-
}
|
|
344
|
-
.mama-settings-heading:hover {
|
|
345
|
-
color: #3d3630;
|
|
346
|
-
}
|
|
347
|
-
.mama-collapse-icon {
|
|
348
|
-
font-size: 14px;
|
|
349
|
-
color: #9b9389;
|
|
350
|
-
transition: transform 0.2s ease;
|
|
351
|
-
display: inline-block;
|
|
352
|
-
}
|
|
353
|
-
.mama-settings-section.collapsed .mama-settings-body {
|
|
354
|
-
display: none;
|
|
355
|
-
}
|
|
356
|
-
.mama-settings-section.collapsed .mama-collapse-icon {
|
|
357
|
-
transform: rotate(-90deg);
|
|
358
|
-
}
|
|
359
|
-
|
|
360
290
|
/* ── Responsive ── */
|
|
361
291
|
@media (max-width: 767px) {
|
|
362
292
|
#mama-sidebar {
|
|
363
293
|
display: none;
|
|
364
294
|
}
|
|
365
|
-
#mama-mobile-tabs {
|
|
366
|
-
display: flex;
|
|
367
|
-
}
|
|
368
295
|
main.flex-1 {
|
|
369
296
|
margin-left: 0 !important;
|
|
370
|
-
padding-bottom:
|
|
371
|
-
}
|
|
372
|
-
/* Hide right panel chat on mobile — use full-screen chat tab instead */
|
|
373
|
-
#chat-resize-handle-bar {
|
|
374
|
-
display: none !important;
|
|
375
|
-
}
|
|
376
|
-
#chat-panel-wrapper {
|
|
377
|
-
display: none !important;
|
|
378
|
-
}
|
|
379
|
-
#chat-collapsed-bar {
|
|
380
|
-
display: none !important;
|
|
381
|
-
}
|
|
382
|
-
/* Full-screen chat overlay when chat tab is active */
|
|
383
|
-
#chat-panel-wrapper.mobile-chat-active {
|
|
384
|
-
display: flex !important;
|
|
385
|
-
position: fixed;
|
|
386
|
-
top: 0;
|
|
387
|
-
left: 0;
|
|
388
|
-
right: 0;
|
|
389
|
-
bottom: 64px;
|
|
390
|
-
width: 100% !important;
|
|
391
|
-
height: auto !important;
|
|
392
|
-
z-index: 90;
|
|
393
|
-
border-left: none;
|
|
394
|
-
flex-direction: column;
|
|
395
|
-
}
|
|
396
|
-
#chat-panel-wrapper.mobile-chat-active #chat-panel {
|
|
397
|
-
flex: 1;
|
|
398
|
-
display: flex;
|
|
399
|
-
flex-direction: column;
|
|
400
|
-
height: 100% !important;
|
|
401
|
-
min-height: 0;
|
|
402
|
-
overflow: visible;
|
|
403
|
-
}
|
|
404
|
-
#chat-panel-wrapper.mobile-chat-active #chat-messages {
|
|
405
|
-
flex: 1;
|
|
406
|
-
min-height: 0;
|
|
407
|
-
overflow-y: auto;
|
|
408
|
-
}
|
|
409
|
-
#chat-panel-wrapper.mobile-chat-active .chat-input-area,
|
|
410
|
-
#chat-panel-wrapper.mobile-chat-active > div:last-child {
|
|
411
|
-
flex-shrink: 0;
|
|
412
|
-
padding-bottom: max(4px, env(safe-area-inset-bottom));
|
|
297
|
+
padding-bottom: 64px;
|
|
413
298
|
}
|
|
414
299
|
}
|
|
415
300
|
@media (min-width: 768px) {
|
|
@@ -420,6 +305,9 @@
|
|
|
420
305
|
</style>
|
|
421
306
|
|
|
422
307
|
<link rel="stylesheet" href="/viewer/viewer.css" />
|
|
308
|
+
<!-- Operator content bundle styles. Its reset is scoped under #operator-root,
|
|
309
|
+
so it cannot fight the viewer's own global reset. -->
|
|
310
|
+
<link rel="stylesheet" href="/viewer/operator/operator.css" />
|
|
423
311
|
</head>
|
|
424
312
|
<body class="bg-background text-mama-black font-body">
|
|
425
313
|
<div id="app" class="h-screen flex flex-col">
|
|
@@ -429,89 +317,85 @@
|
|
|
429
317
|
<img src="/viewer/icons/mama-icon.svg" alt="MAMA" width="28" height="28" />
|
|
430
318
|
<span>MAMA</span>
|
|
431
319
|
</a>
|
|
320
|
+
<!-- Grouped rail. Every entry is a real hash link: the address bar is
|
|
321
|
+
the single source of truth for which route is showing. -->
|
|
432
322
|
<div class="mama-nav-items">
|
|
433
|
-
<
|
|
434
|
-
<
|
|
435
|
-
<
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
data-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
<
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
<
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
class="mama-nav-item"
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
>
|
|
474
|
-
<i data-lucide="bot"></i>
|
|
475
|
-
<span>Agents</span>
|
|
476
|
-
</button>
|
|
477
|
-
<button
|
|
478
|
-
class="mama-nav-item"
|
|
479
|
-
data-tab="settings"
|
|
480
|
-
onclick="window.switchTab && window.switchTab('settings')"
|
|
481
|
-
>
|
|
482
|
-
<i data-lucide="settings"></i>
|
|
483
|
-
<span>Settings</span>
|
|
484
|
-
</button>
|
|
323
|
+
<div class="mama-nav-group">
|
|
324
|
+
<span class="mama-nav-group-label">Operator</span>
|
|
325
|
+
<a class="mama-nav-item" data-route="operator/board" href="#operator/board">
|
|
326
|
+
<i data-lucide="layout-dashboard"></i>
|
|
327
|
+
<span>Board</span>
|
|
328
|
+
</a>
|
|
329
|
+
<a class="mama-nav-item" data-route="operator/tasks" href="#operator/tasks">
|
|
330
|
+
<i data-lucide="list-checks"></i>
|
|
331
|
+
<span>Tasks</span>
|
|
332
|
+
</a>
|
|
333
|
+
<a class="mama-nav-item" data-route="operator/triggers" href="#operator/triggers">
|
|
334
|
+
<i data-lucide="zap"></i>
|
|
335
|
+
<span>Triggers</span>
|
|
336
|
+
</a>
|
|
337
|
+
</div>
|
|
338
|
+
<div class="mama-nav-group">
|
|
339
|
+
<span class="mama-nav-group-label">Knowledge</span>
|
|
340
|
+
<a class="mama-nav-item" data-route="knowledge/memory" href="#knowledge/memory">
|
|
341
|
+
<i data-lucide="brain"></i>
|
|
342
|
+
<span>Memory</span>
|
|
343
|
+
</a>
|
|
344
|
+
<a class="mama-nav-item" data-route="knowledge/wiki" href="#knowledge/wiki">
|
|
345
|
+
<i data-lucide="book-open"></i>
|
|
346
|
+
<span>Wiki</span>
|
|
347
|
+
</a>
|
|
348
|
+
</div>
|
|
349
|
+
<div class="mama-nav-group">
|
|
350
|
+
<span class="mama-nav-group-label">System</span>
|
|
351
|
+
<a class="mama-nav-item" data-route="system/runtime" href="#system/runtime">
|
|
352
|
+
<i data-lucide="activity"></i>
|
|
353
|
+
<span>Runtime</span>
|
|
354
|
+
</a>
|
|
355
|
+
<a class="mama-nav-item" data-route="system/connectors" href="#system/connectors">
|
|
356
|
+
<i data-lucide="plug"></i>
|
|
357
|
+
<span>Connectors</span>
|
|
358
|
+
</a>
|
|
359
|
+
<a class="mama-nav-item" data-route="system/logs" href="#system/logs">
|
|
360
|
+
<i data-lucide="terminal"></i>
|
|
361
|
+
<span>Logs</span>
|
|
362
|
+
</a>
|
|
363
|
+
</div>
|
|
485
364
|
</div>
|
|
486
365
|
</nav>
|
|
487
366
|
|
|
488
|
-
<!-- Content Area
|
|
367
|
+
<!-- Content Area -->
|
|
489
368
|
<div id="content-area" class="flex-1 flex min-h-0 overflow-hidden">
|
|
490
369
|
<!-- Main Content -->
|
|
491
370
|
<main class="flex-1 overflow-hidden flex flex-col min-w-0">
|
|
492
|
-
<!--
|
|
493
|
-
<
|
|
494
|
-
<
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
371
|
+
<!-- Mobile sub-views: the same hash routes the desktop rail uses. -->
|
|
372
|
+
<nav id="mama-subviews" class="mama-subviews" aria-label="Sub views">
|
|
373
|
+
<a class="mama-subview-chip" data-route="operator/board" href="#operator/board"
|
|
374
|
+
>Board</a
|
|
375
|
+
>
|
|
376
|
+
<a class="mama-subview-chip" data-route="operator/tasks" href="#operator/tasks"
|
|
377
|
+
>Tasks</a
|
|
378
|
+
>
|
|
379
|
+
<a class="mama-subview-chip" data-route="operator/triggers" href="#operator/triggers"
|
|
380
|
+
>Triggers</a
|
|
381
|
+
>
|
|
382
|
+
<a class="mama-subview-chip" data-route="knowledge/memory" href="#knowledge/memory"
|
|
383
|
+
>Memory</a
|
|
384
|
+
>
|
|
385
|
+
<a class="mama-subview-chip" data-route="knowledge/wiki" href="#knowledge/wiki">Wiki</a>
|
|
386
|
+
<a class="mama-subview-chip" data-route="system/runtime" href="#system/runtime"
|
|
387
|
+
>Runtime</a
|
|
388
|
+
>
|
|
389
|
+
<a class="mama-subview-chip" data-route="system/connectors" href="#system/connectors"
|
|
390
|
+
>Connectors</a
|
|
391
|
+
>
|
|
392
|
+
<a class="mama-subview-chip" data-route="system/logs" href="#system/logs">Logs</a>
|
|
393
|
+
</nav>
|
|
505
394
|
|
|
506
|
-
<!--
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
<div style="padding: 40px; text-align: center; color: #9e9891; font-size: 14px">
|
|
511
|
-
Loading connectors...
|
|
512
|
-
</div>
|
|
513
|
-
</div>
|
|
514
|
-
</div>
|
|
395
|
+
<!-- Operator: the React content bundle mounts here once and stays
|
|
396
|
+
mounted; other routes only hide it. -->
|
|
397
|
+
<div class="tab-content" id="tab-operator">
|
|
398
|
+
<div id="operator-mount" class="flex-1 min-h-0"></div>
|
|
515
399
|
</div>
|
|
516
400
|
|
|
517
401
|
<!-- Wiki Tab -->
|
|
@@ -525,6 +409,20 @@
|
|
|
525
409
|
</div>
|
|
526
410
|
</div>
|
|
527
411
|
|
|
412
|
+
<!-- Runtime Tab: rendered from /api/runtime/status only. -->
|
|
413
|
+
<div class="tab-content" id="tab-runtime">
|
|
414
|
+
<div class="flex-1 flex flex-col min-h-0 overflow-y-auto p-4 md:p-6">
|
|
415
|
+
<div id="runtime-content"></div>
|
|
416
|
+
</div>
|
|
417
|
+
</div>
|
|
418
|
+
|
|
419
|
+
<!-- Connectors Tab: read-only state from the same runtime snapshot. -->
|
|
420
|
+
<div class="tab-content" id="tab-connectors">
|
|
421
|
+
<div class="flex-1 flex flex-col min-h-0 overflow-y-auto p-4 md:p-6">
|
|
422
|
+
<div id="connectors-list"></div>
|
|
423
|
+
</div>
|
|
424
|
+
</div>
|
|
425
|
+
|
|
528
426
|
<!-- Logs Tab -->
|
|
529
427
|
<div class="tab-content" id="tab-logs">
|
|
530
428
|
<iframe
|
|
@@ -534,236 +432,12 @@
|
|
|
534
432
|
></iframe>
|
|
535
433
|
</div>
|
|
536
434
|
|
|
537
|
-
<!-- Agents Tab -->
|
|
538
|
-
<div class="tab-content" id="tab-agents">
|
|
539
|
-
<div class="flex-1 overflow-y-auto p-4" id="agents-content"></div>
|
|
540
|
-
</div>
|
|
541
|
-
|
|
542
|
-
<!-- Settings Tab -->
|
|
543
|
-
<div class="tab-content" id="tab-settings">
|
|
544
|
-
<div class="flex-1 flex flex-col min-h-0 overflow-y-auto p-2 md:p-3">
|
|
545
|
-
<!-- Compact Header -->
|
|
546
|
-
<div class="flex items-center justify-between mb-3">
|
|
547
|
-
<h1 class="text-sm font-bold text-gray-900">Settings</h1>
|
|
548
|
-
<span class="text-[10px] text-gray-400">MAMA OS Configuration</span>
|
|
549
|
-
</div>
|
|
550
|
-
|
|
551
|
-
<!-- Section 1: Connectors -->
|
|
552
|
-
<div class="mama-settings-section">
|
|
553
|
-
<h3
|
|
554
|
-
class="mama-settings-heading"
|
|
555
|
-
onclick="this.parentElement.classList.toggle('collapsed')"
|
|
556
|
-
>
|
|
557
|
-
Connectors <span class="mama-collapse-icon">▾</span>
|
|
558
|
-
</h3>
|
|
559
|
-
<div class="mama-settings-body">
|
|
560
|
-
<p class="text-[10px] text-gray-500 mb-2">
|
|
561
|
-
Data source connectors for polling external channels. Configure in
|
|
562
|
-
<code>~/.mama/connectors.json</code>.
|
|
563
|
-
</p>
|
|
564
|
-
<div id="settings-connectors-list" class="space-y-1.5">
|
|
565
|
-
<p class="text-[10px] text-gray-400">Loading connectors...</p>
|
|
566
|
-
</div>
|
|
567
|
-
</div>
|
|
568
|
-
</div>
|
|
569
|
-
|
|
570
|
-
<!-- Section 3: System -->
|
|
571
|
-
<div class="mama-settings-section">
|
|
572
|
-
<h3
|
|
573
|
-
class="mama-settings-heading"
|
|
574
|
-
onclick="this.parentElement.classList.toggle('collapsed')"
|
|
575
|
-
>
|
|
576
|
-
System <span class="mama-collapse-icon">▾</span>
|
|
577
|
-
</h3>
|
|
578
|
-
<div class="mama-settings-body">
|
|
579
|
-
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
|
|
580
|
-
<!-- Left: Health + DB Info -->
|
|
581
|
-
<div class="space-y-3">
|
|
582
|
-
<!-- Health Check -->
|
|
583
|
-
<section>
|
|
584
|
-
<h2 class="text-xs font-semibold text-gray-700 mb-1.5">Health</h2>
|
|
585
|
-
<div
|
|
586
|
-
id="settings-system-health"
|
|
587
|
-
class="bg-white border border-gray-200 rounded-lg p-2 shadow-sm"
|
|
588
|
-
>
|
|
589
|
-
<p class="text-[10px] text-gray-400">Health status will appear here.</p>
|
|
590
|
-
</div>
|
|
591
|
-
</section>
|
|
592
|
-
|
|
593
|
-
<!-- Database Info -->
|
|
594
|
-
<section>
|
|
595
|
-
<h2 class="text-xs font-semibold text-gray-700 mb-1.5">Database</h2>
|
|
596
|
-
<div
|
|
597
|
-
id="settings-system-db"
|
|
598
|
-
class="bg-white border border-gray-200 rounded-lg p-2 shadow-sm"
|
|
599
|
-
>
|
|
600
|
-
<p class="text-[10px] text-gray-400">Database info will appear here.</p>
|
|
601
|
-
</div>
|
|
602
|
-
</section>
|
|
603
|
-
</div>
|
|
604
|
-
|
|
605
|
-
<!-- Right: Token Usage + Metrics + Cron -->
|
|
606
|
-
<div class="space-y-3">
|
|
607
|
-
<!-- Token Usage -->
|
|
608
|
-
<section>
|
|
609
|
-
<h2 class="text-xs font-semibold text-gray-700 mb-1.5">Token Budget</h2>
|
|
610
|
-
<div
|
|
611
|
-
id="settings-system-tokens"
|
|
612
|
-
class="bg-white border border-gray-200 rounded-lg p-2 shadow-sm"
|
|
613
|
-
>
|
|
614
|
-
<div class="grid grid-cols-2 gap-1.5">
|
|
615
|
-
<div>
|
|
616
|
-
<label class="block text-[9px] text-gray-500 mb-0.5"
|
|
617
|
-
>Daily Limit</label
|
|
618
|
-
>
|
|
619
|
-
<input
|
|
620
|
-
type="number"
|
|
621
|
-
id="settings-token-daily-limit"
|
|
622
|
-
placeholder="1000000"
|
|
623
|
-
class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50"
|
|
624
|
-
/>
|
|
625
|
-
</div>
|
|
626
|
-
<div>
|
|
627
|
-
<label class="block text-[9px] text-gray-500 mb-0.5">Alert ($)</label>
|
|
628
|
-
<input
|
|
629
|
-
type="number"
|
|
630
|
-
id="settings-token-alert-threshold"
|
|
631
|
-
step="0.01"
|
|
632
|
-
placeholder="5.00"
|
|
633
|
-
class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50"
|
|
634
|
-
/>
|
|
635
|
-
</div>
|
|
636
|
-
</div>
|
|
637
|
-
</div>
|
|
638
|
-
</section>
|
|
639
|
-
|
|
640
|
-
<!-- Metrics -->
|
|
641
|
-
<section>
|
|
642
|
-
<h2 class="text-xs font-semibold text-gray-700 mb-1.5">Metrics</h2>
|
|
643
|
-
<div class="bg-white border border-gray-200 rounded-lg p-2 shadow-sm">
|
|
644
|
-
<div class="grid grid-cols-2 gap-1.5">
|
|
645
|
-
<div>
|
|
646
|
-
<label class="block text-[9px] text-gray-500 mb-0.5"
|
|
647
|
-
>Enable metrics</label
|
|
648
|
-
>
|
|
649
|
-
<input
|
|
650
|
-
type="checkbox"
|
|
651
|
-
id="settings-metrics-enabled"
|
|
652
|
-
checked
|
|
653
|
-
class="w-4 h-4 rounded border-gray-300 text-yellow-500 focus:ring-yellow-400"
|
|
654
|
-
/>
|
|
655
|
-
</div>
|
|
656
|
-
<div>
|
|
657
|
-
<label class="block text-[9px] text-gray-500 mb-0.5"
|
|
658
|
-
>Retention (days)</label
|
|
659
|
-
>
|
|
660
|
-
<input
|
|
661
|
-
type="number"
|
|
662
|
-
id="settings-metrics-retention"
|
|
663
|
-
min="1"
|
|
664
|
-
max="90"
|
|
665
|
-
value="7"
|
|
666
|
-
class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50"
|
|
667
|
-
/>
|
|
668
|
-
</div>
|
|
669
|
-
</div>
|
|
670
|
-
</div>
|
|
671
|
-
</section>
|
|
672
|
-
|
|
673
|
-
<!-- Scheduled Jobs -->
|
|
674
|
-
<section>
|
|
675
|
-
<h2 class="text-xs font-semibold text-gray-700 mb-1.5">Scheduled Jobs</h2>
|
|
676
|
-
<div
|
|
677
|
-
id="settings-cron-container"
|
|
678
|
-
class="bg-white border border-gray-200 rounded-lg p-2 shadow-sm mb-1.5"
|
|
679
|
-
>
|
|
680
|
-
<p class="text-[10px] text-gray-500">Loading...</p>
|
|
681
|
-
</div>
|
|
682
|
-
<details class="bg-white border border-gray-200 rounded-lg shadow-sm">
|
|
683
|
-
<summary
|
|
684
|
-
class="px-2 py-1.5 text-[10px] font-medium text-gray-600 cursor-pointer hover:bg-gray-50"
|
|
685
|
-
>
|
|
686
|
-
+ Add New Job
|
|
687
|
-
</summary>
|
|
688
|
-
<div
|
|
689
|
-
class="p-2 border-t border-gray-100 space-y-1.5"
|
|
690
|
-
id="settings-cron-form"
|
|
691
|
-
>
|
|
692
|
-
<div class="grid grid-cols-2 gap-1.5">
|
|
693
|
-
<input
|
|
694
|
-
type="text"
|
|
695
|
-
id="settings-cron-name"
|
|
696
|
-
placeholder="Job Name"
|
|
697
|
-
aria-label="Cron job name"
|
|
698
|
-
class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50"
|
|
699
|
-
/>
|
|
700
|
-
<input
|
|
701
|
-
type="text"
|
|
702
|
-
id="settings-cron-expr"
|
|
703
|
-
placeholder="0 * * * *"
|
|
704
|
-
aria-label="Cron expression"
|
|
705
|
-
class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50"
|
|
706
|
-
/>
|
|
707
|
-
</div>
|
|
708
|
-
<textarea
|
|
709
|
-
id="settings-cron-prompt"
|
|
710
|
-
rows="2"
|
|
711
|
-
placeholder="Task prompt..."
|
|
712
|
-
aria-label="Cron job task prompt"
|
|
713
|
-
class="w-full px-1.5 py-0.5 text-[10px] border border-gray-200 rounded bg-gray-50 resize-none"
|
|
714
|
-
></textarea>
|
|
715
|
-
<button
|
|
716
|
-
onclick="settingsModule.addCronJob()"
|
|
717
|
-
class="w-full px-2 py-1 text-[10px] font-medium bg-yellow-400 hover:bg-yellow-300 rounded"
|
|
718
|
-
>
|
|
719
|
-
Add
|
|
720
|
-
</button>
|
|
721
|
-
</div>
|
|
722
|
-
</details>
|
|
723
|
-
</section>
|
|
724
|
-
</div>
|
|
725
|
-
</div>
|
|
726
|
-
</div>
|
|
727
|
-
</div>
|
|
728
|
-
|
|
729
|
-
<!-- Action Buttons -->
|
|
730
|
-
<div class="flex items-center justify-between mt-3 pt-3 border-t border-gray-200">
|
|
731
|
-
<button
|
|
732
|
-
onclick="settingsModule.resetForm()"
|
|
733
|
-
class="px-3 py-1.5 text-xs font-medium text-gray-600 bg-gray-100 hover:bg-gray-200 rounded transition-colors"
|
|
734
|
-
>
|
|
735
|
-
Reset
|
|
736
|
-
</button>
|
|
737
|
-
<div class="flex items-center gap-2">
|
|
738
|
-
<span id="settings-status" class="text-[10px] text-gray-500"></span>
|
|
739
|
-
<button
|
|
740
|
-
onclick="settingsModule.saveAndRestart()"
|
|
741
|
-
class="px-4 py-1.5 text-xs font-medium text-black bg-yellow-400 hover:bg-yellow-300 rounded transition-colors flex items-center gap-1.5"
|
|
742
|
-
>
|
|
743
|
-
<svg
|
|
744
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
745
|
-
width="12"
|
|
746
|
-
height="12"
|
|
747
|
-
viewBox="0 0 24 24"
|
|
748
|
-
fill="none"
|
|
749
|
-
stroke="currentColor"
|
|
750
|
-
stroke-width="2"
|
|
751
|
-
>
|
|
752
|
-
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
|
|
753
|
-
<polyline points="17 21 17 13 7 13 7 21" />
|
|
754
|
-
<polyline points="7 3 7 8 15 8" />
|
|
755
|
-
</svg>
|
|
756
|
-
Save & Restart
|
|
757
|
-
</button>
|
|
758
|
-
</div>
|
|
759
|
-
</div>
|
|
760
|
-
</div>
|
|
761
|
-
</div>
|
|
762
|
-
|
|
763
435
|
<div class="tab-content" id="tab-memory">
|
|
764
436
|
<div class="flex-1 flex flex-col min-h-0">
|
|
765
437
|
<!-- Compact Filter Bar (full width, top) -->
|
|
766
|
-
<div
|
|
438
|
+
<div
|
|
439
|
+
class="memory-controls flex items-center gap-2 px-3 py-2 border-b border-gray-200 bg-white"
|
|
440
|
+
>
|
|
767
441
|
<!-- Toggle sidebar -->
|
|
768
442
|
<button
|
|
769
443
|
id="checkpoint-sidebar-toggle"
|
|
@@ -814,7 +488,6 @@
|
|
|
814
488
|
id="topic-filter"
|
|
815
489
|
placeholder="Filter topics..."
|
|
816
490
|
oninput="filterByTopic(this.value)"
|
|
817
|
-
style="width: 160px"
|
|
818
491
|
/>
|
|
819
492
|
<datalist id="topic-filter-list"></datalist>
|
|
820
493
|
<select
|
|
@@ -836,7 +509,6 @@
|
|
|
836
509
|
id="search-input"
|
|
837
510
|
placeholder="Search content..."
|
|
838
511
|
onkeyup="handleGraphSearch(event)"
|
|
839
|
-
style="width: 160px"
|
|
840
512
|
/>
|
|
841
513
|
<button
|
|
842
514
|
onclick="clearFilters()"
|
|
@@ -1199,355 +871,27 @@
|
|
|
1199
871
|
></div>
|
|
1200
872
|
</div>
|
|
1201
873
|
</main>
|
|
1202
|
-
|
|
1203
|
-
<!-- Chat Resize Handle (SmartStore pattern) -->
|
|
1204
|
-
<div
|
|
1205
|
-
id="chat-resize-handle-bar"
|
|
1206
|
-
class="w-1 flex-shrink-0 cursor-col-resize bg-transparent hover:bg-mama-yellow/30 active:bg-mama-yellow/50 transition-colors chat-resize-visible"
|
|
1207
|
-
></div>
|
|
1208
|
-
|
|
1209
|
-
<!-- Right Chat Panel (always visible, SmartStore Layout pattern) -->
|
|
1210
|
-
<div
|
|
1211
|
-
id="chat-panel-wrapper"
|
|
1212
|
-
class="flex-shrink-0 border-l border-gray-200"
|
|
1213
|
-
style="width: 380px; background: #f5f3ef"
|
|
1214
|
-
>
|
|
1215
|
-
<div id="chat-panel" class="flex flex-col">
|
|
1216
|
-
<!-- Header (SmartStore ChatPanel pattern) -->
|
|
1217
|
-
<div
|
|
1218
|
-
id="chat-header"
|
|
1219
|
-
class="px-4 py-3 border-b border-gray-200 flex items-center gap-2.5 bg-white flex-shrink-0"
|
|
1220
|
-
>
|
|
1221
|
-
<div
|
|
1222
|
-
class="w-7 h-7 rounded-lg flex items-center justify-center flex-shrink-0"
|
|
1223
|
-
style="background: #ffce00"
|
|
1224
|
-
>
|
|
1225
|
-
<svg
|
|
1226
|
-
class="w-4 h-4 text-white"
|
|
1227
|
-
fill="none"
|
|
1228
|
-
stroke="currentColor"
|
|
1229
|
-
stroke-width="2"
|
|
1230
|
-
viewBox="0 0 24 24"
|
|
1231
|
-
>
|
|
1232
|
-
<path
|
|
1233
|
-
stroke-linecap="round"
|
|
1234
|
-
stroke-linejoin="round"
|
|
1235
|
-
d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z"
|
|
1236
|
-
/>
|
|
1237
|
-
</svg>
|
|
1238
|
-
</div>
|
|
1239
|
-
<div class="flex-1 min-w-0">
|
|
1240
|
-
<div class="flex items-center gap-1.5">
|
|
1241
|
-
<span class="text-[13px] font-semibold text-mama-black">MAMA Chat</span>
|
|
1242
|
-
<span
|
|
1243
|
-
id="chat-tab-indicator"
|
|
1244
|
-
class="text-[10px] bg-gray-100 text-gray-500 px-1.5 py-0.5 rounded-md font-medium"
|
|
1245
|
-
>Feed</span
|
|
1246
|
-
>
|
|
1247
|
-
<span
|
|
1248
|
-
class="status-indicator w-1.5 h-1.5 rounded-full bg-gray-300 flex-shrink-0"
|
|
1249
|
-
id="chat-status"
|
|
1250
|
-
></span>
|
|
1251
|
-
</div>
|
|
1252
|
-
</div>
|
|
1253
|
-
<div class="flex items-center gap-1 flex-shrink-0">
|
|
1254
|
-
<button
|
|
1255
|
-
class="w-7 h-7 rounded-lg flex items-center justify-center text-gray-400 hover:text-red-500 hover:bg-red-50 transition-colors"
|
|
1256
|
-
id="chat-clear-btn"
|
|
1257
|
-
onclick="clearChatHistory()"
|
|
1258
|
-
title="Clear chat"
|
|
1259
|
-
style="display: none"
|
|
1260
|
-
>
|
|
1261
|
-
<svg
|
|
1262
|
-
class="w-3.5 h-3.5"
|
|
1263
|
-
fill="none"
|
|
1264
|
-
stroke="currentColor"
|
|
1265
|
-
stroke-width="1.5"
|
|
1266
|
-
viewBox="0 0 24 24"
|
|
1267
|
-
>
|
|
1268
|
-
<path
|
|
1269
|
-
stroke-linecap="round"
|
|
1270
|
-
stroke-linejoin="round"
|
|
1271
|
-
d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"
|
|
1272
|
-
/>
|
|
1273
|
-
</svg>
|
|
1274
|
-
</button>
|
|
1275
|
-
<button
|
|
1276
|
-
id="chat-collapse-btn"
|
|
1277
|
-
class="w-7 h-7 rounded-lg flex items-center justify-center text-gray-400 hover:text-mama-black hover:bg-gray-100 transition-colors"
|
|
1278
|
-
title="Collapse chat"
|
|
1279
|
-
onclick="toggleChatPanel()"
|
|
1280
|
-
>
|
|
1281
|
-
<svg
|
|
1282
|
-
class="w-4 h-4"
|
|
1283
|
-
fill="none"
|
|
1284
|
-
stroke="currentColor"
|
|
1285
|
-
stroke-width="2"
|
|
1286
|
-
viewBox="0 0 24 24"
|
|
1287
|
-
>
|
|
1288
|
-
<path stroke-linecap="round" stroke-linejoin="round" d="M9 5l7 7-7 7" />
|
|
1289
|
-
</svg>
|
|
1290
|
-
</button>
|
|
1291
|
-
</div>
|
|
1292
|
-
</div>
|
|
1293
|
-
|
|
1294
|
-
<!-- Messages (SmartStore pattern: flex-1 overflow-auto) -->
|
|
1295
|
-
<div
|
|
1296
|
-
class="flex-1 overflow-auto p-4 space-y-3"
|
|
1297
|
-
id="chat-messages"
|
|
1298
|
-
style="background: #f5f3ef"
|
|
1299
|
-
>
|
|
1300
|
-
<div class="flex flex-col items-center justify-center h-full text-center px-6">
|
|
1301
|
-
<div
|
|
1302
|
-
class="w-12 h-12 rounded-lg bg-mama-yellow/10 flex items-center justify-center mb-4"
|
|
1303
|
-
>
|
|
1304
|
-
<svg
|
|
1305
|
-
class="w-6 h-6 text-mama-yellow"
|
|
1306
|
-
fill="none"
|
|
1307
|
-
stroke="currentColor"
|
|
1308
|
-
stroke-width="1.5"
|
|
1309
|
-
viewBox="0 0 24 24"
|
|
1310
|
-
>
|
|
1311
|
-
<path
|
|
1312
|
-
stroke-linecap="round"
|
|
1313
|
-
stroke-linejoin="round"
|
|
1314
|
-
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z"
|
|
1315
|
-
/>
|
|
1316
|
-
</svg>
|
|
1317
|
-
</div>
|
|
1318
|
-
<p class="text-[13px] text-gray-500 font-medium">Ask MAMA anything</p>
|
|
1319
|
-
<p class="text-[11px] text-gray-400 mt-1">
|
|
1320
|
-
Current page context is sent automatically
|
|
1321
|
-
</p>
|
|
1322
|
-
</div>
|
|
1323
|
-
</div>
|
|
1324
|
-
|
|
1325
|
-
<!-- Attachment preview -->
|
|
1326
|
-
<div
|
|
1327
|
-
id="chat-attachment-preview"
|
|
1328
|
-
class="hidden px-3 pt-2 border-t border-gray-200 flex items-center gap-2 flex-wrap flex-shrink-0"
|
|
1329
|
-
>
|
|
1330
|
-
<img
|
|
1331
|
-
id="chat-attachment-thumb"
|
|
1332
|
-
class="w-12 h-12 object-cover rounded-lg border border-gray-200"
|
|
1333
|
-
src="data:,"
|
|
1334
|
-
alt="preview"
|
|
1335
|
-
/>
|
|
1336
|
-
<span
|
|
1337
|
-
id="chat-attachment-name"
|
|
1338
|
-
class="text-[11px] text-gray-400 truncate max-w-[100px]"
|
|
1339
|
-
></span>
|
|
1340
|
-
<button
|
|
1341
|
-
class="text-gray-400 hover:text-red-500"
|
|
1342
|
-
onclick="clearAttachment()"
|
|
1343
|
-
title="Remove"
|
|
1344
|
-
>
|
|
1345
|
-
<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
1346
|
-
<line x1="18" y1="6" x2="6" y2="18" stroke-width="2" stroke-linecap="round" />
|
|
1347
|
-
<line x1="6" y1="6" x2="18" y2="18" stroke-width="2" stroke-linecap="round" />
|
|
1348
|
-
</svg>
|
|
1349
|
-
</button>
|
|
1350
|
-
</div>
|
|
1351
|
-
|
|
1352
|
-
<!-- Input (SmartStore ChatPanel pattern: inline row) -->
|
|
1353
|
-
<div class="p-3 border-t border-gray-200 bg-white flex-shrink-0">
|
|
1354
|
-
<input
|
|
1355
|
-
type="file"
|
|
1356
|
-
id="chat-file-input"
|
|
1357
|
-
class="hidden"
|
|
1358
|
-
accept="image/*,.pdf,.doc,.docx,.txt,.csv,.xls,.xlsx,.md,.json,.html,.htm,.xml,.zip,.gz,.ppt,.pptx"
|
|
1359
|
-
onchange="handleFileSelect(event)"
|
|
1360
|
-
/>
|
|
1361
|
-
<div class="flex gap-2">
|
|
1362
|
-
<button
|
|
1363
|
-
class="w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 bg-gray-100 text-gray-400 hover:text-mama-black hover:bg-gray-200 transition-all"
|
|
1364
|
-
id="chat-attach"
|
|
1365
|
-
onclick="document.getElementById('chat-file-input').click()"
|
|
1366
|
-
title="Attach file"
|
|
1367
|
-
>
|
|
1368
|
-
<svg
|
|
1369
|
-
class="w-4 h-4"
|
|
1370
|
-
fill="none"
|
|
1371
|
-
stroke="currentColor"
|
|
1372
|
-
stroke-width="1.5"
|
|
1373
|
-
viewBox="0 0 24 24"
|
|
1374
|
-
>
|
|
1375
|
-
<path
|
|
1376
|
-
stroke-linecap="round"
|
|
1377
|
-
stroke-linejoin="round"
|
|
1378
|
-
d="M18.375 12.739l-7.693 7.693a4.5 4.5 0 01-6.364-6.364l10.94-10.94A3 3 0 1119.5 7.372L8.552 18.32m.009-.01l-.01.01m5.699-9.941l-7.81 7.81a1.5 1.5 0 002.112 2.13"
|
|
1379
|
-
/>
|
|
1380
|
-
</svg>
|
|
1381
|
-
</button>
|
|
1382
|
-
<button
|
|
1383
|
-
class="w-9 h-9 rounded-lg flex items-center justify-center flex-shrink-0 bg-gray-100 text-gray-400 hover:text-mama-black hover:bg-gray-200 transition-all relative"
|
|
1384
|
-
id="chat-mic"
|
|
1385
|
-
onclick="toggleVoiceInput()"
|
|
1386
|
-
title="Voice input"
|
|
1387
|
-
>
|
|
1388
|
-
<svg
|
|
1389
|
-
class="w-4 h-4"
|
|
1390
|
-
fill="none"
|
|
1391
|
-
stroke="currentColor"
|
|
1392
|
-
stroke-width="1.5"
|
|
1393
|
-
viewBox="0 0 24 24"
|
|
1394
|
-
>
|
|
1395
|
-
<path
|
|
1396
|
-
stroke-linecap="round"
|
|
1397
|
-
stroke-linejoin="round"
|
|
1398
|
-
d="M12 18.75a6 6 0 006-6v-1.5m-6 7.5a6 6 0 01-6-6v-1.5m6 7.5v3.75m-3.75 0h7.5M12 15.75a3 3 0 01-3-3V4.5a3 3 0 116 0v8.25a3 3 0 01-3 3z"
|
|
1399
|
-
/>
|
|
1400
|
-
</svg>
|
|
1401
|
-
<span
|
|
1402
|
-
class="recording-dot absolute top-0 right-0 w-2 h-2 bg-red-500 rounded-full hidden"
|
|
1403
|
-
></span>
|
|
1404
|
-
</button>
|
|
1405
|
-
<input
|
|
1406
|
-
type="text"
|
|
1407
|
-
id="chat-input"
|
|
1408
|
-
placeholder="Type your message..."
|
|
1409
|
-
class="flex-1 bg-gray-100 border-0 rounded-lg px-3.5 py-2 text-[13px] text-mama-black placeholder:text-gray-400 focus:outline-none focus:ring-2 focus:ring-mama-yellow/20 transition-all disabled:opacity-50"
|
|
1410
|
-
disabled
|
|
1411
|
-
/>
|
|
1412
|
-
<button
|
|
1413
|
-
id="chat-send"
|
|
1414
|
-
onclick="sendChatMessage()"
|
|
1415
|
-
disabled
|
|
1416
|
-
class="w-9 h-9 rounded-lg bg-mama-yellow text-mama-black flex items-center justify-center flex-shrink-0 hover:bg-mama-yellow-hover disabled:opacity-40 transition-all"
|
|
1417
|
-
>
|
|
1418
|
-
<svg
|
|
1419
|
-
class="w-4 h-4"
|
|
1420
|
-
fill="none"
|
|
1421
|
-
stroke="currentColor"
|
|
1422
|
-
stroke-width="2"
|
|
1423
|
-
viewBox="0 0 24 24"
|
|
1424
|
-
>
|
|
1425
|
-
<path
|
|
1426
|
-
stroke-linecap="round"
|
|
1427
|
-
stroke-linejoin="round"
|
|
1428
|
-
d="M6 12L3.269 3.126A59.768 59.768 0 0121.485 12 59.77 59.77 0 013.27 20.876L5.999 12zm0 0h7.5"
|
|
1429
|
-
/>
|
|
1430
|
-
</svg>
|
|
1431
|
-
</button>
|
|
1432
|
-
</div>
|
|
1433
|
-
</div>
|
|
1434
|
-
</div>
|
|
1435
|
-
</div>
|
|
1436
|
-
|
|
1437
|
-
<!-- Chat Collapsed Bar (visible when chat is collapsed) -->
|
|
1438
|
-
<div
|
|
1439
|
-
id="chat-collapsed-bar"
|
|
1440
|
-
class="hidden flex-shrink-0 border-l border-gray-200 bg-white flex flex-col items-center py-3 w-10"
|
|
1441
|
-
>
|
|
1442
|
-
<button
|
|
1443
|
-
onclick="toggleChatPanel()"
|
|
1444
|
-
class="w-8 h-8 rounded-lg flex items-center justify-center text-gray-400 hover:text-mama-yellow hover:bg-mama-yellow/10 transition-colors"
|
|
1445
|
-
title="Open chat"
|
|
1446
|
-
>
|
|
1447
|
-
<svg
|
|
1448
|
-
class="w-4 h-4"
|
|
1449
|
-
fill="none"
|
|
1450
|
-
stroke="currentColor"
|
|
1451
|
-
strokeWidth="1.5"
|
|
1452
|
-
viewBox="0 0 24 24"
|
|
1453
|
-
>
|
|
1454
|
-
<path
|
|
1455
|
-
d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"
|
|
1456
|
-
stroke-width="2"
|
|
1457
|
-
stroke-linecap="round"
|
|
1458
|
-
stroke-linejoin="round"
|
|
1459
|
-
/>
|
|
1460
|
-
</svg>
|
|
1461
|
-
</button>
|
|
1462
|
-
<span
|
|
1463
|
-
id="chat-badge-collapsed"
|
|
1464
|
-
class="hidden w-2 h-2 bg-red-500 rounded-full mt-1 animate-pulse"
|
|
1465
|
-
></span>
|
|
1466
|
-
</div>
|
|
1467
874
|
</div>
|
|
1468
875
|
<!-- /content-area -->
|
|
1469
876
|
|
|
1470
|
-
<!-- Mobile
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
<
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
<button
|
|
1481
|
-
class="mama-mobile-tab"
|
|
1482
|
-
data-tab="agents"
|
|
1483
|
-
onclick="window.switchTab && window.switchTab('agents')"
|
|
1484
|
-
>
|
|
1485
|
-
<i data-lucide="bot"></i>
|
|
1486
|
-
<span>Agents</span>
|
|
1487
|
-
</button>
|
|
1488
|
-
<button
|
|
1489
|
-
class="mama-mobile-tab"
|
|
1490
|
-
data-tab="memory"
|
|
1491
|
-
onclick="window.switchTab && window.switchTab('memory')"
|
|
1492
|
-
>
|
|
877
|
+
<!-- Mobile group bar. The rail is hidden under 768px, so this is the only
|
|
878
|
+
way to cross groups on a phone; the chip row above the content stays
|
|
879
|
+
as sub-navigation WITHIN the active group. Three groups, no overflow
|
|
880
|
+
menu: there is no fourth item to hide behind one. -->
|
|
881
|
+
<nav id="mama-mobile-tabs" aria-label="Groups">
|
|
882
|
+
<a class="mama-mobile-tab" data-group="operator" href="#operator/board">
|
|
883
|
+
<i data-lucide="zap"></i>
|
|
884
|
+
<span>Operator</span>
|
|
885
|
+
</a>
|
|
886
|
+
<a class="mama-mobile-tab" data-group="knowledge" href="#knowledge/memory">
|
|
1493
887
|
<i data-lucide="brain"></i>
|
|
1494
|
-
<span>
|
|
1495
|
-
</
|
|
1496
|
-
<
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
<i data-lucide="menu"></i>
|
|
1502
|
-
<span>More</span>
|
|
1503
|
-
</button>
|
|
1504
|
-
</div>
|
|
1505
|
-
<!-- Mobile "More" overflow menu -->
|
|
1506
|
-
<div
|
|
1507
|
-
id="mama-mobile-more"
|
|
1508
|
-
style="
|
|
1509
|
-
display: none;
|
|
1510
|
-
position: fixed;
|
|
1511
|
-
bottom: 64px;
|
|
1512
|
-
left: 0;
|
|
1513
|
-
right: 0;
|
|
1514
|
-
background: #fff;
|
|
1515
|
-
border-top: 1px solid #ede9e1;
|
|
1516
|
-
z-index: 99;
|
|
1517
|
-
padding: 8px;
|
|
1518
|
-
"
|
|
1519
|
-
>
|
|
1520
|
-
<div style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 4px">
|
|
1521
|
-
<button
|
|
1522
|
-
class="mama-mobile-tab"
|
|
1523
|
-
data-tab="feed"
|
|
1524
|
-
onclick="window.switchTab && window.switchTab('feed')"
|
|
1525
|
-
>
|
|
1526
|
-
<i data-lucide="rss"></i><span>Feed</span>
|
|
1527
|
-
</button>
|
|
1528
|
-
<button
|
|
1529
|
-
class="mama-mobile-tab"
|
|
1530
|
-
data-tab="wiki"
|
|
1531
|
-
onclick="window.switchTab && window.switchTab('wiki')"
|
|
1532
|
-
>
|
|
1533
|
-
<i data-lucide="book-open"></i><span>Wiki</span>
|
|
1534
|
-
</button>
|
|
1535
|
-
<button
|
|
1536
|
-
class="mama-mobile-tab"
|
|
1537
|
-
data-tab="logs"
|
|
1538
|
-
onclick="window.switchTab && window.switchTab('logs')"
|
|
1539
|
-
>
|
|
1540
|
-
<i data-lucide="scroll-text"></i><span>Logs</span>
|
|
1541
|
-
</button>
|
|
1542
|
-
<button
|
|
1543
|
-
class="mama-mobile-tab"
|
|
1544
|
-
data-tab="settings"
|
|
1545
|
-
onclick="window.switchTab && window.switchTab('settings')"
|
|
1546
|
-
>
|
|
1547
|
-
<i data-lucide="settings"></i><span>Settings</span>
|
|
1548
|
-
</button>
|
|
1549
|
-
</div>
|
|
1550
|
-
</div>
|
|
888
|
+
<span>Knowledge</span>
|
|
889
|
+
</a>
|
|
890
|
+
<a class="mama-mobile-tab" data-group="system" href="#system/runtime">
|
|
891
|
+
<i data-lucide="settings"></i>
|
|
892
|
+
<span>System</span>
|
|
893
|
+
</a>
|
|
894
|
+
</nav>
|
|
1551
895
|
|
|
1552
896
|
<div
|
|
1553
897
|
class="modal-overlay fixed inset-0 bg-black/50 backdrop-blur-sm z-50 flex items-center justify-center"
|
|
@@ -1914,201 +1258,235 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
|
|
|
1914
1258
|
</div>
|
|
1915
1259
|
|
|
1916
1260
|
<script type="module">
|
|
1917
|
-
import { ChatModule } from '/viewer/js/modules/chat.js';
|
|
1918
1261
|
import { GraphModule } from '/viewer/js/modules/graph.js';
|
|
1919
1262
|
import { MemoryModule } from '/viewer/js/modules/memory.js';
|
|
1920
|
-
import { DashboardModule } from '/viewer/js/modules/dashboard.js';
|
|
1921
|
-
import { SettingsModule } from '/viewer/js/modules/settings.js';
|
|
1922
|
-
import { ConnectorFeedModule } from '/viewer/js/modules/connector-feed.js';
|
|
1923
1263
|
import { WikiModule } from '/viewer/js/modules/wiki.js';
|
|
1924
|
-
import {
|
|
1264
|
+
import { renderRuntimeView, renderConnectorsView } from '/viewer/js/modules/system.js';
|
|
1925
1265
|
import { API } from '/viewer/js/utils/api.js';
|
|
1926
1266
|
import { startUICommandPolling, reportPageContext } from '/viewer/js/utils/ui-commands.js';
|
|
1927
1267
|
|
|
1928
1268
|
const memory = new MemoryModule();
|
|
1929
|
-
const chat = new ChatModule(memory);
|
|
1930
1269
|
const graph = new GraphModule();
|
|
1931
|
-
const dashboard = new DashboardModule();
|
|
1932
|
-
const settings = new SettingsModule();
|
|
1933
|
-
const connectorFeed = new ConnectorFeedModule();
|
|
1934
1270
|
const wiki = new WikiModule();
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1271
|
+
|
|
1272
|
+
// Exposed for inline handlers and debugging.
|
|
1273
|
+
window.graphModule = graph;
|
|
1274
|
+
window.memoryModule = memory;
|
|
1275
|
+
|
|
1276
|
+
// ── Routing ─────────────────────────────────────────────────────────────
|
|
1277
|
+
// The hash is the single source of truth: every route is `<group>/<view>`,
|
|
1278
|
+
// with no per-tab implicit defaults. Nav entries are plain hash links, so
|
|
1279
|
+
// back/forward and deep links work without any pushState of our own.
|
|
1280
|
+
const ROUTES = {
|
|
1281
|
+
'operator/board': { group: 'operator', view: 'board', region: 'tab-operator' },
|
|
1282
|
+
'operator/tasks': { group: 'operator', view: 'tasks', region: 'tab-operator' },
|
|
1283
|
+
'operator/triggers': { group: 'operator', view: 'triggers', region: 'tab-operator' },
|
|
1284
|
+
'knowledge/memory': { group: 'knowledge', view: 'memory', region: 'tab-memory' },
|
|
1285
|
+
'knowledge/wiki': { group: 'knowledge', view: 'wiki', region: 'tab-wiki' },
|
|
1286
|
+
'system/runtime': { group: 'system', view: 'runtime', region: 'tab-runtime' },
|
|
1287
|
+
'system/connectors': { group: 'system', view: 'connectors', region: 'tab-connectors' },
|
|
1288
|
+
'system/logs': { group: 'system', view: 'logs', region: 'tab-logs' },
|
|
1289
|
+
};
|
|
1290
|
+
const DEFAULT_ROUTE = 'operator/board';
|
|
1291
|
+
|
|
1292
|
+
// Bare view names an agent navigate command or an old bookmark may carry.
|
|
1293
|
+
// Anything else falls back to the default rather than showing nothing.
|
|
1294
|
+
const ROUTE_ALIASES = {
|
|
1295
|
+
board: 'operator/board',
|
|
1296
|
+
tasks: 'operator/tasks',
|
|
1297
|
+
triggers: 'operator/triggers',
|
|
1298
|
+
memory: 'knowledge/memory',
|
|
1299
|
+
wiki: 'knowledge/wiki',
|
|
1300
|
+
runtime: 'system/runtime',
|
|
1301
|
+
connectors: 'system/connectors',
|
|
1302
|
+
logs: 'system/logs',
|
|
1303
|
+
};
|
|
1304
|
+
|
|
1305
|
+
function parseViewerHash() {
|
|
1306
|
+
const raw = String(window.location.hash || '').replace(/^#/, '');
|
|
1307
|
+
const [rawPath, query = ''] = raw.split('?');
|
|
1308
|
+
// Aliases must resolve here too, or a bookmarked '#logs' would land on
|
|
1309
|
+
// the default route while a navigate command for it works.
|
|
1310
|
+
const path = normalizeRoutePath(rawPath);
|
|
1311
|
+
const route = ROUTES[path];
|
|
1312
|
+
return {
|
|
1313
|
+
path,
|
|
1314
|
+
group: route.group,
|
|
1315
|
+
view: route.view,
|
|
1316
|
+
params: new URLSearchParams(query),
|
|
1317
|
+
};
|
|
1960
1318
|
}
|
|
1961
|
-
window.toggleChatPanel = toggleChatPanel;
|
|
1962
1319
|
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1320
|
+
function writeViewerHash(path, params) {
|
|
1321
|
+
const query = params ? params.toString() : '';
|
|
1322
|
+
const next = '#' + path + (query ? '?' + query : '');
|
|
1323
|
+
if (window.location.hash !== next) {
|
|
1324
|
+
window.location.hash = next;
|
|
1325
|
+
}
|
|
1966
1326
|
}
|
|
1967
1327
|
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1328
|
+
function normalizeRoutePath(target) {
|
|
1329
|
+
const raw = String(target || '')
|
|
1330
|
+
.replace(/^#/, '')
|
|
1331
|
+
.trim();
|
|
1332
|
+
if (ROUTES[raw]) return raw;
|
|
1333
|
+
const alias = ROUTE_ALIASES[raw];
|
|
1334
|
+
return alias && ROUTES[alias] ? alias : DEFAULT_ROUTE;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function routeKey(path, params) {
|
|
1338
|
+
const query = params ? params.toString() : '';
|
|
1339
|
+
return query ? path + '?' + query : path;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// Hash selection params are `task` and `trigger` (the cross-surface
|
|
1343
|
+
// contract); the mount options keep the bundle's own taskId/triggerId
|
|
1344
|
+
// field names.
|
|
1345
|
+
function paramsToSelection(params) {
|
|
1346
|
+
const selection = {};
|
|
1347
|
+
const rawTask = params.get('task');
|
|
1348
|
+
const trigger = params.get('trigger');
|
|
1349
|
+
if (rawTask !== null && rawTask.trim() !== '') {
|
|
1350
|
+
// A task id is a positive integer. '-1', '0', '3.5', '1e3' and 'abc'
|
|
1351
|
+
// are not ids, and silently coercing them would open a drawer on
|
|
1352
|
+
// whatever row happened to answer.
|
|
1353
|
+
const taskId = Number(rawTask);
|
|
1354
|
+
if (Number.isInteger(taskId) && taskId > 0) {
|
|
1355
|
+
selection.taskId = taskId;
|
|
1356
|
+
}
|
|
1975
1357
|
}
|
|
1358
|
+
if (trigger) {
|
|
1359
|
+
selection.triggerId = trigger;
|
|
1360
|
+
}
|
|
1361
|
+
return Object.keys(selection).length > 0 ? selection : undefined;
|
|
1976
1362
|
}
|
|
1977
1363
|
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1364
|
+
function selectionToParams(selection) {
|
|
1365
|
+
const params = new URLSearchParams();
|
|
1366
|
+
if (selection && selection.taskId !== undefined && selection.taskId !== null) {
|
|
1367
|
+
params.set('task', String(selection.taskId));
|
|
1368
|
+
}
|
|
1369
|
+
if (selection && selection.triggerId) {
|
|
1370
|
+
params.set('trigger', selection.triggerId);
|
|
1371
|
+
}
|
|
1372
|
+
return params;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
function selectedItemFor(selection) {
|
|
1376
|
+
if (!selection) return undefined;
|
|
1377
|
+
if (selection.taskId !== undefined) return { type: 'task', id: String(selection.taskId) };
|
|
1378
|
+
if (selection.triggerId) return { type: 'trigger', id: selection.triggerId };
|
|
1379
|
+
return undefined;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// ── Operator content bundle ─────────────────────────────────────────────
|
|
1383
|
+
// Mounted lazily on the first Operator route and then kept for the page
|
|
1384
|
+
// lifetime: unmounting would drop the report SSE stream and the query
|
|
1385
|
+
// cache and force a cold refetch every time the user comes back.
|
|
1386
|
+
let operatorHandle = null;
|
|
1387
|
+
let operatorMountPromise = null;
|
|
1388
|
+
|
|
1389
|
+
function renderOperatorLoadError(root, error) {
|
|
1390
|
+
root.innerHTML =
|
|
1391
|
+
'<div id="operator-load-error" class="m-6 max-w-md rounded-lg border border-gray-200 bg-white p-4 shadow-sm">' +
|
|
1392
|
+
'<p class="text-sm font-semibold text-mama-black">Operator failed to load</p>' +
|
|
1393
|
+
'<p class="mt-1 text-xs text-gray-500" data-detail></p>' +
|
|
1394
|
+
'<button type="button" class="mt-3 rounded bg-mama-yellow px-3 py-1.5 text-xs font-medium text-mama-black" data-reload>Reload</button>' +
|
|
1395
|
+
'</div>';
|
|
1396
|
+
const detail = root.querySelector('[data-detail]');
|
|
1397
|
+
if (detail) {
|
|
1398
|
+
detail.textContent = String((error && error.message) || error || 'Unknown error');
|
|
1399
|
+
}
|
|
1400
|
+
const reload = root.querySelector('[data-reload]');
|
|
1401
|
+
if (reload) {
|
|
1402
|
+
reload.addEventListener('click', () => window.location.reload());
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1983
1405
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
const startWidth = wrapper.offsetWidth;
|
|
1988
|
-
const onMove = (ev) => {
|
|
1989
|
-
const delta = startX - ev.clientX;
|
|
1990
|
-
const w = Math.max(MIN_CHAT, Math.min(MAX_CHAT, startWidth + delta));
|
|
1991
|
-
wrapper.style.width = w + 'px';
|
|
1992
|
-
};
|
|
1993
|
-
const onUp = () => {
|
|
1994
|
-
document.removeEventListener('mousemove', onMove);
|
|
1995
|
-
document.removeEventListener('mouseup', onUp);
|
|
1996
|
-
document.body.style.cursor = '';
|
|
1997
|
-
document.body.style.userSelect = '';
|
|
1998
|
-
localStorage.setItem(CHAT_WIDTH_KEY, String(wrapper.offsetWidth));
|
|
1999
|
-
};
|
|
2000
|
-
document.body.style.cursor = 'col-resize';
|
|
2001
|
-
document.body.style.userSelect = 'none';
|
|
2002
|
-
document.addEventListener('mousemove', onMove);
|
|
2003
|
-
document.addEventListener('mouseup', onUp);
|
|
2004
|
-
});
|
|
2005
|
-
})();
|
|
1406
|
+
async function ensureOperator(view, selection) {
|
|
1407
|
+
const operatorRoot = document.getElementById('operator-mount');
|
|
1408
|
+
if (!operatorRoot) return;
|
|
2006
1409
|
|
|
2007
|
-
|
|
2008
|
-
|
|
1410
|
+
if (operatorHandle) {
|
|
1411
|
+
operatorHandle.update(view, selection);
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
2009
1414
|
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
1415
|
+
if (!operatorMountPromise) {
|
|
1416
|
+
operatorMountPromise = import('/viewer/operator/operator.js')
|
|
1417
|
+
.then(({ mountOperator }) => {
|
|
1418
|
+
operatorHandle = mountOperator(operatorRoot, {
|
|
1419
|
+
initialView: view,
|
|
1420
|
+
initialSelection: selection,
|
|
1421
|
+
// In-content navigation is reported back so the address bar
|
|
1422
|
+
// stays truthful; host-driven update() deliberately does not
|
|
1423
|
+
// echo, so there is no loop here.
|
|
1424
|
+
onViewChange: (nextView, nextSelection) => {
|
|
1425
|
+
switchTab('operator/' + nextView, selectionToParams(nextSelection));
|
|
1426
|
+
},
|
|
1427
|
+
});
|
|
1428
|
+
})
|
|
1429
|
+
.catch((error) => {
|
|
1430
|
+
operatorMountPromise = null;
|
|
1431
|
+
console.error('[MAMA] Operator bundle failed to load:', error);
|
|
1432
|
+
renderOperatorLoadError(operatorRoot, error);
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
2016
1435
|
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
1436
|
+
await operatorMountPromise;
|
|
1437
|
+
if (operatorHandle) {
|
|
1438
|
+
operatorHandle.update(view, selection);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
2021
1441
|
|
|
2022
1442
|
const STATE = {
|
|
2023
|
-
|
|
2024
|
-
|
|
1443
|
+
route: DEFAULT_ROUTE,
|
|
1444
|
+
routeKey: null,
|
|
1445
|
+
theme: 'light',
|
|
2025
1446
|
graphLoaded: false,
|
|
2026
1447
|
};
|
|
2027
1448
|
|
|
2028
|
-
async function
|
|
2029
|
-
STATE.
|
|
1449
|
+
async function applyRoute(route) {
|
|
1450
|
+
STATE.route = route.path;
|
|
1451
|
+
STATE.routeKey = routeKey(route.path, route.params);
|
|
2030
1452
|
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
if (moreMenu) moreMenu.style.display = 'none';
|
|
2034
|
-
|
|
2035
|
-
// Also highlight the source tab in More menu if it was selected from there
|
|
2036
|
-
document.querySelectorAll('#mama-mobile-more .mama-mobile-tab').forEach((btn) => {
|
|
2037
|
-
btn.classList.toggle('mama-nav-active', btn.dataset.tab === tabName);
|
|
1453
|
+
document.querySelectorAll('[data-route]').forEach((el) => {
|
|
1454
|
+
el.classList.toggle('mama-nav-active', el.dataset.route === route.path);
|
|
2038
1455
|
});
|
|
2039
|
-
|
|
2040
|
-
//
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
tabIndicator.textContent = tabName.charAt(0).toUpperCase() + tabName.slice(1);
|
|
2044
|
-
}
|
|
2045
|
-
|
|
2046
|
-
// Update sidebar nav items
|
|
2047
|
-
document.querySelectorAll('#mama-sidebar .mama-nav-item').forEach((btn) => {
|
|
2048
|
-
btn.classList.toggle('mama-nav-active', btn.dataset.tab === tabName);
|
|
2049
|
-
});
|
|
2050
|
-
|
|
2051
|
-
// Update mobile tab bar
|
|
2052
|
-
document.querySelectorAll('#mama-mobile-tabs .mama-mobile-tab').forEach((btn) => {
|
|
2053
|
-
btn.classList.toggle('mama-nav-active', btn.dataset.tab === tabName);
|
|
1456
|
+
// Mobile chips show only the active group's sub-views; the bottom bar
|
|
1457
|
+
// is what crosses groups.
|
|
1458
|
+
document.querySelectorAll('.mama-subview-chip').forEach((chip) => {
|
|
1459
|
+
chip.hidden = String(chip.dataset.route).split('/')[0] !== route.group;
|
|
2054
1460
|
});
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
if (moreBtn) {
|
|
2058
|
-
moreBtn.classList.toggle('mama-nav-active', overflowTabs.includes(tabName));
|
|
2059
|
-
}
|
|
2060
|
-
|
|
2061
|
-
// Legacy: update any remaining [data-tab] buttons outside sidebar/mobile
|
|
2062
|
-
document.querySelectorAll('[data-tab]').forEach((btn) => {
|
|
2063
|
-
if (btn.closest('#mama-sidebar') || btn.closest('#mama-mobile-tabs')) return;
|
|
2064
|
-
const isActive = btn.dataset.tab === tabName;
|
|
2065
|
-
btn.classList.toggle('bg-indigo-100', isActive);
|
|
2066
|
-
btn.classList.toggle('bg-mama-lavender/30', isActive);
|
|
2067
|
-
btn.classList.toggle('text-mama-black', isActive);
|
|
1461
|
+
document.querySelectorAll('#mama-mobile-tabs .mama-mobile-tab').forEach((tab) => {
|
|
1462
|
+
tab.classList.toggle('mama-nav-active', tab.dataset.group === route.group);
|
|
2068
1463
|
});
|
|
2069
1464
|
|
|
1465
|
+
const regionId = ROUTES[route.path].region;
|
|
2070
1466
|
document.querySelectorAll('.tab-content').forEach((content) => {
|
|
2071
|
-
content.classList.toggle('active', content.id ===
|
|
1467
|
+
content.classList.toggle('active', content.id === regionId);
|
|
2072
1468
|
});
|
|
2073
1469
|
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
1470
|
+
const selection = paramsToSelection(route.params);
|
|
1471
|
+
reportPageContext(
|
|
1472
|
+
route.path,
|
|
1473
|
+
{
|
|
1474
|
+
pageType: 'route',
|
|
1475
|
+
group: route.group,
|
|
1476
|
+
view: route.view,
|
|
1477
|
+
params: Object.fromEntries(route.params.entries()),
|
|
1478
|
+
},
|
|
1479
|
+
selectedItemFor(selection)
|
|
1480
|
+
);
|
|
2083
1481
|
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
tabName,
|
|
2089
|
-
{ pageType: 'tab-switch', tab: tabName, ...(params || {}) },
|
|
2090
|
-
selectedItem
|
|
2091
|
-
);
|
|
2092
|
-
};
|
|
1482
|
+
if (route.group === 'operator') {
|
|
1483
|
+
await ensureOperator(route.view, selection);
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
2093
1486
|
|
|
2094
|
-
if (
|
|
2095
|
-
dashboard.init();
|
|
2096
|
-
publishBasicTabContext();
|
|
2097
|
-
} else if (tabName === 'settings') {
|
|
2098
|
-
settings.init();
|
|
2099
|
-
loadConnectorsList();
|
|
2100
|
-
publishBasicTabContext();
|
|
2101
|
-
} else if (tabName === 'memory') {
|
|
2102
|
-
// Initialize sidebar state for current screen size
|
|
1487
|
+
if (route.view === 'memory') {
|
|
2103
1488
|
updateCheckpointSidebarForScreenSize();
|
|
2104
|
-
|
|
2105
|
-
// Load checkpoints
|
|
2106
|
-
loadCheckpoints();
|
|
2107
|
-
|
|
2108
|
-
// Load memory stats for analytics bar
|
|
2109
1489
|
updateMemoryStats();
|
|
2110
|
-
|
|
2111
|
-
// Load graph
|
|
2112
1490
|
if (!STATE.graphLoaded) {
|
|
2113
1491
|
try {
|
|
2114
1492
|
const data = await graph.fetchData();
|
|
@@ -2119,38 +1497,68 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
|
|
|
2119
1497
|
} catch (error) {
|
|
2120
1498
|
console.error('[MAMA] Failed to load graph:', error);
|
|
2121
1499
|
const loadingEl = document.getElementById('graph-loading');
|
|
2122
|
-
if (loadingEl)
|
|
1500
|
+
if (loadingEl) {
|
|
2123
1501
|
loadingEl.innerHTML = '<span class="text-red-500">Failed to load graph data</span>';
|
|
1502
|
+
}
|
|
2124
1503
|
}
|
|
2125
1504
|
}
|
|
2126
|
-
|
|
2127
|
-
}
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
} else if (tabName === 'agents') {
|
|
2131
|
-
agentsModule.init();
|
|
2132
|
-
// Handle deep navigation: params.id opens agent detail, params.tab selects tab
|
|
2133
|
-
if (params?.id) {
|
|
2134
|
-
await agentsModule.navigateTo(params.id, params.tab);
|
|
2135
|
-
} else {
|
|
2136
|
-
agentsModule.showList();
|
|
2137
|
-
}
|
|
2138
|
-
} else if (tabName === 'wiki') {
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
if (route.view === 'wiki') {
|
|
2139
1509
|
wiki.init();
|
|
2140
|
-
await wiki.navigateTo(params
|
|
2141
|
-
|
|
1510
|
+
await wiki.navigateTo(route.params.get('path') || undefined);
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
// System is read-only truth: both views render the one authoritative
|
|
1515
|
+
// runtime snapshot, so the screen can never name a backend, model or
|
|
1516
|
+
// connector state the daemon is not actually running.
|
|
1517
|
+
if (route.view === 'runtime') {
|
|
1518
|
+
await renderRuntimeView(document.getElementById('runtime-content'));
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
if (route.view === 'connectors') {
|
|
1523
|
+
await renderConnectorsView(document.getElementById('connectors-list'));
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
if (route.view === 'logs') {
|
|
2142
1528
|
const iframe = document.getElementById('logs-iframe');
|
|
2143
1529
|
if (iframe && !iframe.getAttribute('src')) {
|
|
2144
1530
|
iframe.src = '/viewer/log-viewer.html';
|
|
2145
1531
|
}
|
|
2146
|
-
publishBasicTabContext();
|
|
2147
|
-
} else if (tabName === 'chat') {
|
|
2148
|
-
publishBasicTabContext();
|
|
2149
|
-
} else {
|
|
2150
|
-
publishBasicTabContext();
|
|
2151
1532
|
}
|
|
2152
1533
|
}
|
|
2153
1534
|
|
|
1535
|
+
/**
|
|
1536
|
+
* Navigate by route path (`operator/tasks`) or bare view name (`tasks`).
|
|
1537
|
+
* Writes the hash first, then applies: the hashchange listener sees the
|
|
1538
|
+
* key it already holds and stays out of the way.
|
|
1539
|
+
*/
|
|
1540
|
+
async function switchTab(target, params) {
|
|
1541
|
+
const path = normalizeRoutePath(target);
|
|
1542
|
+
const search =
|
|
1543
|
+
params instanceof URLSearchParams ? params : new URLSearchParams(params || {});
|
|
1544
|
+
writeViewerHash(path, search);
|
|
1545
|
+
await applyRoute({
|
|
1546
|
+
path,
|
|
1547
|
+
group: ROUTES[path].group,
|
|
1548
|
+
view: ROUTES[path].view,
|
|
1549
|
+
params: search,
|
|
1550
|
+
});
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
window.addEventListener('hashchange', () => {
|
|
1554
|
+
const route = parseViewerHash();
|
|
1555
|
+
if (routeKey(route.path, route.params) === STATE.routeKey) return;
|
|
1556
|
+
applyRoute(route);
|
|
1557
|
+
});
|
|
1558
|
+
|
|
1559
|
+
// Agent → viewer navigation commands ride the same route resolver.
|
|
1560
|
+
startUICommandPolling(switchTab);
|
|
1561
|
+
|
|
2154
1562
|
function toggleTheme() {
|
|
2155
1563
|
// Light-only theme (brand unification) - no toggle needed
|
|
2156
1564
|
}
|
|
@@ -2527,104 +1935,6 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
|
|
|
2527
1935
|
.catch(() => (result.innerHTML = '<span class="text-red-500">Connection failed</span>'));
|
|
2528
1936
|
}
|
|
2529
1937
|
|
|
2530
|
-
async function sendChatMessage() {
|
|
2531
|
-
if (pendingAttachment) {
|
|
2532
|
-
const file = pendingAttachment;
|
|
2533
|
-
// Don't clear attachment yet - wait for successful send
|
|
2534
|
-
|
|
2535
|
-
const formData = new FormData();
|
|
2536
|
-
formData.append('file', file);
|
|
2537
|
-
|
|
2538
|
-
try {
|
|
2539
|
-
const resp = await fetch('/api/upload', { method: 'POST', body: formData });
|
|
2540
|
-
if (!resp.ok) throw new Error('Upload failed');
|
|
2541
|
-
const data = await resp.json();
|
|
2542
|
-
|
|
2543
|
-
// Check WS connection before sending
|
|
2544
|
-
if (!chat.ws || chat.ws.readyState !== WebSocket.OPEN) {
|
|
2545
|
-
chat.addSystemMessage('Not connected. Please wait for reconnection.', 'error');
|
|
2546
|
-
return;
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
const input = document.getElementById('chat-input');
|
|
2550
|
-
const isImage = file.type.startsWith('image/');
|
|
2551
|
-
const defaultMsg = isImage ? 'What is in this image?' : 'Analyze this document';
|
|
2552
|
-
const message = input.value.trim() || defaultMsg;
|
|
2553
|
-
|
|
2554
|
-
const mediaUrl = '/api/media/' + encodeURIComponent(data.filename);
|
|
2555
|
-
const attachMeta = {
|
|
2556
|
-
mediaUrl,
|
|
2557
|
-
filename: data.filename,
|
|
2558
|
-
originalName: file.name,
|
|
2559
|
-
contentType: file.type,
|
|
2560
|
-
isImage,
|
|
2561
|
-
};
|
|
2562
|
-
|
|
2563
|
-
// Save with attachment metadata for history persistence
|
|
2564
|
-
chat.addUserMessageWithAttachment(message, attachMeta);
|
|
2565
|
-
|
|
2566
|
-
chat.enableSend(false);
|
|
2567
|
-
chat.ws.send(
|
|
2568
|
-
JSON.stringify({
|
|
2569
|
-
type: 'send',
|
|
2570
|
-
sessionId: chat.sessionId,
|
|
2571
|
-
content: message,
|
|
2572
|
-
attachments: [
|
|
2573
|
-
{
|
|
2574
|
-
filename: data.filename,
|
|
2575
|
-
contentType: data.contentType,
|
|
2576
|
-
},
|
|
2577
|
-
],
|
|
2578
|
-
})
|
|
2579
|
-
);
|
|
2580
|
-
|
|
2581
|
-
// Clear attachment only after successful send
|
|
2582
|
-
clearAttachment();
|
|
2583
|
-
input.value = '';
|
|
2584
|
-
} catch (err) {
|
|
2585
|
-
chat.addSystemMessage('Upload failed: ' + err.message, 'error');
|
|
2586
|
-
// Keep pendingAttachment so user can retry
|
|
2587
|
-
}
|
|
2588
|
-
return;
|
|
2589
|
-
}
|
|
2590
|
-
chat.send();
|
|
2591
|
-
}
|
|
2592
|
-
|
|
2593
|
-
// Send quiz choice as message
|
|
2594
|
-
window.sendQuizChoice = function (choice) {
|
|
2595
|
-
if (chat && chat.ws && chat.ws.readyState === WebSocket.OPEN) {
|
|
2596
|
-
// Set the input to the choice and send
|
|
2597
|
-
const input = document.getElementById('chat-input');
|
|
2598
|
-
if (input) {
|
|
2599
|
-
input.value = choice;
|
|
2600
|
-
chat.send();
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
};
|
|
2604
|
-
|
|
2605
|
-
function toggleTTS() {
|
|
2606
|
-
chat.toggleTTS && chat.toggleTTS();
|
|
2607
|
-
}
|
|
2608
|
-
|
|
2609
|
-
function toggleTTSPopup() {
|
|
2610
|
-
const popup = document.getElementById('tts-speed-popup');
|
|
2611
|
-
popup.classList.toggle('hidden');
|
|
2612
|
-
}
|
|
2613
|
-
|
|
2614
|
-
function updateTTSSpeed(value) {
|
|
2615
|
-
document.getElementById('tts-speed-label').textContent = value + 'x';
|
|
2616
|
-
document.getElementById('tts-speed-value').textContent = value + 'x';
|
|
2617
|
-
chat.setTTSRate && chat.setTTSRate(parseFloat(value));
|
|
2618
|
-
}
|
|
2619
|
-
|
|
2620
|
-
function toggleHandsFree() {
|
|
2621
|
-
chat.toggleHandsFree && chat.toggleHandsFree();
|
|
2622
|
-
}
|
|
2623
|
-
|
|
2624
|
-
function toggleVoiceInput() {
|
|
2625
|
-
chat.toggleVoice && chat.toggleVoice();
|
|
2626
|
-
}
|
|
2627
|
-
|
|
2628
1938
|
function filterByTopic(topic) {
|
|
2629
1939
|
graph.filterByTopic && graph.filterByTopic(topic);
|
|
2630
1940
|
}
|
|
@@ -2633,11 +1943,17 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
|
|
|
2633
1943
|
graph.filterByOutcome && graph.filterByOutcome(outcome);
|
|
2634
1944
|
}
|
|
2635
1945
|
|
|
1946
|
+
// Clear is scoped to the Graph module instance that owns the filters. Each
|
|
1947
|
+
// control is looked up defensively: the bar is markup that can be edited,
|
|
1948
|
+
// and one missing input must not take the whole Clear action down.
|
|
2636
1949
|
function clearFilters() {
|
|
2637
|
-
document.getElementById('topic-filter')
|
|
2638
|
-
document.getElementById('outcome-filter')
|
|
2639
|
-
document.getElementById('search-input')
|
|
2640
|
-
|
|
1950
|
+
const topicFilter = document.getElementById('topic-filter');
|
|
1951
|
+
const outcomeFilter = document.getElementById('outcome-filter');
|
|
1952
|
+
const searchInput = document.getElementById('search-input');
|
|
1953
|
+
if (topicFilter) topicFilter.value = '';
|
|
1954
|
+
if (outcomeFilter) outcomeFilter.value = '';
|
|
1955
|
+
if (searchInput) searchInput.value = '';
|
|
1956
|
+
graph?.clearFilters?.();
|
|
2641
1957
|
}
|
|
2642
1958
|
|
|
2643
1959
|
async function exportDecisions(format) {
|
|
@@ -2692,174 +2008,8 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
|
|
|
2692
2008
|
}
|
|
2693
2009
|
}
|
|
2694
2010
|
|
|
2695
|
-
let checkpointsData = [];
|
|
2696
|
-
|
|
2697
|
-
function renderMarkdown(text) {
|
|
2698
|
-
if (!text) return '';
|
|
2699
|
-
try {
|
|
2700
|
-
if (typeof marked !== 'undefined' && marked.parse) {
|
|
2701
|
-
return marked.parse(text);
|
|
2702
|
-
}
|
|
2703
|
-
} catch (e) {
|
|
2704
|
-
console.error('[MAMA] Markdown parse error:', e);
|
|
2705
|
-
}
|
|
2706
|
-
return text.replace(/</g, '<').replace(/>/g, '>').replace(/\n/g, '<br>');
|
|
2707
|
-
}
|
|
2708
|
-
|
|
2709
|
-
async function loadCheckpoints() {
|
|
2710
|
-
const container = document.getElementById('checkpoint-list');
|
|
2711
|
-
if (!container) {
|
|
2712
|
-
console.error('[MAMA] checkpoint-list container not found');
|
|
2713
|
-
return;
|
|
2714
|
-
}
|
|
2715
|
-
|
|
2716
|
-
console.log('[MAMA] Loading checkpoints...');
|
|
2717
|
-
|
|
2718
|
-
try {
|
|
2719
|
-
const data = await API.get('/api/checkpoints');
|
|
2720
|
-
checkpointsData = data.checkpoints || [];
|
|
2721
|
-
|
|
2722
|
-
console.log('[MAMA] Loaded', checkpointsData.length, 'checkpoints');
|
|
2723
|
-
|
|
2724
|
-
if (checkpointsData.length > 0) {
|
|
2725
|
-
container.innerHTML = checkpointsData
|
|
2726
|
-
.map((cp, idx) => {
|
|
2727
|
-
const fullSummary = renderMarkdown(cp.summary || '');
|
|
2728
|
-
return `
|
|
2729
|
-
<div class="checkpoint-item p-3 bg-white bg-white border border-gray-200 border-gray-300 rounded-lg cursor-pointer hover:border-indigo-500 transition-colors" onclick="toggleCheckpoint(${idx})">
|
|
2730
|
-
<div class="text-xs font-semibold text-mama-black text-mama-black mb-1">${new Date(cp.timestamp).toLocaleString()}</div>
|
|
2731
|
-
<div class="checkpoint-summary text-sm text-gray-700 text-gray-700 markdown-content line-clamp-3 overflow-hidden">${fullSummary}</div>
|
|
2732
|
-
<div class="checkpoint-details hidden mt-3 pt-3 border-t border-gray-200 border-gray-300">
|
|
2733
|
-
<div class="text-sm text-gray-700 text-gray-700 markdown-content max-h-64 overflow-y-auto">${fullSummary}</div>
|
|
2734
|
-
</div>
|
|
2735
|
-
</div>
|
|
2736
|
-
`;
|
|
2737
|
-
})
|
|
2738
|
-
.join('');
|
|
2739
|
-
} else {
|
|
2740
|
-
container.innerHTML =
|
|
2741
|
-
'<div class="text-center text-sm text-gray-500 py-4">No checkpoints found</div>';
|
|
2742
|
-
}
|
|
2743
|
-
} catch (error) {
|
|
2744
|
-
console.error('[MAMA] Failed to load checkpoints:', error);
|
|
2745
|
-
container.innerHTML =
|
|
2746
|
-
'<div class="text-center text-sm text-red-500 py-4">Failed to load checkpoints</div>';
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
function toggleCheckpoint(idx) {
|
|
2751
|
-
const items = document.querySelectorAll('.checkpoint-item');
|
|
2752
|
-
items.forEach((item, i) => {
|
|
2753
|
-
const details = item.querySelector('.checkpoint-details');
|
|
2754
|
-
const summary = item.querySelector('.checkpoint-summary');
|
|
2755
|
-
if (i === idx) {
|
|
2756
|
-
const isExpanded = !details.classList.contains('hidden');
|
|
2757
|
-
details.classList.toggle('hidden');
|
|
2758
|
-
summary.classList.toggle('hidden', !isExpanded);
|
|
2759
|
-
item.classList.toggle('border-indigo-500', !isExpanded);
|
|
2760
|
-
} else {
|
|
2761
|
-
details.classList.add('hidden');
|
|
2762
|
-
summary.classList.remove('hidden');
|
|
2763
|
-
item.classList.remove('border-indigo-500');
|
|
2764
|
-
}
|
|
2765
|
-
});
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
// Connector list loader for Settings
|
|
2769
|
-
const CONNECTOR_NAMES = [
|
|
2770
|
-
'slack',
|
|
2771
|
-
'telegram',
|
|
2772
|
-
'discord',
|
|
2773
|
-
'chatwork',
|
|
2774
|
-
'gmail',
|
|
2775
|
-
'calendar',
|
|
2776
|
-
'notion',
|
|
2777
|
-
'obsidian',
|
|
2778
|
-
'kagemusha',
|
|
2779
|
-
'sheets',
|
|
2780
|
-
'trello',
|
|
2781
|
-
'drive',
|
|
2782
|
-
'imessage',
|
|
2783
|
-
];
|
|
2784
|
-
const CONNECTOR_CATEGORIES = {
|
|
2785
|
-
Messaging: ['slack', 'telegram', 'discord', 'chatwork'],
|
|
2786
|
-
Google: ['gmail', 'calendar', 'drive', 'sheets'],
|
|
2787
|
-
Knowledge: ['notion', 'obsidian', 'kagemusha'],
|
|
2788
|
-
'Dev Tools': ['claude-code'],
|
|
2789
|
-
Other: ['trello', 'imessage'],
|
|
2790
|
-
};
|
|
2791
|
-
async function loadConnectorsList() {
|
|
2792
|
-
const container = document.getElementById('settings-connectors-list');
|
|
2793
|
-
if (!container) return;
|
|
2794
|
-
let connectorStatus = {};
|
|
2795
|
-
try {
|
|
2796
|
-
const res = await fetch('/api/connectors/status');
|
|
2797
|
-
if (res.ok) {
|
|
2798
|
-
const data = await res.json();
|
|
2799
|
-
for (const c of data.connectors || []) connectorStatus[c.name] = c;
|
|
2800
|
-
}
|
|
2801
|
-
} catch {}
|
|
2802
|
-
// Load config
|
|
2803
|
-
let config = {};
|
|
2804
|
-
try {
|
|
2805
|
-
const res = await fetch('/api/config');
|
|
2806
|
-
if (res.ok) {
|
|
2807
|
-
const data = await res.json();
|
|
2808
|
-
config = data?.connectors || {};
|
|
2809
|
-
}
|
|
2810
|
-
} catch {}
|
|
2811
|
-
let html = '';
|
|
2812
|
-
for (const [category, names] of Object.entries(CONNECTOR_CATEGORIES)) {
|
|
2813
|
-
html += `<div style="margin-bottom:12px"><div style="font-family:Inter,'Noto Sans KR',sans-serif;font-size:12px;font-weight:600;color:#6B6560;margin-bottom:6px;text-transform:uppercase;letter-spacing:0.5px">${category}</div><div class="grid grid-cols-2 lg:grid-cols-3 gap-1.5">`;
|
|
2814
|
-
for (const name of names) {
|
|
2815
|
-
const status = connectorStatus[name];
|
|
2816
|
-
const cfg = config[name];
|
|
2817
|
-
const enabled = cfg?.enabled || status?.enabled || false;
|
|
2818
|
-
const healthy = status?.healthy || false;
|
|
2819
|
-
const lastPoll = status?.lastPollTime
|
|
2820
|
-
? new Date(status.lastPollTime).toLocaleTimeString('ko-KR', {
|
|
2821
|
-
hour: '2-digit',
|
|
2822
|
-
minute: '2-digit',
|
|
2823
|
-
})
|
|
2824
|
-
: null;
|
|
2825
|
-
const channels = status?.channelCount || Object.keys(cfg?.channels || {}).length || 0;
|
|
2826
|
-
const statusDot = enabled ? (healthy ? '#3A9E7E' : '#E8A040') : '#9E9891';
|
|
2827
|
-
html += `<div style="background:#fff;border:1px solid #EDE9E1;border-radius:4px;padding:8px 10px;font-size:12px;display:flex;align-items:center;gap:8px">`;
|
|
2828
|
-
html += `<span style="display:inline-block;width:6px;height:6px;background:${statusDot};border-radius:1px;flex-shrink:0"></span>`;
|
|
2829
|
-
html += `<span style="font-weight:600;color:#1A1A1A;flex:1">${name}</span>`;
|
|
2830
|
-
if (channels > 0)
|
|
2831
|
-
html += `<span style="font-size:10px;color:#9E9891">${channels}ch</span>`;
|
|
2832
|
-
if (lastPoll) html += `<span style="font-size:10px;color:#9E9891">${lastPoll}</span>`;
|
|
2833
|
-
if (!enabled)
|
|
2834
|
-
html += `<span style="font-size:9px;color:#9E9891;background:#F5F2ED;padding:1px 4px;border-radius:2px">off</span>`;
|
|
2835
|
-
html += `</div>`;
|
|
2836
|
-
}
|
|
2837
|
-
html += `</div></div>`;
|
|
2838
|
-
}
|
|
2839
|
-
container.innerHTML = html;
|
|
2840
|
-
}
|
|
2841
|
-
|
|
2842
|
-
// Mobile "More" menu
|
|
2843
|
-
function toggleMobileMore() {
|
|
2844
|
-
const more = document.getElementById('mama-mobile-more');
|
|
2845
|
-
if (!more) return;
|
|
2846
|
-
const isVisible = more.style.display !== 'none';
|
|
2847
|
-
more.style.display = isVisible ? 'none' : 'block';
|
|
2848
|
-
// Update More button active state
|
|
2849
|
-
const moreBtn = document.querySelector('#mama-mobile-tabs [data-tab="more"]');
|
|
2850
|
-
if (moreBtn) moreBtn.classList.toggle('mama-nav-active', !isVisible);
|
|
2851
|
-
}
|
|
2852
|
-
function closeMobileMore() {
|
|
2853
|
-
const more = document.getElementById('mama-mobile-more');
|
|
2854
|
-
if (more) more.style.display = 'none';
|
|
2855
|
-
const moreBtn = document.querySelector('#mama-mobile-tabs [data-tab="more"]');
|
|
2856
|
-
if (moreBtn) moreBtn.classList.remove('mama-nav-active');
|
|
2857
|
-
}
|
|
2858
|
-
|
|
2859
2011
|
// Expose to window for onclick handlers
|
|
2860
2012
|
window.switchTab = switchTab;
|
|
2861
|
-
window.toggleMobileMore = toggleMobileMore;
|
|
2862
|
-
window.closeMobileMore = closeMobileMore;
|
|
2863
2013
|
window.toggleTheme = toggleTheme;
|
|
2864
2014
|
window.toggleLegend = toggleLegend;
|
|
2865
2015
|
window.closeDetailModal = closeDetailModal;
|
|
@@ -2869,266 +2019,32 @@ export MAMA_AUTH_TOKEN="your-secure-token-here"</pre
|
|
|
2869
2019
|
window.showSaveDecisionForm = showSaveDecisionForm;
|
|
2870
2020
|
window.hideSaveDecisionForm = hideSaveDecisionForm;
|
|
2871
2021
|
window.submitSaveDecision = submitSaveDecision;
|
|
2872
|
-
window.copyNgrokCommand = copyNgrokCommand;
|
|
2873
2022
|
window.testConnection = testConnection;
|
|
2874
|
-
window.
|
|
2875
|
-
window.
|
|
2876
|
-
window.
|
|
2877
|
-
window.updateTTSSpeed = updateTTSSpeed;
|
|
2878
|
-
window.toggleHandsFree = toggleHandsFree;
|
|
2879
|
-
window.toggleVoiceInput = toggleVoiceInput;
|
|
2880
|
-
|
|
2881
|
-
// File attachment state
|
|
2882
|
-
let pendingAttachment = null;
|
|
2883
|
-
|
|
2884
|
-
const FILE_ICONS = {
|
|
2885
|
-
'application/pdf': '📄',
|
|
2886
|
-
'text/plain': '📝',
|
|
2887
|
-
'text/csv': '📊',
|
|
2888
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': '📃',
|
|
2889
|
-
'application/msword': '📃',
|
|
2890
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': '📊',
|
|
2891
|
-
'application/vnd.ms-excel': '📊',
|
|
2892
|
-
};
|
|
2893
|
-
|
|
2894
|
-
function getFileIcon(mimeType) {
|
|
2895
|
-
return FILE_ICONS[mimeType] || '📎';
|
|
2896
|
-
}
|
|
2897
|
-
|
|
2898
|
-
function handleFileSelect(event) {
|
|
2899
|
-
const file = event.target.files[0];
|
|
2900
|
-
if (!file) return;
|
|
2901
|
-
|
|
2902
|
-
const preview = document.getElementById('chat-attachment-preview');
|
|
2903
|
-
const thumb = document.getElementById('chat-attachment-thumb');
|
|
2904
|
-
const name = document.getElementById('chat-attachment-name');
|
|
2905
|
-
|
|
2906
|
-
if (file.type.startsWith('image/')) {
|
|
2907
|
-
const reader = new FileReader();
|
|
2908
|
-
reader.onload = (e) => {
|
|
2909
|
-
thumb.src = e.target.result;
|
|
2910
|
-
};
|
|
2911
|
-
reader.readAsDataURL(file);
|
|
2912
|
-
thumb.style.display = '';
|
|
2913
|
-
} else {
|
|
2914
|
-
// Show emoji icon for documents
|
|
2915
|
-
thumb.style.display = 'none';
|
|
2916
|
-
name.textContent = getFileIcon(file.type) + ' ' + file.name;
|
|
2917
|
-
}
|
|
2918
|
-
|
|
2919
|
-
if (file.type.startsWith('image/')) {
|
|
2920
|
-
name.textContent = file.name;
|
|
2921
|
-
}
|
|
2922
|
-
preview.classList.remove('hidden');
|
|
2923
|
-
pendingAttachment = file;
|
|
2924
|
-
// cleared
|
|
2925
|
-
}
|
|
2926
|
-
|
|
2927
|
-
function clearAttachment() {
|
|
2928
|
-
pendingAttachment = null;
|
|
2929
|
-
// cleared
|
|
2930
|
-
document.getElementById('chat-attachment-preview').classList.add('hidden');
|
|
2931
|
-
document.getElementById('chat-file-input').value = '';
|
|
2932
|
-
const thumb = document.getElementById('chat-attachment-thumb');
|
|
2933
|
-
thumb.src = 'data:,';
|
|
2934
|
-
thumb.style.display = '';
|
|
2935
|
-
}
|
|
2936
|
-
|
|
2937
|
-
window.handleFileSelect = handleFileSelect;
|
|
2938
|
-
window.clearAttachment = clearAttachment;
|
|
2939
|
-
|
|
2940
|
-
// Drag & drop support
|
|
2941
|
-
const chatMessages = document.getElementById('chat-messages');
|
|
2942
|
-
if (chatMessages) {
|
|
2943
|
-
const panel = chatMessages.closest('#chat-panel') || chatMessages.parentElement;
|
|
2944
|
-
['dragenter', 'dragover'].forEach((evt) => {
|
|
2945
|
-
panel.addEventListener(evt, (e) => {
|
|
2946
|
-
e.preventDefault();
|
|
2947
|
-
e.stopPropagation();
|
|
2948
|
-
panel.classList.add('ring-2', 'ring-mama-yellow');
|
|
2949
|
-
});
|
|
2950
|
-
});
|
|
2951
|
-
['dragleave', 'drop'].forEach((evt) => {
|
|
2952
|
-
panel.addEventListener(evt, (e) => {
|
|
2953
|
-
e.preventDefault();
|
|
2954
|
-
e.stopPropagation();
|
|
2955
|
-
panel.classList.remove('ring-2', 'ring-mama-yellow');
|
|
2956
|
-
});
|
|
2957
|
-
});
|
|
2958
|
-
panel.addEventListener('drop', (e) => {
|
|
2959
|
-
const file = e.dataTransfer.files[0];
|
|
2960
|
-
if (!file) return;
|
|
2961
|
-
|
|
2962
|
-
// Allowed MIME types and extensions (match file input accept attribute)
|
|
2963
|
-
const allowedTypes = [
|
|
2964
|
-
'image/',
|
|
2965
|
-
'application/pdf',
|
|
2966
|
-
'application/msword',
|
|
2967
|
-
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
2968
|
-
'text/plain',
|
|
2969
|
-
'text/csv',
|
|
2970
|
-
'text/markdown',
|
|
2971
|
-
'text/html',
|
|
2972
|
-
'text/xml',
|
|
2973
|
-
'application/json',
|
|
2974
|
-
'application/xml',
|
|
2975
|
-
'application/vnd.ms-excel',
|
|
2976
|
-
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
2977
|
-
'application/vnd.ms-powerpoint',
|
|
2978
|
-
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
2979
|
-
'application/zip',
|
|
2980
|
-
'application/gzip',
|
|
2981
|
-
];
|
|
2982
|
-
const allowedExts = [
|
|
2983
|
-
'.pdf',
|
|
2984
|
-
'.doc',
|
|
2985
|
-
'.docx',
|
|
2986
|
-
'.txt',
|
|
2987
|
-
'.csv',
|
|
2988
|
-
'.xls',
|
|
2989
|
-
'.xlsx',
|
|
2990
|
-
'.md',
|
|
2991
|
-
'.json',
|
|
2992
|
-
'.html',
|
|
2993
|
-
'.htm',
|
|
2994
|
-
'.xml',
|
|
2995
|
-
'.zip',
|
|
2996
|
-
'.gz',
|
|
2997
|
-
'.ppt',
|
|
2998
|
-
'.pptx',
|
|
2999
|
-
];
|
|
3000
|
-
const ext = '.' + (file.name.split('.').pop() || '').toLowerCase();
|
|
3001
|
-
|
|
3002
|
-
const isAllowed =
|
|
3003
|
-
allowedTypes.some((t) => file.type.startsWith(t)) || allowedExts.includes(ext);
|
|
3004
|
-
if (isAllowed) {
|
|
3005
|
-
const dt = new DataTransfer();
|
|
3006
|
-
dt.items.add(file);
|
|
3007
|
-
document.getElementById('chat-file-input').files = dt.files;
|
|
3008
|
-
handleFileSelect({ target: { files: [file] } });
|
|
3009
|
-
} else {
|
|
3010
|
-
chat.addSystemMessage(
|
|
3011
|
-
'Unsupported file type. Allowed: images, PDF, DOC, TXT, CSV, XLS, PPT, MD, JSON, HTML, ZIP',
|
|
3012
|
-
'error'
|
|
3013
|
-
);
|
|
3014
|
-
}
|
|
3015
|
-
});
|
|
3016
|
-
}
|
|
3017
|
-
|
|
2023
|
+
window.filterByOutcome = filterByOutcome;
|
|
2024
|
+
window.clearFilters = clearFilters;
|
|
2025
|
+
window.exportDecisions = exportDecisions;
|
|
3018
2026
|
window.filterByTopic = filterByTopic;
|
|
3019
2027
|
window.handleGraphSearch = handleGraphSearch;
|
|
3020
2028
|
window.saveOutcome = saveOutcome;
|
|
3021
|
-
window.loadCheckpoints = loadCheckpoints;
|
|
3022
|
-
window.toggleCheckpoint = toggleCheckpoint;
|
|
3023
2029
|
|
|
3024
2030
|
document.addEventListener('DOMContentLoaded', () => {
|
|
3025
2031
|
const userLang = navigator.language || navigator.userLanguage;
|
|
3026
2032
|
document.documentElement.lang = userLang.split('-')[0];
|
|
3027
2033
|
|
|
3028
|
-
// Initialize Lucide icons (
|
|
2034
|
+
// Initialize Lucide icons (rail + chip row)
|
|
3029
2035
|
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
|
3030
2036
|
lucide.createIcons();
|
|
3031
2037
|
}
|
|
3032
2038
|
|
|
3033
2039
|
initTheme();
|
|
3034
|
-
// Initialize floating chat panel bindings (session init is lazy, on first open)
|
|
3035
|
-
chat.initFloating();
|
|
3036
|
-
// Default to feed tab
|
|
3037
|
-
switchTab('feed');
|
|
3038
|
-
});
|
|
3039
2040
|
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
if (event.data.type === 'viewer:sendToChat') {
|
|
3046
|
-
const message = typeof event.data.message === 'string' ? event.data.message : '';
|
|
3047
|
-
if (!message) {
|
|
3048
|
-
return;
|
|
3049
|
-
}
|
|
3050
|
-
const input = document.getElementById('chat-input');
|
|
3051
|
-
if (input && chat) {
|
|
3052
|
-
input.value = message;
|
|
3053
|
-
setTimeout(() => chat.send(), 50);
|
|
3054
|
-
}
|
|
3055
|
-
}
|
|
2041
|
+
// Route from the address bar; an empty or unknown hash is normalised to
|
|
2042
|
+
// the default so the URL always names what is on screen.
|
|
2043
|
+
const initialRoute = parseViewerHash();
|
|
2044
|
+
writeViewerHash(initialRoute.path, initialRoute.params);
|
|
2045
|
+
applyRoute(initialRoute);
|
|
3056
2046
|
});
|
|
3057
2047
|
|
|
3058
|
-
// Cleanup resources on page unload
|
|
3059
|
-
window.addEventListener('beforeunload', () => {
|
|
3060
|
-
// Cleanup dashboard intervals
|
|
3061
|
-
if (window.dashboardModule?.cleanup) {
|
|
3062
|
-
window.dashboardModule.cleanup();
|
|
3063
|
-
}
|
|
3064
|
-
// Cleanup chat resources
|
|
3065
|
-
if (window.chatModule?.cleanup) {
|
|
3066
|
-
window.chatModule.cleanup();
|
|
3067
|
-
}
|
|
3068
|
-
console.log('[MAMA] Resources cleaned up');
|
|
3069
|
-
});
|
|
3070
|
-
|
|
3071
|
-
// Context-aware help: ? button sends tab-specific prompt to chat
|
|
3072
|
-
const TAB_HELP_PROMPTS = {
|
|
3073
|
-
dashboard: `The user clicked the help button on the MAMA OS Dashboard tab.
|
|
3074
|
-
Please explain what this tab shows:
|
|
3075
|
-
- Gateway connection status (Discord, Slack, Telegram, Chatwork)
|
|
3076
|
-
- MCP server status
|
|
3077
|
-
- Multi-Agent swarm overview
|
|
3078
|
-
- Token usage and cost tracking
|
|
3079
|
-
- Scheduled jobs (Cron) status
|
|
3080
|
-
Explain what each section means and what actions the user can take. Be friendly and concise.`,
|
|
3081
|
-
|
|
3082
|
-
memory: `The user clicked the help button on the Memory tab.
|
|
3083
|
-
Please explain this tab's features:
|
|
3084
|
-
- Decision Graph: Visual graph showing relationships between decisions (click nodes for details)
|
|
3085
|
-
- Checkpoints: Session checkpoint list (resume previous work)
|
|
3086
|
-
- Stats bar: Total decisions, weekly activity, outcome distribution
|
|
3087
|
-
- Search: Find past decisions by keyword
|
|
3088
|
-
- Save new decisions: Record insights with the + button
|
|
3089
|
-
Be friendly and concise.`,
|
|
3090
|
-
|
|
3091
|
-
settings: `The user clicked the help button on the Settings tab.
|
|
3092
|
-
Please explain what can be configured here:
|
|
3093
|
-
- Gateways: Discord, Slack, Telegram, Chatwork bot connections
|
|
3094
|
-
- Agent: Backend model, max turns, timeout, and other agent settings
|
|
3095
|
-
- Multi-Agent: Per-agent model, backend, tier, and delegation settings
|
|
3096
|
-
- Cron: Create, edit, and run scheduled jobs
|
|
3097
|
-
- Token Budget: Set daily token limits and alerts
|
|
3098
|
-
- Metrics: Enable/disable metrics collection and set retention period
|
|
3099
|
-
Be friendly and concise.`,
|
|
3100
|
-
|
|
3101
|
-
logs: `The user clicked the help button on the Logs tab.
|
|
3102
|
-
Please explain this tab's features:
|
|
3103
|
-
- Shows real-time daemon log output with auto-polling
|
|
3104
|
-
- Filter by log level (ERROR, WARN, INFO, DEBUG)
|
|
3105
|
-
- Search logs by keyword
|
|
3106
|
-
- Pause/Resume auto-refresh
|
|
3107
|
-
Be friendly and concise.`,
|
|
3108
|
-
};
|
|
3109
|
-
|
|
3110
|
-
window.askContextHelp = function () {
|
|
3111
|
-
const currentTab = STATE.currentTab || 'feed';
|
|
3112
|
-
const prompt = TAB_HELP_PROMPTS[currentTab];
|
|
3113
|
-
if (!prompt) return;
|
|
3114
|
-
|
|
3115
|
-
if (window.innerWidth < 768) {
|
|
3116
|
-
switchTab('chat');
|
|
3117
|
-
} else {
|
|
3118
|
-
const wrapper = document.getElementById('chat-panel-wrapper');
|
|
3119
|
-
if (wrapper && wrapper.style.display === 'none') {
|
|
3120
|
-
toggleChatPanel();
|
|
3121
|
-
}
|
|
3122
|
-
}
|
|
3123
|
-
|
|
3124
|
-
// Set input and send
|
|
3125
|
-
const input = document.getElementById('chat-input');
|
|
3126
|
-
if (input && chat) {
|
|
3127
|
-
input.value = prompt;
|
|
3128
|
-
setTimeout(() => chat.send(), 150);
|
|
3129
|
-
}
|
|
3130
|
-
};
|
|
3131
|
-
|
|
3132
2048
|
// Register Service Worker for PWA
|
|
3133
2049
|
if ('serviceWorker' in navigator) {
|
|
3134
2050
|
window.addEventListener('load', () => {
|