pi-mega-compact 0.7.8 โ 0.8.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 +11 -12
- package/dist/extensions/dashboard-server/html.js +1023 -0
- package/dist/extensions/dashboard-server/html.test.js +41 -0
- package/dist/extensions/dashboard-server/index-reader.js +133 -0
- package/dist/extensions/dashboard-server/server.js +530 -0
- package/dist/extensions/dashboard-server/server.test.js +120 -0
- package/dist/extensions/dashboard-server/snapshot.js +43 -0
- package/dist/extensions/dashboard-server/state.js +30 -0
- package/dist/extensions/dashboard-server/types.js +5 -0
- package/dist/extensions/dashboard-server-s32.test.js +181 -0
- package/dist/extensions/dashboard-server.js +7 -1315
- package/dist/extensions/mega-commands.js +162 -134
- package/dist/extensions/mega-compact.js +3 -0
- package/dist/extensions/mega-compact.test.js +90 -21
- package/dist/extensions/mega-conflict-cmds.js +5 -1
- package/dist/extensions/mega-dashboard-cmds.js +29 -22
- package/dist/extensions/mega-db-cmds.js +11 -2
- package/dist/extensions/mega-events/agent-handlers.js +222 -0
- package/dist/extensions/mega-events/compact-handlers.js +162 -0
- package/dist/extensions/mega-events/context-handler.js +249 -0
- package/dist/extensions/mega-events/register.js +21 -0
- package/dist/extensions/mega-events/session-handlers.js +142 -0
- package/dist/extensions/mega-events.js +15 -699
- package/dist/extensions/mega-game-cmds.js +106 -0
- package/dist/extensions/mega-game-cmds.test.js +113 -0
- package/dist/extensions/mega-pipeline/compact.js +324 -0
- package/dist/extensions/mega-pipeline/memory-review.js +38 -0
- package/dist/extensions/mega-pipeline/recall.js +147 -0
- package/dist/extensions/mega-pipeline.js +9 -480
- package/dist/extensions/mega-runtime/helpers.js +40 -0
- package/dist/extensions/mega-runtime/query.js +29 -0
- package/dist/extensions/mega-runtime/state.js +877 -0
- package/dist/extensions/mega-runtime/state.test.js +171 -0
- package/dist/extensions/mega-runtime/widget.js +270 -0
- package/dist/extensions/mega-runtime/widget.test.js +160 -0
- package/dist/extensions/mega-runtime.js +15 -947
- package/dist/src/config/themes.js +84 -0
- package/dist/src/config/themes.test.js +94 -0
- package/dist/src/game/scoring.js +105 -0
- package/dist/src/game/scoring.test.js +98 -0
- package/dist/src/store/sqlite/checkpoints.js +145 -0
- package/dist/src/store/sqlite/dedup-mirror.js +64 -0
- package/dist/src/store/sqlite/foundation.js +38 -0
- package/dist/src/store/sqlite/game-achievements.js +111 -0
- package/dist/src/store/sqlite/game-achievements.test.js +67 -0
- package/dist/src/store/sqlite/game-scores.js +105 -0
- package/dist/src/store/sqlite/game-scores.test.js +106 -0
- package/dist/src/store/sqlite/game-state.js +54 -0
- package/dist/src/store/sqlite/game-state.test.js +76 -0
- package/dist/src/store/sqlite/global-index.js +224 -0
- package/dist/src/store/sqlite/maintenance.js +235 -0
- package/dist/src/store/sqlite/memories.js +164 -0
- package/dist/src/store/sqlite/meta.js +82 -0
- package/dist/src/store/sqlite/model-snapshots.js +47 -0
- package/dist/src/store/sqlite/raptor.js +57 -0
- package/dist/src/store/sqlite/raw-transcript.js +134 -0
- package/dist/src/store/sqlite/schema.js +294 -0
- package/dist/src/store/sqlite/session-state.js +28 -0
- package/dist/src/store/sqlite/stats.js +66 -0
- package/dist/src/store/sqlite/utils.js +120 -0
- package/dist/src/store/sqlite.js +23 -1607
- package/extensions/dashboard-server/html.test.ts +50 -0
- package/extensions/dashboard-server/html.ts +1026 -0
- package/extensions/dashboard-server/index-reader.ts +130 -0
- package/extensions/dashboard-server/server.test.ts +131 -0
- package/extensions/dashboard-server/server.ts +505 -0
- package/extensions/dashboard-server/snapshot.ts +44 -0
- package/extensions/dashboard-server/state.ts +33 -0
- package/extensions/dashboard-server/types.ts +134 -0
- package/extensions/dashboard-server-s32.test.ts +195 -0
- package/extensions/dashboard-server.ts +7 -1431
- package/extensions/mega-commands.ts +33 -10
- package/extensions/mega-compact.test.ts +198 -43
- package/extensions/mega-compact.ts +3 -0
- package/extensions/mega-conflict-cmds.ts +6 -2
- package/extensions/mega-dashboard-cmds.ts +30 -23
- package/extensions/mega-db-cmds.ts +11 -3
- package/extensions/mega-events/agent-handlers.ts +262 -0
- package/extensions/mega-events/compact-handlers.ts +192 -0
- package/extensions/mega-events/context-handler.ts +290 -0
- package/extensions/mega-events/register.ts +37 -0
- package/extensions/mega-events/session-handlers.ts +165 -0
- package/extensions/mega-events.ts +15 -780
- package/extensions/mega-game-cmds.test.ts +137 -0
- package/extensions/mega-game-cmds.ts +122 -0
- package/extensions/mega-pipeline/compact.ts +366 -0
- package/extensions/mega-pipeline/memory-review.ts +46 -0
- package/extensions/mega-pipeline/recall.ts +165 -0
- package/extensions/mega-pipeline.ts +9 -537
- package/extensions/mega-runtime/helpers.ts +68 -0
- package/extensions/mega-runtime/query.ts +29 -0
- package/extensions/mega-runtime/state.test.ts +171 -0
- package/extensions/mega-runtime/state.ts +967 -0
- package/extensions/mega-runtime/widget.test.ts +185 -0
- package/extensions/mega-runtime/widget.ts +359 -0
- package/extensions/mega-runtime.ts +15 -1093
- package/package.json +4 -3
- package/src/config/themes.test.ts +116 -0
- package/src/config/themes.ts +124 -0
- package/src/game/scoring.test.ts +103 -0
- package/src/game/scoring.ts +158 -0
- package/src/store/sqlite/checkpoints.ts +204 -0
- package/src/store/sqlite/dedup-mirror.ts +114 -0
- package/src/store/sqlite/foundation.ts +63 -0
- package/src/store/sqlite/game-achievements.test.ts +80 -0
- package/src/store/sqlite/game-achievements.ts +147 -0
- package/src/store/sqlite/game-scores.test.ts +132 -0
- package/src/store/sqlite/game-scores.ts +168 -0
- package/src/store/sqlite/game-state.test.ts +89 -0
- package/src/store/sqlite/game-state.ts +87 -0
- package/src/store/sqlite/global-index.ts +305 -0
- package/src/store/sqlite/maintenance.ts +294 -0
- package/src/store/sqlite/memories.ts +217 -0
- package/src/store/sqlite/meta.ts +108 -0
- package/src/store/sqlite/model-snapshots.ts +83 -0
- package/src/store/sqlite/raptor.ts +107 -0
- package/src/store/sqlite/raw-transcript.ts +221 -0
- package/src/store/sqlite/schema.ts +305 -0
- package/src/store/sqlite/session-state.ts +38 -0
- package/src/store/sqlite/stats.ts +127 -0
- package/src/store/sqlite/utils.ts +125 -0
- package/src/store/sqlite.ts +23 -2204
|
@@ -0,0 +1,1023 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dashboard-server/html.ts โ single-page HTML dashboard template.
|
|
3
|
+
*/
|
|
4
|
+
import { dashboardServerVersion } from "./state.js";
|
|
5
|
+
import { THEMES, DEFAULT_THEME, themeDataBlock } from "../../src/config/themes.js";
|
|
6
|
+
// Server-side injection of every theme's :root[data-theme="<id>"] CSS-var
|
|
7
|
+
// override block so the client can switch themes instantly by setting
|
|
8
|
+
// document.documentElement.dataset.theme. PREVENT-PI-004: pure local, no network.
|
|
9
|
+
const THEME_STYLE_BLOCKS = THEMES.map(themeDataBlock).join("\n");
|
|
10
|
+
const THEME_OPTIONS = THEMES.map((t) => `<option value="${t.id}">${t.label}</option>`).join("");
|
|
11
|
+
export function dashboardHtml(tierName) {
|
|
12
|
+
return `<!DOCTYPE html>
|
|
13
|
+
<html lang="en" data-theme="${DEFAULT_THEME}">
|
|
14
|
+
<head>
|
|
15
|
+
<meta charset="utf-8">
|
|
16
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
17
|
+
<title>mega-compact dashboard</title>
|
|
18
|
+
<style>
|
|
19
|
+
/* S32: CSS-variable skin. Base :root holds the CURRENT hardcoded hexes so the
|
|
20
|
+
default (data-theme="transparent") is visually identical to the pre-S32
|
|
21
|
+
dashboard. Each :root[data-theme="<id>"] block (injected below from
|
|
22
|
+
src/config/themes.ts) overrides the 4 theme vars (--bg/--fg/--accent/--mega).
|
|
23
|
+
Non-theme palette tokens (card bg, borders, muted text, meter colors) stay
|
|
24
|
+
fixed so visual parity is preserved under the transparent default. */
|
|
25
|
+
:root {
|
|
26
|
+
--bg: #0d1117; /* page background (transparent theme -> transparent) */
|
|
27
|
+
--fg: #c9d1d9; /* default foreground */
|
|
28
|
+
--accent: #3fb950; /* accent (bars, ok values, on-bullets) */
|
|
29
|
+
--mega: #f0883e; /* MEGA CACHE highlight */
|
|
30
|
+
--fg-strong: #f0f6fc; /* headings, strong values */
|
|
31
|
+
--muted: #8b949e; /* labels, sub-text */
|
|
32
|
+
--dim: #484f58; /* timestamps, empty states */
|
|
33
|
+
--card-bg: #161b22; /* card / events / table background */
|
|
34
|
+
--border: #30363d; /* card / table borders */
|
|
35
|
+
--border-soft: #21262d; /* meter track, ev borders, table row borders */
|
|
36
|
+
--blue: #1f6feb; /* tier pill, active tab */
|
|
37
|
+
--green-bar: #238636; /* green meter fill, safe border */
|
|
38
|
+
--yellow-bar: #d29922; /* yellow meter fill, na-bullet */
|
|
39
|
+
--red-bar: #f85149; /* red meter fill, offline banner */
|
|
40
|
+
--purple: #a371f7; /* recall events, repo model, cost h2 */
|
|
41
|
+
--purple-pill: #6e40c9; /* model pill bg */
|
|
42
|
+
--hover-row: #1c2128; /* table row hover */
|
|
43
|
+
--link: #58a6ff; /* repo-link hover */
|
|
44
|
+
--th-bg: #0d1117; /* table header background */
|
|
45
|
+
}
|
|
46
|
+
/* html backdrop keeps the page dark even when --bg is transparent (transparent
|
|
47
|
+
theme) so there's no white flash โ visually identical to the pre-S32 fill. */
|
|
48
|
+
html { background: #0d1117; }
|
|
49
|
+
${THEME_STYLE_BLOCKS}
|
|
50
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
51
|
+
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; background: var(--bg); color: var(--fg); padding: 24px; line-height: 1.5; }
|
|
52
|
+
h1 { font-size: 20px; font-weight: 600; margin-bottom: 20px; display: flex; align-items: center; gap: 10px; color: var(--fg-strong); }
|
|
53
|
+
h1 .tier { background: var(--blue); color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
|
|
54
|
+
h1 .version-pill { background: var(--border); color: var(--muted); font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 10px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
55
|
+
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-bottom: 20px; }
|
|
56
|
+
.card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }
|
|
57
|
+
.card.safe { border-color: var(--green-bar); }
|
|
58
|
+
.card.safe h2 { color: var(--accent); }
|
|
59
|
+
.safe-note { font-size: 12px; color: var(--muted); margin: 12px 0 0; line-height: 1.5; }
|
|
60
|
+
.value.ok { color: var(--accent); }
|
|
61
|
+
.label {
|
|
62
|
+
cursor: help;
|
|
63
|
+
border-bottom: 1px dotted var(--dim);
|
|
64
|
+
}
|
|
65
|
+
.card.legend { grid-column: 1 / -1; }
|
|
66
|
+
.legend-list { margin: 0; padding-left: 18px; color: var(--fg); }
|
|
67
|
+
.legend-list li { margin-bottom: 8px; line-height: 1.5; }
|
|
68
|
+
.legend-list b { color: var(--fg-strong); }
|
|
69
|
+
.legend-note { font-size: 12px; color: var(--muted); margin: 12px 0 0; font-style: italic; }
|
|
70
|
+
.card h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); margin-bottom: 12px; font-weight: 600; }
|
|
71
|
+
.meter-track { background: var(--border-soft); border-radius: 4px; height: 20px; overflow: hidden; margin: 8px 0; }
|
|
72
|
+
.meter-fill { height: 100%; border-radius: 4px; transition: width .6s ease; min-width: 2px; }
|
|
73
|
+
.meter-green { background: var(--green-bar); }
|
|
74
|
+
.meter-yellow { background: var(--yellow-bar); }
|
|
75
|
+
.meter-red { background: var(--red-bar); }
|
|
76
|
+
.meter-label { font-size: 24px; font-weight: 700; color: var(--fg-strong); }
|
|
77
|
+
.meter-sub { font-size: 12px; color: var(--muted); }
|
|
78
|
+
.status-row { display: flex; align-items: center; gap: 8px; margin: 6px 0; font-size: 14px; }
|
|
79
|
+
.status-row .bullet { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
|
|
80
|
+
.bullet-on { background: var(--accent); box-shadow: 0 0 6px #3fb95088; }
|
|
81
|
+
.bullet-off { background: var(--dim); }
|
|
82
|
+
.bullet-na { background: var(--yellow-bar); }
|
|
83
|
+
.state-text { font-size: 13px; color: var(--muted); margin-top: 8px; font-family: monospace; }
|
|
84
|
+
.stat-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 14px; }
|
|
85
|
+
.stat-grid .label { color: var(--muted); }
|
|
86
|
+
.stat-grid .value { color: var(--fg-strong); font-weight: 600; font-family: monospace; }
|
|
87
|
+
.conf-grid { display: grid; grid-template-columns: auto 1fr; gap: 4px 12px; font-size: 14px; }
|
|
88
|
+
.conf-grid .label { color: var(--muted); }
|
|
89
|
+
.conf-grid .value { color: var(--fg-strong); font-family: monospace; }
|
|
90
|
+
.events { background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }
|
|
91
|
+
.events h2 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); margin-bottom: 12px; font-weight: 600; }
|
|
92
|
+
.events-wrap { max-height: 240px; overflow-y: auto; font-family: monospace; font-size: 12px; }
|
|
93
|
+
.ev { padding: 3px 0; border-bottom: 1px solid var(--border-soft); display: flex; gap: 8px; align-items: baseline; }
|
|
94
|
+
.ev:last-child { border-bottom: none; }
|
|
95
|
+
.ev-type { font-weight: 700; min-width: 70px; text-align: right; }
|
|
96
|
+
.ev-type-compact { color: var(--accent); }
|
|
97
|
+
.ev-type-recall { color: var(--purple); }
|
|
98
|
+
.ev-time { color: var(--dim); font-size: 10px; min-width: 80px; }
|
|
99
|
+
.ev-detail { color: var(--muted); flex: 1; }
|
|
100
|
+
.updated { font-size: 11px; color: var(--dim); margin-top: 16px; text-align: right; }
|
|
101
|
+
.empty { color: var(--dim); font-style: italic; font-size: 13px; padding: 8px 0; }
|
|
102
|
+
.offline-banner { background: #f8514922; border: 1px solid var(--red-bar); border-radius: 6px; padding: 10px 16px; margin-bottom: 16px; font-size: 13px; color: var(--red-bar); display: none; }
|
|
103
|
+
.tabs { display: flex; gap: 8px; margin-bottom: 20px; }
|
|
104
|
+
.tab { background: var(--card-bg); color: var(--muted); border: 1px solid var(--border); border-radius: 6px; padding: 8px 16px; font-size: 13px; font-weight: 600; cursor: pointer; transition: all .15s ease; }
|
|
105
|
+
.tab:hover { color: var(--fg); border-color: var(--dim); }
|
|
106
|
+
.tab.active { background: var(--blue); color: #fff; border-color: var(--blue); }
|
|
107
|
+
.tab-panel { display: none; }
|
|
108
|
+
.tab-panel.active { display: block; }
|
|
109
|
+
/* S32: header settings strip (game-mode toggle + theme + TUI display-mode) */
|
|
110
|
+
.settings-strip { display: flex; align-items: center; gap: 16px; margin-bottom: 20px; padding: 10px 14px; background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; font-size: 13px; color: var(--muted); flex-wrap: wrap; }
|
|
111
|
+
.settings-strip label { display: flex; align-items: center; gap: 6px; font-weight: 600; }
|
|
112
|
+
.settings-strip input[type="checkbox"] { width: 16px; height: 16px; cursor: pointer; accent-color: var(--accent); }
|
|
113
|
+
.settings-strip select { background: var(--th-bg); color: var(--fg); border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px; font-size: 13px; cursor: pointer; }
|
|
114
|
+
.settings-strip select:hover { border-color: var(--dim); }
|
|
115
|
+
.summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 16px; margin-bottom: 20px; }
|
|
116
|
+
.summary-card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }
|
|
117
|
+
.summary-card .num { font-size: 24px; font-weight: 700; color: var(--fg-strong); }
|
|
118
|
+
.summary-card .lbl { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; margin-top: 4px; }
|
|
119
|
+
table.repos { width: 100%; border-collapse: collapse; background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
|
120
|
+
table.repos th, table.repos td { text-align: left; padding: 10px 14px; font-size: 13px; border-bottom: 1px solid var(--border-soft); }
|
|
121
|
+
table.repos th { color: var(--muted); text-transform: uppercase; letter-spacing: .5px; font-size: 11px; background: var(--th-bg); }
|
|
122
|
+
table.repos td.num { font-family: monospace; color: var(--fg-strong); text-align: right; }
|
|
123
|
+
table.repos tr:last-child td { border-bottom: none; }
|
|
124
|
+
table.repos tr:hover td { background: var(--hover-row); }
|
|
125
|
+
.repo-model { color: var(--purple); }
|
|
126
|
+
.repo-none { color: var(--dim); font-style: italic; }
|
|
127
|
+
.updated { font-size: 11px; color: var(--dim); margin-top: 16px; text-align: right; }
|
|
128
|
+
.model-pill { background: var(--purple-pill); color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; letter-spacing: .5px; }
|
|
129
|
+
.card.cost h2 { color: var(--purple); }
|
|
130
|
+
.cost-usd { font-size: 22px; font-weight: 700; color: var(--accent); }
|
|
131
|
+
.cost-sub { font-size: 12px; color: var(--muted); margin-top: 4px; }
|
|
132
|
+
.repo-link { cursor: pointer; }
|
|
133
|
+
.repo-link:hover td { color: var(--link); }
|
|
134
|
+
.repo-detail { position: fixed; inset: 0; background: rgba(0,0,0,.6); display: none; align-items: center; justify-content: center; z-index: 50; }
|
|
135
|
+
.repo-detail.open { display: flex; }
|
|
136
|
+
.repo-detail-box { background: var(--card-bg); border: 1px solid var(--border); border-radius: 10px; padding: 24px; width: 560px; max-width: 92vw; max-height: 86vh; overflow-y: auto; }
|
|
137
|
+
.repo-detail-box h2 { font-size: 14px; color: var(--fg-strong); margin-bottom: 14px; display: flex; justify-content: space-between; align-items: center; }
|
|
138
|
+
.repo-close { cursor: pointer; color: var(--muted); font-size: 20px; line-height: 1; border: none; background: none; padding: 0 4px; }
|
|
139
|
+
.repo-close:hover { color: var(--fg-strong); }
|
|
140
|
+
.repo-path { font-size: 11px; color: var(--dim); word-break: break-all; margin: -8px 0 12px; }
|
|
141
|
+
/* S34: Game Mode tab โ leaderboards, MEGA CACHE banner, Opie unlock tile */
|
|
142
|
+
#panel-game .game-leaderboards { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin-top: 16px; }
|
|
143
|
+
#panel-game .lb-card { background: var(--card-bg); border: 1px solid var(--border); border-radius: 8px; padding: 14px; }
|
|
144
|
+
#panel-game .lb-card h3 { font-size: 12px; text-transform: uppercase; letter-spacing: .5px; color: var(--muted); margin-bottom: 10px; font-weight: 600; }
|
|
145
|
+
#panel-game table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
|
146
|
+
#panel-game td { padding: 4px 8px; border-bottom: 1px solid var(--border-soft); }
|
|
147
|
+
#panel-game td.num { text-align: right; font-family: monospace; color: var(--fg-strong); font-weight: 600; }
|
|
148
|
+
#panel-game .lb-meta { color: var(--muted); font-size: 11px; margin-left: 6px; }
|
|
149
|
+
#panel-game .repos-badge { display: inline-block; background: var(--blue); color: #fff; font-size: 11px; font-weight: 700; padding: 2px 8px; border-radius: 10px; margin-left: 8px; }
|
|
150
|
+
#mega-cache-banner { display: none; background: var(--mega); color: #1a1006; font-weight: 700; padding: 10px 14px; border-radius: 8px; margin: 12px 0; }
|
|
151
|
+
.achievement-tile { display: none; background: linear-gradient(135deg, #f0883e, #ffd700); color: #1a1006; font-weight: 700; padding: 12px 16px; border-radius: 8px; margin: 12px 0; box-shadow: 0 0 16px #f0883e88; }
|
|
152
|
+
.achievement-tile .ach-detail { display: block; font-weight: 500; font-size: 12px; margin-top: 4px; }
|
|
153
|
+
#mega-cache-toast { display: none; position: fixed; top: 16px; left: 50%; transform: translateX(-50%); background: var(--mega); color: #1a1006; font-weight: 700; padding: 10px 18px; border-radius: 8px; z-index: 1000; box-shadow: 0 4px 20px #0008; }
|
|
154
|
+
#mega-cache-toast.show { display: block; animation: mega-flash 0.6s ease-in-out 2; }
|
|
155
|
+
.level-up { animation: level-up-pulse 1.2s ease-in-out; }
|
|
156
|
+
#game-empty { color: var(--dim); font-style: italic; padding: 12px 0; }
|
|
157
|
+
@keyframes level-up-pulse { 0%{transform:scale(1)} 50%{transform:scale(1.08); filter:brightness(1.3)} 100%{transform:scale(1)} }
|
|
158
|
+
@keyframes mega-flash { 0%{background:transparent} 25%{background:var(--mega-bg, gold)} 100%{background:transparent} }
|
|
159
|
+
/* S35: achievements tile row + unlock toast */
|
|
160
|
+
.ach-tiles { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px; margin: 8px 0 16px; }
|
|
161
|
+
.ach-tile { padding: 10px 12px; border-radius: 8px; border: 1px solid var(--border); font-size: 12px; background: var(--card-bg); }
|
|
162
|
+
.ach-tile.unlocked { background: linear-gradient(135deg, #f0883e, #ffd700); color: #1a1006; font-weight: 700; box-shadow: 0 0 12px #f0883e66; }
|
|
163
|
+
.ach-tile.locked { opacity: .55; }
|
|
164
|
+
.ach-tile.just-unlocked { animation: ach-unlock-pulse 0.6s ease-out; }
|
|
165
|
+
.ach-tile .ach-detail { display: block; font-weight: 500; font-size: 11px; margin-top: 3px; }
|
|
166
|
+
#ach-toast { display: none; position: fixed; top: 16px; left: 50%; transform: translateX(-50%); background: var(--blue); color: #fff; font-weight: 700; padding: 10px 18px; border-radius: 8px; z-index: 1000; box-shadow: 0 4px 20px #0008; }
|
|
167
|
+
#ach-toast.show { display: block; }
|
|
168
|
+
@keyframes ach-unlock-pulse { 0%{transform:scale(.9);opacity:.4} 60%{transform:scale(1.05)} 100%{transform:scale(1);opacity:1} }
|
|
169
|
+
</style>
|
|
170
|
+
</head>
|
|
171
|
+
<body>
|
|
172
|
+
|
|
173
|
+
<div class="offline-banner" id="offline-banner">Dashboard data unavailable โ waiting for a pi session to write snapshot...</div>
|
|
174
|
+
|
|
175
|
+
<h1><span>mega-compact</span><span class="tier" id="hdr-tier">${tierName}</span><span class="version-pill">v${dashboardServerVersion}</span><span class="model-pill" id="hdr-model">โ</span></h1>
|
|
176
|
+
|
|
177
|
+
<div class="settings-strip">
|
|
178
|
+
<label title="Turn game mode on/off (themes the widget + dashboard)"><input type="checkbox" id="set-game-mode"> Game mode</label>
|
|
179
|
+
<label title="Visual theme (applies instantly)">Theme <select id="set-theme">${THEME_OPTIONS}</select></label>
|
|
180
|
+
<label title="TUI widget display density">TUI <select id="set-tui-mode"><option value="full">Full</option><option value="minimal">Minimal</option></select></label>
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<nav class="tabs">
|
|
184
|
+
<button class="tab active" data-tab="current">Current repo</button>
|
|
185
|
+
<button class="tab" data-tab="all">All repos</button>
|
|
186
|
+
<button class="tab" data-tab="active">Active Repos</button>
|
|
187
|
+
<button class="tab" data-tab="summary">Summary</button>
|
|
188
|
+
<button class="tab" data-tab="game">Game Mode</button>
|
|
189
|
+
</nav>
|
|
190
|
+
|
|
191
|
+
<!-- Current repo (existing single-repo view) -->
|
|
192
|
+
<div class="tab-panel" id="panel-current">
|
|
193
|
+
<div class="grid">
|
|
194
|
+
<div class="card">
|
|
195
|
+
<h2>Context Window</h2>
|
|
196
|
+
<div class="meter-label" id="ctx-pct">โ</div>
|
|
197
|
+
<div class="meter-track"><div class="meter-fill" id="ctx-bar" style="width:0%"></div></div>
|
|
198
|
+
<div class="meter-sub" id="ctx-sub">waiting for data</div>
|
|
199
|
+
</div>
|
|
200
|
+
<div class="card">
|
|
201
|
+
<h2>Trigger Status</h2>
|
|
202
|
+
<div class="status-row"><div class="bullet" id="tr-armed"></div><span>Armed (context โฅ fast gate)</span></div>
|
|
203
|
+
<div class="status-row"><div class="bullet" id="tr-ready"></div><span>Ready (tokens โฅ threshold)</span></div>
|
|
204
|
+
<div class="state-text" id="tr-state">waiting</div>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="card">
|
|
207
|
+
<h2>Vector Store</h2>
|
|
208
|
+
<div class="stat-grid">
|
|
209
|
+
<span class="label" title="A saved summary of a chunk of your conversation that was compacted to free up space.">Checkpoints</span><span class="value" id="st-count">0</span>
|
|
210
|
+
<span class="label" title="Total size of the original conversation text dropped into compaction this session, including redundant regions skipped by dedup. This is the 'in'.">Original (dropped)</span><span class="value" id="st-in">0</span>
|
|
211
|
+
<span class="label" title="Compact summaries we are currently holding as 'memory' for this session (the 'out'). Smaller is better.">Kept (summaries)</span><span class="value" id="st-kept">0</span>
|
|
212
|
+
<span class="label" title="Conversation space freed = dropped โ kept (the 'saved').">Freed (dropped โ kept)</span><span class="value" id="st-freed">0</span>
|
|
213
|
+
<span class="label" title="How many times old context was automatically brought back into the conversation because it was relevant to what you were doing.">Injected</span><span class="value" id="st-injected">0</span>
|
|
214
|
+
<span class="label" title="Of the times we recalled old context, how often it was actually on-topic.">Recall Relevance</span><span class="value" id="st-dedup">0%</span>
|
|
215
|
+
<span class="label" title="How often new content matched something we already had, so we skipped storing a duplicate copy. Higher = less wasted space.">Storage Dedup</span><span class="value" id="st-sdedup">0%</span>
|
|
216
|
+
<span class="label" title="How many duplicate chunks we collapsed into one instead of storing separately.">Collapsed</span><span class="value" id="st-collapsed">0</span>
|
|
217
|
+
<span class="label" title="The ID of the most recent saved checkpoint.">Last ID</span><span class="value" id="st-lastid">โ</span>
|
|
218
|
+
</div>
|
|
219
|
+
<div class="meter-track" style="margin-top:10px"><div class="meter-fill" id="st-compress-bar" style="width:0%"></div></div>
|
|
220
|
+
<div class="meter-sub" id="st-compress-sub">waiting for compactionโฆ</div>
|
|
221
|
+
</div>
|
|
222
|
+
<div class="card">
|
|
223
|
+
<h2>Repo (all sessions)</h2>
|
|
224
|
+
<div class="stat-grid">
|
|
225
|
+
<span class="label">Checkpoints</span><span class="value" id="rp-count">0</span>
|
|
226
|
+
<span class="label">Original (dropped)</span><span class="value" id="rp-in">0</span>
|
|
227
|
+
<span class="label">Kept (summaries)</span><span class="value" id="rp-kept">0</span>
|
|
228
|
+
<span class="label">Freed (dropped โ kept)</span><span class="value" id="rp-freed">0</span>
|
|
229
|
+
<span class="label">Sessions</span><span class="value" id="rp-sessions">0</span>
|
|
230
|
+
<span class="label">Collapsed</span><span class="value" id="rp-collapsed">0</span>
|
|
231
|
+
<span class="label">Storage Dedup</span><span class="value" id="rp-sdedup">0%</span>
|
|
232
|
+
</div>
|
|
233
|
+
<div class="meter-track" style="margin-top:10px"><div class="meter-fill" id="rp-compress-bar" style="width:0%"></div></div>
|
|
234
|
+
<div class="meter-sub" id="rp-compress-sub">waiting for compactionโฆ</div>
|
|
235
|
+
</div>
|
|
236
|
+
<div class="card safe">
|
|
237
|
+
<h2>๐ก Data Safety</h2>
|
|
238
|
+
<div class="stat-grid">
|
|
239
|
+
<span class="label">Regions Retained</span><span class="value" id="ig-retained">0</span>
|
|
240
|
+
<span class="label">Compressed-Original</span><span class="value" id="ig-bytes">0 B</span>
|
|
241
|
+
<span class="label">Dedup Duplicates</span><span class="value" id="ig-dupes">0</span>
|
|
242
|
+
<span class="label">Permanently Deleted</span><span class="value ok" id="ig-deleted">0 B</span>
|
|
243
|
+
</div>
|
|
244
|
+
<p class="safe-note">Every compacted region is kept verbatim (compressed). "Drop" = removed from the live window only. We never delete your data.</p>
|
|
245
|
+
</div>
|
|
246
|
+
<div class="card">
|
|
247
|
+
<h2>Configuration</h2>
|
|
248
|
+
<div class="conf-grid">
|
|
249
|
+
<span class="label" title="Live pressure band โ climbs lowโmega as context fills the window.">Tier (live)</span><span class="value" id="cf-tier">${tierName}</span>
|
|
250
|
+
<span class="label" title="The env-resolved base compaction preset (low/medium/high/ultra/mega) that set the token threshold.">Preset</span><span class="value" id="cf-preset">โ</span>
|
|
251
|
+
<span class="label" title="Live pressure = currentTokens / threshold โ % of the model context window (threshold fires at the tier's % of window).">Pressure</span><span class="value" id="cf-pressure">โ</span>
|
|
252
|
+
<span class="label" title="Compaction threshold = tierPct ร model context window โ mega-compact trims BELOW pi's native ~80% auto-compact for any model size.">Threshold</span><span class="value" id="cf-threshold">โ</span>
|
|
253
|
+
<span class="label" title="Fast-gate arming floor โ the live trim arms once context passes this % of the window.">Fast Gate</span><span class="value" id="cf-gate">โ</span>
|
|
254
|
+
<span class="label">Auto</span><span class="value" id="cf-auto">โ</span>
|
|
255
|
+
<span class="label">Anchor</span><span class="value" id="cf-anchor">โ</span>
|
|
256
|
+
</div>
|
|
257
|
+
</div>
|
|
258
|
+
<div class="card cost">
|
|
259
|
+
<h2>๐ฐ Model & Cost Savings</h2>
|
|
260
|
+
<div class="cost-usd" id="cost-usd">โ $0.00 saved</div>
|
|
261
|
+
<div class="cost-sub" id="cost-windows">0 context-windows extended</div>
|
|
262
|
+
<div class="stat-grid" style="margin-top:12px">
|
|
263
|
+
<span class="label" title="The model pi is currently using โ its pricing drives the cost figure.">Model</span><span class="value" id="md-name">โ</span>
|
|
264
|
+
<span class="label" title="The provider serving the model.">Provider</span><span class="value" id="md-provider">โ</span>
|
|
265
|
+
<span class="label" title="USD per input token, from the model's pricing.">Input Rate</span><span class="value" id="md-input">โ</span>
|
|
266
|
+
<span class="label" title="USD per output token, from the model's pricing.">Output Rate</span><span class="value" id="md-output">โ</span>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
<div class="card">
|
|
270
|
+
<h2>Crew / Agents</h2>
|
|
271
|
+
<div class="stat-grid">
|
|
272
|
+
<span class="label">Active Agents</span><span class="value" id="cr-agents">0</span>
|
|
273
|
+
<span class="label">Current Turn</span><span class="value" id="cr-turn">0</span>
|
|
274
|
+
<span class="label">Status</span><span class="value" id="cr-status">idle</span>
|
|
275
|
+
</div>
|
|
276
|
+
</div>
|
|
277
|
+
<div class="card legend">
|
|
278
|
+
<h2>What these numbers mean</h2>
|
|
279
|
+
<ul class="legend-list">
|
|
280
|
+
<li><b>Original (dropped)</b> โ everything compacted away (including duplicates caught by dedup). The "in."</li>
|
|
281
|
+
<li><b>Kept (summaries)</b> โ compact summaries still held as "memory" (the "out").</li>
|
|
282
|
+
<li><b>Freed</b> = dropped โ kept โ tokens saved so far (higher = better).</li>
|
|
283
|
+
<li><b>Compression %</b> โ Freed รท Dropped โ the headline efficiency number. Higher = more space reclaimed.</li>
|
|
284
|
+
<li><b>Storage dedup %</b> โ how often new content matched something already saved, so no duplicate copy was written.</li>
|
|
285
|
+
<li><b>Data safety</b> โ every compacted region is kept verbatim (compressed). Nothing is permanently deleted; you can restore any of it.</li>
|
|
286
|
+
</ul>
|
|
287
|
+
<p class="legend-note">Hover any label above for a quick explanation.</p>
|
|
288
|
+
</div>
|
|
289
|
+
<div class="card">
|
|
290
|
+
<h2>๐พ Cache Hits & Compactions</h2>
|
|
291
|
+
<div class="stat-grid">
|
|
292
|
+
<span class="label">Cache Hits (session)</span><span class="value" id="ch-session">0</span>
|
|
293
|
+
<span class="label">Cache Hits (total)</span><span class="value" id="ch-total">0</span>
|
|
294
|
+
<span class="label">Tokens Saved (session)</span><span class="value" id="ch-tok-session">0</span>
|
|
295
|
+
<span class="label">Tokens Saved (total)</span><span class="value" id="ch-tok-total">0</span>
|
|
296
|
+
<span class="label">Compactions (session)</span><span class="value" id="cp-session">0</span>
|
|
297
|
+
<span class="label">Compactions (total)</span><span class="value" id="cp-total">0</span>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
<div class="card">
|
|
301
|
+
<h2>โฑ Time Saved (est.)</h2>
|
|
302
|
+
<div class="stat-grid">
|
|
303
|
+
<span class="label">Compact (session)</span><span class="value" id="ts-compact-session">0</span>
|
|
304
|
+
<span class="label">Compact (total)</span><span class="value" id="ts-compact-total">0</span>
|
|
305
|
+
<span class="label">Cache Hit (session)</span><span class="value" id="ts-cache-session">0</span>
|
|
306
|
+
<span class="label">Cache Hit (total)</span><span class="value" id="ts-cache-total">0</span>
|
|
307
|
+
</div>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<div class="events">
|
|
312
|
+
<h2>Event Stream</h2>
|
|
313
|
+
<div class="events-wrap" id="events"><div class="empty">connectingโฆ</div></div>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<h2 style="margin-top:24px;font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px">All Repositories</h2>
|
|
317
|
+
<table class="repos">
|
|
318
|
+
<thead>
|
|
319
|
+
<tr>
|
|
320
|
+
<th>Repo</th><th>Model</th>
|
|
321
|
+
<th style="text-align:right">Checkpoints</th>
|
|
322
|
+
<th style="text-align:right">Tokens Saved</th>
|
|
323
|
+
<th style="text-align:right">Retained</th>
|
|
324
|
+
<th style="text-align:right">Last Compacted</th>
|
|
325
|
+
</tr>
|
|
326
|
+
</thead>
|
|
327
|
+
<tbody id="cur-rows"><tr><td colspan="6" class="repo-none">loadingโฆ</td></tr></tbody>
|
|
328
|
+
</table>
|
|
329
|
+
<div class="updated" id="cur-updated"></div>
|
|
330
|
+
|
|
331
|
+
<div class="updated" id="updated"></div>
|
|
332
|
+
</div><!-- /panel-current -->
|
|
333
|
+
|
|
334
|
+
<!-- Active repos (live cache-hit / compaction stats across machines) -->
|
|
335
|
+
<div class="tab-panel" id="panel-active">
|
|
336
|
+
<div class="card">
|
|
337
|
+
<h2>Active Repos โ Live Cache Hits & Compactions</h2>
|
|
338
|
+
<p class="legend-note">Repos seen within the last 30 minutes, with their per-repo cache-hit, compaction, and time-saved (est.) totals pulled live from each repo's dashboard.json.</p>
|
|
339
|
+
<table class="repos">
|
|
340
|
+
<thead>
|
|
341
|
+
<tr>
|
|
342
|
+
<th>Repo</th><th>Model</th><th>Tier</th>
|
|
343
|
+
<th style="text-align:right">Context %</th><th>State</th>
|
|
344
|
+
<th style="text-align:right">Compactions (s/t)</th>
|
|
345
|
+
<th style="text-align:right">Cache Hits (s/t)</th>
|
|
346
|
+
<th style="text-align:right">Compact s/t (s)</th>
|
|
347
|
+
<th style="text-align:right">CacheHit s/t (s)</th>
|
|
348
|
+
</tr>
|
|
349
|
+
</thead>
|
|
350
|
+
<tbody id="active-rows"><tr><td colspan="9" class="repo-none">loadingโฆ</td></tr></tbody>
|
|
351
|
+
</table>
|
|
352
|
+
<div class="updated" id="active-updated"></div>
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
|
|
356
|
+
<!-- Per-repo detail modal -->
|
|
357
|
+
<div class="repo-detail" id="repo-detail">
|
|
358
|
+
<div class="repo-detail-box">
|
|
359
|
+
<h2><span id="rd-name">Repo</span><button class="repo-close" id="rd-close" title="Close">ร</button></h2>
|
|
360
|
+
<div class="repo-path" id="rd-path"></div>
|
|
361
|
+
<div class="stat-grid">
|
|
362
|
+
<span class="label">Model</span><span class="value" id="rd-model">โ</span>
|
|
363
|
+
<span class="label">Checkpoints</span><span class="value" id="rd-cp">0</span>
|
|
364
|
+
<span class="label">Tokens Saved</span><span class="value" id="rd-saved">0</span>
|
|
365
|
+
<span class="label">Compressed-Original</span><span class="value" id="rd-bytes">0 B</span>
|
|
366
|
+
<span class="label">Last Compacted</span><span class="value" id="rd-when">โ</span>
|
|
367
|
+
<span class="label">Provider</span><span class="value" id="rd-provider">โ</span>
|
|
368
|
+
</div>
|
|
369
|
+
</div>
|
|
370
|
+
</div>
|
|
371
|
+
|
|
372
|
+
<!-- All repos (machine-wide registry from index.sqlite) -->
|
|
373
|
+
<div class="tab-panel" id="panel-all">
|
|
374
|
+
<table class="repos">
|
|
375
|
+
<thead>
|
|
376
|
+
<tr>
|
|
377
|
+
<th>Repo</th><th>Model</th>
|
|
378
|
+
<th style="text-align:right">Checkpoints</th>
|
|
379
|
+
<th style="text-align:right">Tokens Saved</th>
|
|
380
|
+
<th style="text-align:right">Retained</th>
|
|
381
|
+
<th style="text-align:right">Last Compacted</th>
|
|
382
|
+
</tr>
|
|
383
|
+
</thead>
|
|
384
|
+
<tbody id="all-rows"><tr><td colspan="6" class="repo-none">loadingโฆ</td></tr></tbody>
|
|
385
|
+
</table>
|
|
386
|
+
<div class="updated" id="all-updated"></div>
|
|
387
|
+
</div>
|
|
388
|
+
|
|
389
|
+
<!-- Summary (aggregate across all repos) -->
|
|
390
|
+
<div class="tab-panel" id="panel-summary">
|
|
391
|
+
<div class="summary-grid">
|
|
392
|
+
<div class="summary-card"><div class="num" id="sm-repos">0</div><div class="lbl">Repositories</div></div>
|
|
393
|
+
<div class="summary-card"><div class="num" id="sm-checkpoints">0</div><div class="lbl">Total Checkpoints</div></div>
|
|
394
|
+
<div class="summary-card"><div class="num" id="sm-saved">0</div><div class="lbl">Total Tokens Saved</div></div>
|
|
395
|
+
<div class="summary-card"><div class="num" id="sm-bytes">0 B</div><div class="lbl">Compressed-Original</div></div>
|
|
396
|
+
</div>
|
|
397
|
+
|
|
398
|
+
<h2 style="margin-top:24px;font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px">Savings by Model</h2>
|
|
399
|
+
<p class="legend-note" style="margin-bottom:10px">How much context & cost mega-compact has reclaimed, grouped by the model you were running. Compression ratio reflects workload/content, not model quality.</p>
|
|
400
|
+
<table class="repos">
|
|
401
|
+
<thead>
|
|
402
|
+
<tr>
|
|
403
|
+
<th>Model</th><th>Provider</th>
|
|
404
|
+
<th style="text-align:right" title="Tokens dropped from context by compaction (the input reclaimed)">Tokens In</th>
|
|
405
|
+
<th style="text-align:right" title="Tokens kept as compacted summaries still in context (the output retained)">Tokens Out</th>
|
|
406
|
+
<th style="text-align:right">Freed</th>
|
|
407
|
+
<th style="text-align:right" title="Model context window (max input tokens the model accepts)">Ctx Window</th>
|
|
408
|
+
<th style="text-align:right" title="Model max output tokens per turn">Max Out</th>
|
|
409
|
+
<th style="text-align:right" title="Reasoning-capable model">Reas.</th>
|
|
410
|
+
<th style="text-align:right" title="Distinct sessions with at least one checkpoint">Sessions</th>
|
|
411
|
+
<th style="text-align:right">Checkpoints</th>
|
|
412
|
+
<th style="text-align:right" title="USD per input token">In $/tok</th>
|
|
413
|
+
<th style="text-align:right" title="USD per output token">Out $/tok</th>
|
|
414
|
+
<th style="text-align:right">$ Saved</th>
|
|
415
|
+
<th style="text-align:right">Last Used</th>
|
|
416
|
+
</tr>
|
|
417
|
+
</thead>
|
|
418
|
+
<tbody id="bm-rows"><tr><td colspan="14" class="repo-none">loadingโฆ</td></tr></tbody>
|
|
419
|
+
</table>
|
|
420
|
+
<p class="legend-note" style="margin-top:8px">Tokens In = ฮฃ original region tokens dropped by compaction. Tokens Out = ฮฃ compacted summary tokens still retained in context. Freed = Tokens In โ Tokens Out (net context reclaimed). Ctx Window / Max Out / Reas. come from the latest captured model snapshot for each repo.</p>
|
|
421
|
+
|
|
422
|
+
<div class="updated" id="sm-updated"></div>
|
|
423
|
+
</div>
|
|
424
|
+
|
|
425
|
+
<!-- Game Mode (S34) โ high-score leaderboards, MEGA CACHE banner, Opie unlock -->
|
|
426
|
+
<div class="tab-panel" id="panel-game">
|
|
427
|
+
<h2 style="font-size:13px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px">High Scores</h2>
|
|
428
|
+
<div id="mega-cache-banner"></div>
|
|
429
|
+
<div id="mega-cache-toast"></div>
|
|
430
|
+
<div class="achievement-tile" id="opie-tile"></div>
|
|
431
|
+
<h3 style="font-size:12px;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-top:16px">Achievements</h3>
|
|
432
|
+
<div id="ach-toast"></div>
|
|
433
|
+
<div id="ach-tiles" class="ach-tiles">loadingโฆ</div>
|
|
434
|
+
<div class="game-leaderboards">
|
|
435
|
+
<div class="lb-card"><h3>Cache % <span class="repos-badge" id="repos-badge"></span></h3><table><tbody id="lb-cache"><tr><td colspan="2" class="repo-none">loadingโฆ</td></tr></tbody></table></div>
|
|
436
|
+
<div class="lb-card"><h3>Dedupe (collapsed)</h3><table><tbody id="lb-dedupe"><tr><td colspan="2" class="repo-none">loadingโฆ</td></tr></tbody></table></div>
|
|
437
|
+
<div class="lb-card"><h3>Turns <span id="turns-level"></span></h3><table><tbody id="lb-turns"><tr><td colspan="2" class="repo-none">loadingโฆ</td></tr></tbody></table></div>
|
|
438
|
+
<div class="lb-card"><h3>MEGA CACHE trophies</h3><table><tbody id="lb-mega_cache"><tr><td colspan="2" class="repo-none">loadingโฆ</td></tr></tbody></table></div>
|
|
439
|
+
</div>
|
|
440
|
+
<div id="game-empty">No scores yet โ run a session with game mode on.</div>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<script>
|
|
444
|
+
(function() {
|
|
445
|
+
var evBox = document.getElementById('events');
|
|
446
|
+
var evBuffer = [];
|
|
447
|
+
var MAX_EV = 50;
|
|
448
|
+
var offlineBanner = document.getElementById('offline-banner');
|
|
449
|
+
|
|
450
|
+
function bullet(el, on, na) {
|
|
451
|
+
el.className = 'bullet ' + (na ? 'bullet-na' : on ? 'bullet-on' : 'bullet-off');
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function renderSnapshot(d) {
|
|
455
|
+
if (!d || !d.updatedAt) { offlineBanner.style.display = 'block'; return; }
|
|
456
|
+
offlineBanner.style.display = 'none';
|
|
457
|
+
|
|
458
|
+
var pct = d.context.percent || 0;
|
|
459
|
+
document.getElementById('ctx-pct').textContent = pct + '%';
|
|
460
|
+
var bar = document.getElementById('ctx-bar');
|
|
461
|
+
bar.style.width = Math.max(pct, 1) + '%';
|
|
462
|
+
bar.className = 'meter-fill ' + (pct >= 90 ? 'meter-red' : pct >= 70 ? 'meter-yellow' : 'meter-green');
|
|
463
|
+
var tok = d.context.tokens != null ? d.context.tokens.toLocaleString() : '?';
|
|
464
|
+
var win = d.context.contextWindow ? d.context.contextWindow.toLocaleString() : '?';
|
|
465
|
+
document.getElementById('ctx-sub').textContent = tok + ' / ' + win + ' tokens';
|
|
466
|
+
|
|
467
|
+
bullet(document.getElementById('tr-armed'), d.trigger.armed, false);
|
|
468
|
+
bullet(document.getElementById('tr-ready'), d.trigger.ready, !d.trigger.armed);
|
|
469
|
+
var state = d.trigger.ready ? 'THRESHOLD EXCEEDED โ compacting next event' :
|
|
470
|
+
d.trigger.armed ? 'past fast gate โ monitoring token count' : 'idle โ below fast gate';
|
|
471
|
+
document.getElementById('tr-state').textContent = state;
|
|
472
|
+
|
|
473
|
+
// ---- Vector Store โ reconciled token accounting (same formula as widget) -
|
|
474
|
+
document.getElementById('st-count').textContent = d.store.checkpointCount;
|
|
475
|
+
// Compression block from the snapshot (Freed = In โ Out, single formula).
|
|
476
|
+
var c = d.compression || {};
|
|
477
|
+
var sess = c.session || { tokensIn:0, tokensOut:0, tokensFreed:0, compressionPct:0, dedupPct:0 };
|
|
478
|
+
var cRepo = c.repo || { tokensIn:0, tokensOut:0, tokensFreed:0, compressionPct:0, dedupPct:0 };
|
|
479
|
+
document.getElementById('st-in').textContent = sess.tokensIn.toLocaleString();
|
|
480
|
+
document.getElementById('st-kept').textContent = sess.tokensOut.toLocaleString();
|
|
481
|
+
document.getElementById('st-freed').textContent = sess.tokensFreed.toLocaleString();
|
|
482
|
+
var sp = sess.compressionPct || 0;
|
|
483
|
+
document.getElementById('st-compress-bar').style.width = Math.max(sp * 100, 0.5) + '%';
|
|
484
|
+
document.getElementById('st-compress-bar').className = 'meter-fill ' + (sp >= 0.9 ? 'meter-green' : sp >= 0.6 ? 'meter-yellow' : 'meter-red');
|
|
485
|
+
document.getElementById('st-compress-sub').textContent = (sp * 100 >= 10 ? Math.round(sp * 100) : (sp * 100).toFixed(1)) + '% tokens saved ยท dedup: ' + (sess.dedupPct * 100 >= 10 ? Math.round(sess.dedupPct * 100) : (sess.dedupPct * 100).toFixed(1)) + '%';
|
|
486
|
+
// ------
|
|
487
|
+
document.getElementById('st-injected').textContent = d.store.injectedCount;
|
|
488
|
+
document.getElementById('st-dedup').textContent = Math.round(d.store.dedupHitRate * 100) + '%';
|
|
489
|
+
var sdr = d.store.storageDedupRate || 0;
|
|
490
|
+
document.getElementById('st-sdedup').textContent = (sdr * 100 >= 10 ? Math.round(sdr * 100) : (sdr * 100).toFixed(1)) + '%';
|
|
491
|
+
document.getElementById('st-collapsed').textContent = d.store.dedupCollapsed || 0;
|
|
492
|
+
document.getElementById('st-lastid').textContent = d.session.lastCheckpointId || 'โ';
|
|
493
|
+
|
|
494
|
+
// ---- Repo (all sessions) โ same compression fields, repo scope ----------
|
|
495
|
+
document.getElementById('rp-count').textContent = (d.repo && d.repo.checkpointCount || 0).toLocaleString();
|
|
496
|
+
document.getElementById('rp-in').textContent = cRepo.tokensIn.toLocaleString();
|
|
497
|
+
document.getElementById('rp-kept').textContent = cRepo.tokensOut.toLocaleString();
|
|
498
|
+
document.getElementById('rp-freed').textContent = cRepo.tokensFreed.toLocaleString();
|
|
499
|
+
document.getElementById('rp-sessions').textContent = (d.repo && d.repo.sessionCount || 0).toLocaleString();
|
|
500
|
+
document.getElementById('rp-collapsed').textContent = (d.repo && d.repo.dedupCollapsed || 0).toLocaleString();
|
|
501
|
+
var rdr = d.repo && d.repo.storageDedupRate || 0;
|
|
502
|
+
document.getElementById('rp-sdedup').textContent = (rdr * 100 >= 10 ? Math.round(rdr * 100) : (rdr * 100).toFixed(1)) + '%';
|
|
503
|
+
var rp = cRepo.compressionPct || 0;
|
|
504
|
+
document.getElementById('rp-compress-bar').style.width = Math.max(rp * 100, 0.5) + '%';
|
|
505
|
+
document.getElementById('rp-compress-bar').className = 'meter-fill ' + (rp >= 0.9 ? 'meter-green' : rp >= 0.6 ? 'meter-yellow' : 'meter-red');
|
|
506
|
+
document.getElementById('rp-compress-sub').textContent = (rp * 100 >= 10 ? Math.round(rp * 100) : (rp * 100).toFixed(1)) + '% tokens saved ยท dedup: ' + (cRepo.dedupPct * 100 >= 10 ? Math.round(cRepo.dedupPct * 100) : (cRepo.dedupPct * 100).toFixed(1)) + '%';
|
|
507
|
+
|
|
508
|
+
// Data-safety invariant (Phase 0 โ trust foundation).
|
|
509
|
+
var ig = d.integrity || { regionsRetained: 0, compressedOriginalBytes: 0, duplicatesCollapsed: 0, bytesPermanentlyDeleted: 0 };
|
|
510
|
+
function fmtBytes(b) {
|
|
511
|
+
b = b || 0;
|
|
512
|
+
if (b >= 1048576) return (b / 1048576).toFixed(1) + ' MiB';
|
|
513
|
+
if (b >= 1024) return (b / 1024).toFixed(1) + ' KiB';
|
|
514
|
+
return b + ' B';
|
|
515
|
+
}
|
|
516
|
+
document.getElementById('ig-retained').textContent = (ig.regionsRetained || 0).toLocaleString();
|
|
517
|
+
document.getElementById('ig-bytes').textContent = fmtBytes(ig.compressedOriginalBytes);
|
|
518
|
+
document.getElementById('ig-dupes').textContent = (ig.duplicatesCollapsed || 0).toLocaleString();
|
|
519
|
+
document.getElementById('ig-deleted').textContent = fmtBytes(ig.bytesPermanentlyDeleted);
|
|
520
|
+
|
|
521
|
+
// Crew / agents (live sub-agent activity + turn).
|
|
522
|
+
var crew = d.crew || { activeAgents: 0, currentTurn: 0 };
|
|
523
|
+
document.getElementById('cr-agents').textContent = crew.activeAgents || 0;
|
|
524
|
+
document.getElementById('cr-turn').textContent = crew.currentTurn || 0;
|
|
525
|
+
document.getElementById('cr-status').textContent = (crew.activeAgents > 0)
|
|
526
|
+
? ('โถ ' + crew.activeAgents + ' running') : 'idle';
|
|
527
|
+
|
|
528
|
+
// S24: headline tier is the LIVE pressure band; the config card shows the
|
|
529
|
+
// env preset + live pressure ratio so the user sees the system react.
|
|
530
|
+
document.getElementById('hdr-tier').textContent = d.tier;
|
|
531
|
+
document.getElementById('cf-tier').textContent = d.tier + ' (live)';
|
|
532
|
+
document.getElementById('cf-preset').textContent = d.presetTier;
|
|
533
|
+
document.getElementById('cf-pressure').textContent = Math.round((d.pressure || 0) * 100) + '%';
|
|
534
|
+
// (b) Threshold: show the effective token threshold AND the % of the model
|
|
535
|
+
// context window it represents (percentage-based tiers). d.config.tierPct
|
|
536
|
+
// is present on the live snapshot written by the runtime (Phase-1/2a).
|
|
537
|
+
var cfgPct = d.config.tierPct;
|
|
538
|
+
var cw = d.context.contextWindow || 0;
|
|
539
|
+
var thresholdTxt = d.config.thresholdTokens.toLocaleString();
|
|
540
|
+
if (cfgPct != null && cw > 0) {
|
|
541
|
+
thresholdTxt += ' (' + Math.round(cfgPct * 100) + '% of ' + cw.toLocaleString() + ')';
|
|
542
|
+
}
|
|
543
|
+
document.getElementById('cf-threshold').textContent = thresholdTxt;
|
|
544
|
+
// (c) Fast Gate: arming floor โ live trim arms once context passes this %.
|
|
545
|
+
document.getElementById('cf-gate').textContent = d.config.fastGatePct + '%';
|
|
546
|
+
document.getElementById('cf-auto').textContent = d.config.auto ? 'enabled' : 'disabled';
|
|
547
|
+
document.getElementById('cf-anchor').textContent = d.config.anchorUserMessages;
|
|
548
|
+
|
|
549
|
+
// --- Active model + cost savings (same calc as /mega-status) ---------------
|
|
550
|
+
var model = d.model;
|
|
551
|
+
document.getElementById('hdr-model').textContent = model && model.name ? model.name : 'โ';
|
|
552
|
+
document.getElementById('md-name').textContent = model && model.name ? model.name : 'โ';
|
|
553
|
+
document.getElementById('md-provider').textContent = model && model.providerName ? model.providerName : (model && model.provider ? model.provider : 'โ');
|
|
554
|
+
document.getElementById('md-input').textContent = model && model.inputRate ? '$' + (model.inputRate).toFixed(6) : 'โ';
|
|
555
|
+
document.getElementById('md-output').textContent = model && model.outputRate ? '$' + (model.outputRate).toFixed(6) : 'โ';
|
|
556
|
+
var repoSaved = cRepo.tokensFreed || 0;
|
|
557
|
+
if (model && model.inputRate && repoSaved > 0) {
|
|
558
|
+
var usd = (repoSaved * model.inputRate);
|
|
559
|
+
var win = d.context.contextWindow || 0;
|
|
560
|
+
var windows = win > 0 ? (repoSaved / win).toFixed(1) : '0';
|
|
561
|
+
document.getElementById('cost-usd').textContent = 'โ $' + usd.toFixed(4) + ' saved';
|
|
562
|
+
document.getElementById('cost-windows').textContent = windows + ' context-windows extended';
|
|
563
|
+
} else {
|
|
564
|
+
document.getElementById('cost-usd').textContent = 'โ $0.00 saved';
|
|
565
|
+
document.getElementById('cost-windows').textContent = '0 context-windows extended';
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// --- Cache hits & compactions (live counters) ---------------------------
|
|
569
|
+
var ch = d.cacheHits || { session: 0, total: 0, sessionTokensSaved: 0, totalTokensSaved: 0 };
|
|
570
|
+
var cp = d.compacts || { session: 0, total: 0 };
|
|
571
|
+
var ts = d.timeSaved || { compact: { sessionSec: 0, totalSec: 0 }, cacheHit: { sessionSec: 0, totalSec: 0 } };
|
|
572
|
+
document.getElementById('ch-session').textContent = (ch.session || 0).toLocaleString();
|
|
573
|
+
document.getElementById('ch-total').textContent = (ch.total || 0).toLocaleString();
|
|
574
|
+
document.getElementById('ch-tok-session').textContent = (ch.sessionTokensSaved || 0).toLocaleString();
|
|
575
|
+
document.getElementById('ch-tok-total').textContent = (ch.totalTokensSaved || 0).toLocaleString();
|
|
576
|
+
document.getElementById('cp-session').textContent = (cp.session || 0).toLocaleString();
|
|
577
|
+
document.getElementById('cp-total').textContent = (cp.total || 0).toLocaleString();
|
|
578
|
+
document.getElementById('ts-compact-session').textContent = fmtSec(ts.compact.sessionSec);
|
|
579
|
+
document.getElementById('ts-compact-total').textContent = fmtSec(ts.compact.totalSec);
|
|
580
|
+
document.getElementById('ts-cache-session').textContent = fmtSec(ts.cacheHit.sessionSec);
|
|
581
|
+
document.getElementById('ts-cache-total').textContent = fmtSec(ts.cacheHit.totalSec);
|
|
582
|
+
|
|
583
|
+
document.getElementById('updated').textContent = 'Updated ' + new Date(d.updatedAt).toLocaleTimeString();
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function sanitize(s) {
|
|
587
|
+
return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function renderEvent(ev) {
|
|
591
|
+
evBuffer.unshift(ev);
|
|
592
|
+
if (evBuffer.length > MAX_EV) evBuffer.length = MAX_EV;
|
|
593
|
+
evBox.innerHTML = evBuffer.map(function(e) {
|
|
594
|
+
var t = e.ts ? new Date(e.ts).toLocaleTimeString() : '';
|
|
595
|
+
var detail = '';
|
|
596
|
+
if (e.data) {
|
|
597
|
+
if (e.data.checkpointId) detail = sanitize(e.data.checkpointId);
|
|
598
|
+
else if (e.data.query) detail = sanitize(e.data.query.slice(0, 80));
|
|
599
|
+
if (e.data.tokenEstimate != null) detail += ' ' + e.data.tokenEstimate + ' tok';
|
|
600
|
+
if (e.data.deduped) detail += ' (deduped)';
|
|
601
|
+
if (e.data.injected != null) detail = 'injected: ' + e.data.injected + (e.data.empty ? ' (empty)' : '');
|
|
602
|
+
}
|
|
603
|
+
return '<div class="ev">' +
|
|
604
|
+
'<span class="ev-time">' + t + '</span>' +
|
|
605
|
+
'<span class="ev-type ev-type-' + sanitize(e.type) + '">' + sanitize(e.type) + '</span>' +
|
|
606
|
+
'<span class="ev-detail">' + detail + '</span></div>';
|
|
607
|
+
}).join('');
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// Poll snapshot every 2s
|
|
611
|
+
function pollSnapshot() {
|
|
612
|
+
fetch('/api/snapshot').then(function(r) { return r.json(); }).then(renderSnapshot).catch(function() {}); // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
|
|
613
|
+
}
|
|
614
|
+
pollSnapshot();
|
|
615
|
+
renderGameScores();
|
|
616
|
+
renderAchievements();
|
|
617
|
+
setInterval(pollSnapshot, 2000);
|
|
618
|
+
setInterval(renderGameScores, 2000);
|
|
619
|
+
setInterval(renderAchievements, 2000);
|
|
620
|
+
|
|
621
|
+
// SSE for events
|
|
622
|
+
function connectSSE() {
|
|
623
|
+
var es = new EventSource('/api/events');
|
|
624
|
+
es.onmessage = function(msg) {
|
|
625
|
+
try { renderEvent(JSON.parse(msg.data)); } catch(e) {}
|
|
626
|
+
};
|
|
627
|
+
es.onerror = function() {
|
|
628
|
+
es.close();
|
|
629
|
+
setTimeout(connectSSE, 3000);
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
connectSSE();
|
|
633
|
+
|
|
634
|
+
// --- Multi-repo (index.sqlite via /api/index) ---------------------------
|
|
635
|
+
function fmtBytesTop(b) {
|
|
636
|
+
b = b || 0;
|
|
637
|
+
if (b >= 1048576) return (b / 1048576).toFixed(1) + ' MiB';
|
|
638
|
+
if (b >= 1024) return (b / 1024).toFixed(1) + ' KiB';
|
|
639
|
+
return b + ' B';
|
|
640
|
+
}
|
|
641
|
+
function renderIndex(d) {
|
|
642
|
+
d = d || { updatedAt: null, summary: null, repos: [] };
|
|
643
|
+
var repos = d.repos || [];
|
|
644
|
+
var s = d.summary || { totalRepos: 0, totalCheckpoints: 0, totalTokensSaved: 0, totalCompressedOriginalBytes: 0 };
|
|
645
|
+
document.getElementById('sm-repos').textContent = (s.totalRepos || 0).toLocaleString();
|
|
646
|
+
document.getElementById('sm-checkpoints').textContent = (s.totalCheckpoints || 0).toLocaleString();
|
|
647
|
+
document.getElementById('sm-saved').textContent = (s.totalTokensSaved || 0).toLocaleString();
|
|
648
|
+
document.getElementById('sm-bytes').textContent = fmtBytesTop(s.totalCompressedOriginalBytes);
|
|
649
|
+
|
|
650
|
+
// Shared clickable-row renderer for both the in-current table and the
|
|
651
|
+
// All-repos tab โ each row opens the per-repo detail modal.
|
|
652
|
+
function rowsHtml() {
|
|
653
|
+
if (!repos.length) return '<tr><td colspan="6" class="repo-none">No repositories registered yet.</td></tr>';
|
|
654
|
+
return repos.map(function(r) {
|
|
655
|
+
var model = r.modelName
|
|
656
|
+
? '<span class="repo-model">' + sanitize(r.modelName) + '</span>'
|
|
657
|
+
: '<span class="repo-none">โ</span>';
|
|
658
|
+
var when = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : 'โ';
|
|
659
|
+
return '<tr class="repo-link" data-repo="' + sanitize(r.repoRoot) + '">' +
|
|
660
|
+
'<td title="' + sanitize(r.repoRoot) + '">' + sanitize(r.displayName || r.repoRoot) + '</td>' +
|
|
661
|
+
'<td>' + model + '</td>' +
|
|
662
|
+
'<td class="num">' + (r.checkpointCount || 0).toLocaleString() + '</td>' +
|
|
663
|
+
'<td class="num">' + (r.tokensSaved || 0).toLocaleString() + '</td>' +
|
|
664
|
+
'<td class="num">' + fmtBytesTop(r.compressedOriginalBytes) + '</td>' +
|
|
665
|
+
'<td class="num">' + sanitize(when) + '</td>' +
|
|
666
|
+
'</tr>';
|
|
667
|
+
}).join('');
|
|
668
|
+
}
|
|
669
|
+
document.getElementById('cur-rows').innerHTML = rowsHtml();
|
|
670
|
+
document.getElementById('all-rows').innerHTML = rowsHtml();
|
|
671
|
+
bindRepoRows();
|
|
672
|
+
|
|
673
|
+
var stamp = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
|
|
674
|
+
document.getElementById('cur-updated').textContent = stamp;
|
|
675
|
+
document.getElementById('all-updated').textContent = stamp;
|
|
676
|
+
document.getElementById('sm-updated').textContent = stamp;
|
|
677
|
+
renderByModel(repos);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// Savings-by-model aggregation for the Summary tab โ groups the machine-
|
|
681
|
+
// wide repo registry by (modelName || '(unknown)') so the user can see how
|
|
682
|
+
// much context + cost mega-compact has reclaimed, broken down by which model
|
|
683
|
+
// they were running. $ Saved = ฮฃ(tokensSaved ร inputRate) per model. Sorted
|
|
684
|
+
// by tokens saved descending so the biggest-reclaim model wins the top row.
|
|
685
|
+
function renderByModel(repos) {
|
|
686
|
+
var rows = document.getElementById('bm-rows');
|
|
687
|
+
if (!rows) return;
|
|
688
|
+
if (!repos || !repos.length) {
|
|
689
|
+
rows.innerHTML = '<tr><td colspan="14" class="repo-none">No repositories registered yet.</td></tr>';
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
var groups = {};
|
|
693
|
+
for (var i = 0; i < repos.length; i++) {
|
|
694
|
+
var r = repos[i];
|
|
695
|
+
var key = (r.modelName && String(r.modelName).trim()) || '(unknown)';
|
|
696
|
+
if (!groups[key]) groups[key] = {
|
|
697
|
+
model: key, provider: r.providerName || r.provider || 'โ', repos: 0, checkpoints: 0,
|
|
698
|
+
tokensSaved: 0, tokensIn: 0, tokensOut: 0, sessions: 0, usd: 0, lastAt: 0,
|
|
699
|
+
inRates: [], outRates: [], ctxWindows: [], maxTokens: [], reasoning: null,
|
|
700
|
+
};
|
|
701
|
+
var g = groups[key];
|
|
702
|
+
g.repos++;
|
|
703
|
+
g.checkpoints += (r.checkpointCount || 0);
|
|
704
|
+
g.tokensSaved += (r.tokensSaved || 0);
|
|
705
|
+
g.tokensIn += (r.tokensDropped || 0);
|
|
706
|
+
g.tokensOut += (r.tokensKept || 0);
|
|
707
|
+
g.sessions += (r.sessions || 0);
|
|
708
|
+
if (r.inputRate) { g.usd += (r.tokensSaved || 0) * r.inputRate; g.inRates.push(r.inputRate); }
|
|
709
|
+
if (r.outputRate) g.outRates.push(r.outputRate);
|
|
710
|
+
if (r.contextWindow) g.ctxWindows.push(r.contextWindow);
|
|
711
|
+
if (r.maxTokens) g.maxTokens.push(r.maxTokens);
|
|
712
|
+
if (r.reasoning != null) g.reasoning = r.reasoning;
|
|
713
|
+
if (r.lastCompactedAt && r.lastCompactedAt > g.lastAt) g.lastAt = r.lastCompactedAt;
|
|
714
|
+
}
|
|
715
|
+
var arr = [];
|
|
716
|
+
for (var k in groups) { if (Object.prototype.hasOwnProperty.call(groups, k)) arr.push(groups[k]); }
|
|
717
|
+
arr.sort(function(a, b) { return b.tokensSaved - a.tokensSaved; });
|
|
718
|
+
// Helper: a set of numeric samples collapses to a single value when all
|
|
719
|
+
// repos in the group agree, otherwise shows the range (minโmax) so the
|
|
720
|
+
// user can see mixed-config model groups at a glance.
|
|
721
|
+
function collapseNum(samples) {
|
|
722
|
+
if (!samples || !samples.length) return 'โ';
|
|
723
|
+
var lo = Math.min.apply(null, samples), hi = Math.max.apply(null, samples);
|
|
724
|
+
return lo === hi ? lo.toLocaleString() : lo.toLocaleString() + 'โ' + hi.toLocaleString();
|
|
725
|
+
}
|
|
726
|
+
function collapseRate(samples) {
|
|
727
|
+
if (!samples || !samples.length) return 'โ';
|
|
728
|
+
var lo = Math.min.apply(null, samples), hi = Math.max.apply(null, samples);
|
|
729
|
+
var fmt = function(v) { return '$' + v.toFixed(6); };
|
|
730
|
+
return lo === hi ? fmt(lo) : fmt(lo) + 'โ' + fmt(hi);
|
|
731
|
+
}
|
|
732
|
+
rows.innerHTML = arr.map(function(g) {
|
|
733
|
+
var freed = (g.tokensIn || 0) - (g.tokensOut || 0);
|
|
734
|
+
var usd = g.usd > 0 ? '$' + g.usd.toFixed(4) : 'โ';
|
|
735
|
+
var when = g.lastAt ? new Date(g.lastAt).toLocaleString() : 'โ';
|
|
736
|
+
var reas = g.reasoning == null ? 'โ' : (g.reasoning ? 'yes' : 'no');
|
|
737
|
+
return '<tr>' +
|
|
738
|
+
'<td><span class="repo-model">' + sanitize(g.model) + '</span></td>' +
|
|
739
|
+
'<td>' + sanitize(g.provider) + '</td>' +
|
|
740
|
+
'<td class="num">' + (g.tokensIn || 0).toLocaleString() + '</td>' +
|
|
741
|
+
'<td class="num">' + (g.tokensOut || 0).toLocaleString() + '</td>' +
|
|
742
|
+
'<td class="num">' + freed.toLocaleString() + '</td>' +
|
|
743
|
+
'<td class="num">' + collapseNum(g.ctxWindows) + '</td>' +
|
|
744
|
+
'<td class="num">' + collapseNum(g.maxTokens) + '</td>' +
|
|
745
|
+
'<td class="num">' + reas + '</td>' +
|
|
746
|
+
'<td class="num">' + g.sessions.toLocaleString() + '</td>' +
|
|
747
|
+
'<td class="num">' + g.checkpoints.toLocaleString() + '</td>' +
|
|
748
|
+
'<td class="num">' + collapseRate(g.inRates) + '</td>' +
|
|
749
|
+
'<td class="num">' + collapseRate(g.outRates) + '</td>' +
|
|
750
|
+
'<td class="num">' + sanitize(usd) + '</td>' +
|
|
751
|
+
'<td class="num">' + sanitize(when) + '</td>' +
|
|
752
|
+
'</tr>';
|
|
753
|
+
}).join('');
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
// Per-repo detail modal ---------------------------------------------------
|
|
757
|
+
var detailEl = document.getElementById('repo-detail');
|
|
758
|
+
var indexCache = { repos: [] };
|
|
759
|
+
function openRepoDetail(root) {
|
|
760
|
+
var r = null;
|
|
761
|
+
for (var i = 0; i < indexCache.repos.length; i++) {
|
|
762
|
+
if (indexCache.repos[i].repoRoot === root) { r = indexCache.repos[i]; break; }
|
|
763
|
+
}
|
|
764
|
+
if (!r) return;
|
|
765
|
+
document.getElementById('rd-name').textContent = r.displayName || r.repoRoot;
|
|
766
|
+
document.getElementById('rd-path').textContent = r.repoRoot;
|
|
767
|
+
document.getElementById('rd-model').textContent = r.modelName || 'โ';
|
|
768
|
+
document.getElementById('rd-provider').textContent = r.providerName || (r.provider || 'โ');
|
|
769
|
+
document.getElementById('rd-cp').textContent = (r.checkpointCount || 0).toLocaleString();
|
|
770
|
+
document.getElementById('rd-saved').textContent = (r.tokensSaved || 0).toLocaleString();
|
|
771
|
+
document.getElementById('rd-bytes').textContent = fmtBytesTop(r.compressedOriginalBytes);
|
|
772
|
+
document.getElementById('rd-when').textContent = r.lastCompactedAt ? new Date(r.lastCompactedAt).toLocaleString() : 'โ';
|
|
773
|
+
detailEl.classList.add('open');
|
|
774
|
+
}
|
|
775
|
+
document.getElementById('rd-close').addEventListener('click', function() { detailEl.classList.remove('open'); });
|
|
776
|
+
detailEl.addEventListener('click', function(e) { if (e.target === detailEl) detailEl.classList.remove('open'); });
|
|
777
|
+
function bindRepoRows() {
|
|
778
|
+
var rows = document.querySelectorAll('.repo-link');
|
|
779
|
+
for (var i = 0; i < rows.length; i++) {
|
|
780
|
+
rows[i].addEventListener('click', function() { openRepoDetail(this.getAttribute('data-repo')); });
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
function pollIndex() {
|
|
784
|
+
fetch('/api/index').then(function(r) { return r.json(); }).then(function(d) { // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
|
|
785
|
+
indexCache = d && d.repos ? d : indexCache;
|
|
786
|
+
renderIndex(d);
|
|
787
|
+
}).catch(function() {});
|
|
788
|
+
}
|
|
789
|
+
pollIndex();
|
|
790
|
+
setInterval(pollIndex, 5000);
|
|
791
|
+
|
|
792
|
+
// --- Active repos (live cache-hit / compaction stats) ---------------------
|
|
793
|
+
function fmtSec(s) {
|
|
794
|
+
s = s || 0;
|
|
795
|
+
if (s >= 3600) return (s / 3600).toFixed(1) + 'h';
|
|
796
|
+
if (s >= 60) return Math.round(s / 60) + 'm';
|
|
797
|
+
if (s >= 1) return s.toFixed(1) + 's';
|
|
798
|
+
return Math.round(s * 1000) + 'ms';
|
|
799
|
+
}
|
|
800
|
+
function renderActiveRepos(d) {
|
|
801
|
+
d = d || { updatedAt: null, servers: [] };
|
|
802
|
+
var servers = d.servers || [];
|
|
803
|
+
var rowsEl = document.getElementById('active-rows');
|
|
804
|
+
if (!rowsEl) return;
|
|
805
|
+
if (!servers.length) {
|
|
806
|
+
rowsEl.innerHTML = '<tr><td colspan="9" class="repo-none">No active repositories.</td></tr>';
|
|
807
|
+
} else {
|
|
808
|
+
rowsEl.innerHTML = servers.map(function(r) {
|
|
809
|
+
var ch = r.cacheHits || { session: 0, total: 0, sessionTokensSaved: 0, totalTokensSaved: 0 };
|
|
810
|
+
var cp = r.compacts || { session: 0, total: 0 };
|
|
811
|
+
var ts = r.timeSaved || { compact: { sessionSec: 0, totalSec: 0 }, cacheHit: { sessionSec: 0, totalSec: 0 } };
|
|
812
|
+
return '<tr>' +
|
|
813
|
+
'<td title="' + sanitize(r.repoRoot) + '">' + sanitize(r.displayName || r.repoRoot) + '</td>' +
|
|
814
|
+
'<td>' + sanitize(r.model || 'โ') + '</td>' +
|
|
815
|
+
'<td>' + sanitize(r.tier || 'โ') + '</td>' +
|
|
816
|
+
'<td class="num">' + (r.contextPct != null ? Math.round(r.contextPct * 100) + '%' : 'โ') + '</td>' +
|
|
817
|
+
'<td>' + sanitize(r.state || 'โ') + '</td>' +
|
|
818
|
+
'<td class="num">' + (cp.session || 0) + ' / ' + (cp.total || 0) + '</td>' +
|
|
819
|
+
'<td class="num">' + (ch.session || 0) + ' / ' + (ch.total || 0) + '</td>' +
|
|
820
|
+
'<td class="num">' + fmtSec(ts.compact.sessionSec) + ' / ' + fmtSec(ts.compact.totalSec) + '</td>' +
|
|
821
|
+
'<td class="num">' + fmtSec(ts.cacheHit.sessionSec) + ' / ' + fmtSec(ts.cacheHit.totalSec) + '</td>' +
|
|
822
|
+
'</tr>';
|
|
823
|
+
}).join('');
|
|
824
|
+
}
|
|
825
|
+
var upd = document.getElementById('active-updated');
|
|
826
|
+
if (upd) upd.textContent = d.updatedAt ? 'Updated ' + new Date(d.updatedAt).toLocaleTimeString() : '';
|
|
827
|
+
}
|
|
828
|
+
function pollServers() {
|
|
829
|
+
fetch('/api/servers').then(function(r) { return r.json(); }).then(renderActiveRepos).catch(function() {}); // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
|
|
830
|
+
}
|
|
831
|
+
pollServers();
|
|
832
|
+
setInterval(pollServers, 5000);
|
|
833
|
+
|
|
834
|
+
// --- Game-mode settings strip (S32) -------------------------------------
|
|
835
|
+
// Polls GET /api/game-state and applies the row to the settings controls +
|
|
836
|
+
// the document theme. On any control change, PUTs a partial patch back. The
|
|
837
|
+
// dashboard server is a detached child with no MegaRuntime ref, so it reads /
|
|
838
|
+
// writes the game_state SQLite row directly; the in-process MegaRuntime picks
|
|
839
|
+
// up the change via its fs.watch cache-eviction watcher (S32).
|
|
840
|
+
var gmCheckbox = document.getElementById('set-game-mode');
|
|
841
|
+
var gmTheme = document.getElementById('set-theme');
|
|
842
|
+
var gmTui = document.getElementById('set-tui-mode');
|
|
843
|
+
|
|
844
|
+
function applyGameState(gs) {
|
|
845
|
+
if (!gs) return;
|
|
846
|
+
if (gs.theme) document.documentElement.dataset.theme = gs.theme;
|
|
847
|
+
if (gmCheckbox) gmCheckbox.checked = !!gs.game_mode_on;
|
|
848
|
+
if (gmTheme) gmTheme.value = gs.theme || 'transparent';
|
|
849
|
+
if (gmTui) gmTui.value = gs.tui_display_mode || 'full';
|
|
850
|
+
}
|
|
851
|
+
function pollGameState() {
|
|
852
|
+
fetch('/api/game-state').then(function(r) { return r.json(); }).then(applyGameState).catch(function() {}); // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
|
|
853
|
+
}
|
|
854
|
+
function putGameState(patch) {
|
|
855
|
+
fetch('/api/game-state', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(patch) }).then(function(r) { return r.json(); }).then(applyGameState).catch(function() {}); // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
|
|
856
|
+
}
|
|
857
|
+
if (gmCheckbox) gmCheckbox.addEventListener('change', function() { putGameState({ game_mode_on: gmCheckbox.checked }); });
|
|
858
|
+
if (gmTheme) gmTheme.addEventListener('change', function() { putGameState({ theme: gmTheme.value }); });
|
|
859
|
+
if (gmTui) gmTui.addEventListener('change', function() { putGameState({ tui_display_mode: gmTui.value }); });
|
|
860
|
+
pollGameState();
|
|
861
|
+
setInterval(pollGameState, 5000);
|
|
862
|
+
|
|
863
|
+
// --- Game Mode leaderboards (S34) ----------------------------------------
|
|
864
|
+
// Polls GET /api/game-scores per metric, renders the per-repo leaderboard
|
|
865
|
+
// tables, the MEGA CACHE banner, the hidden Opie's Wild Ride unlock tile, and
|
|
866
|
+
// the transient oopsie toast (fires when a NEW mega_cache trophy row appears
|
|
867
|
+
// since the last poll). Browser-side fetch only (PREVENT-PI-004).
|
|
868
|
+
var GAME_METRICS = ['cache', 'dedupe', 'turns', 'mega_cache'];
|
|
869
|
+
var GAME_EMPTY = true;
|
|
870
|
+
var lastMegaTs = 0;
|
|
871
|
+
var lastMaxLevel = 0;
|
|
872
|
+
function fmtPct(v) { return (Math.round(v * 10) / 10) + '%'; }
|
|
873
|
+
function fmtDate(ts) { return ts ? new Date(ts).toLocaleString() : 'โ'; }
|
|
874
|
+
function trophyMeta(m) { try { return (m && typeof m === 'object') ? m : {}; } catch (e) { return {}; } }
|
|
875
|
+
function renderGameScores() {
|
|
876
|
+
var results = {};
|
|
877
|
+
var pending = GAME_METRICS.length + 1; // metrics + repos badge
|
|
878
|
+
function done() {
|
|
879
|
+
if (--pending > 0) return;
|
|
880
|
+
var cache = results['cache'] || [];
|
|
881
|
+
if (cache.length) GAME_EMPTY = false;
|
|
882
|
+
document.getElementById('lb-cache').innerHTML = cache.map(function(r) {
|
|
883
|
+
return '<tr><td title="' + sanitize(r.repo_root) + '">' + sanitize(r.repo_root.split('/').pop()) + '</td><td class="num">' + sanitize(String(r.value)) + '</td></tr>';
|
|
884
|
+
}).join('') || '<tr><td colspan="2" class="repo-none">no data</td></tr>';
|
|
885
|
+
var dedupe = results['dedupe'] || [];
|
|
886
|
+
if (dedupe.length) GAME_EMPTY = false;
|
|
887
|
+
document.getElementById('lb-dedupe').innerHTML = dedupe.map(function(r) {
|
|
888
|
+
return '<tr><td title="' + sanitize(r.repo_root) + '">' + sanitize(r.repo_root.split('/').pop()) + '</td><td class="num">' + sanitize(String(r.value)) + '</td></tr>';
|
|
889
|
+
}).join('') || '<tr><td colspan="2" class="repo-none">no data</td></tr>';
|
|
890
|
+
var turns = results['turns'] || [];
|
|
891
|
+
var maxTurns = turns.reduce(function(mx, r) { return Math.max(mx, r.value); }, 0);
|
|
892
|
+
var lvl = Math.floor(Math.log2(maxTurns + 1)) + 1;
|
|
893
|
+
var lvlEl = document.getElementById('turns-level');
|
|
894
|
+
if (lvlEl) lvlEl.textContent = 'LVL ' + lvl;
|
|
895
|
+
if (turns.length) GAME_EMPTY = false;
|
|
896
|
+
// level-up pulse: when max level increases vs last poll, pulse the cache bar
|
|
897
|
+
if (lvl > lastMaxLevel && lastMaxLevel > 0) {
|
|
898
|
+
var bar = document.getElementById('ctx-bar');
|
|
899
|
+
if (bar) { bar.classList.add('level-up'); setTimeout(function() { bar.classList.remove('level-up'); }, 1200); }
|
|
900
|
+
}
|
|
901
|
+
if (lvl > lastMaxLevel) lastMaxLevel = lvl;
|
|
902
|
+
document.getElementById('lb-turns').innerHTML = turns.map(function(r) {
|
|
903
|
+
return '<tr><td title="' + sanitize(r.repo_root) + '">' + sanitize(r.repo_root.split('/').pop()) + '</td><td class="num">' + sanitize(String(r.value)) + '</td></tr>';
|
|
904
|
+
}).join('') || '<tr><td colspan="2" class="repo-none">no data</td></tr>';
|
|
905
|
+
// mega_cache trophies + banner + Opie tile + transient toast
|
|
906
|
+
var mega = results['mega_cache'] || [];
|
|
907
|
+
var megaBody = document.getElementById('lb-mega_cache');
|
|
908
|
+
if (megaBody) {
|
|
909
|
+
megaBody.innerHTML = mega.map(function(r) {
|
|
910
|
+
var m = trophyMeta(r.meta);
|
|
911
|
+
var fs = m.firstSeenTs || m.firstSeen || r.ts;
|
|
912
|
+
var extra = fs ? ' <span class="lb-meta">' + sanitize(fmtDate(fs)) + '</span>' : '';
|
|
913
|
+
return '<tr><td title="' + sanitize(r.repo_root) + '">' + sanitize(r.repo_root.split('/').pop()) + '</td><td class="num">' + sanitize(fmtPct(r.value)) + extra + '</td></tr>';
|
|
914
|
+
}).join('') || '<tr><td colspan="2" class="repo-none">no trophies yet</td></tr>';
|
|
915
|
+
}
|
|
916
|
+
var banner = document.getElementById('mega-cache-banner');
|
|
917
|
+
var tile = document.getElementById('opie-tile');
|
|
918
|
+
var best = null, firstSeen = null;
|
|
919
|
+
mega.forEach(function(r) {
|
|
920
|
+
if (best == null || r.value > best) best = r.value;
|
|
921
|
+
var m = trophyMeta(r.meta);
|
|
922
|
+
var fs = m.firstSeenTs || m.firstSeen || r.ts;
|
|
923
|
+
if (firstSeen == null || fs < firstSeen) firstSeen = fs;
|
|
924
|
+
});
|
|
925
|
+
if (banner) {
|
|
926
|
+
if (best != null && best > 100) {
|
|
927
|
+
banner.style.display = 'block';
|
|
928
|
+
banner.textContent = '๐ฅง MEGA CACHE! peak ' + fmtPct(best) + ' โ first reached ' + fmtDate(firstSeen);
|
|
929
|
+
} else { banner.style.display = 'none'; }
|
|
930
|
+
}
|
|
931
|
+
if (tile) {
|
|
932
|
+
if (best != null && best > 100) {
|
|
933
|
+
tile.style.display = 'block';
|
|
934
|
+
tile.className = 'achievement-tile unlocked';
|
|
935
|
+
tile.innerHTML = '๐ Opie\'s Wild Ride<span class="ach-detail">best ' + sanitize(fmtPct(best)) + ' ยท first ' + sanitize(fmtDate(firstSeen)) + '</span>';
|
|
936
|
+
} else { tile.style.display = 'none'; tile.className = 'achievement-tile'; }
|
|
937
|
+
}
|
|
938
|
+
// transient oopsie toast: a NEW mega_cache trophy row since the last poll
|
|
939
|
+
var maxTs = mega.reduce(function(mx, r) { return Math.max(mx, r.ts); }, 0);
|
|
940
|
+
var newRow = mega.find(function(r) { return r.ts > lastMegaTs && r.value > 100; });
|
|
941
|
+
if (lastMegaTs && newRow) {
|
|
942
|
+
var toast = document.getElementById('mega-cache-toast');
|
|
943
|
+
if (toast) {
|
|
944
|
+
toast.textContent = 'oopsie! cache went to ' + Math.round(newRow.value) + '% โ MEGA CACHE ๐ฅง';
|
|
945
|
+
toast.classList.add('show');
|
|
946
|
+
setTimeout(function() { toast.classList.remove('show'); }, 4000);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
if (maxTs > lastMegaTs) lastMegaTs = maxTs;
|
|
950
|
+
// empty state
|
|
951
|
+
var emptyEl = document.getElementById('game-empty');
|
|
952
|
+
if (emptyEl) emptyEl.style.display = GAME_EMPTY ? 'block' : 'none';
|
|
953
|
+
}
|
|
954
|
+
GAME_METRICS.forEach(function(m) {
|
|
955
|
+
fetch('/api/game-scores?metric=' + encodeURIComponent(m) + '&limit=25').then(function(r) { return r.ok ? r.json() : []; }).then(function(rows) { results[m] = rows || []; }).catch(function() { results[m] = []; }).then(done);
|
|
956
|
+
});
|
|
957
|
+
fetch('/api/game-scores?metric=repos&limit=1').then(function(r) { return r.ok ? r.json() : []; }).then(function(rows) {
|
|
958
|
+
var badge = document.getElementById('repos-badge');
|
|
959
|
+
if (badge) badge.textContent = ((rows && rows.length) ? rows[0].value : 0) + ' repos';
|
|
960
|
+
if (rows && rows.length && rows[0].value > 0) GAME_EMPTY = false;
|
|
961
|
+
}).catch(function() {}).then(done);
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// --- Achievements tile row (S35) ------------------------------------------
|
|
965
|
+
// Polls GET /api/achievements; renders the tile row (hidden+locked render
|
|
966
|
+
// NOTHING; unlocked show icon+title+date; visible-but-locked show ??? teaser)
|
|
967
|
+
// and fires a transient toast when a newly-unlocked achievement appears.
|
|
968
|
+
// Browser-side fetch only (PREVENT-PI-004).
|
|
969
|
+
var lastAchMaxTs = 0;
|
|
970
|
+
function renderAchievements() {
|
|
971
|
+
fetch('/api/achievements').then(function(r) { return r.ok ? r.json() : []; }).then(function(rows) { // guardrails-allow PREVENT-PI-004: browser-side fetch in dashboard HTML template (not Node runtime)
|
|
972
|
+
var box = document.getElementById('ach-tiles');
|
|
973
|
+
if (!box) return;
|
|
974
|
+
var html = '';
|
|
975
|
+
var maxTs = 0;
|
|
976
|
+
(rows || []).forEach(function(a) {
|
|
977
|
+
if (a.hidden === 1 && a.unlocked_at == null) return; // hidden invariant: render nothing
|
|
978
|
+
if (a.unlocked_at != null) {
|
|
979
|
+
var isNew = a.unlocked_at > lastAchMaxTs && lastAchMaxTs > 0;
|
|
980
|
+
maxTs = Math.max(maxTs, a.unlocked_at);
|
|
981
|
+
html += '<div class="ach-tile unlocked' + (isNew ? ' just-unlocked' : '') + '">' + sanitize(a.icon || '') + ' ' + sanitize(a.title) + '<span class="ach-detail">unlocked ' + sanitize(fmtDate(a.unlocked_at)) + '</span></div>';
|
|
982
|
+
} else {
|
|
983
|
+
html += '<div class="ach-tile locked">??? ' + sanitize(a.title) + '</div>';
|
|
984
|
+
}
|
|
985
|
+
});
|
|
986
|
+
box.innerHTML = html || '<span class="repo-none">no achievements yet</span>';
|
|
987
|
+
var newly = (rows || []).filter(function(a) { return a.unlocked_at != null && a.unlocked_at > lastAchMaxTs; });
|
|
988
|
+
if (lastAchMaxTs && newly.length) {
|
|
989
|
+
var toast = document.getElementById('ach-toast');
|
|
990
|
+
if (toast) {
|
|
991
|
+
toast.textContent = newly.map(function(a) { return (a.icon || '') + ' ' + a.title; }).join(', ') + ' unlocked!';
|
|
992
|
+
toast.classList.add('show');
|
|
993
|
+
setTimeout(function() { toast.classList.remove('show'); }, 4000);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
if (maxTs > lastAchMaxTs) lastAchMaxTs = maxTs;
|
|
997
|
+
}).catch(function() {});
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
// --- Tab switching ------------------------------------------------------
|
|
1001
|
+
var tabs = document.querySelectorAll('.tab');
|
|
1002
|
+
var panels = { current: 'panel-current', all: 'panel-all', active: 'panel-active', summary: 'panel-summary', game: 'panel-game' };
|
|
1003
|
+
for (var i = 0; i < tabs.length; i++) {
|
|
1004
|
+
tabs[i].addEventListener('click', function() {
|
|
1005
|
+
var name = this.getAttribute('data-tab');
|
|
1006
|
+
for (var j = 0; j < tabs.length; j++) tabs[j].classList.remove('active');
|
|
1007
|
+
this.classList.add('active');
|
|
1008
|
+
for (var k in panels) {
|
|
1009
|
+
if (Object.prototype.hasOwnProperty.call(panels, k)) {
|
|
1010
|
+
var el = document.getElementById(panels[k]);
|
|
1011
|
+
if (el) el.classList.toggle('active', k === name);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
if (name === 'all' || name === 'summary') pollIndex();
|
|
1015
|
+
if (name === 'active') pollServers();
|
|
1016
|
+
if (name === 'game') { renderGameScores(); renderAchievements(); }
|
|
1017
|
+
});
|
|
1018
|
+
}
|
|
1019
|
+
})();
|
|
1020
|
+
</script>
|
|
1021
|
+
</body>
|
|
1022
|
+
</html>`;
|
|
1023
|
+
}
|