agentgui 1.0.819 → 1.0.820
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1 -0
- package/CLAUDE.md +2 -1
- package/package.json +1 -1
- package/static/index.html +4 -0
- package/static/js/agent-auth-oauth.js +159 -0
- package/static/js/agent-auth.js +15 -162
- package/static/js/event-filter-config.js +36 -0
- package/static/js/event-filter.js +118 -252
- package/static/js/ui-components-rendering.js +183 -0
- package/static/js/ui-components.js +3 -185
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
## 2026-04-11
|
|
2
2
|
- refactor: split jsonl-watcher.js parse/event logic into jsonl-parser.js; watcher retains file watching/polling only
|
|
3
3
|
- refactor: split tool-version.js into tool-version-check.js (sync) and tool-version-fetch.js (async/network)
|
|
4
|
+
- refactor: split ui-components.js into core class (createModal/createTabs/createAlert/createSpinner/escapeHtml) and ui-components-rendering.js (createProgressBar/createCollapsible/createInput/createSelect/createButtonGroup/createBadge/copyToClipboard/downloadFile); both ≤200 lines
|
|
4
5
|
|
|
5
6
|
## [Unreleased]
|
|
6
7
|
### Refactor
|
package/CLAUDE.md
CHANGED
|
@@ -88,7 +88,8 @@ static/js/stt-handler.js Speech-to-text recording and upload
|
|
|
88
88
|
static/js/features.js View toggle, drag-drop upload, model progress indicator
|
|
89
89
|
static/js/tools-manager.js Tool install/update UI orchestrator
|
|
90
90
|
static/js/tools-manager-ui.js Tool card rendering + voice selector helpers
|
|
91
|
-
static/js/agent-auth.js Agent authentication UI (
|
|
91
|
+
static/js/agent-auth.js Agent authentication UI (dropdown, auth-status, provider keys)
|
|
92
|
+
static/js/agent-auth-oauth.js OAuth modal functions (triggerAuth, onWsMessage, paste fallback)
|
|
92
93
|
static/js/dialogs.js Modal dialog system
|
|
93
94
|
static/js/image-loader.js Lazy image loading for agent file read events
|
|
94
95
|
static/js/pm2-monitor.js PM2 process monitor UI
|
package/package.json
CHANGED
package/static/index.html
CHANGED
|
@@ -299,6 +299,7 @@
|
|
|
299
299
|
<script defer src="/gm/js/syntax-highlighter.js"></script>
|
|
300
300
|
<script defer src="/gm/js/dialogs.js"></script>
|
|
301
301
|
<script defer src="/gm/js/ui-components.js"></script>
|
|
302
|
+
<script defer src="/gm/js/ui-components-rendering.js"></script>
|
|
302
303
|
<script defer src="/gm/js/state-barrier.js"></script>
|
|
303
304
|
<script defer src="/gm/js/terminal.js"></script>
|
|
304
305
|
<script defer src="/gm/js/script-runner.js"></script>
|
|
@@ -307,9 +308,12 @@
|
|
|
307
308
|
<script defer src="/gm/js/stt-handler.js"></script>
|
|
308
309
|
<script defer src="/gm/js/voice.js"></script>
|
|
309
310
|
<script defer src="/gm/js/pm2-monitor.js"></script>
|
|
311
|
+
<script defer src="/gm/js/event-filter-config.js"></script>
|
|
312
|
+
<script defer src="/gm/js/event-filter.js"></script>
|
|
310
313
|
<script defer src="/gm/js/client.js"></script>
|
|
311
314
|
<script defer src="/gm/js/features.js"></script>
|
|
312
315
|
<script defer src="/gm/js/agent-auth.js"></script>
|
|
316
|
+
<script defer src="/gm/js/agent-auth-oauth.js"></script>
|
|
313
317
|
<script defer src="/gm/js/app-shortcuts.js"></script>
|
|
314
318
|
<script defer src="/gm/app.js"></script>
|
|
315
319
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var AUTH_CONV_ID = '__agent_auth__';
|
|
3
|
+
var oauthPollInterval = null, oauthPollTimeout = null, oauthFallbackTimer = null;
|
|
4
|
+
|
|
5
|
+
function state() { return window.__agentAuthState; }
|
|
6
|
+
|
|
7
|
+
function cleanupOAuthPolling() {
|
|
8
|
+
if (oauthPollInterval) { clearInterval(oauthPollInterval); oauthPollInterval = null; }
|
|
9
|
+
if (oauthPollTimeout) { clearTimeout(oauthPollTimeout); oauthPollTimeout = null; }
|
|
10
|
+
if (oauthFallbackTimer) { clearTimeout(oauthFallbackTimer); oauthFallbackTimer = null; }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function showOAuthWaitingModal() {
|
|
14
|
+
removeOAuthModal();
|
|
15
|
+
var overlay = document.createElement('div');
|
|
16
|
+
overlay.id = 'oauthWaitingModal';
|
|
17
|
+
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;z-index:9999;';
|
|
18
|
+
overlay.innerHTML = '<div style="background:var(--color-bg-secondary,#1f2937);border-radius:1rem;padding:2rem;max-width:28rem;width:calc(100% - 2rem);box-shadow:0 25px 50px rgba(0,0,0,0.5);color:var(--color-text-primary,white);font-family:system-ui,sans-serif;" onclick="event.stopPropagation()">' +
|
|
19
|
+
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">' +
|
|
20
|
+
'<h2 style="font-size:1.125rem;font-weight:700;margin:0;">Google Sign-In</h2>' +
|
|
21
|
+
'<button id="oauthWaitingClose" style="background:none;border:none;color:var(--color-text-secondary,#9ca3af);font-size:1.5rem;cursor:pointer;padding:0;line-height:1;">\u00d7</button></div>' +
|
|
22
|
+
'<div id="oauthWaitingContent" style="text-align:center;padding:1.5rem 0;">' +
|
|
23
|
+
'<div style="font-size:2rem;margin-bottom:1rem;animation:pulse 2s infinite;">⏳</div>' +
|
|
24
|
+
'<p style="font-size:0.85rem;color:var(--color-text-secondary,#d1d5db);margin:0 0 0.5rem;">Waiting for Google sign-in to complete...</p>' +
|
|
25
|
+
'<p style="font-size:0.75rem;color:var(--color-text-secondary,#6b7280);margin:0;">Complete the sign-in in the tab that just opened.</p>' +
|
|
26
|
+
'<p style="font-size:0.75rem;color:var(--color-text-secondary,#6b7280);margin:0.25rem 0 0;">This dialog will close automatically when done.</p></div>' +
|
|
27
|
+
'<div id="oauthPasteFallback" style="display:none;">' +
|
|
28
|
+
'<div style="margin-bottom:1rem;padding:1rem;background:var(--color-bg-tertiary,rgba(255,255,255,0.05));border-radius:0.5rem;">' +
|
|
29
|
+
'<p style="font-size:0.8rem;color:var(--color-text-secondary,#d1d5db);margin:0 0 0.5rem;">The automatic relay did not complete. This can happen when accessing the server remotely.</p>' +
|
|
30
|
+
'<p style="font-size:0.8rem;color:var(--color-text-secondary,#d1d5db);margin:0;">Copy the <span style="color:white;font-weight:600;">entire URL</span> from the sign-in tab and paste it below.</p></div>' +
|
|
31
|
+
'<input type="text" id="oauthPasteInput" placeholder="http://localhost:3000/gm/oauth2callback?code=..." style="width:100%;box-sizing:border-box;padding:0.75rem 1rem;background:var(--color-bg-primary,#374151);border:1px solid var(--color-border,#4b5563);border-radius:0.5rem;color:var(--color-text-primary,white);font-size:0.8rem;font-family:monospace;outline:none;" />' +
|
|
32
|
+
'<p id="oauthPasteError" style="font-size:0.75rem;color:#ef4444;margin:0.5rem 0 0;display:none;"></p></div>' +
|
|
33
|
+
'<div style="display:flex;gap:0.75rem;margin-top:1.25rem;">' +
|
|
34
|
+
'<button id="oauthWaitingCancel" style="flex:1;padding:0.625rem;border-radius:0.5rem;border:1px solid var(--color-border,#4b5563);background:transparent;color:var(--color-text-primary,white);font-size:0.8rem;cursor:pointer;font-weight:600;">Cancel</button>' +
|
|
35
|
+
'<button id="oauthPasteSubmit" style="flex:1;padding:0.625rem;border-radius:0.5rem;border:none;background:var(--color-primary,#3b82f6);color:white;font-size:0.8rem;cursor:pointer;font-weight:600;display:none;">Complete Sign-In</button></div>' +
|
|
36
|
+
'<style>@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}}</style></div>';
|
|
37
|
+
document.body.appendChild(overlay);
|
|
38
|
+
var dismiss = function() { cleanupOAuthPolling(); state().authRunning = false; removeOAuthModal(); };
|
|
39
|
+
document.getElementById('oauthWaitingClose').addEventListener('click', dismiss);
|
|
40
|
+
document.getElementById('oauthWaitingCancel').addEventListener('click', dismiss);
|
|
41
|
+
document.getElementById('oauthPasteSubmit').addEventListener('click', submitOAuthPasteUrl);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function showOAuthPasteFallback() {
|
|
45
|
+
var fallback = document.getElementById('oauthPasteFallback');
|
|
46
|
+
var waitContent = document.getElementById('oauthWaitingContent');
|
|
47
|
+
var submitBtn = document.getElementById('oauthPasteSubmit');
|
|
48
|
+
if (fallback) fallback.style.display = 'block';
|
|
49
|
+
if (waitContent) waitContent.style.display = 'none';
|
|
50
|
+
if (submitBtn) submitBtn.style.display = 'block';
|
|
51
|
+
var input = document.getElementById('oauthPasteInput');
|
|
52
|
+
if (input) {
|
|
53
|
+
input.addEventListener('keydown', function(e) { if (e.key === 'Enter') submitOAuthPasteUrl(); });
|
|
54
|
+
setTimeout(function() { input.focus(); }, 100);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function removeOAuthModal() {
|
|
59
|
+
var el = document.getElementById('oauthWaitingModal');
|
|
60
|
+
if (el) el.remove();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function submitOAuthPasteUrl() {
|
|
64
|
+
var input = document.getElementById('oauthPasteInput');
|
|
65
|
+
var errorEl = document.getElementById('oauthPasteError');
|
|
66
|
+
var submitBtn = document.getElementById('oauthPasteSubmit');
|
|
67
|
+
if (!input) return;
|
|
68
|
+
var url = input.value.trim();
|
|
69
|
+
if (!url) {
|
|
70
|
+
if (errorEl) { errorEl.textContent = 'Please paste the URL from the redirected page.'; errorEl.style.display = 'block'; }
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Verifying...'; }
|
|
74
|
+
if (errorEl) errorEl.style.display = 'none';
|
|
75
|
+
window.wsClient.rpc('gemini.complete', { url: url })
|
|
76
|
+
.then(function(data) {
|
|
77
|
+
if (data.success) {
|
|
78
|
+
cleanupOAuthPolling();
|
|
79
|
+
state().authRunning = false;
|
|
80
|
+
removeOAuthModal();
|
|
81
|
+
state().refresh();
|
|
82
|
+
} else {
|
|
83
|
+
if (errorEl) { errorEl.textContent = data.error || 'Failed to complete authentication.'; errorEl.style.display = 'block'; }
|
|
84
|
+
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
|
|
85
|
+
}
|
|
86
|
+
}).catch(function(e) {
|
|
87
|
+
if (errorEl) { errorEl.textContent = e.message; errorEl.style.display = 'block'; }
|
|
88
|
+
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function triggerAuth(agentId) {
|
|
93
|
+
if (state().authRunning) return;
|
|
94
|
+
window.wsClient.rpc('agent.auth', { id: agentId })
|
|
95
|
+
.then(function(data) {
|
|
96
|
+
if (data.ok) {
|
|
97
|
+
state().authRunning = true; showTerminalTab(); switchToTerminalView();
|
|
98
|
+
var term = getTerminal();
|
|
99
|
+
if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + agentId + ']\x1b[0m\r\n'); }
|
|
100
|
+
if (data.authUrl) {
|
|
101
|
+
window.open(data.authUrl, '_blank');
|
|
102
|
+
if (agentId === 'gemini') {
|
|
103
|
+
showOAuthWaitingModal();
|
|
104
|
+
cleanupOAuthPolling();
|
|
105
|
+
oauthPollInterval = setInterval(function() {
|
|
106
|
+
window.wsClient.rpc('gemini.status')
|
|
107
|
+
.then(function(status) {
|
|
108
|
+
if (status.status === 'success') {
|
|
109
|
+
cleanupOAuthPolling(); state().authRunning = false; removeOAuthModal(); state().refresh();
|
|
110
|
+
} else if (status.status === 'error') {
|
|
111
|
+
cleanupOAuthPolling(); state().authRunning = false; removeOAuthModal();
|
|
112
|
+
}
|
|
113
|
+
}).catch(function() {});
|
|
114
|
+
}, 1500);
|
|
115
|
+
oauthFallbackTimer = setTimeout(function() {
|
|
116
|
+
if (state().authRunning) showOAuthPasteFallback();
|
|
117
|
+
}, 30000);
|
|
118
|
+
oauthPollTimeout = setTimeout(function() {
|
|
119
|
+
cleanupOAuthPolling();
|
|
120
|
+
if (state().authRunning) { state().authRunning = false; removeOAuthModal(); }
|
|
121
|
+
}, 5 * 60 * 1000);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}).catch(function() {});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function onWsMessage(e) {
|
|
129
|
+
var data = e.detail;
|
|
130
|
+
if (!data || data.conversationId !== AUTH_CONV_ID) return;
|
|
131
|
+
if (data.type === 'script_started') {
|
|
132
|
+
state().authRunning = true; showTerminalTab(); switchToTerminalView();
|
|
133
|
+
var term = getTerminal();
|
|
134
|
+
if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + (data.agentId || '') + ']\x1b[0m\r\n'); }
|
|
135
|
+
} else if (data.type === 'script_output') {
|
|
136
|
+
showTerminalTab();
|
|
137
|
+
var term = getTerminal();
|
|
138
|
+
if (term) term.write(data.data);
|
|
139
|
+
} else if (data.type === 'script_stopped') {
|
|
140
|
+
state().authRunning = false;
|
|
141
|
+
removeOAuthModal();
|
|
142
|
+
cleanupOAuthPolling();
|
|
143
|
+
var term = getTerminal();
|
|
144
|
+
var msg = data.error ? data.error : ('exited with code ' + (data.code || 0));
|
|
145
|
+
if (term) term.writeln('\r\n\x1b[90m[auth ' + msg + ']\x1b[0m');
|
|
146
|
+
setTimeout(function() { state().refresh(); }, 1000);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function showTerminalTab() { var t = document.getElementById('terminalTabBtn'); if (t) t.style.display = ''; }
|
|
151
|
+
function switchToTerminalView() {
|
|
152
|
+
var bar = document.getElementById('viewToggleBar');
|
|
153
|
+
if (!bar) return;
|
|
154
|
+
var t = bar.querySelector('[data-view="terminal"]'); if (t) t.click();
|
|
155
|
+
}
|
|
156
|
+
function getTerminal() { return window.scriptRunner ? window.scriptRunner.getTerminal() : null; }
|
|
157
|
+
|
|
158
|
+
window.__agentAuthOAuth = { triggerAuth: triggerAuth, onWsMessage: onWsMessage };
|
|
159
|
+
})();
|
package/static/js/agent-auth.js
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
var btn = document.getElementById('agentAuthBtn');
|
|
3
3
|
var dropdown = document.getElementById('agentAuthDropdown');
|
|
4
4
|
var agents = [], providers = {}, authRunning = false, editingProvider = null;
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
window.__agentAuthState = {
|
|
7
|
+
get authRunning() { return authRunning; },
|
|
8
|
+
set authRunning(v) { authRunning = v; },
|
|
9
|
+
refresh: function() { refresh(); },
|
|
10
|
+
closeDropdown: function() { closeDropdown(); }
|
|
11
|
+
};
|
|
6
12
|
|
|
7
13
|
function init() {
|
|
8
14
|
if (!btn || !dropdown) return;
|
|
@@ -12,7 +18,9 @@
|
|
|
12
18
|
if (!btn.contains(e.target) && !dropdown.contains(e.target)) closeDropdown();
|
|
13
19
|
});
|
|
14
20
|
window.addEventListener('conversation-selected', function() { refresh(); });
|
|
15
|
-
window.addEventListener('ws-message',
|
|
21
|
+
window.addEventListener('ws-message', function(e) {
|
|
22
|
+
if (window.__agentAuthOAuth && window.__agentAuthOAuth.onWsMessage) window.__agentAuthOAuth.onWsMessage(e);
|
|
23
|
+
});
|
|
16
24
|
refresh();
|
|
17
25
|
}
|
|
18
26
|
|
|
@@ -48,7 +56,11 @@
|
|
|
48
56
|
agents.forEach(function(agent) {
|
|
49
57
|
var dotClass = agent.authenticated ? 'ok' : (agent.detail === 'unknown' ? 'unknown' : 'missing');
|
|
50
58
|
var item = makeItem(dotClass, agent.name, agent.detail);
|
|
51
|
-
item.addEventListener('click', function(e) {
|
|
59
|
+
item.addEventListener('click', function(e) {
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
closeDropdown();
|
|
62
|
+
if (window.__agentAuthOAuth && window.__agentAuthOAuth.triggerAuth) window.__agentAuthOAuth.triggerAuth(agent.id);
|
|
63
|
+
});
|
|
52
64
|
dropdown.appendChild(item);
|
|
53
65
|
});
|
|
54
66
|
}
|
|
@@ -126,165 +138,6 @@
|
|
|
126
138
|
}
|
|
127
139
|
|
|
128
140
|
function closeDropdown() { dropdown.classList.remove('open'); editingProvider = null; }
|
|
129
|
-
|
|
130
|
-
var oauthPollInterval = null, oauthPollTimeout = null, oauthFallbackTimer = null;
|
|
131
|
-
|
|
132
|
-
function cleanupOAuthPolling() {
|
|
133
|
-
if (oauthPollInterval) { clearInterval(oauthPollInterval); oauthPollInterval = null; }
|
|
134
|
-
if (oauthPollTimeout) { clearTimeout(oauthPollTimeout); oauthPollTimeout = null; }
|
|
135
|
-
if (oauthFallbackTimer) { clearTimeout(oauthFallbackTimer); oauthFallbackTimer = null; }
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function showOAuthWaitingModal() {
|
|
139
|
-
removeOAuthModal();
|
|
140
|
-
var overlay = document.createElement('div');
|
|
141
|
-
overlay.id = 'oauthWaitingModal';
|
|
142
|
-
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.7);display:flex;align-items:center;justify-content:center;z-index:9999;';
|
|
143
|
-
overlay.innerHTML = '<div style="background:var(--color-bg-secondary,#1f2937);border-radius:1rem;padding:2rem;max-width:28rem;width:calc(100% - 2rem);box-shadow:0 25px 50px rgba(0,0,0,0.5);color:var(--color-text-primary,white);font-family:system-ui,sans-serif;" onclick="event.stopPropagation()">' +
|
|
144
|
-
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">' +
|
|
145
|
-
'<h2 style="font-size:1.125rem;font-weight:700;margin:0;">Google Sign-In</h2>' +
|
|
146
|
-
'<button id="oauthWaitingClose" style="background:none;border:none;color:var(--color-text-secondary,#9ca3af);font-size:1.5rem;cursor:pointer;padding:0;line-height:1;">\u00d7</button></div>' +
|
|
147
|
-
'<div id="oauthWaitingContent" style="text-align:center;padding:1.5rem 0;">' +
|
|
148
|
-
'<div style="font-size:2rem;margin-bottom:1rem;animation:pulse 2s infinite;">⏳</div>' +
|
|
149
|
-
'<p style="font-size:0.85rem;color:var(--color-text-secondary,#d1d5db);margin:0 0 0.5rem;">Waiting for Google sign-in to complete...</p>' +
|
|
150
|
-
'<p style="font-size:0.75rem;color:var(--color-text-secondary,#6b7280);margin:0;">Complete the sign-in in the tab that just opened.</p>' +
|
|
151
|
-
'<p style="font-size:0.75rem;color:var(--color-text-secondary,#6b7280);margin:0.25rem 0 0;">This dialog will close automatically when done.</p></div>' +
|
|
152
|
-
'<div id="oauthPasteFallback" style="display:none;">' +
|
|
153
|
-
'<div style="margin-bottom:1rem;padding:1rem;background:var(--color-bg-tertiary,rgba(255,255,255,0.05));border-radius:0.5rem;">' +
|
|
154
|
-
'<p style="font-size:0.8rem;color:var(--color-text-secondary,#d1d5db);margin:0 0 0.5rem;">The automatic relay did not complete. This can happen when accessing the server remotely.</p>' +
|
|
155
|
-
'<p style="font-size:0.8rem;color:var(--color-text-secondary,#d1d5db);margin:0;">Copy the <span style="color:white;font-weight:600;">entire URL</span> from the sign-in tab and paste it below.</p></div>' +
|
|
156
|
-
'<input type="text" id="oauthPasteInput" placeholder="http://localhost:3000/gm/oauth2callback?code=..." style="width:100%;box-sizing:border-box;padding:0.75rem 1rem;background:var(--color-bg-primary,#374151);border:1px solid var(--color-border,#4b5563);border-radius:0.5rem;color:var(--color-text-primary,white);font-size:0.8rem;font-family:monospace;outline:none;" />' +
|
|
157
|
-
'<p id="oauthPasteError" style="font-size:0.75rem;color:#ef4444;margin:0.5rem 0 0;display:none;"></p></div>' +
|
|
158
|
-
'<div style="display:flex;gap:0.75rem;margin-top:1.25rem;">' +
|
|
159
|
-
'<button id="oauthWaitingCancel" style="flex:1;padding:0.625rem;border-radius:0.5rem;border:1px solid var(--color-border,#4b5563);background:transparent;color:var(--color-text-primary,white);font-size:0.8rem;cursor:pointer;font-weight:600;">Cancel</button>' +
|
|
160
|
-
'<button id="oauthPasteSubmit" style="flex:1;padding:0.625rem;border-radius:0.5rem;border:none;background:var(--color-primary,#3b82f6);color:white;font-size:0.8rem;cursor:pointer;font-weight:600;display:none;">Complete Sign-In</button></div>' +
|
|
161
|
-
'<style>@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.5}}</style></div>';
|
|
162
|
-
document.body.appendChild(overlay);
|
|
163
|
-
var dismiss = function() { cleanupOAuthPolling(); authRunning = false; removeOAuthModal(); };
|
|
164
|
-
document.getElementById('oauthWaitingClose').addEventListener('click', dismiss);
|
|
165
|
-
document.getElementById('oauthWaitingCancel').addEventListener('click', dismiss);
|
|
166
|
-
document.getElementById('oauthPasteSubmit').addEventListener('click', submitOAuthPasteUrl);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function showOAuthPasteFallback() {
|
|
170
|
-
var fallback = document.getElementById('oauthPasteFallback');
|
|
171
|
-
var waitContent = document.getElementById('oauthWaitingContent');
|
|
172
|
-
var submitBtn = document.getElementById('oauthPasteSubmit');
|
|
173
|
-
if (fallback) fallback.style.display = 'block';
|
|
174
|
-
if (waitContent) waitContent.style.display = 'none';
|
|
175
|
-
if (submitBtn) submitBtn.style.display = 'block';
|
|
176
|
-
var input = document.getElementById('oauthPasteInput');
|
|
177
|
-
if (input) {
|
|
178
|
-
input.addEventListener('keydown', function(e) { if (e.key === 'Enter') submitOAuthPasteUrl(); });
|
|
179
|
-
setTimeout(function() { input.focus(); }, 100);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
function removeOAuthModal() {
|
|
184
|
-
var el = document.getElementById('oauthWaitingModal');
|
|
185
|
-
if (el) el.remove();
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function submitOAuthPasteUrl() {
|
|
189
|
-
var input = document.getElementById('oauthPasteInput');
|
|
190
|
-
var errorEl = document.getElementById('oauthPasteError');
|
|
191
|
-
var submitBtn = document.getElementById('oauthPasteSubmit');
|
|
192
|
-
if (!input) return;
|
|
193
|
-
var url = input.value.trim();
|
|
194
|
-
if (!url) {
|
|
195
|
-
if (errorEl) { errorEl.textContent = 'Please paste the URL from the redirected page.'; errorEl.style.display = 'block'; }
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
|
-
if (submitBtn) { submitBtn.disabled = true; submitBtn.textContent = 'Verifying...'; }
|
|
199
|
-
if (errorEl) errorEl.style.display = 'none';
|
|
200
|
-
|
|
201
|
-
window.wsClient.rpc('gemini.complete', { url: url })
|
|
202
|
-
.then(function(data) {
|
|
203
|
-
if (data.success) {
|
|
204
|
-
cleanupOAuthPolling();
|
|
205
|
-
authRunning = false;
|
|
206
|
-
removeOAuthModal();
|
|
207
|
-
refresh();
|
|
208
|
-
} else {
|
|
209
|
-
if (errorEl) { errorEl.textContent = data.error || 'Failed to complete authentication.'; errorEl.style.display = 'block'; }
|
|
210
|
-
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
|
|
211
|
-
}
|
|
212
|
-
}).catch(function(e) {
|
|
213
|
-
if (errorEl) { errorEl.textContent = e.message; errorEl.style.display = 'block'; }
|
|
214
|
-
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = 'Complete Sign-In'; }
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function triggerAuth(agentId) {
|
|
219
|
-
if (authRunning) return;
|
|
220
|
-
window.wsClient.rpc('agent.auth', { id: agentId })
|
|
221
|
-
.then(function(data) {
|
|
222
|
-
if (data.ok) {
|
|
223
|
-
authRunning = true; showTerminalTab(); switchToTerminalView();
|
|
224
|
-
var term = getTerminal();
|
|
225
|
-
if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + agentId + ']\x1b[0m\r\n'); }
|
|
226
|
-
if (data.authUrl) {
|
|
227
|
-
window.open(data.authUrl, '_blank');
|
|
228
|
-
if (agentId === 'gemini') {
|
|
229
|
-
showOAuthWaitingModal();
|
|
230
|
-
cleanupOAuthPolling();
|
|
231
|
-
oauthPollInterval = setInterval(function() {
|
|
232
|
-
window.wsClient.rpc('gemini.status')
|
|
233
|
-
.then(function(status) {
|
|
234
|
-
if (status.status === 'success') {
|
|
235
|
-
cleanupOAuthPolling();
|
|
236
|
-
authRunning = false;
|
|
237
|
-
removeOAuthModal();
|
|
238
|
-
refresh();
|
|
239
|
-
} else if (status.status === 'error') {
|
|
240
|
-
cleanupOAuthPolling();
|
|
241
|
-
authRunning = false;
|
|
242
|
-
removeOAuthModal();
|
|
243
|
-
}
|
|
244
|
-
}).catch(function() {});
|
|
245
|
-
}, 1500);
|
|
246
|
-
oauthFallbackTimer = setTimeout(function() {
|
|
247
|
-
if (authRunning) showOAuthPasteFallback();
|
|
248
|
-
}, 30000);
|
|
249
|
-
oauthPollTimeout = setTimeout(function() {
|
|
250
|
-
cleanupOAuthPolling();
|
|
251
|
-
if (authRunning) { authRunning = false; removeOAuthModal(); }
|
|
252
|
-
}, 5 * 60 * 1000);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
}).catch(function() {});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
function onWsMessage(e) {
|
|
260
|
-
var data = e.detail;
|
|
261
|
-
if (!data || data.conversationId !== AUTH_CONV_ID) return;
|
|
262
|
-
if (data.type === 'script_started') {
|
|
263
|
-
authRunning = true; showTerminalTab(); switchToTerminalView();
|
|
264
|
-
var term = getTerminal();
|
|
265
|
-
if (term) { term.clear(); term.writeln('\x1b[36m[authenticating ' + (data.agentId || '') + ']\x1b[0m\r\n'); }
|
|
266
|
-
} else if (data.type === 'script_output') {
|
|
267
|
-
showTerminalTab();
|
|
268
|
-
var term = getTerminal();
|
|
269
|
-
if (term) term.write(data.data);
|
|
270
|
-
} else if (data.type === 'script_stopped') {
|
|
271
|
-
authRunning = false;
|
|
272
|
-
removeOAuthModal();
|
|
273
|
-
cleanupOAuthPolling();
|
|
274
|
-
var term = getTerminal();
|
|
275
|
-
var msg = data.error ? data.error : ('exited with code ' + (data.code || 0));
|
|
276
|
-
if (term) term.writeln('\r\n\x1b[90m[auth ' + msg + ']\x1b[0m');
|
|
277
|
-
setTimeout(refresh, 1000);
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function showTerminalTab() { var t = document.getElementById('terminalTabBtn'); if (t) t.style.display = ''; }
|
|
282
|
-
function switchToTerminalView() {
|
|
283
|
-
var bar = document.getElementById('viewToggleBar');
|
|
284
|
-
if (!bar) return;
|
|
285
|
-
var t = bar.querySelector('[data-view="terminal"]'); if (t) t.click();
|
|
286
|
-
}
|
|
287
|
-
function getTerminal() { return window.scriptRunner ? window.scriptRunner.getTerminal() : null; }
|
|
288
141
|
function esc(s) { var d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
|
|
289
142
|
|
|
290
143
|
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
window.EVENT_FILTER_CSV_HEADERS = ['timestamp', 'type', 'id', 'sessionId', 'message'];
|
|
3
|
+
|
|
4
|
+
window.exportEventsAsJSON = function(events) {
|
|
5
|
+
return JSON.stringify(events, null, 2);
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
window.exportEventsAsCSV = function(events) {
|
|
9
|
+
const headers = window.EVENT_FILTER_CSV_HEADERS;
|
|
10
|
+
const rows = [headers.join(',')];
|
|
11
|
+
for (const event of events) {
|
|
12
|
+
const row = [
|
|
13
|
+
new Date(event.timestamp || event.trackedAt).toISOString(),
|
|
14
|
+
event.type,
|
|
15
|
+
event.id || '',
|
|
16
|
+
event.sessionId || '',
|
|
17
|
+
JSON.stringify(event.message || event.content || event.text || '')
|
|
18
|
+
];
|
|
19
|
+
rows.push(row.map(v => `"${v}"`).join(','));
|
|
20
|
+
}
|
|
21
|
+
return rows.join('\n');
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
window.exportEventsAsMarkdown = function(events) {
|
|
25
|
+
const lines = ['# Event Export\n'];
|
|
26
|
+
let currentType = null;
|
|
27
|
+
for (const event of events) {
|
|
28
|
+
if (event.type !== currentType) {
|
|
29
|
+
currentType = event.type;
|
|
30
|
+
lines.push(`\n## ${currentType}\n`);
|
|
31
|
+
}
|
|
32
|
+
const time = new Date(event.timestamp || event.trackedAt).toLocaleTimeString();
|
|
33
|
+
lines.push(`- **${time}**: ${JSON.stringify(event)}`);
|
|
34
|
+
}
|
|
35
|
+
return lines.join('\n');
|
|
36
|
+
};
|
|
@@ -1,252 +1,118 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
this.
|
|
5
|
-
this.
|
|
6
|
-
this.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.filterState.
|
|
47
|
-
|
|
48
|
-
this.filterState.
|
|
49
|
-
}
|
|
50
|
-
this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
this.
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
return results.sort((a, b) => b.matchCount - a.matchCount);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
getStats() {
|
|
123
|
-
const stats = {
|
|
124
|
-
total: this.allEvents.length,
|
|
125
|
-
byType: {},
|
|
126
|
-
byTime: {
|
|
127
|
-
oldest: null,
|
|
128
|
-
newest: null,
|
|
129
|
-
span: 0
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
for (const event of this.allEvents) {
|
|
134
|
-
stats.byType[event.type] = (stats.byType[event.type] || 0) + 1;
|
|
135
|
-
|
|
136
|
-
const time = event.timestamp || event.trackedAt;
|
|
137
|
-
if (!stats.byTime.oldest || time < stats.byTime.oldest) {
|
|
138
|
-
stats.byTime.oldest = time;
|
|
139
|
-
}
|
|
140
|
-
if (!stats.byTime.newest || time > stats.byTime.newest) {
|
|
141
|
-
stats.byTime.newest = time;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (stats.byTime.oldest && stats.byTime.newest) {
|
|
146
|
-
stats.byTime.span = stats.byTime.newest - stats.byTime.oldest;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return stats;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
async startReplay(events = null, speed = 1) {
|
|
153
|
-
const replayEvents = events || this.filteredEvents;
|
|
154
|
-
if (replayEvents.length === 0) return;
|
|
155
|
-
|
|
156
|
-
this.replayState.isReplaying = true;
|
|
157
|
-
this.replayState.currentIndex = 0;
|
|
158
|
-
this.replayState.speed = speed;
|
|
159
|
-
|
|
160
|
-
this.renderer.clear();
|
|
161
|
-
|
|
162
|
-
for (const event of replayEvents) {
|
|
163
|
-
if (!this.replayState.isReplaying) break;
|
|
164
|
-
|
|
165
|
-
const delay = 100 / this.replayState.speed;
|
|
166
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
167
|
-
|
|
168
|
-
this.renderer.queueEvent(event);
|
|
169
|
-
this.replayState.currentIndex++;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
this.replayState.isReplaying = false;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
stopReplay() {
|
|
176
|
-
this.replayState.isReplaying = false;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
getReplayProgress() {
|
|
180
|
-
const total = this.filteredEvents.length;
|
|
181
|
-
const current = this.replayState.currentIndex;
|
|
182
|
-
return {
|
|
183
|
-
current,
|
|
184
|
-
total,
|
|
185
|
-
percentage: total > 0 ? (current / total) * 100 : 0,
|
|
186
|
-
isReplaying: this.replayState.isReplaying
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export(format = 'json') {
|
|
191
|
-
const events = this.filterState.isActive ? this.filteredEvents : this.allEvents;
|
|
192
|
-
|
|
193
|
-
switch (format) {
|
|
194
|
-
case 'json':
|
|
195
|
-
return JSON.stringify(events, null, 2);
|
|
196
|
-
|
|
197
|
-
case 'csv':
|
|
198
|
-
return this.exportAsCSV(events);
|
|
199
|
-
|
|
200
|
-
case 'markdown':
|
|
201
|
-
return this.exportAsMarkdown(events);
|
|
202
|
-
|
|
203
|
-
default:
|
|
204
|
-
return JSON.stringify(events);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
exportAsCSV(events) {
|
|
209
|
-
const headers = ['timestamp', 'type', 'id', 'sessionId', 'message'];
|
|
210
|
-
const rows = [headers.join(',')];
|
|
211
|
-
|
|
212
|
-
for (const event of events) {
|
|
213
|
-
const row = [
|
|
214
|
-
new Date(event.timestamp || event.trackedAt).toISOString(),
|
|
215
|
-
event.type,
|
|
216
|
-
event.id || '',
|
|
217
|
-
event.sessionId || '',
|
|
218
|
-
JSON.stringify(event.message || event.content || event.text || '')
|
|
219
|
-
];
|
|
220
|
-
rows.push(row.map(v => `"${v}"`).join(','));
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
return rows.join('\n');
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
exportAsMarkdown(events) {
|
|
227
|
-
const lines = ['# Event Export\n'];
|
|
228
|
-
let currentType = null;
|
|
229
|
-
|
|
230
|
-
for (const event of events) {
|
|
231
|
-
if (event.type !== currentType) {
|
|
232
|
-
currentType = event.type;
|
|
233
|
-
lines.push(`\n## ${currentType}\n`);
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
const time = new Date(event.timestamp || event.trackedAt).toLocaleTimeString();
|
|
237
|
-
lines.push(`- **${time}**: ${JSON.stringify(event)}`);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return lines.join('\n');
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
clear() {
|
|
244
|
-
this.allEvents = [];
|
|
245
|
-
this.filteredEvents = [];
|
|
246
|
-
this.stopReplay();
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
251
|
-
module.exports = EventFilter;
|
|
252
|
-
}
|
|
1
|
+
class EventFilter {
|
|
2
|
+
constructor(renderer) {
|
|
3
|
+
this.renderer = renderer;
|
|
4
|
+
this.allEvents = [];
|
|
5
|
+
this.filteredEvents = [];
|
|
6
|
+
this.filterState = {
|
|
7
|
+
types: new Set(),
|
|
8
|
+
searchText: '',
|
|
9
|
+
startTime: null,
|
|
10
|
+
endTime: null,
|
|
11
|
+
isActive: false
|
|
12
|
+
};
|
|
13
|
+
this.replayState = {
|
|
14
|
+
isReplaying: false,
|
|
15
|
+
currentIndex: 0,
|
|
16
|
+
speed: 1
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
trackEvent(event) {
|
|
21
|
+
this.allEvents.push({
|
|
22
|
+
...event,
|
|
23
|
+
trackingId: this.allEvents.length,
|
|
24
|
+
trackedAt: Date.now()
|
|
25
|
+
});
|
|
26
|
+
if (this.allEvents.length > 5000) this.allEvents.shift();
|
|
27
|
+
if (this.filterState.isActive) this.applyFilters();
|
|
28
|
+
return event;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
setTypeFilter(types) { this.filterState.types = new Set(types); this.applyFilters(); }
|
|
32
|
+
|
|
33
|
+
toggleType(type) {
|
|
34
|
+
if (this.filterState.types.has(type)) this.filterState.types.delete(type);
|
|
35
|
+
else this.filterState.types.add(type);
|
|
36
|
+
this.applyFilters();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
setSearchText(text) { this.filterState.searchText = text.toLowerCase(); this.applyFilters(); }
|
|
40
|
+
|
|
41
|
+
setTimeRange(startTime, endTime) { this.filterState.startTime = startTime; this.filterState.endTime = endTime; this.applyFilters(); }
|
|
42
|
+
|
|
43
|
+
applyFilters() {
|
|
44
|
+
this.filterState.isActive =
|
|
45
|
+
this.filterState.types.size > 0 ||
|
|
46
|
+
this.filterState.searchText.length > 0 ||
|
|
47
|
+
this.filterState.startTime !== null ||
|
|
48
|
+
this.filterState.endTime !== null;
|
|
49
|
+
if (!this.filterState.isActive) { this.filteredEvents = [...this.allEvents]; return this.filteredEvents; }
|
|
50
|
+
this.filteredEvents = this.allEvents.filter(event => {
|
|
51
|
+
if (this.filterState.types.size > 0 && !this.filterState.types.has(event.type)) return false;
|
|
52
|
+
if (this.filterState.searchText.length > 0) {
|
|
53
|
+
if (!JSON.stringify(event).toLowerCase().includes(this.filterState.searchText)) return false;
|
|
54
|
+
}
|
|
55
|
+
const eventTime = event.timestamp || event.trackedAt;
|
|
56
|
+
if (this.filterState.startTime && eventTime < this.filterState.startTime) return false;
|
|
57
|
+
if (this.filterState.endTime && eventTime > this.filterState.endTime) return false;
|
|
58
|
+
return true;
|
|
59
|
+
});
|
|
60
|
+
return this.filteredEvents;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
search(query) {
|
|
64
|
+
const lowerQuery = query.toLowerCase();
|
|
65
|
+
return this.allEvents.map((event, i) => {
|
|
66
|
+
const searchable = JSON.stringify(event).toLowerCase();
|
|
67
|
+
if (!searchable.includes(lowerQuery)) return null;
|
|
68
|
+
return { event, index: i, matchCount: (searchable.match(new RegExp(lowerQuery, 'g')) || []).length };
|
|
69
|
+
}).filter(Boolean).sort((a, b) => b.matchCount - a.matchCount);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
getStats() {
|
|
73
|
+
const stats = { total: this.allEvents.length, byType: {}, byTime: { oldest: null, newest: null, span: 0 } };
|
|
74
|
+
for (const event of this.allEvents) {
|
|
75
|
+
stats.byType[event.type] = (stats.byType[event.type] || 0) + 1;
|
|
76
|
+
const time = event.timestamp || event.trackedAt;
|
|
77
|
+
if (!stats.byTime.oldest || time < stats.byTime.oldest) stats.byTime.oldest = time;
|
|
78
|
+
if (!stats.byTime.newest || time > stats.byTime.newest) stats.byTime.newest = time;
|
|
79
|
+
}
|
|
80
|
+
if (stats.byTime.oldest && stats.byTime.newest) stats.byTime.span = stats.byTime.newest - stats.byTime.oldest;
|
|
81
|
+
return stats;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async startReplay(events = null, speed = 1) {
|
|
85
|
+
const replayEvents = events || this.filteredEvents;
|
|
86
|
+
if (replayEvents.length === 0) return;
|
|
87
|
+
this.replayState = { isReplaying: true, currentIndex: 0, speed };
|
|
88
|
+
this.renderer.clear();
|
|
89
|
+
for (const event of replayEvents) {
|
|
90
|
+
if (!this.replayState.isReplaying) break;
|
|
91
|
+
await new Promise(resolve => setTimeout(resolve, 100 / this.replayState.speed));
|
|
92
|
+
this.renderer.queueEvent(event);
|
|
93
|
+
this.replayState.currentIndex++;
|
|
94
|
+
}
|
|
95
|
+
this.replayState.isReplaying = false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
stopReplay() { this.replayState.isReplaying = false; }
|
|
99
|
+
|
|
100
|
+
getReplayProgress() {
|
|
101
|
+
const total = this.filteredEvents.length;
|
|
102
|
+
const current = this.replayState.currentIndex;
|
|
103
|
+
return { current, total, percentage: total > 0 ? (current / total) * 100 : 0, isReplaying: this.replayState.isReplaying };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export(format = 'json') {
|
|
107
|
+
const events = this.filterState.isActive ? this.filteredEvents : this.allEvents;
|
|
108
|
+
if (format === 'csv') return window.exportEventsAsCSV(events);
|
|
109
|
+
if (format === 'markdown') return window.exportEventsAsMarkdown(events);
|
|
110
|
+
return window.exportEventsAsJSON(events);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
clear() { this.allEvents = []; this.filteredEvents = []; this.stopReplay(); }
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
window.EventFilter = EventFilter;
|
|
117
|
+
|
|
118
|
+
if (typeof module !== 'undefined' && module.exports) module.exports = EventFilter;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
|
|
2
|
+
UIComponents.copyToClipboard = function(text) {
|
|
3
|
+
return navigator.clipboard.writeText(text).catch(err => {
|
|
4
|
+
console.error('Failed to copy:', err);
|
|
5
|
+
return false;
|
|
6
|
+
});
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
UIComponents.downloadFile = function(data, filename, mimeType = 'text/plain') {
|
|
10
|
+
const blob = new Blob([data], { type: mimeType });
|
|
11
|
+
const url = URL.createObjectURL(blob);
|
|
12
|
+
const link = document.createElement('a');
|
|
13
|
+
link.href = url;
|
|
14
|
+
link.download = filename;
|
|
15
|
+
document.body.appendChild(link);
|
|
16
|
+
link.click();
|
|
17
|
+
document.body.removeChild(link);
|
|
18
|
+
URL.revokeObjectURL(url);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
UIComponents.createProgressBar = function(config = {}) {
|
|
22
|
+
const {
|
|
23
|
+
percentage = 0,
|
|
24
|
+
label = '',
|
|
25
|
+
showLabel = true
|
|
26
|
+
} = config;
|
|
27
|
+
|
|
28
|
+
const container = document.createElement('div');
|
|
29
|
+
container.className = 'progress-container';
|
|
30
|
+
|
|
31
|
+
let html = '';
|
|
32
|
+
if (label && showLabel) {
|
|
33
|
+
html += `<div class="flex justify-between mb-2 text-sm"><span>${UIComponents.escapeHtml(label)}</span><span>${Math.round(percentage)}%</span></div>`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
html += `
|
|
37
|
+
<progress class="progress progress-primary progress-xs w-full" value="${Math.min(100, Math.max(0, percentage))}" max="100"></progress>
|
|
38
|
+
`;
|
|
39
|
+
|
|
40
|
+
container.innerHTML = html;
|
|
41
|
+
return container;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
UIComponents.createCollapsible = function(config = {}) {
|
|
45
|
+
const {
|
|
46
|
+
title = 'Details',
|
|
47
|
+
content = '',
|
|
48
|
+
isOpen = false
|
|
49
|
+
} = config;
|
|
50
|
+
|
|
51
|
+
const container = document.createElement('div');
|
|
52
|
+
container.className = 'collapsible';
|
|
53
|
+
|
|
54
|
+
container.innerHTML = `
|
|
55
|
+
<details ${isOpen ? 'open' : ''}>
|
|
56
|
+
<summary class="cursor-pointer font-semibold hover:bg-base-200 px-2 py-1 rounded transition-colors">
|
|
57
|
+
${UIComponents.escapeHtml(title)}
|
|
58
|
+
</summary>
|
|
59
|
+
<div class="content mt-2 ml-4">
|
|
60
|
+
${typeof content === 'string' ? content : ''}
|
|
61
|
+
</div>
|
|
62
|
+
</details>
|
|
63
|
+
`;
|
|
64
|
+
|
|
65
|
+
return container;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
UIComponents.createInput = function(config = {}) {
|
|
69
|
+
const {
|
|
70
|
+
type = 'text',
|
|
71
|
+
name = '',
|
|
72
|
+
label = '',
|
|
73
|
+
placeholder = '',
|
|
74
|
+
value = '',
|
|
75
|
+
required = false
|
|
76
|
+
} = config;
|
|
77
|
+
|
|
78
|
+
const container = document.createElement('div');
|
|
79
|
+
container.className = 'form-group mb-4';
|
|
80
|
+
|
|
81
|
+
let html = '';
|
|
82
|
+
if (label) {
|
|
83
|
+
html += `<label class="block text-sm font-medium mb-2">${UIComponents.escapeHtml(label)}</label>`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
html += `
|
|
87
|
+
<input
|
|
88
|
+
type="${type}"
|
|
89
|
+
name="${name}"
|
|
90
|
+
placeholder="${UIComponents.escapeHtml(placeholder)}"
|
|
91
|
+
value="${UIComponents.escapeHtml(value)}"
|
|
92
|
+
${required ? 'required' : ''}
|
|
93
|
+
class="input input-block input-solid"
|
|
94
|
+
/>
|
|
95
|
+
`;
|
|
96
|
+
|
|
97
|
+
container.innerHTML = html;
|
|
98
|
+
return container;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
UIComponents.createSelect = function(config = {}) {
|
|
102
|
+
const {
|
|
103
|
+
name = '',
|
|
104
|
+
label = '',
|
|
105
|
+
options = [],
|
|
106
|
+
value = '',
|
|
107
|
+
required = false
|
|
108
|
+
} = config;
|
|
109
|
+
|
|
110
|
+
const container = document.createElement('div');
|
|
111
|
+
container.className = 'form-group mb-4';
|
|
112
|
+
|
|
113
|
+
let html = '';
|
|
114
|
+
if (label) {
|
|
115
|
+
html += `<label class="block text-sm font-medium mb-2">${UIComponents.escapeHtml(label)}</label>`;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
html += `
|
|
119
|
+
<select
|
|
120
|
+
name="${name}"
|
|
121
|
+
${required ? 'required' : ''}
|
|
122
|
+
class="select select-block select-solid"
|
|
123
|
+
>
|
|
124
|
+
${options.map(opt => `
|
|
125
|
+
<option value="${opt.value}" ${opt.value === value ? 'selected' : ''}>
|
|
126
|
+
${UIComponents.escapeHtml(opt.label)}
|
|
127
|
+
</option>
|
|
128
|
+
`).join('')}
|
|
129
|
+
</select>
|
|
130
|
+
`;
|
|
131
|
+
|
|
132
|
+
container.innerHTML = html;
|
|
133
|
+
return container;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
UIComponents.createButtonGroup = function(config = {}) {
|
|
137
|
+
const {
|
|
138
|
+
buttons = [],
|
|
139
|
+
vertical = false
|
|
140
|
+
} = config;
|
|
141
|
+
|
|
142
|
+
const container = document.createElement('div');
|
|
143
|
+
container.className = `button-group flex gap-2 ${vertical ? 'flex-col' : 'flex-row'}`;
|
|
144
|
+
|
|
145
|
+
buttons.forEach(btn => {
|
|
146
|
+
const button = document.createElement('button');
|
|
147
|
+
button.className = `btn btn-${btn.variant || 'secondary'} flex-${vertical ? '1' : 'none'}`;
|
|
148
|
+
button.textContent = btn.label;
|
|
149
|
+
if (btn.onClick) {
|
|
150
|
+
button.addEventListener('click', btn.onClick);
|
|
151
|
+
}
|
|
152
|
+
container.appendChild(button);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return container;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
UIComponents.createBadge = function(config = {}) {
|
|
159
|
+
const {
|
|
160
|
+
label = '',
|
|
161
|
+
variant = 'default',
|
|
162
|
+
size = 'medium'
|
|
163
|
+
} = config;
|
|
164
|
+
|
|
165
|
+
const sizeClasses = {
|
|
166
|
+
'small': 'badge-sm',
|
|
167
|
+
'medium': 'badge-md',
|
|
168
|
+
'large': 'badge-lg'
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const variantClasses = {
|
|
172
|
+
'default': 'badge-flat',
|
|
173
|
+
'primary': 'badge-flat-primary',
|
|
174
|
+
'success': 'badge-flat-success',
|
|
175
|
+
'warning': 'badge-flat-warning',
|
|
176
|
+
'error': 'badge-flat-error'
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const badge = document.createElement('span');
|
|
180
|
+
badge.className = `badge ${sizeClasses[size] || sizeClasses['medium']} ${variantClasses[variant] || variantClasses['default']}`;
|
|
181
|
+
badge.textContent = label;
|
|
182
|
+
return badge;
|
|
183
|
+
};
|
|
@@ -6,7 +6,7 @@ class UIComponents {
|
|
|
6
6
|
content = '',
|
|
7
7
|
buttons = [],
|
|
8
8
|
onClose = null,
|
|
9
|
-
size = 'medium'
|
|
9
|
+
size = 'medium'
|
|
10
10
|
} = config;
|
|
11
11
|
|
|
12
12
|
const modal = document.createElement('div');
|
|
@@ -122,7 +122,7 @@ class UIComponents {
|
|
|
122
122
|
static createAlert(config = {}) {
|
|
123
123
|
const {
|
|
124
124
|
message = '',
|
|
125
|
-
type = 'info',
|
|
125
|
+
type = 'info',
|
|
126
126
|
duration = 5000,
|
|
127
127
|
dismissible = true
|
|
128
128
|
} = config;
|
|
@@ -157,7 +157,7 @@ class UIComponents {
|
|
|
157
157
|
|
|
158
158
|
static createSpinner(config = {}) {
|
|
159
159
|
const {
|
|
160
|
-
size = 'medium',
|
|
160
|
+
size = 'medium',
|
|
161
161
|
text = 'Loading...'
|
|
162
162
|
} = config;
|
|
163
163
|
|
|
@@ -176,192 +176,10 @@ class UIComponents {
|
|
|
176
176
|
return container;
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
static createProgressBar(config = {}) {
|
|
180
|
-
const {
|
|
181
|
-
percentage = 0,
|
|
182
|
-
label = '',
|
|
183
|
-
showLabel = true
|
|
184
|
-
} = config;
|
|
185
|
-
|
|
186
|
-
const container = document.createElement('div');
|
|
187
|
-
container.className = 'progress-container';
|
|
188
|
-
|
|
189
|
-
let html = '';
|
|
190
|
-
if (label && showLabel) {
|
|
191
|
-
html += `<div class="flex justify-between mb-2 text-sm"><span>${UIComponents.escapeHtml(label)}</span><span>${Math.round(percentage)}%</span></div>`;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
html += `
|
|
195
|
-
<progress class="progress progress-primary progress-xs w-full" value="${Math.min(100, Math.max(0, percentage))}" max="100"></progress>
|
|
196
|
-
`;
|
|
197
|
-
|
|
198
|
-
container.innerHTML = html;
|
|
199
|
-
return container;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
static createCollapsible(config = {}) {
|
|
203
|
-
const {
|
|
204
|
-
title = 'Details',
|
|
205
|
-
content = '',
|
|
206
|
-
isOpen = false
|
|
207
|
-
} = config;
|
|
208
|
-
|
|
209
|
-
const container = document.createElement('div');
|
|
210
|
-
container.className = 'collapsible';
|
|
211
|
-
|
|
212
|
-
container.innerHTML = `
|
|
213
|
-
<details ${isOpen ? 'open' : ''}>
|
|
214
|
-
<summary class="cursor-pointer font-semibold hover:bg-base-200 px-2 py-1 rounded transition-colors">
|
|
215
|
-
${UIComponents.escapeHtml(title)}
|
|
216
|
-
</summary>
|
|
217
|
-
<div class="content mt-2 ml-4">
|
|
218
|
-
${typeof content === 'string' ? content : ''}
|
|
219
|
-
</div>
|
|
220
|
-
</details>
|
|
221
|
-
`;
|
|
222
|
-
|
|
223
|
-
return container;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
static createInput(config = {}) {
|
|
227
|
-
const {
|
|
228
|
-
type = 'text',
|
|
229
|
-
name = '',
|
|
230
|
-
label = '',
|
|
231
|
-
placeholder = '',
|
|
232
|
-
value = '',
|
|
233
|
-
required = false
|
|
234
|
-
} = config;
|
|
235
|
-
|
|
236
|
-
const container = document.createElement('div');
|
|
237
|
-
container.className = 'form-group mb-4';
|
|
238
|
-
|
|
239
|
-
let html = '';
|
|
240
|
-
if (label) {
|
|
241
|
-
html += `<label class="block text-sm font-medium mb-2">${UIComponents.escapeHtml(label)}</label>`;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
html += `
|
|
245
|
-
<input
|
|
246
|
-
type="${type}"
|
|
247
|
-
name="${name}"
|
|
248
|
-
placeholder="${UIComponents.escapeHtml(placeholder)}"
|
|
249
|
-
value="${UIComponents.escapeHtml(value)}"
|
|
250
|
-
${required ? 'required' : ''}
|
|
251
|
-
class="input input-block input-solid"
|
|
252
|
-
/>
|
|
253
|
-
`;
|
|
254
|
-
|
|
255
|
-
container.innerHTML = html;
|
|
256
|
-
return container;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
static createSelect(config = {}) {
|
|
260
|
-
const {
|
|
261
|
-
name = '',
|
|
262
|
-
label = '',
|
|
263
|
-
options = [],
|
|
264
|
-
value = '',
|
|
265
|
-
required = false
|
|
266
|
-
} = config;
|
|
267
|
-
|
|
268
|
-
const container = document.createElement('div');
|
|
269
|
-
container.className = 'form-group mb-4';
|
|
270
|
-
|
|
271
|
-
let html = '';
|
|
272
|
-
if (label) {
|
|
273
|
-
html += `<label class="block text-sm font-medium mb-2">${UIComponents.escapeHtml(label)}</label>`;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
html += `
|
|
277
|
-
<select
|
|
278
|
-
name="${name}"
|
|
279
|
-
${required ? 'required' : ''}
|
|
280
|
-
class="select select-block select-solid"
|
|
281
|
-
>
|
|
282
|
-
${options.map(opt => `
|
|
283
|
-
<option value="${opt.value}" ${opt.value === value ? 'selected' : ''}>
|
|
284
|
-
${UIComponents.escapeHtml(opt.label)}
|
|
285
|
-
</option>
|
|
286
|
-
`).join('')}
|
|
287
|
-
</select>
|
|
288
|
-
`;
|
|
289
|
-
|
|
290
|
-
container.innerHTML = html;
|
|
291
|
-
return container;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
static createButtonGroup(config = {}) {
|
|
295
|
-
const {
|
|
296
|
-
buttons = [],
|
|
297
|
-
vertical = false
|
|
298
|
-
} = config;
|
|
299
|
-
|
|
300
|
-
const container = document.createElement('div');
|
|
301
|
-
container.className = `button-group flex gap-2 ${vertical ? 'flex-col' : 'flex-row'}`;
|
|
302
|
-
|
|
303
|
-
buttons.forEach(btn => {
|
|
304
|
-
const button = document.createElement('button');
|
|
305
|
-
button.className = `btn btn-${btn.variant || 'secondary'} flex-${vertical ? '1' : 'none'}`;
|
|
306
|
-
button.textContent = btn.label;
|
|
307
|
-
if (btn.onClick) {
|
|
308
|
-
button.addEventListener('click', btn.onClick);
|
|
309
|
-
}
|
|
310
|
-
container.appendChild(button);
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
return container;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
static createBadge(config = {}) {
|
|
317
|
-
const {
|
|
318
|
-
label = '',
|
|
319
|
-
variant = 'default', // default, primary, success, warning, error
|
|
320
|
-
size = 'medium' // small, medium, large
|
|
321
|
-
} = config;
|
|
322
|
-
|
|
323
|
-
const sizeClasses = {
|
|
324
|
-
'small': 'badge-sm',
|
|
325
|
-
'medium': 'badge-md',
|
|
326
|
-
'large': 'badge-lg'
|
|
327
|
-
};
|
|
328
|
-
|
|
329
|
-
const variantClasses = {
|
|
330
|
-
'default': 'badge-flat',
|
|
331
|
-
'primary': 'badge-flat-primary',
|
|
332
|
-
'success': 'badge-flat-success',
|
|
333
|
-
'warning': 'badge-flat-warning',
|
|
334
|
-
'error': 'badge-flat-error'
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
const badge = document.createElement('span');
|
|
338
|
-
badge.className = `badge ${sizeClasses[size] || sizeClasses['medium']} ${variantClasses[variant] || variantClasses['default']}`;
|
|
339
|
-
badge.textContent = label;
|
|
340
|
-
return badge;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
179
|
static escapeHtml(text) {
|
|
344
180
|
return window._escHtml(text);
|
|
345
181
|
}
|
|
346
182
|
|
|
347
|
-
static copyToClipboard(text) {
|
|
348
|
-
return navigator.clipboard.writeText(text).catch(err => {
|
|
349
|
-
console.error('Failed to copy:', err);
|
|
350
|
-
return false;
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
static downloadFile(data, filename, mimeType = 'text/plain') {
|
|
355
|
-
const blob = new Blob([data], { type: mimeType });
|
|
356
|
-
const url = URL.createObjectURL(blob);
|
|
357
|
-
const link = document.createElement('a');
|
|
358
|
-
link.href = url;
|
|
359
|
-
link.download = filename;
|
|
360
|
-
document.body.appendChild(link);
|
|
361
|
-
link.click();
|
|
362
|
-
document.body.removeChild(link);
|
|
363
|
-
URL.revokeObjectURL(url);
|
|
364
|
-
}
|
|
365
183
|
}
|
|
366
184
|
|
|
367
185
|
if (typeof module !== 'undefined' && module.exports) {
|