@symerian/symi 3.5.0 → 3.5.1
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/dist/build-info.json +3 -3
- package/dist/bundled/boot-md/handler.js +4 -4
- package/dist/bundled/session-memory/handler.js +4 -4
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/{chrome-C_I81hbq.js → chrome-B7-rO4i9.js} +4 -4
- package/dist/{chrome-BKUACyeO.js → chrome-DPjznJQ-.js} +4 -4
- package/dist/control-ui/css/revert-red-theme.md +141 -0
- package/dist/control-ui/css/style.css +5843 -0
- package/dist/control-ui/css/style.css.backup-2026-03-03-162525 +3546 -0
- package/dist/control-ui/css/style.css.backup-before-red-2026-03-03-162525 +3546 -0
- package/dist/control-ui/css/style.css.backup-before-red-theme-2026-03-03-162530 +3546 -0
- package/dist/control-ui/css/style.css.pre-2row +2165 -0
- package/dist/control-ui/css/style.css.pre-brand +1776 -0
- package/dist/control-ui/css/style.css.pre-history +1974 -0
- package/dist/control-ui/css/style.css.pre-nav +2264 -0
- package/dist/control-ui/css/style.css.pre-newsession +1898 -0
- package/dist/control-ui/css/style.css.pre-queue +2195 -0
- package/dist/control-ui/css/style.css.pre-red-prompt +2524 -0
- package/dist/control-ui/css/style.css.pre-stop +2239 -0
- package/dist/control-ui/css/style.css.pre-textarea +2184 -0
- package/dist/control-ui/css/style.css.pre-watchdog +1848 -0
- package/dist/control-ui/css/style.css.red-theme +2999 -0
- package/dist/control-ui/index.html +1049 -0
- package/dist/control-ui/js/app.js +1304 -0
- package/dist/control-ui/js/app.js.pre-2row +463 -0
- package/dist/control-ui/js/app.js.pre-heartbeat-filter +595 -0
- package/dist/control-ui/js/app.js.pre-newsession +408 -0
- package/dist/control-ui/js/app.js.pre-queue +476 -0
- package/dist/control-ui/js/app.js.pre-stop +564 -0
- package/dist/control-ui/js/app.js.pre-textarea +467 -0
- package/dist/control-ui/js/app.js.pre-watchdog +293 -0
- package/dist/control-ui/js/connections.js +438 -0
- package/dist/control-ui/js/gateway.js +233 -0
- package/dist/control-ui/js/gateway.js.pre-stop +110 -0
- package/dist/control-ui/js/history.js +732 -0
- package/dist/control-ui/js/logs.js +238 -0
- package/dist/control-ui/js/menu.js +232 -0
- package/dist/control-ui/js/menu.js.pre-nav +66 -0
- package/dist/control-ui/js/metrics.js +53 -0
- package/dist/control-ui/js/models.js +138 -0
- package/dist/control-ui/js/render.js +882 -0
- package/dist/control-ui/js/render.test.js +112 -0
- package/dist/control-ui/js/scheduling.js +461 -0
- package/dist/control-ui/js/settings.js +910 -0
- package/dist/control-ui/js/slash-autocomplete.js +168 -0
- package/dist/control-ui/js/subagents.js +560 -0
- package/dist/control-ui/js/utils.js +29 -0
- package/dist/control-ui/vendor/highlight.min.js +2518 -0
- package/dist/control-ui/vendor/marked.min.js +69 -0
- package/dist/{deliver-DyO3QD8O.js → deliver-DTRkeYm3.js} +4 -4
- package/dist/{deliver-Cjyb6h4g.js → deliver-oWGJwzFf.js} +4 -4
- package/dist/extensionAPI.js +4 -4
- package/dist/llm-slug-generator.js +4 -4
- package/dist/{manager-rvtFoeFT.js → manager-CFenq_aO.js} +1 -1
- package/dist/{manager-PTSjHNVq.js → manager-CsxTf96V.js} +1 -1
- package/dist/{pi-embedded-BPuUM-gD.js → pi-embedded-Cdub5Vs9.js} +10 -10
- package/dist/{pw-ai-BFS9ezWe.js → pw-ai-BOOB8qoi.js} +1 -1
- package/dist/{pw-ai-Cx-Ko_FL.js → pw-ai-D2pEVS5n.js} +1 -1
- package/dist/{synthesis-7UL3pCpj.js → synthesis-Be9nYyDd.js} +4 -4
- package/dist/{synthesis-fD8J2vag.js → synthesis-CBIT6Vnk.js} +4 -4
- package/dist/{unified-runner-BIiKFnNF.js → unified-runner-BVvvnjXW.js} +10 -10
- package/package.json +1 -1
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
// ── Native Logs Viewer ────────────────────────────────────────────────
|
|
2
|
+
// Fetches logs via the Glass UI's gateway RPC and renders them natively.
|
|
3
|
+
// No iframe, no SPA proxy — direct gateway connection.
|
|
4
|
+
|
|
5
|
+
const LOGS_POLL_MS = 3000;
|
|
6
|
+
const LOGS_LIMIT = 500;
|
|
7
|
+
const LOGS_MAX_BYTES = 250000;
|
|
8
|
+
|
|
9
|
+
const LEVEL_COLORS = {
|
|
10
|
+
TRACE: '#6b7280',
|
|
11
|
+
DEBUG: '#8b5cf6',
|
|
12
|
+
INFO: '#60a5fa',
|
|
13
|
+
WARN: '#f59e0b',
|
|
14
|
+
ERROR: '#ef4444',
|
|
15
|
+
FATAL: '#dc2626',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const LEVEL_ORDER = ['TRACE', 'DEBUG', 'INFO', 'WARN', 'ERROR', 'FATAL'];
|
|
19
|
+
|
|
20
|
+
let logsContainer = null;
|
|
21
|
+
let logsCursor = null;
|
|
22
|
+
let logsTimer = null;
|
|
23
|
+
let logsAutoFollow = true;
|
|
24
|
+
let logsLevelFilters = new Set(LEVEL_ORDER);
|
|
25
|
+
let logsFilterText = '';
|
|
26
|
+
let logsEntries = [];
|
|
27
|
+
|
|
28
|
+
function createLogsUI() {
|
|
29
|
+
const overlay = document.querySelector('body > .page-overlay');
|
|
30
|
+
const frame = document.getElementById('page-overlay-frame');
|
|
31
|
+
|
|
32
|
+
// Hide the iframe
|
|
33
|
+
if (frame) frame.style.display = 'none';
|
|
34
|
+
|
|
35
|
+
// Create or get native logs container
|
|
36
|
+
let container = document.getElementById('native-logs-container');
|
|
37
|
+
if (!container) {
|
|
38
|
+
container = document.createElement('div');
|
|
39
|
+
container.id = 'native-logs-container';
|
|
40
|
+
container.innerHTML = `
|
|
41
|
+
<div class="logs-toolbar">
|
|
42
|
+
<div class="logs-filters">
|
|
43
|
+
${LEVEL_ORDER.map(l => `
|
|
44
|
+
<label class="logs-level-toggle" data-level="${l}">
|
|
45
|
+
<input type="checkbox" checked data-level="${l}">
|
|
46
|
+
<span class="logs-level-badge" style="color:${LEVEL_COLORS[l]}">${l}</span>
|
|
47
|
+
</label>
|
|
48
|
+
`).join('')}
|
|
49
|
+
</div>
|
|
50
|
+
<div class="logs-search">
|
|
51
|
+
<input type="text" id="logs-search-input" placeholder="Filter logs..." />
|
|
52
|
+
</div>
|
|
53
|
+
<div class="logs-actions">
|
|
54
|
+
<label class="logs-auto-follow">
|
|
55
|
+
<input type="checkbox" id="logs-auto-follow-cb" checked> Auto-follow
|
|
56
|
+
</label>
|
|
57
|
+
<button id="logs-export-btn" title="Export visible">Export</button>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
<div class="logs-scroll" id="logs-scroll">
|
|
61
|
+
<div class="logs-entries" id="logs-entries"></div>
|
|
62
|
+
</div>
|
|
63
|
+
`;
|
|
64
|
+
overlay.appendChild(container);
|
|
65
|
+
|
|
66
|
+
// Wire up level filters
|
|
67
|
+
container.querySelectorAll('.logs-level-toggle input').forEach(cb => {
|
|
68
|
+
cb.addEventListener('change', () => {
|
|
69
|
+
const level = cb.dataset.level;
|
|
70
|
+
if (cb.checked) logsLevelFilters.add(level);
|
|
71
|
+
else logsLevelFilters.delete(level);
|
|
72
|
+
renderLogs();
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Wire up search
|
|
77
|
+
document.getElementById('logs-search-input').addEventListener('input', e => {
|
|
78
|
+
logsFilterText = e.target.value.toLowerCase();
|
|
79
|
+
renderLogs();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Wire up auto-follow
|
|
83
|
+
document.getElementById('logs-auto-follow-cb').addEventListener('change', e => {
|
|
84
|
+
logsAutoFollow = e.target.checked;
|
|
85
|
+
if (logsAutoFollow) scrollToBottom();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Wire up export
|
|
89
|
+
document.getElementById('logs-export-btn').addEventListener('click', () => {
|
|
90
|
+
const visible = getVisibleEntries();
|
|
91
|
+
const text = visible.map(e => e.raw).join('\n');
|
|
92
|
+
const blob = new Blob([text], { type: 'text/plain' });
|
|
93
|
+
const a = document.createElement('a');
|
|
94
|
+
a.href = URL.createObjectURL(blob);
|
|
95
|
+
a.download = `symi-logs-${new Date().toISOString().slice(0,19)}.txt`;
|
|
96
|
+
a.click();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
logsContainer = container;
|
|
101
|
+
container.style.display = 'flex';
|
|
102
|
+
return container;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function destroyLogsUI() {
|
|
106
|
+
if (logsTimer) { clearInterval(logsTimer); logsTimer = null; }
|
|
107
|
+
const container = document.getElementById('native-logs-container');
|
|
108
|
+
if (container) container.style.display = 'none';
|
|
109
|
+
const frame = document.getElementById('page-overlay-frame');
|
|
110
|
+
if (frame) frame.style.display = '';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseLogLine(line) {
|
|
114
|
+
try {
|
|
115
|
+
const obj = JSON.parse(line);
|
|
116
|
+
const level = obj._meta?.logLevelName || 'INFO';
|
|
117
|
+
const time = obj.time || obj._meta?.date || '';
|
|
118
|
+
const msg = obj['1'] || obj.msg || obj.message || '';
|
|
119
|
+
const sub = obj['0'] || '';
|
|
120
|
+
return { level, time, msg, sub, raw: line, parsed: obj };
|
|
121
|
+
} catch {
|
|
122
|
+
// Plain text line
|
|
123
|
+
return { level: 'INFO', time: '', msg: line, sub: '', raw: line, parsed: null };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getVisibleEntries() {
|
|
128
|
+
return logsEntries.filter(e => {
|
|
129
|
+
if (!logsLevelFilters.has(e.level)) return false;
|
|
130
|
+
if (logsFilterText && !e.raw.toLowerCase().includes(logsFilterText)) return false;
|
|
131
|
+
return true;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function renderLogs() {
|
|
136
|
+
const el = document.getElementById('logs-entries');
|
|
137
|
+
if (!el) return;
|
|
138
|
+
|
|
139
|
+
const visible = getVisibleEntries();
|
|
140
|
+
const html = visible.map(e => {
|
|
141
|
+
const color = LEVEL_COLORS[e.level] || '#999';
|
|
142
|
+
const timeStr = e.time ? new Date(e.time).toLocaleTimeString() : '';
|
|
143
|
+
const escapedMsg = escapeHtml(e.msg).substring(0, 500);
|
|
144
|
+
const escapedSub = escapeHtml(e.sub);
|
|
145
|
+
return `<div class="log-line">
|
|
146
|
+
<span class="log-time">${timeStr}</span>
|
|
147
|
+
<span class="log-level" style="color:${color}">${e.level.padEnd(5)}</span>
|
|
148
|
+
${escapedSub ? `<span class="log-sub">${escapedSub}</span>` : ''}
|
|
149
|
+
<span class="log-msg">${escapedMsg}</span>
|
|
150
|
+
</div>`;
|
|
151
|
+
}).join('');
|
|
152
|
+
|
|
153
|
+
el.innerHTML = html || '<div class="log-empty">No log entries.</div>';
|
|
154
|
+
|
|
155
|
+
if (logsAutoFollow) scrollToBottom();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function scrollToBottom() {
|
|
159
|
+
const scroll = document.getElementById('logs-scroll');
|
|
160
|
+
if (scroll) scroll.scrollTop = scroll.scrollHeight;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function escapeHtml(s) {
|
|
164
|
+
return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function fetchLogs() {
|
|
168
|
+
console.log('[logs] fetchLogs called, gateway exists:', !!window.gateway, 'connected:', window.gateway?.connected);
|
|
169
|
+
if (!window.gateway?.connected) return;
|
|
170
|
+
try {
|
|
171
|
+
const params = { limit: LOGS_LIMIT, maxBytes: LOGS_MAX_BYTES };
|
|
172
|
+
if (logsCursor != null) params.cursor = logsCursor;
|
|
173
|
+
console.log('[logs] calling rpc logs.tail...');
|
|
174
|
+
const result = await window.gateway.rpc('logs.tail', params);
|
|
175
|
+
console.log('[logs] rpc result:', result ? 'ok, lines=' + (result.lines?.length || 0) : 'null');
|
|
176
|
+
if (!result) return;
|
|
177
|
+
|
|
178
|
+
if (result.reset) {
|
|
179
|
+
logsEntries = [];
|
|
180
|
+
logsCursor = null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (result.lines && result.lines.length > 0) {
|
|
184
|
+
const newEntries = result.lines.map(parseLogLine);
|
|
185
|
+
logsEntries.push(...newEntries);
|
|
186
|
+
// Cap at 2000 entries
|
|
187
|
+
if (logsEntries.length > 2000) {
|
|
188
|
+
logsEntries = logsEntries.slice(logsEntries.length - 2000);
|
|
189
|
+
}
|
|
190
|
+
renderLogs();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (typeof result.cursor === 'number') {
|
|
194
|
+
logsCursor = result.cursor;
|
|
195
|
+
}
|
|
196
|
+
} catch (err) {
|
|
197
|
+
console.error('[logs] fetch error:', err);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function startLogsPolling() {
|
|
202
|
+
console.log('[logs] startLogsPolling, gateway:', !!window.gateway, 'connected:', window.gateway?.connected);
|
|
203
|
+
// Initial fetch
|
|
204
|
+
logsCursor = null;
|
|
205
|
+
logsEntries = [];
|
|
206
|
+
|
|
207
|
+
// If gateway isn't connected yet, wait for it
|
|
208
|
+
if (!window.gateway?.connected) {
|
|
209
|
+
console.log('[logs] gateway not connected, waiting...');
|
|
210
|
+
const waitIv = setInterval(() => {
|
|
211
|
+
console.log('[logs] waiting for gateway... connected:', window.gateway?.connected);
|
|
212
|
+
if (window.gateway?.connected) {
|
|
213
|
+
clearInterval(waitIv);
|
|
214
|
+
console.log('[logs] gateway connected! starting fetch...');
|
|
215
|
+
fetchLogs();
|
|
216
|
+
if (logsTimer) clearInterval(logsTimer);
|
|
217
|
+
logsTimer = setInterval(fetchLogs, LOGS_POLL_MS);
|
|
218
|
+
}
|
|
219
|
+
}, 500);
|
|
220
|
+
setTimeout(() => clearInterval(waitIv), 30000);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
fetchLogs();
|
|
225
|
+
// Poll every few seconds
|
|
226
|
+
if (logsTimer) clearInterval(logsTimer);
|
|
227
|
+
logsTimer = setInterval(fetchLogs, LOGS_POLL_MS);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Public API
|
|
231
|
+
window.openNativeLogs = function() {
|
|
232
|
+
createLogsUI();
|
|
233
|
+
startLogsPolling();
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
window.closeNativeLogs = function() {
|
|
237
|
+
destroyLogsUI();
|
|
238
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// ── Command Menu ──────────────────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
const menuOverlay = document.getElementById("cmd-overlay");
|
|
4
|
+
const menuTrigger = document.getElementById("menu-trigger");
|
|
5
|
+
const menuBackdrop = document.getElementById("cmd-backdrop");
|
|
6
|
+
|
|
7
|
+
let menuIsOpen = false;
|
|
8
|
+
|
|
9
|
+
function openMenu() {
|
|
10
|
+
menuIsOpen = true;
|
|
11
|
+
menuOverlay.classList.add("open");
|
|
12
|
+
menuOverlay.removeAttribute("aria-hidden");
|
|
13
|
+
menuTrigger.classList.add("menu-open");
|
|
14
|
+
menuTrigger.setAttribute("aria-expanded", "true");
|
|
15
|
+
document.addEventListener("keydown", onMenuKey);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function closeMenu() {
|
|
19
|
+
menuIsOpen = false;
|
|
20
|
+
menuOverlay.classList.remove("open");
|
|
21
|
+
menuOverlay.setAttribute("aria-hidden", "true");
|
|
22
|
+
menuTrigger.classList.remove("menu-open");
|
|
23
|
+
menuTrigger.setAttribute("aria-expanded", "false");
|
|
24
|
+
document.removeEventListener("keydown", onMenuKey);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toggleMenu() {
|
|
28
|
+
if (menuIsOpen) {
|
|
29
|
+
closeMenu();
|
|
30
|
+
} else {
|
|
31
|
+
openMenu();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function onMenuKey(e) {
|
|
36
|
+
if (e.key === "Escape") {
|
|
37
|
+
closeMenu();
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Trigger & backdrop ────────────────────────────────────────────────────────
|
|
42
|
+
menuTrigger.addEventListener("click", toggleMenu);
|
|
43
|
+
menuBackdrop.addEventListener("click", closeMenu);
|
|
44
|
+
|
|
45
|
+
// Keyboard shortcut: press M to open (when not focused on input)
|
|
46
|
+
document.addEventListener("keydown", (e) => {
|
|
47
|
+
if (menuIsOpen) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (e.key === "m" || e.key === "M") {
|
|
51
|
+
if (
|
|
52
|
+
document.activeElement.tagName === "INPUT" ||
|
|
53
|
+
document.activeElement.tagName === "TEXTAREA"
|
|
54
|
+
) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
e.preventDefault();
|
|
58
|
+
openMenu();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ── Page Overlay (native in-app navigation) ───────────────────────────────────
|
|
63
|
+
// Use direct-child selectors to avoid matching duplicates inside chat messages
|
|
64
|
+
function getPageOverlay() {
|
|
65
|
+
return document.querySelector("body > .page-overlay");
|
|
66
|
+
}
|
|
67
|
+
function _getPageBackBtn() {
|
|
68
|
+
return document.querySelector("body > .page-overlay #page-back-btn");
|
|
69
|
+
}
|
|
70
|
+
function getPageTitle() {
|
|
71
|
+
return document.querySelector("body > .page-overlay #page-overlay-title");
|
|
72
|
+
}
|
|
73
|
+
function getPagePopoutBtn() {
|
|
74
|
+
return document.querySelector("body > .page-overlay #page-popout-btn");
|
|
75
|
+
}
|
|
76
|
+
function getPageFrame() {
|
|
77
|
+
return document.querySelector("body > .page-overlay #page-overlay-frame");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let overlayIsOpen = false;
|
|
81
|
+
|
|
82
|
+
function openPageOverlay(name, standardUrl) {
|
|
83
|
+
// Derive the proxy URL: strip 18789 origin, prefix with /standard, add ?embed=1
|
|
84
|
+
// Also pass token + gatewayUrl via hash so the SPA auto-connects
|
|
85
|
+
let proxyUrl;
|
|
86
|
+
try {
|
|
87
|
+
const parsed = new URL(standardUrl);
|
|
88
|
+
proxyUrl = `/standard${parsed.pathname}${parsed.search ? parsed.search + "&embed=1" : "?embed=1"}`;
|
|
89
|
+
} catch {
|
|
90
|
+
proxyUrl = `/standard/?embed=1`;
|
|
91
|
+
}
|
|
92
|
+
// The SPA reads token and gatewayUrl from hash params → triggers auto-connect
|
|
93
|
+
proxyUrl +=
|
|
94
|
+
"#token=" +
|
|
95
|
+
encodeURIComponent(window.__SYMI_EMBED_TOKEN || "") +
|
|
96
|
+
"&gatewayUrl=" +
|
|
97
|
+
encodeURIComponent("ws://127.0.0.1:3333");
|
|
98
|
+
|
|
99
|
+
getPageTitle().textContent = name.toUpperCase();
|
|
100
|
+
getPagePopoutBtn().href = standardUrl;
|
|
101
|
+
getPageFrame().src = proxyUrl;
|
|
102
|
+
|
|
103
|
+
overlayIsOpen = true;
|
|
104
|
+
getPageOverlay().classList.add("open");
|
|
105
|
+
getPageOverlay().setAttribute("aria-hidden", "false");
|
|
106
|
+
document.addEventListener("keydown", onOverlayKey);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function closePageOverlay() {
|
|
110
|
+
overlayIsOpen = false;
|
|
111
|
+
// Clean up native settings panel if open
|
|
112
|
+
if (window.closeNativeSettings) {
|
|
113
|
+
window.closeNativeSettings();
|
|
114
|
+
}
|
|
115
|
+
getPageOverlay().classList.remove("open");
|
|
116
|
+
getPageOverlay().setAttribute("aria-hidden", "true");
|
|
117
|
+
// Restore iframe + popout button for next use
|
|
118
|
+
getPageFrame().style.display = "";
|
|
119
|
+
getPagePopoutBtn().style.display = "";
|
|
120
|
+
// Delay clearing src so the close animation plays cleanly
|
|
121
|
+
setTimeout(() => {
|
|
122
|
+
if (!overlayIsOpen) {
|
|
123
|
+
getPageFrame().src = "";
|
|
124
|
+
}
|
|
125
|
+
}, 300);
|
|
126
|
+
document.removeEventListener("keydown", onOverlayKey);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function onOverlayKey(e) {
|
|
130
|
+
if (e.key === "Escape") {
|
|
131
|
+
closePageOverlay();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
document.querySelector("body > .page-overlay #page-back-btn")?.addEventListener("click", () => {
|
|
136
|
+
// Go back to the menu instead of closing everything
|
|
137
|
+
closePageOverlay();
|
|
138
|
+
openMenu();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ── Item routing ──────────────────────────────────────────────────────────────
|
|
142
|
+
// Page display names (mapped from the menu item text)
|
|
143
|
+
const PAGE_NAMES = {
|
|
144
|
+
"/overview": "Overview",
|
|
145
|
+
"/channels": "Channels",
|
|
146
|
+
"/instances": "Instances",
|
|
147
|
+
"/sessions": "Sessions",
|
|
148
|
+
"/usage": "Usage",
|
|
149
|
+
"/cron": "Cron Jobs",
|
|
150
|
+
"/tasks": "Background Tasks",
|
|
151
|
+
"/agents": "Agents",
|
|
152
|
+
"/skills": "Skills",
|
|
153
|
+
"/nodes": "Nodes",
|
|
154
|
+
"/config": "Config",
|
|
155
|
+
"/debug": "Debug",
|
|
156
|
+
"/tools": "Agent Tools",
|
|
157
|
+
"/logs": "Logs",
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
// All internal pages rendered natively (no iframe)
|
|
161
|
+
const NATIVE_PAGES = {
|
|
162
|
+
"/overview": "overview",
|
|
163
|
+
"/channels": "channels",
|
|
164
|
+
"/instances": "instances",
|
|
165
|
+
"/sessions": "sessions",
|
|
166
|
+
"/usage": "usage",
|
|
167
|
+
"/cron": "cron",
|
|
168
|
+
"/tasks": "tasks",
|
|
169
|
+
"/agents": "agents",
|
|
170
|
+
"/skills": "skills",
|
|
171
|
+
"/nodes": "nodes",
|
|
172
|
+
"/config": "config",
|
|
173
|
+
"/debug": "debug",
|
|
174
|
+
"/tools": "tools",
|
|
175
|
+
"/logs": "logs",
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
function openNativePage(pageName, pageKey) {
|
|
179
|
+
if (window.closeNativeSettings) {
|
|
180
|
+
window.closeNativeSettings();
|
|
181
|
+
}
|
|
182
|
+
getPageTitle().textContent = pageName.toUpperCase();
|
|
183
|
+
getPagePopoutBtn().style.display = "none";
|
|
184
|
+
getPageFrame().style.display = "none";
|
|
185
|
+
overlayIsOpen = true;
|
|
186
|
+
getPageOverlay().classList.add("open");
|
|
187
|
+
getPageOverlay().setAttribute("aria-hidden", "false");
|
|
188
|
+
document.addEventListener("keydown", onOverlayKey);
|
|
189
|
+
if (window.openNativeSettings) {
|
|
190
|
+
window.openNativeSettings(pageKey);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
document.querySelectorAll(".cmd-item[data-href]").forEach((item) => {
|
|
195
|
+
item.addEventListener("click", (e) => {
|
|
196
|
+
e.preventDefault();
|
|
197
|
+
const href = item.dataset.href;
|
|
198
|
+
|
|
199
|
+
if (href === "#chat") {
|
|
200
|
+
closeMenu();
|
|
201
|
+
if (window.closeNativeSettings) {
|
|
202
|
+
window.closeNativeSettings();
|
|
203
|
+
}
|
|
204
|
+
closePageOverlay();
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Internal pages rendered natively
|
|
209
|
+
if (href.includes("127.0.0.1:18789")) {
|
|
210
|
+
let pathname = "/";
|
|
211
|
+
try {
|
|
212
|
+
pathname = new URL(href).pathname;
|
|
213
|
+
} catch {}
|
|
214
|
+
|
|
215
|
+
const pageKey = NATIVE_PAGES[pathname];
|
|
216
|
+
if (pageKey) {
|
|
217
|
+
const pageName = PAGE_NAMES[pathname] ?? "Page";
|
|
218
|
+
closeMenu();
|
|
219
|
+
openNativePage(pageName, pageKey);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// External links open in new tab
|
|
225
|
+
window.open(href, "_blank", "noopener");
|
|
226
|
+
closeMenu();
|
|
227
|
+
});
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Expose for external use (e.g., if other modules need to close the overlay)
|
|
231
|
+
window.openPageOverlay = openPageOverlay;
|
|
232
|
+
window.closePageOverlay = closePageOverlay;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// ── Command Menu ──────────────────────────────────────────────────────────────
|
|
2
|
+
|
|
3
|
+
const menuOverlay = document.getElementById('cmd-overlay');
|
|
4
|
+
const menuTrigger = document.getElementById('menu-trigger');
|
|
5
|
+
const menuBackdrop = document.getElementById('cmd-backdrop');
|
|
6
|
+
|
|
7
|
+
let menuIsOpen = false;
|
|
8
|
+
|
|
9
|
+
function openMenu() {
|
|
10
|
+
menuIsOpen = true;
|
|
11
|
+
menuOverlay.classList.add('open');
|
|
12
|
+
menuOverlay.removeAttribute('aria-hidden');
|
|
13
|
+
menuTrigger.classList.add('menu-open');
|
|
14
|
+
menuTrigger.setAttribute('aria-expanded', 'true');
|
|
15
|
+
document.addEventListener('keydown', onMenuKey);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function closeMenu() {
|
|
19
|
+
menuIsOpen = false;
|
|
20
|
+
menuOverlay.classList.remove('open');
|
|
21
|
+
menuOverlay.setAttribute('aria-hidden', 'true');
|
|
22
|
+
menuTrigger.classList.remove('menu-open');
|
|
23
|
+
menuTrigger.setAttribute('aria-expanded', 'false');
|
|
24
|
+
document.removeEventListener('keydown', onMenuKey);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toggleMenu() {
|
|
28
|
+
menuIsOpen ? closeMenu() : openMenu();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function onMenuKey(e) {
|
|
32
|
+
if (e.key === 'Escape') closeMenu();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ── Trigger & backdrop ────────────────────────────────────────────────────────
|
|
36
|
+
menuTrigger.addEventListener('click', toggleMenu);
|
|
37
|
+
menuBackdrop.addEventListener('click', closeMenu);
|
|
38
|
+
|
|
39
|
+
// Keyboard shortcut: press M to open (when not focused on input)
|
|
40
|
+
document.addEventListener('keydown', e => {
|
|
41
|
+
if (menuIsOpen) return;
|
|
42
|
+
if (e.key === 'm' || e.key === 'M') {
|
|
43
|
+
if (document.activeElement.tagName === 'INPUT' ||
|
|
44
|
+
document.activeElement.tagName === 'TEXTAREA') return;
|
|
45
|
+
e.preventDefault();
|
|
46
|
+
openMenu();
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// ── Item routing ──────────────────────────────────────────────────────────────
|
|
51
|
+
document.querySelectorAll('.cmd-item[data-href]').forEach(item => {
|
|
52
|
+
item.addEventListener('click', e => {
|
|
53
|
+
e.preventDefault();
|
|
54
|
+
const href = item.dataset.href;
|
|
55
|
+
|
|
56
|
+
if (href === '#chat') {
|
|
57
|
+
// Already on the chat — just close the menu
|
|
58
|
+
closeMenu();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// All other items: open in new tab (preserves new UI in current tab)
|
|
63
|
+
window.open(href, '_blank', 'noopener');
|
|
64
|
+
closeMenu();
|
|
65
|
+
});
|
|
66
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// ── Live metrics via /api/metrics (served by Node.js app layer) ───────
|
|
2
|
+
|
|
3
|
+
function setBar(barId, valId, pct, label) {
|
|
4
|
+
const bar = document.getElementById(barId);
|
|
5
|
+
const val = document.getElementById(valId);
|
|
6
|
+
if (bar) {
|
|
7
|
+
bar.style.width = Math.min(pct, 100) + "%";
|
|
8
|
+
}
|
|
9
|
+
if (val && label !== undefined) {
|
|
10
|
+
val.textContent = label;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function updateMetrics() {
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch("/api/metrics");
|
|
17
|
+
const data = await res.json();
|
|
18
|
+
|
|
19
|
+
setBar("cpu-bar", "cpu-val", data.cpu, data.cpu + "%");
|
|
20
|
+
setBar("ram-bar", "ram-val", data.memPct, data.memUsedGB + "G");
|
|
21
|
+
setBar("gpu-bar", "gpu-val", 5, "5%"); // GPU live data future enhancement
|
|
22
|
+
setBar("disk-bar", "disk-val", 5, "5%");
|
|
23
|
+
|
|
24
|
+
const uptimeEl = document.getElementById("uptime");
|
|
25
|
+
if (uptimeEl) {
|
|
26
|
+
uptimeEl.textContent = data.uptime;
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
// Server not ready yet — no-op
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function updateUptime() {
|
|
34
|
+
// Static uptime from system — weeks:days:hours
|
|
35
|
+
const el = document.getElementById("uptime");
|
|
36
|
+
if (el) {
|
|
37
|
+
el.textContent = "2w 3d 21h";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function updateSubagents() {
|
|
42
|
+
const el = document.getElementById("subagent-count");
|
|
43
|
+
if (el) {
|
|
44
|
+
el.textContent = "0 active";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Initial + interval updates
|
|
49
|
+
void updateMetrics();
|
|
50
|
+
updateUptime();
|
|
51
|
+
updateSubagents();
|
|
52
|
+
|
|
53
|
+
setInterval(updateMetrics, 2000);
|