@enigmax/dashboard 0.1.113 → 0.1.115
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/assets/index.html +227 -65
- package/package.json +1 -1
package/assets/index.html
CHANGED
|
@@ -25,6 +25,9 @@
|
|
|
25
25
|
--elev: linear-gradient(180deg, rgba(255,255,255,0.045), rgba(255,255,255,0) 42%);
|
|
26
26
|
--shadow-1: 0 1px 2px rgba(0,0,0,0.4);
|
|
27
27
|
--shadow-2: 0 18px 40px -22px rgba(0,0,0,0.75);
|
|
28
|
+
/* Hover borders, scrollbars and the text that sits on a filled accent or good pill. */
|
|
29
|
+
--border-strong: #2f3744; --scrollbar: #2b3340;
|
|
30
|
+
--on-accent: #1a1206; --on-good: #06210f;
|
|
28
31
|
}
|
|
29
32
|
* { box-sizing: border-box; }
|
|
30
33
|
/* An icon never gives up its size to the text beside it. Every svg here is an icon, and
|
|
@@ -60,6 +63,8 @@
|
|
|
60
63
|
otherwise stay glued to the sidebar with all the empty space piled on the right. */
|
|
61
64
|
.content { flex: 1; min-width: 0; padding: 30px 30px 48px; max-width: 1560px; margin-inline: auto; }
|
|
62
65
|
.iconbtn.nav-burger { display: none; }
|
|
66
|
+
/* The rail carries the wordmark; once the rail is a drawer the header shows it instead. */
|
|
67
|
+
.wordmark.mob-brand { display: none; }
|
|
63
68
|
.nav-scrim { position: fixed; inset: 0; z-index: 25; background: rgba(0,0,0,0.6); }
|
|
64
69
|
a { color: var(--accent2); }
|
|
65
70
|
/* Quality floor: one visible keyboard-focus ring for every interactive control
|
|
@@ -133,7 +138,7 @@
|
|
|
133
138
|
}
|
|
134
139
|
/* A faint top-edge highlight so a card reads as a lit surface, not a flat rectangle. */
|
|
135
140
|
.card::before { content: ""; position: absolute; inset: 0; background: var(--elev); pointer-events: none; }
|
|
136
|
-
.card:hover { border-color:
|
|
141
|
+
.card:hover { border-color: var(--border-strong); transform: translateY(-2px); box-shadow: var(--shadow-1), var(--shadow-2); }
|
|
137
142
|
/* Hero KPI (Money Saved): spans two columns, accent-lit - the loudest thing on the page. */
|
|
138
143
|
.card.hero { grid-column: span 2; border-color: rgba(224,164,88,0.35); background: linear-gradient(135deg, var(--accent-weak), transparent 62%), var(--surface); }
|
|
139
144
|
.card.hero .value { font-size: 42px; }
|
|
@@ -171,7 +176,7 @@
|
|
|
171
176
|
transition: color .15s, background .15s, border-color .15s;
|
|
172
177
|
}
|
|
173
178
|
.ranges button:hover { color: var(--text); }
|
|
174
|
-
.ranges button.active { background: var(--accent); color:
|
|
179
|
+
.ranges button.active { background: var(--accent); color: var(--on-accent); border-color: var(--accent); font-weight: 600; }
|
|
175
180
|
.chartwrap { position: relative; width: 100%; height: 280px; }
|
|
176
181
|
/* Suppress the charting library's attribution logo. */
|
|
177
182
|
#tv-attr-logo { display: none !important; }
|
|
@@ -271,8 +276,8 @@
|
|
|
271
276
|
.iconbtn.spinning svg { animation: spin .7s linear infinite; }
|
|
272
277
|
.toggle.with-icon { display: inline-flex; align-items: center; gap: 6px; }
|
|
273
278
|
.toggle.with-icon svg { width: 14px; height: 14px; display: block; }
|
|
274
|
-
.toggle:hover { color: var(--text); border-color:
|
|
275
|
-
.toggle.on { background: var(--good); color:
|
|
279
|
+
.toggle:hover { color: var(--text); border-color: var(--border-strong); }
|
|
280
|
+
.toggle.on { background: var(--good); color: var(--on-good); border-color: var(--good); font-weight: 600; }
|
|
276
281
|
/* Boolean settings render as a sliding switch. Only the bool controls carry
|
|
277
282
|
data-kind="bool" - action buttons reuse .toggle and stay buttons. The On/Off
|
|
278
283
|
text is kept for screen readers but pushed off-screen; the knob is drawn with
|
|
@@ -288,12 +293,12 @@
|
|
|
288
293
|
width: 18px; height: 18px; border-radius: 50%; background: #cdd2da;
|
|
289
294
|
transition: left .18s, background .18s;
|
|
290
295
|
}
|
|
291
|
-
.toggle[data-kind="bool"]:hover { border-color:
|
|
296
|
+
.toggle[data-kind="bool"]:hover { border-color: var(--border-strong); }
|
|
292
297
|
.toggle[data-kind="bool"].on { background: var(--good); border-color: var(--good); }
|
|
293
|
-
.toggle[data-kind="bool"].on::after { left: 23px; background:
|
|
298
|
+
.toggle[data-kind="bool"].on::after { left: 23px; background: var(--on-good); }
|
|
294
299
|
/* Compact icon-only button (e.g. Edit), instead of a text button. */
|
|
295
300
|
.iconbtn { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 30px; padding: 0; background: var(--surface2); color: var(--muted); border: 1px solid var(--border); border-radius: var(--radius-sm); cursor: pointer; transition: color .15s, border-color .15s, transform .1s; }
|
|
296
|
-
.iconbtn:hover { color: var(--accent); border-color:
|
|
301
|
+
.iconbtn:hover { color: var(--accent); border-color: var(--border-strong); }
|
|
297
302
|
.iconbtn svg { width: 15px; height: 15px; display: block; }
|
|
298
303
|
select.choice {
|
|
299
304
|
background: var(--surface2); color: var(--text); border: 1px solid var(--border);
|
|
@@ -432,8 +437,8 @@
|
|
|
432
437
|
/* Per-agent skill toggles: one pill per app (the app's icon), green when the skill is on for it; the app name is the tooltip. */
|
|
433
438
|
.set-agents { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 8px; }
|
|
434
439
|
.achip { display: inline-flex; align-items: center; justify-content: center; padding: 5px 8px; border-radius: 999px; border: 1px solid var(--border); background: var(--surface2); color: var(--muted); cursor: pointer; text-decoration: none; transition: color .15s, background .15s, border-color .15s; }
|
|
435
|
-
.achip:hover { border-color:
|
|
436
|
-
.achip.on { background: var(--good); color:
|
|
440
|
+
.achip:hover { border-color: var(--border-strong); color: var(--text); }
|
|
441
|
+
.achip.on { background: var(--good); color: var(--on-good); border-color: var(--good); }
|
|
437
442
|
.achip:disabled { opacity: .35; cursor: not-allowed; }
|
|
438
443
|
.achip svg { width: 14px; height: 14px; display: block; }
|
|
439
444
|
/* Inline app icons in the "where deployed" tag (static, name in tooltip). */
|
|
@@ -462,6 +467,15 @@
|
|
|
462
467
|
.pg-textarea { min-height: 92px; resize: vertical; line-height: 1.5; }
|
|
463
468
|
.pg-check { display: flex; align-items: center; gap: 7px; font-size: 12px; color: var(--text); padding-top: 4px; }
|
|
464
469
|
.pg-check label { color: var(--muted); }
|
|
470
|
+
.ad-pool { display: flex; flex-wrap: wrap; gap: 8px 16px; }
|
|
471
|
+
.ad-pool label { display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--text); cursor: pointer; min-width: 0; max-width: 100%; }
|
|
472
|
+
.ad-pool label > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
473
|
+
.ad-pool input { accent-color: var(--good); }
|
|
474
|
+
.ad-pool .aicon { color: var(--muted); }
|
|
475
|
+
.ad-pool.off { opacity: .5; }
|
|
476
|
+
.ad-fixed.off { opacity: .5; }
|
|
477
|
+
.ad-err { color: var(--bad); font-size: 11px; }
|
|
478
|
+
.ad-err:empty { display: none; }
|
|
465
479
|
.pg-out { background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; font-size: 12px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; white-space: pre-wrap; word-break: break-word; overflow-x: auto; max-height: 360px; overflow-y: auto; margin: 0; }
|
|
466
480
|
/* Claude-style usage window gauges (current session / weekly all / weekly Sonnet). */
|
|
467
481
|
.wgrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-bottom: 18px; }
|
|
@@ -494,10 +508,10 @@
|
|
|
494
508
|
/* Both halves hold text of unpredictable length (a setting label, a port, a model id, a
|
|
495
509
|
command), so the row is built to shrink rather than to push: min-width:0 defeats the
|
|
496
510
|
default min-width:auto that makes a flex/grid item refuse to go below its content
|
|
497
|
-
width,
|
|
498
|
-
|
|
511
|
+
width, the two grid columns keep the state on the right of every card (a wrapping
|
|
512
|
+
flex row put it under the label in some cards and beside it in others), and overflow-wrap:anywhere lets a long unbroken label break (unlike break-word,
|
|
499
513
|
it also lowers min-content, which is what stops the grid track being forced wider). */
|
|
500
|
-
.sys-item { display:
|
|
514
|
+
.sys-item { display: grid; grid-template-columns: minmax(min(100%, 140px), 1fr) minmax(0, auto); align-items: center; gap: 6px 12px; min-width: 0; padding: 11px 14px; font-size: 13px; background: var(--surface2); border: 1px solid var(--border-soft); border-radius: var(--radius-sm); }
|
|
501
515
|
.sys-item .k { color: var(--text); min-width: 0; overflow-wrap: anywhere; }
|
|
502
516
|
.sys-item .v { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; flex: 0 1 auto; min-width: 0; }
|
|
503
517
|
.sys-item .v .tag { overflow-wrap: anywhere; }
|
|
@@ -505,13 +519,13 @@
|
|
|
505
519
|
.sys-item .pill.off { background: transparent; }
|
|
506
520
|
/* Rows whose value is a list of chips span the full width and let the chips wrap with
|
|
507
521
|
even spacing, instead of cramming many tags into one compact half-column cell. */
|
|
508
|
-
.sys-item.wide { grid-column: 1 / -1; flex-direction: column; align-items: stretch; gap: 8px; }
|
|
522
|
+
.sys-item.wide { grid-column: 1 / -1; display: flex; flex-direction: column; align-items: stretch; gap: 8px; }
|
|
509
523
|
.sys-item.wide .v { display: flex; flex-wrap: wrap; gap: 6px; justify-content: flex-start; }
|
|
510
524
|
.sys-item.wide .v .tag { margin-left: 0; }
|
|
511
525
|
.pill { font-size: 11px; border-radius: 6px; padding: 2px 9px; border: 1px solid var(--border); white-space: nowrap; }
|
|
512
|
-
.pill.on { color:
|
|
526
|
+
.pill.on { color: var(--on-good); background: var(--good); border-color: var(--good); font-weight: 600; }
|
|
513
527
|
.pill.off { color: var(--muted); background: var(--surface2); }
|
|
514
|
-
.pill.lvl { color:
|
|
528
|
+
.pill.lvl { color: var(--on-accent); background: var(--accent); border-color: var(--accent); font-weight: 600; }
|
|
515
529
|
/* Claude operational-status pill (status.claude.com). */
|
|
516
530
|
.cstatus { display: inline-flex; align-items: center; gap: 7px; font-family: var(--mono); font-size: 12px; color: var(--muted); padding: 5px 11px; background: rgba(0,0,0,0.25); border: 1px solid var(--border-soft); border-radius: 999px; white-space: nowrap; }
|
|
517
531
|
.cdot { width: 8px; height: 8px; border-radius: 50%; background: var(--muted); flex: 0 0 auto; }
|
|
@@ -577,6 +591,7 @@
|
|
|
577
591
|
}
|
|
578
592
|
.sidebar.open { transform: none; }
|
|
579
593
|
.iconbtn.side-x, .iconbtn.nav-burger { display: inline-flex; }
|
|
594
|
+
.wordmark.mob-brand { display: inline-flex; font-size: 18px; }
|
|
580
595
|
.content { padding: 18px; }
|
|
581
596
|
.tabs .tab { padding: 11px 12px; font-size: 13.5px; }
|
|
582
597
|
body.nav-open { overflow: hidden; }
|
|
@@ -585,7 +600,9 @@
|
|
|
585
600
|
.content { padding: 16px; }
|
|
586
601
|
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
587
602
|
header { gap: 12px; }
|
|
588
|
-
|
|
603
|
+
/* Burger and wordmark share the first row; the toolbar takes a full row below. */
|
|
604
|
+
.nav { margin-left: 0; flex-basis: 100%; flex-wrap: wrap; gap: 10px; }
|
|
605
|
+
.nav .cmdk { flex: 1 1 auto; }
|
|
589
606
|
/* A settings row is label + controls side by side, and the control cluster is
|
|
590
607
|
flex-shrink: 0 so a wide one (account actions, a select plus three buttons)
|
|
591
608
|
pushes the row past the viewport. Here the row wraps and the cluster is
|
|
@@ -600,11 +617,11 @@
|
|
|
600
617
|
.set-ctl { flex-shrink: 1; min-width: 0; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; }
|
|
601
618
|
}
|
|
602
619
|
/* Pretty scrollbars across the whole dashboard (thin, rounded, themed). */
|
|
603
|
-
* { scrollbar-width: thin; scrollbar-color:
|
|
620
|
+
* { scrollbar-width: thin; scrollbar-color: var(--scrollbar) transparent; }
|
|
604
621
|
::-webkit-scrollbar { width: 10px; height: 10px; }
|
|
605
622
|
::-webkit-scrollbar-track { background: transparent; }
|
|
606
623
|
::-webkit-scrollbar-corner { background: transparent; }
|
|
607
|
-
::-webkit-scrollbar-thumb { background:
|
|
624
|
+
::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 999px; }
|
|
608
625
|
::-webkit-scrollbar-thumb:hover { background: #3a4150; }
|
|
609
626
|
/* Scope selector (global vs a project) in the header. */
|
|
610
627
|
.scope-wrap { display: inline-flex; align-items: center; gap: 6px; }
|
|
@@ -719,7 +736,7 @@
|
|
|
719
736
|
picker is a custom listbox - the logo shows on the button AND inside every option. */
|
|
720
737
|
.dd { position: relative; }
|
|
721
738
|
.dd-btn { display: inline-flex; align-items: center; gap: 8px; min-width: 170px; background: var(--surface2); color: var(--text); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 6px 10px; font-size: 12px; font-family: var(--mono); cursor: pointer; }
|
|
722
|
-
.dd-btn:hover { border-color:
|
|
739
|
+
.dd-btn:hover { border-color: var(--border-strong); }
|
|
723
740
|
.dd-btn .dd-label { flex: 1; text-align: left; }
|
|
724
741
|
.dd-btn .dd-caret { width: 13px; height: 13px; flex: 0 0 auto; color: var(--muted); }
|
|
725
742
|
.dd-ic { display: inline-flex; align-items: center; }
|
|
@@ -749,7 +766,7 @@
|
|
|
749
766
|
border-radius: var(--radius-sm); padding: 11px 13px; font-size: 13px; font-family: var(--mono);
|
|
750
767
|
transition: border-color .15s, background .15s, transform .1s;
|
|
751
768
|
}
|
|
752
|
-
.pick-card:hover { border-color:
|
|
769
|
+
.pick-card:hover { border-color: var(--border-strong); }
|
|
753
770
|
.pick-card:active { transform: scale(0.98); }
|
|
754
771
|
.pick-card.sel { border-color: var(--accent); }
|
|
755
772
|
.pick-card svg { width: 20px; height: 20px; display: block; flex: none; color: var(--muted); }
|
|
@@ -828,7 +845,7 @@
|
|
|
828
845
|
</a>
|
|
829
846
|
<a href="#/playground" data-view="playground" class="tab">
|
|
830
847
|
<svg class="tab-ic" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m4 17 6-6-6-6"/><path d="M12 19h8"/></svg>
|
|
831
|
-
<span>
|
|
848
|
+
<span>Local API</span>
|
|
832
849
|
</a>
|
|
833
850
|
</div>
|
|
834
851
|
<div class="nav-group" role="group" aria-labelledby="ng-code">
|
|
@@ -897,6 +914,7 @@
|
|
|
897
914
|
<button type="button" id="navOpen" class="iconbtn nav-burger" title="Menu" aria-label="Open the menu" aria-expanded="false" aria-controls="sidebar">
|
|
898
915
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><path d="M3 6h18M3 12h18M3 18h18"/></svg>
|
|
899
916
|
</button>
|
|
917
|
+
<div class="wordmark mob-brand" aria-hidden="true">Enigma<span>.</span></div>
|
|
900
918
|
<div class="nav">
|
|
901
919
|
<button type="button" id="palOpen" class="cmdk" aria-label="Search views and projects" aria-haspopup="dialog">
|
|
902
920
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path d="m20 20-3.5-3.5"/></svg>
|
|
@@ -1418,9 +1436,44 @@
|
|
|
1418
1436
|
|
|
1419
1437
|
<section id="view-playground" style="display:none">
|
|
1420
1438
|
<div class="page-head">
|
|
1421
|
-
<h1>API
|
|
1422
|
-
<p>
|
|
1439
|
+
<h1>Local API</h1>
|
|
1440
|
+
<p>Set which account answers <code>enigma api</code> requests, then try it below.</p>
|
|
1423
1441
|
</div>
|
|
1442
|
+
<div class="panel" id="adPanel">
|
|
1443
|
+
<div class="set-cat-h">Server defaults</div>
|
|
1444
|
+
<div class="set-cat-b">What <code>enigma api</code> starts with. A command-line flag overrides them for one run; changes apply the next time the server starts.</div>
|
|
1445
|
+
<div class="pg-grid">
|
|
1446
|
+
<label class="pg-field"><span>Account rotation</span>
|
|
1447
|
+
<select id="adRotation"></select>
|
|
1448
|
+
</label>
|
|
1449
|
+
<label class="pg-field ad-fixed"><span>Default account</span>
|
|
1450
|
+
<select id="adAccount"><option value="">(active account)</option></select>
|
|
1451
|
+
</label>
|
|
1452
|
+
<label class="pg-field ad-fixed"><span>Default profile</span>
|
|
1453
|
+
<select id="adProfile"><option value="">(none)</option></select>
|
|
1454
|
+
</label>
|
|
1455
|
+
<label class="pg-field ad-fixed"><span>Default pack</span>
|
|
1456
|
+
<select id="adPack"><option value="">(none)</option></select>
|
|
1457
|
+
</label>
|
|
1458
|
+
<label class="pg-field"><span>Port</span>
|
|
1459
|
+
<input id="adPort" class="linput" type="number" min="1" max="65535" step="1" inputmode="numeric" aria-describedby="adPortErr">
|
|
1460
|
+
<small id="adPortErr" class="ad-err" role="alert"></small>
|
|
1461
|
+
</label>
|
|
1462
|
+
<div class="pg-field"><span>Callers choose the account</span>
|
|
1463
|
+
<button id="adClient" type="button" class="toggle on" data-kind="bool" role="switch" aria-checked="true" aria-label="Let requests pick their own account, profile or pack">On</button>
|
|
1464
|
+
</div>
|
|
1465
|
+
</div>
|
|
1466
|
+
<div id="adRotationHint" class="set-hint"></div>
|
|
1467
|
+
<div class="pg-field" id="adPoolWrap"><span>Accounts in the rotation <small>(none checked = every account; fill-first uses them in the order you check them)</small></span>
|
|
1468
|
+
<div id="adPool" class="ad-pool"></div>
|
|
1469
|
+
</div>
|
|
1470
|
+
<div class="ladd" style="gap:8px;align-items:center">
|
|
1471
|
+
<button id="adSave" type="button" class="toggle on" disabled>Save defaults</button>
|
|
1472
|
+
<button id="adDiscard" type="button" class="laddb" style="display:none">Discard changes</button>
|
|
1473
|
+
<span id="adNote" class="set-hint"></span>
|
|
1474
|
+
</div>
|
|
1475
|
+
</div>
|
|
1476
|
+
<div class="set-cat-h" style="margin:18px 0 8px">Playground</div>
|
|
1424
1477
|
<div class="panel">
|
|
1425
1478
|
<div class="pg-grid">
|
|
1426
1479
|
<label class="pg-field"><span>Run</span>
|
|
@@ -1445,13 +1498,13 @@
|
|
|
1445
1498
|
<input id="pgKey" class="linput" type="password" placeholder="Bearer token / ENIGMA_API_KEY">
|
|
1446
1499
|
</label>
|
|
1447
1500
|
<label class="pg-field"><span>Account <small>(optional)</small></span>
|
|
1448
|
-
<select id="pgAccount"><option value="">(
|
|
1501
|
+
<select id="pgAccount"><option value="">(server default)</option></select>
|
|
1449
1502
|
</label>
|
|
1450
1503
|
<label class="pg-field"><span>Profile <small>(optional)</small></span>
|
|
1451
|
-
<select id="pgProfile"><option value="">(
|
|
1504
|
+
<select id="pgProfile"><option value="">(server default)</option></select>
|
|
1452
1505
|
</label>
|
|
1453
1506
|
<label class="pg-field"><span>Pack <small>(optional)</small></span>
|
|
1454
|
-
<select id="pgPack"><option value="">(
|
|
1507
|
+
<select id="pgPack"><option value="">(server default)</option></select>
|
|
1455
1508
|
</label>
|
|
1456
1509
|
<div class="pg-field pg-tools"><span>Agent tools <small>(Read/Write/Bash...)</small></span>
|
|
1457
1510
|
<button id="pgTools" type="button" class="toggle on" data-kind="bool" role="switch" aria-checked="true" aria-label="Enable agent tools">On</button>
|
|
@@ -1460,10 +1513,6 @@
|
|
|
1460
1513
|
<input id="pgImage" type="file" accept="image/*" class="linput">
|
|
1461
1514
|
</label>
|
|
1462
1515
|
</div>
|
|
1463
|
-
<div class="ladd" style="gap:8px;align-items:center">
|
|
1464
|
-
<button id="pgSaveDefault" type="button" class="laddb">Save context as <code>enigma api</code> default</button>
|
|
1465
|
-
<span id="pgDefaultNote" class="set-hint"></span>
|
|
1466
|
-
</div>
|
|
1467
1516
|
<label class="pg-field"><span>System prompt <small>(optional)</small></span>
|
|
1468
1517
|
<input id="pgSystem" class="linput" placeholder="e.g. You are a terse assistant.">
|
|
1469
1518
|
</label>
|
|
@@ -3651,8 +3700,9 @@ export {
|
|
|
3651
3700
|
|
|
3652
3701
|
function renderSettings(cats) {
|
|
3653
3702
|
const el = $("settingsBody");
|
|
3654
|
-
// Skill-related settings live in the Skills tab
|
|
3655
|
-
|
|
3703
|
+
// Skill-related settings live in the Skills tab and the API server's in the Local API tab;
|
|
3704
|
+
// drop them here so they aren't shown twice.
|
|
3705
|
+
cats = (cats || []).map((c) => ({ ...c, settings: (c.settings || []).filter((s) => SKILL_SETTING_KEYS.indexOf(s.key) === -1 && !/^api-/.test(s.key)) })).filter((c) => c.settings.length);
|
|
3656
3706
|
if (!cats.length) { el.innerHTML = '<div class="empty" style="padding:24px 0">No settings available.</div>'; return; }
|
|
3657
3707
|
// One panel per category; settings laid out in a responsive 2-column grid so a
|
|
3658
3708
|
// long config list stays scannable on a wide screen.
|
|
@@ -4050,7 +4100,7 @@ export {
|
|
|
4050
4100
|
// config stores `opencode` and `rovodev`, the reader sees OpenCode and Rovo Dev.
|
|
4051
4101
|
const AGENT_LABELS = {
|
|
4052
4102
|
auto: "Auto", claude: "Claude Code", codex: "Codex",
|
|
4053
|
-
opencode: "OpenCode", rovodev: "Rovo Dev", pi: "Pi"
|
|
4103
|
+
opencode: "OpenCode", kimi: "Kimi Code", rovodev: "Rovo Dev", pi: "Pi"
|
|
4054
4104
|
};
|
|
4055
4105
|
// App label -> internal name. Normalized so every label variant maps to an icon:
|
|
4056
4106
|
// "Claude Code", "Codex"/"OpenAI Codex", "opencode"/"OpenCode", "Rovo Dev", "Pi".
|
|
@@ -5081,7 +5131,7 @@ export {
|
|
|
5081
5131
|
|
|
5082
5132
|
/**
|
|
5083
5133
|
* The palette's engine, from @enigmax/primitives: subsequence ranking (so "qgate"
|
|
5084
|
-
* finds "Quality gate" and "
|
|
5134
|
+
* finds "Quality gate" and "lapi" finds "Local API"), the debounce and the
|
|
5085
5135
|
* cancellation. The scorer this replaced is the one the package now ships, so the
|
|
5086
5136
|
* dashboard and every other consumer rank the same way.
|
|
5087
5137
|
*/
|
|
@@ -6333,30 +6383,149 @@ export {
|
|
|
6333
6383
|
if (!opts.length) opts.push('<option value="claude" data-icon="claude">Claude Code (default)</option>');
|
|
6334
6384
|
sel.innerHTML = opts.join("");
|
|
6335
6385
|
syncDD(sel);
|
|
6336
|
-
// Context selectors: account (labeled by tool), profile, pack.
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
const
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
acc.value = def.account || ""; syncDD(acc);
|
|
6350
|
-
prof.value = def.profile || ""; syncDD(prof);
|
|
6351
|
-
pk.value = def.pack || ""; syncDD(pk);
|
|
6352
|
-
renderPgDefault(def);
|
|
6353
|
-
$("pgBase").placeholder = "http://127.0.0.1:" + (info.apiPort || 8000);
|
|
6386
|
+
// Context selectors: account (labeled by tool), profile, pack. Blank = whatever the
|
|
6387
|
+
// server defaults below pick (the rotation included), so a test matches `enigma api`.
|
|
6388
|
+
fillContextSelect($("pgAccount"), "(server default)", accountOptions(info.accounts));
|
|
6389
|
+
fillContextSelect($("pgProfile"), "(server default)", (info.profiles || []).map((n) => ({ value: n, label: n })));
|
|
6390
|
+
fillContextSelect($("pgPack"), "(server default)", packOptions(info.packs));
|
|
6391
|
+
fillContextSelect($("adAccount"), "(active account)", accountOptions(info.accounts));
|
|
6392
|
+
fillContextSelect($("adProfile"), "(none)", (info.profiles || []).map((n) => ({ value: n, label: n })));
|
|
6393
|
+
fillContextSelect($("adPack"), "(none)", packOptions(info.packs));
|
|
6394
|
+
const rot = $("adRotation");
|
|
6395
|
+
rot.innerHTML = (info.rotations || ["off"]).map((r) => '<option value="' + esc(r) + '">' + esc(prettyChoice(r)) + "</option>").join("");
|
|
6396
|
+
adInfo = info;
|
|
6397
|
+
adApply(info.defaults);
|
|
6398
|
+
$("pgBase").placeholder = "http://127.0.0.1:" + ((info.defaults && info.defaults.port) || info.apiPort || 8000);
|
|
6354
6399
|
pgSyncMode();
|
|
6355
6400
|
}
|
|
6356
|
-
function
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6401
|
+
function fillContextSelect(sel, blank, items) {
|
|
6402
|
+
sel.innerHTML = '<option value="">' + esc(blank) + "</option>" + items.map((o) =>
|
|
6403
|
+
'<option value="' + esc(o.value) + '"' + (o.icon ? ' data-icon="' + esc(o.icon) + '"' : "") + ">" + esc(o.label) + "</option>").join("");
|
|
6404
|
+
syncDD(sel);
|
|
6405
|
+
}
|
|
6406
|
+
function accountOptions(accounts) {
|
|
6407
|
+
return (accounts || []).map((a) => ({ value: a.name, label: a.name + " (" + agentLabel(a.tool) + ")", icon: agentName(a.tool) }));
|
|
6408
|
+
}
|
|
6409
|
+
function packOptions(packs) {
|
|
6410
|
+
return (packs || []).map((p) => ({ value: p.id, label: p.label + (p.installed ? "" : " - not installed") }));
|
|
6411
|
+
}
|
|
6412
|
+
|
|
6413
|
+
// --- Server defaults for `enigma api` (account, rotation, pool, caller choice, port) ---
|
|
6414
|
+
let adInfo = null;
|
|
6415
|
+
let adSaved = null;
|
|
6416
|
+
let adPicked = [];
|
|
6417
|
+
const AD_ROTATION_HINT = {
|
|
6418
|
+
"off": "Requests that name no account use the default account, profile or pack below.",
|
|
6419
|
+
"round-robin": "Requests that name no account take turns across the accounts below. One that hits a usage limit is retried on the next.",
|
|
6420
|
+
"least-used": "Requests that name no account go to the one with the least use in its current 5-hour window. One that hits a usage limit is retried on the next.",
|
|
6421
|
+
"fill-first": "Requests use the first account below until it hits a usage limit, then the next one.",
|
|
6422
|
+
"random": "Requests that name no account go to a random account below. One that hits a usage limit is retried on the next.",
|
|
6423
|
+
};
|
|
6424
|
+
// Unique account names (the pool is by name; the same name can exist for several agents), saved pool order first.
|
|
6425
|
+
function adPoolNames(pool) {
|
|
6426
|
+
const all = [];
|
|
6427
|
+
((adInfo && adInfo.accounts) || []).forEach((a) => { if (all.indexOf(a.name) === -1) all.push(a.name); });
|
|
6428
|
+
return (pool || []).filter((n) => all.indexOf(n) !== -1).concat(all.filter((n) => (pool || []).indexOf(n) === -1));
|
|
6429
|
+
}
|
|
6430
|
+
function adRenderPool(pool) {
|
|
6431
|
+
const el = $("adPool");
|
|
6432
|
+
const names = adPoolNames(pool);
|
|
6433
|
+
if (!names.length) { el.innerHTML = '<span class="lnone">No accounts yet - add one in Accounts.</span>'; return; }
|
|
6434
|
+
el.innerHTML = names.map((n) => {
|
|
6435
|
+
const tools = ((adInfo && adInfo.accounts) || []).filter((a) => a.name === n).map((a) => a.tool);
|
|
6436
|
+
const icon = tools.length === 1 ? '<span class="aicon" aria-hidden="true">' + (agentSvg(agentName(tools[0])) || "") + "</span>" : "";
|
|
6437
|
+
return '<label title="' + esc(n + " (" + tools.map(agentLabel).join(", ") + ")") + '"><input type="checkbox" data-pool="' + esc(n) + '"'
|
|
6438
|
+
+ ((pool || []).indexOf(n) !== -1 ? " checked" : "") + ">" + icon + "<span>" + esc(n) + "</span></label>";
|
|
6439
|
+
}).join("");
|
|
6440
|
+
}
|
|
6441
|
+
function adApply(def) {
|
|
6442
|
+
def = def || { account: "", profile: "", pack: "", rotation: "off", pool: [], clientContext: true, port: 8000 };
|
|
6443
|
+
adSaved = { account: def.account || "", profile: def.profile || "", pack: def.pack || "", rotation: def.rotation || "off", pool: (def.pool || []).slice(), clientContext: def.clientContext !== false, port: def.port || 8000 };
|
|
6444
|
+
$("adAccount").value = adSaved.account; syncDD($("adAccount"));
|
|
6445
|
+
$("adProfile").value = adSaved.profile; syncDD($("adProfile"));
|
|
6446
|
+
$("adPack").value = adSaved.pack; syncDD($("adPack"));
|
|
6447
|
+
$("adRotation").value = adSaved.rotation; syncDD($("adRotation"));
|
|
6448
|
+
$("adPort").value = String(adSaved.port);
|
|
6449
|
+
paintSwitch($("adClient"), adSaved.clientContext, "On", "Off");
|
|
6450
|
+
adPicked = adSaved.pool.slice();
|
|
6451
|
+
adRenderPool(adSaved.pool);
|
|
6452
|
+
adSync();
|
|
6453
|
+
}
|
|
6454
|
+
function adRead() {
|
|
6455
|
+
const checked = [...$("adPool").querySelectorAll("input[data-pool]:checked")].map((i) => i.dataset.pool);
|
|
6456
|
+
// Pool order is the order accounts were checked in (fill-first walks it), saved ones first.
|
|
6457
|
+
const pool = adPicked.filter((n) => checked.indexOf(n) !== -1).concat(checked.filter((n) => adPicked.indexOf(n) === -1));
|
|
6458
|
+
return {
|
|
6459
|
+
account: $("adAccount").value, profile: $("adProfile").value, pack: $("adPack").value,
|
|
6460
|
+
rotation: $("adRotation").value, pool,
|
|
6461
|
+
clientContext: $("adClient").getAttribute("aria-checked") === "true",
|
|
6462
|
+
port: $("adPort").value.trim(),
|
|
6463
|
+
};
|
|
6464
|
+
}
|
|
6465
|
+
function adPortError(v) {
|
|
6466
|
+
if (v === "") return "Enter a port.";
|
|
6467
|
+
const n = Number(v);
|
|
6468
|
+
return Number.isInteger(n) && n >= 1 && n <= 65535 ? "" : "Use a whole number from 1 to 65535.";
|
|
6469
|
+
}
|
|
6470
|
+
// Dirty = the form differs from what was loaded, so a value changed and put back leaves Save off.
|
|
6471
|
+
function adDirty(cur) {
|
|
6472
|
+
if (!adSaved) return false;
|
|
6473
|
+
return cur.account !== adSaved.account || cur.profile !== adSaved.profile || cur.pack !== adSaved.pack
|
|
6474
|
+
|| cur.rotation !== adSaved.rotation || cur.clientContext !== adSaved.clientContext
|
|
6475
|
+
|| Number(cur.port) !== adSaved.port || cur.pool.join("\n") !== adSaved.pool.join("\n");
|
|
6476
|
+
}
|
|
6477
|
+
function adSync() {
|
|
6478
|
+
const cur = adRead();
|
|
6479
|
+
const rotating = cur.rotation !== "off";
|
|
6480
|
+
$("adRotationHint").textContent = AD_ROTATION_HINT[cur.rotation] || "";
|
|
6481
|
+
$("adPool").classList.toggle("off", !rotating);
|
|
6482
|
+
$("adPoolWrap").style.display = rotating ? "" : "none";
|
|
6483
|
+
document.querySelectorAll("#adPanel .ad-fixed").forEach((el) => el.classList.toggle("off", rotating));
|
|
6484
|
+
const portErr = adPortError(cur.port);
|
|
6485
|
+
$("adPortErr").textContent = portErr;
|
|
6486
|
+
$("adPort").setAttribute("aria-invalid", portErr ? "true" : "false");
|
|
6487
|
+
const dirty = adDirty(cur);
|
|
6488
|
+
$("adSave").disabled = !dirty || !!portErr;
|
|
6489
|
+
$("adSave").title = portErr ? portErr : (dirty ? "" : "Nothing changed");
|
|
6490
|
+
$("adDiscard").style.display = dirty ? "" : "none";
|
|
6491
|
+
}
|
|
6492
|
+
async function adSave() {
|
|
6493
|
+
const cur = adRead();
|
|
6494
|
+
if (adPortError(cur.port) || !adDirty(cur)) return;
|
|
6495
|
+
const btn = $("adSave");
|
|
6496
|
+
btn.disabled = true;
|
|
6497
|
+
setNote("adNote", "Saving defaults...");
|
|
6498
|
+
try {
|
|
6499
|
+
const out = await apiJson("/api/playground", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ op: "set-defaults", ...cur, port: Number(cur.port) }) });
|
|
6500
|
+
if (!out.ok) {
|
|
6501
|
+
setNote("adNote", "Could not save: " + (out.error || "error"), "err");
|
|
6502
|
+
const focus = { account: "adAccount", profile: "adProfile", pack: "adPack", rotation: "adRotation", port: "adPort", clientContext: "adClient" }[out.field];
|
|
6503
|
+
if (focus && $(focus)) $(focus).focus();
|
|
6504
|
+
adSync();
|
|
6505
|
+
return;
|
|
6506
|
+
}
|
|
6507
|
+
adApply(out.defaults);
|
|
6508
|
+
setNote("adNote", "Saved. Restart 'enigma api' to use them.", "ok");
|
|
6509
|
+
} catch (e) {
|
|
6510
|
+
setNote("adNote", "Could not save: " + e.message, "err");
|
|
6511
|
+
adSync();
|
|
6512
|
+
}
|
|
6513
|
+
}
|
|
6514
|
+
function wireApiDefaults() {
|
|
6515
|
+
["adAccount", "adProfile", "adPack", "adRotation"].forEach((id) => $(id).addEventListener("change", adSync));
|
|
6516
|
+
$("adPort").addEventListener("input", adSync);
|
|
6517
|
+
$("adPool").addEventListener("change", (e) => {
|
|
6518
|
+
const box = e.target.closest("input[data-pool]");
|
|
6519
|
+
if (box) {
|
|
6520
|
+
adPicked = adPicked.filter((n) => n !== box.dataset.pool);
|
|
6521
|
+
if (box.checked) adPicked.push(box.dataset.pool);
|
|
6522
|
+
}
|
|
6523
|
+
adSync();
|
|
6524
|
+
});
|
|
6525
|
+
const cl = $("adClient");
|
|
6526
|
+
cl.addEventListener("click", () => { paintSwitch(cl, cl.getAttribute("aria-checked") !== "true", "On", "Off"); adSync(); });
|
|
6527
|
+
$("adSave").addEventListener("click", adSave);
|
|
6528
|
+
$("adDiscard").addEventListener("click", () => adApply(adSaved));
|
|
6360
6529
|
}
|
|
6361
6530
|
function pgSyncMode() {
|
|
6362
6531
|
const http = $("pgMode").value === "http";
|
|
@@ -6408,6 +6577,7 @@ export {
|
|
|
6408
6577
|
const meta = [];
|
|
6409
6578
|
if (out.format) meta.push(out.format === "anthropic" ? "Anthropic" : "OpenAI");
|
|
6410
6579
|
if (out.tool) meta.push(out.tool);
|
|
6580
|
+
if (out.account) meta.push("account " + out.account);
|
|
6411
6581
|
if (typeof out.status === "number") meta.push("HTTP " + out.status);
|
|
6412
6582
|
if (out.usage) meta.push(out.usage.input + " in / " + out.usage.output + " out");
|
|
6413
6583
|
$("pgMeta").textContent = meta.join(" · ");
|
|
@@ -6415,17 +6585,9 @@ export {
|
|
|
6415
6585
|
$("pgRaw").textContent = out.response ? (typeof out.response === "string" ? out.response : JSON.stringify(out.response, null, 2)) : "";
|
|
6416
6586
|
$("pgCurl").textContent = out.curl || "";
|
|
6417
6587
|
}
|
|
6418
|
-
async function pgSaveDefault() {
|
|
6419
|
-
const payload = { op: "set-defaults", account: $("pgAccount").value || "", profile: $("pgProfile").value || "", pack: $("pgPack").value || "" };
|
|
6420
|
-
try {
|
|
6421
|
-
const out = await (await fetch("/api/playground", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(payload) })).json();
|
|
6422
|
-
if (out.ok) { renderPgDefault(out.defaults); toast.success("Saved as the enigma api default"); }
|
|
6423
|
-
else toast.error(out.error || "Could not save the default");
|
|
6424
|
-
} catch { toast.error("Could not reach the dashboard server."); }
|
|
6425
|
-
}
|
|
6426
6588
|
function wirePlayground() {
|
|
6589
|
+
wireApiDefaults();
|
|
6427
6590
|
$("pgSend").addEventListener("click", pgSend);
|
|
6428
|
-
$("pgSaveDefault").addEventListener("click", pgSaveDefault);
|
|
6429
6591
|
const tog = $("pgTools");
|
|
6430
6592
|
tog.addEventListener("click", () => paintSwitch(tog, tog.getAttribute("aria-checked") !== "true", "On", "Off"));
|
|
6431
6593
|
$("pgMode").addEventListener("change", pgSyncMode);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@enigmax/dashboard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.115",
|
|
4
4
|
"description": "Local browser dashboard UI for enigma: the static page and chart assets enigma serves on its loopback dashboard (savings, real tool usage, in-browser settings). Installed on demand by enigma-cli; not a runtime dependency.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|