agentgui 1.0.823 → 1.0.825

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 CHANGED
@@ -1,45 +1,17 @@
1
- ## [Unreleased]
2
-
3
- ### Refactor
4
- - Split `conversations.js` (737L) into 4 files: conversations.js (117L), conv-sidebar-actions.js (185L), conv-sidebar-clone.js (92L), conv-list-renderer.js (198L)
5
- - Split `websocket-manager.js` (643L) into 3 files: ws-core.js (163L), ws-latency.js (89L), websocket-manager.js (108L)
6
- - Split `ui-components.js` (370L) into 2 files: ui-components.js (89L), ui-components-rendering.js (63L)
7
- - Split `agent-auth.js` into agent-auth.js (147L) and agent-auth-oauth.js (160L)
8
- - Deleted dead code: event-filter.js (zero external references)
9
- - Updated index.html script load order for all new split files
10
-
11
1
  ## 2026-04-11
12
- - refactor: split conversations.js (742L→116L) into conv-sidebar-actions.js (prototype extension: drag/drop, bulk actions, folder browser), conv-list-renderer.js (loadConversations, render, vnode), conv-sidebar-clone.js (clone UI, delete-all); all ≤200 lines
13
- - refactor: split websocket-manager.js (643L) into ws-core.js (class constructor, connect, onOpen, onMessage, reconnect), websocket-manager.js (sendMessage, subscriptions, state); ws-latency/connection/heartbeat empty stubs deleted
14
- - refactor: split event-filter.js into event-filter.js + event-filter-config.js (window export/CSV/Markdown helpers)
15
- - refactor: split agent-auth.js into agent-auth.js + agent-auth-oauth.js (IIFE, OAuth modal flows)
16
- - refactor: fix index.html script load order ws-core.js before websocket-manager.js; remove empty stub script tags (ws-connection, ws-latency, ws-heartbeat)
17
-
18
- ## 2026-04-11
19
- - refactor: split jsonl-watcher.js parse/event logic into jsonl-parser.js; watcher retains file watching/polling only
20
- - refactor: split tool-version.js into tool-version-check.js (sync) and tool-version-fetch.js (async/network)
21
- - 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
22
-
23
- ## [Unreleased]
24
- ### Refactor
25
- - Strip all standalone comment lines (// and /* */ block lines) from 14 static JS files: event-filter.js, image-loader.js, syntax-highlighter.js, client.js, streaming-renderer.js, ui-components.js, conversations.js, websocket-manager.js, conv-machine.js, ws-machine.js, ws-client.js, state-barrier.js, script-runner.js, theme.js. Total 781 comment lines removed. All files syntax-verified.
26
-
27
- ## [Unreleased]
28
-
29
- ### Changed
30
- - Remove GMGUIApp dead code from app.js; strip 900+ lines, keep 5 live IIFEs
31
- - Add app.js and app-shortcuts.js to index.html script loading (previously unreferenced)
32
- - Remove unused BASE_URL declaration from app-shortcuts.js
33
- - Extend window.__debug.getSyncState() with promptMachineState, toolInstallMachineStates, voiceMachineState, convListMachineState
34
-
35
-
36
- ### Fixed
37
- - Thinking blocks now use theme-aware CSS vars for light/dark backgrounds (`--color-thinking-bg`)
38
- - All code blocks (renderBlockCode, renderCodeWithHighlight, renderFileRead, renderCommand) now use CSS vars instead of hardcoded dark hex colors
39
- - Removed hardcoded `#1e293b`, `#e2e8f0`, `#d1d5db` from all inline styles in streaming-renderer.js
40
- - tool-result-pre text color now adapts to theme via `--color-code-text`
41
- - Added CSS vars to :root (light) and html.dark for --color-bg-code, --color-code-text, --color-code-border, --color-thinking-bg
42
- - Thinking block content now renders full markdown (headers, lists, blockquotes, bold, italic, inline code, links)
43
- - parseAndRenderMarkdown() extended with headers (##/###), ul/ol lists, blockquotes (>), horizontal rules (---)
44
- - Thinking content dark mode text color set to #c4b5fd (readable purple)
45
-
2
+ - refactor: replace jsonl-watcher.js with @lanmower/cc-tail npm package (subclass CCTailWatcher, override _line() via JsonlParser); remove custom file-watching logic
3
+ - refactor: split syntax-highlighter.js (216L) into syntax-highlighter.js (132L) + syntax-highlighter-render.js (73L)
4
+ - refactor: split dialogs.js (268L) into dialogs.js (54L) + dialogs-types.js (112L, window.UIDialog)
5
+ - refactor: split image-loader.js (221L) into image-loader.js (147L) + image-loader-element.js (77L)
6
+ - refactor: split conversations.js (742L) into conversations.js (116L) + conv-sidebar-actions.js (185L) + conv-list-renderer.js (198L) + conv-sidebar-clone.js (92L)
7
+ - refactor: split websocket-manager.js (643L) into ws-core.js (163L) + websocket-manager.js (108L) + ws-latency.js (89L)
8
+ - refactor: split ui-components.js into ui-components.js (89L) + ui-components-rendering.js (63L)
9
+ - refactor: split agent-auth.js into agent-auth.js (147L) + agent-auth-oauth.js (160L)
10
+ - refactor: split event-filter.js into event-filter-config.js (37L); delete event-filter.js dead code (zero external refs)
11
+ - refactor: split jsonl-watcher.js parse logic into jsonl-parser.js (180L); watcher retains file watching only
12
+ - refactor: split tool-version.js into tool-version-check.js + tool-version-fetch.js
13
+ - refactor: fix index.html script load order for all new split files
14
+ - fix: thinking blocks use theme-aware CSS vars for light/dark backgrounds
15
+ - fix: all code blocks use CSS vars instead of hardcoded dark hex colors
16
+ - fix: remove GMGUIApp dead code from app.js; add app.js and app-shortcuts.js to index.html
17
+ - fix: extend window.__debug.getSyncState() with all XState machine snapshots
@@ -1,36 +1,26 @@
1
1
  import fs from 'fs';
2
- import path from 'path';
3
- import os from 'os';
2
+ import { JsonlWatcher as CCTailWatcher } from '@lanmower/cc-tail';
4
3
  import { JsonlParser } from './jsonl-parser.js';
5
4
 
6
- const PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects');
7
- const DEBOUNCE_MS = 16;
8
-
9
- export class JsonlWatcher {
5
+ export class JsonlWatcher extends CCTailWatcher {
10
6
  constructor({ broadcastSync, queries, ownedSessionIds }) {
7
+ super();
11
8
  this._parser = new JsonlParser({ broadcastSync, queries, ownedSessionIds });
12
- this._tails = new Map();
13
- this._timers = new Map();
14
- this._watcher = null;
15
9
  }
16
10
 
17
- start() {
18
- if (!fs.existsSync(PROJECTS_DIR)) return;
19
- this._scanDir(PROJECTS_DIR, 0);
20
- for (const [fp, t] of this._timers.entries()) { clearTimeout(t); this._timers.delete(fp); this._read(fp); }
21
- this._parser.endAllStreaming();
22
- try {
23
- this._watcher = fs.watch(PROJECTS_DIR, { recursive: true }, (_, f) => {
24
- if (f && f.endsWith('.jsonl')) this._debounce(path.join(PROJECTS_DIR, f));
25
- });
26
- this._watcher.on('error', (e) => console.error('[JsonlWatcher]', e.message));
27
- } catch (e) { console.error('[JsonlWatcher] start failed:', e.message); }
11
+ _line(line) {
12
+ line = line.trim();
13
+ if (!line) return;
14
+ let e;
15
+ try { e = JSON.parse(line); } catch (_) { return; }
16
+ if (!e || !e.sessionId) return;
17
+ const cid = this._parser._conv(e.sessionId, e);
18
+ if (cid) this._parser._route(cid, e.sessionId, e);
28
19
  }
29
20
 
30
21
  stop() {
31
- if (this._watcher) try { this._watcher.close(); } catch (_) {}
32
- for (const s of this._tails.values()) if (s.fd !== null) try { fs.closeSync(s.fd); } catch (_) {}
33
- for (const t of this._timers.values()) clearTimeout(t);
22
+ super.stop();
23
+ this._parser.endAllStreaming();
34
24
  }
35
25
 
36
26
  removeConversation(conversationId) {
@@ -54,45 +44,4 @@ export class JsonlWatcher {
54
44
  this._timers.clear();
55
45
  this._parser.clear();
56
46
  }
57
-
58
- _scanDir(dir, depth) {
59
- if (depth > 4) return;
60
- try {
61
- for (const d of fs.readdirSync(dir, { withFileTypes: true })) {
62
- const fp = path.join(dir, d.name);
63
- if (d.isFile() && d.name.endsWith('.jsonl')) this._debounce(fp);
64
- else if (d.isDirectory()) this._scanDir(fp, depth + 1);
65
- }
66
- } catch (_) {}
67
- }
68
-
69
- _debounce(fp) {
70
- const t = this._timers.get(fp);
71
- if (t) clearTimeout(t);
72
- this._timers.set(fp, setTimeout(() => { this._timers.delete(fp); this._read(fp); }, DEBOUNCE_MS));
73
- }
74
-
75
- _read(fp) {
76
- let s = this._tails.get(fp);
77
- if (!s) { s = { fd: null, offset: 0, partial: '' }; this._tails.set(fp, s); }
78
- try {
79
- if (s.fd === null) s.fd = fs.openSync(fp, 'r');
80
- const stat = fs.fstatSync(s.fd);
81
- if (stat.size <= s.offset) return;
82
- const buf = Buffer.allocUnsafe(stat.size - s.offset);
83
- const n = fs.readSync(s.fd, buf, 0, buf.length, s.offset);
84
- s.offset += n;
85
- const text = s.partial + buf.toString('utf8', 0, n);
86
- const t0 = process.hrtime.bigint();
87
- const lines = []; let start = 0, idx;
88
- while ((idx = text.indexOf('\n', start)) !== -1) { lines.push(text.slice(start, idx)); start = idx + 1; }
89
- s.partial = text.slice(start);
90
- const ms = Number(process.hrtime.bigint() - t0) / 1e6;
91
- if (ms > 5) console.warn(`[JsonlWatcher] hot path ${ms.toFixed(1)}ms (${lines.length} lines)`);
92
- for (const l of lines) this._parser._line(fp, l);
93
- } catch (e) {
94
- if (e.code !== 'ENOENT') console.error('[JsonlWatcher] read error:', e.message);
95
- if (s.fd !== null) { try { fs.closeSync(s.fd); } catch (_) {} s.fd = null; }
96
- }
97
- }
98
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.823",
3
+ "version": "1.0.825",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "electron/main.js",
@@ -28,6 +28,7 @@
28
28
  "@google/gemini-cli": "latest",
29
29
  "@huggingface/transformers": "^3.8.1",
30
30
  "@kilocode/cli": "latest",
31
+ "@lanmower/cc-tail": "^1.0.2",
31
32
  "audio-decode": "^2.2.3",
32
33
  "better-sqlite3": "^12.6.2",
33
34
  "busboy": "^1.6.0",
package/static/index.html CHANGED
@@ -282,6 +282,7 @@
282
282
  <script defer src="/gm/js/event-processor.js"></script>
283
283
  <script defer src="/gm/js/streaming-renderer.js"></script>
284
284
  <script defer src="/gm/js/image-loader.js"></script>
285
+ <script defer src="/gm/js/image-loader-element.js"></script>
285
286
  <script defer src="/gm/lib/webjsx.js"></script>
286
287
  <script defer src="/gm/lib/xstate.umd.min.js"></script>
287
288
  <script defer src="/gm/js/ws-machine.js"></script>
@@ -302,7 +303,9 @@
302
303
  <script defer src="/gm/js/websocket-manager.js"></script>
303
304
  <script defer src="/gm/js/ws-client.js"></script>
304
305
  <script defer src="/gm/js/syntax-highlighter.js"></script>
306
+ <script defer src="/gm/js/syntax-highlighter-render.js"></script>
305
307
  <script defer src="/gm/js/dialogs.js"></script>
308
+ <script defer src="/gm/js/dialogs-types.js"></script>
306
309
  <script defer src="/gm/js/ui-components.js"></script>
307
310
  <script defer src="/gm/js/ui-components-rendering.js"></script>
308
311
  <script defer src="/gm/js/state-barrier.js"></script>
@@ -0,0 +1,111 @@
1
+ (function() {
2
+ var c = window._dialogCore;
3
+
4
+ window.UIDialog = {
5
+ alert: function(message, title) {
6
+ return new Promise(function(resolve) {
7
+ var overlay = c.createOverlay();
8
+ var dialog = document.createElement('div');
9
+ dialog.className = 'dialog-container';
10
+ dialog.innerHTML =
11
+ '<div class="dialog-box"><div class="dialog-header"><h3 class="dialog-title">' + c.escapeHtml(title || 'Alert') + '</h3></div>' +
12
+ '<div class="dialog-body"><p class="dialog-message">' + c.escapeHtml(message) + '</p></div>' +
13
+ '<div class="dialog-footer"><button class="dialog-btn dialog-btn-primary" data-action="ok">OK</button></div></div>';
14
+ var okBtn = dialog.querySelector('[data-action="ok"]');
15
+ okBtn.addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(true); });
16
+ overlay.querySelector('.dialog-backdrop').addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(true); });
17
+ document.addEventListener('keydown', function handler(e) {
18
+ if (e.key === 'Escape' || e.key === 'Enter') { document.removeEventListener('keydown', handler); c.closeDialog(dialog, overlay); resolve(true); }
19
+ });
20
+ c.showDialog(dialog, overlay);
21
+ });
22
+ },
23
+
24
+ confirm: function(message, title) {
25
+ return new Promise(function(resolve) {
26
+ var overlay = c.createOverlay();
27
+ var dialog = document.createElement('div');
28
+ dialog.className = 'dialog-container';
29
+ dialog.innerHTML =
30
+ '<div class="dialog-box"><div class="dialog-header"><h3 class="dialog-title">' + c.escapeHtml(title || 'Confirm') + '</h3></div>' +
31
+ '<div class="dialog-body"><p class="dialog-message">' + c.escapeHtml(message).replace(/\n/g, '<br>') + '</p></div>' +
32
+ '<div class="dialog-footer"><button class="dialog-btn dialog-btn-secondary" data-action="cancel">Cancel</button>' +
33
+ '<button class="dialog-btn dialog-btn-primary dialog-btn-danger" data-action="confirm">Confirm</button></div></div>';
34
+ var cancelBtn = dialog.querySelector('[data-action="cancel"]');
35
+ var confirmBtn = dialog.querySelector('[data-action="confirm"]');
36
+ cancelBtn.addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(false); });
37
+ confirmBtn.addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(true); });
38
+ overlay.querySelector('.dialog-backdrop').addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(false); });
39
+ document.addEventListener('keydown', function handler(e) {
40
+ if (e.key === 'Escape') { document.removeEventListener('keydown', handler); c.closeDialog(dialog, overlay); resolve(false); }
41
+ else if (e.key === 'Enter') { document.removeEventListener('keydown', handler); c.closeDialog(dialog, overlay); resolve(true); }
42
+ });
43
+ c.showDialog(dialog, overlay);
44
+ });
45
+ },
46
+
47
+ prompt: function(message, defaultValue, title) {
48
+ return new Promise(function(resolve) {
49
+ var overlay = c.createOverlay();
50
+ var dialog = document.createElement('div');
51
+ dialog.className = 'dialog-container';
52
+ dialog.innerHTML =
53
+ '<div class="dialog-box"><div class="dialog-header"><h3 class="dialog-title">' + c.escapeHtml(title || 'Input') + '</h3></div>' +
54
+ '<div class="dialog-body"><label class="dialog-label">' + c.escapeHtml(message) + '</label>' +
55
+ '<input type="text" class="dialog-input" value="' + c.escapeHtml(defaultValue || '') + '"></div>' +
56
+ '<div class="dialog-footer"><button class="dialog-btn dialog-btn-secondary" data-action="cancel">Cancel</button>' +
57
+ '<button class="dialog-btn dialog-btn-primary" data-action="ok">OK</button></div></div>';
58
+ var input = dialog.querySelector('.dialog-input');
59
+ var cancelBtn = dialog.querySelector('[data-action="cancel"]');
60
+ var okBtn = dialog.querySelector('[data-action="ok"]');
61
+ cancelBtn.addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(null); });
62
+ okBtn.addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(input.value); });
63
+ input.addEventListener('keydown', function(e) { if (e.key === 'Enter') { c.closeDialog(dialog, overlay); resolve(input.value); } });
64
+ overlay.querySelector('.dialog-backdrop').addEventListener('click', function() { c.closeDialog(dialog, overlay); resolve(null); });
65
+ document.addEventListener('keydown', function handler(e) {
66
+ if (e.key === 'Escape') { document.removeEventListener('keydown', handler); c.closeDialog(dialog, overlay); resolve(null); }
67
+ });
68
+ c.showDialog(dialog, overlay);
69
+ });
70
+ },
71
+
72
+ showProgress: function(config) {
73
+ var overlay = c.createOverlay();
74
+ var dialog = document.createElement('div');
75
+ dialog.className = 'dialog-container';
76
+ dialog.innerHTML =
77
+ '<div class="dialog-box dialog-box-progress"><div class="dialog-header"><h3 class="dialog-title">' + c.escapeHtml(config.title || 'Please wait') + '</h3></div>' +
78
+ '<div class="dialog-body"><p class="dialog-message progress-message">' + c.escapeHtml(config.message || 'Loading...') + '</p>' +
79
+ '<div class="dialog-progress-bar"><div class="dialog-progress-fill" style="width: 0%"></div></div>' +
80
+ '<p class="dialog-progress-percent">0%</p></div></div>';
81
+ c.showDialog(dialog, overlay);
82
+ var progressFill = dialog.querySelector('.dialog-progress-fill');
83
+ var progressPercent = dialog.querySelector('.dialog-progress-percent');
84
+ var progressMessage = dialog.querySelector('.progress-message');
85
+ return {
86
+ update: function(percent, message) {
87
+ progressFill.style.width = percent + '%';
88
+ progressPercent.textContent = Math.round(percent) + '%';
89
+ if (message) progressMessage.textContent = message;
90
+ },
91
+ close: function() { c.closeDialog(dialog, overlay); }
92
+ };
93
+ },
94
+
95
+ showToast: function(message, type, duration) {
96
+ var existing = document.querySelector('.toast-notification');
97
+ if (existing) existing.remove();
98
+ var toast = document.createElement('div');
99
+ toast.className = 'toast-notification toast-' + (type || 'info');
100
+ toast.innerHTML = '<span class="toast-message">' + c.escapeHtml(message) + '</span>';
101
+ document.body.appendChild(toast);
102
+ requestAnimationFrame(function() { toast.classList.add('visible'); });
103
+ setTimeout(function() {
104
+ toast.classList.remove('visible');
105
+ setTimeout(function() { if (toast.parentNode) toast.remove(); }, 300);
106
+ }, duration || 3000);
107
+ },
108
+
109
+ closeAll: c.closeAllDialogs
110
+ };
111
+ })();
@@ -1,267 +1,53 @@
1
- (function() {
2
- var activeDialogs = [];
3
- var dialogZIndex = 10000;
4
-
5
- function escapeHtml(text) {
6
- if (typeof window._escHtml === 'function') return window._escHtml(text);
7
- var d = document.createElement('div'); d.textContent = text; return d.innerHTML;
8
- }
9
-
10
- function createOverlay() {
11
- var overlay = document.createElement('div');
12
- overlay.className = 'dialog-overlay';
13
- overlay.innerHTML = '<div class="dialog-backdrop"></div>';
14
- return overlay;
15
- }
16
-
17
- function showDialog(dialog, overlay) {
18
- dialogZIndex++;
19
- if (overlay) {
20
- overlay.style.zIndex = dialogZIndex;
21
- document.body.appendChild(overlay);
22
- }
23
- dialog.style.zIndex = dialogZIndex + 1;
24
- document.body.appendChild(dialog);
25
- activeDialogs.push({ dialog: dialog, overlay: overlay });
26
-
27
- requestAnimationFrame(function() {
28
- dialog.classList.add('visible');
29
- if (overlay) overlay.classList.add('visible');
30
- var input = dialog.querySelector('input, textarea');
31
- if (input) input.focus();
32
- else {
33
- var btn = dialog.querySelector('.dialog-btn-primary');
34
- if (btn) btn.focus();
35
- }
36
- });
37
- }
38
-
39
- function closeDialog(dialog, overlay) {
40
- dialog.classList.remove('visible');
41
- if (overlay) overlay.classList.remove('visible');
42
- setTimeout(function() {
43
- if (dialog.parentNode) dialog.remove();
44
- if (overlay && overlay.parentNode) overlay.remove();
45
- }, 200);
46
- activeDialogs = activeDialogs.filter(function(d) {
47
- return d.dialog !== dialog;
48
- });
49
- }
50
-
51
- function closeAllDialogs() {
52
- activeDialogs.forEach(function(d) {
53
- closeDialog(d.dialog, d.overlay);
54
- });
55
- }
56
-
57
- window.UIDialog = {
58
- alert: function(message, title) {
59
- return new Promise(function(resolve) {
60
- var overlay = createOverlay();
61
- var dialog = document.createElement('div');
62
- dialog.className = 'dialog-container';
63
- dialog.innerHTML =
64
- '<div class="dialog-box">' +
65
- '<div class="dialog-header">' +
66
- '<h3 class="dialog-title">' + escapeHtml(title || 'Alert') + '</h3>' +
67
- '</div>' +
68
- '<div class="dialog-body">' +
69
- '<p class="dialog-message">' + escapeHtml(message) + '</p>' +
70
- '</div>' +
71
- '<div class="dialog-footer">' +
72
- '<button class="dialog-btn dialog-btn-primary" data-action="ok">OK</button>' +
73
- '</div>' +
74
- '</div>';
75
-
76
- var okBtn = dialog.querySelector('[data-action="ok"]');
77
- okBtn.addEventListener('click', function() {
78
- closeDialog(dialog, overlay);
79
- resolve(true);
80
- });
81
-
82
- overlay.querySelector('.dialog-backdrop').addEventListener('click', function() {
83
- closeDialog(dialog, overlay);
84
- resolve(true);
85
- });
86
-
87
- document.addEventListener('keydown', function handler(e) {
88
- if (e.key === 'Escape' || e.key === 'Enter') {
89
- document.removeEventListener('keydown', handler);
90
- closeDialog(dialog, overlay);
91
- resolve(true);
92
- }
93
- });
94
-
95
- showDialog(dialog, overlay);
96
- });
97
- },
98
-
99
- confirm: function(message, title) {
100
- return new Promise(function(resolve) {
101
- var overlay = createOverlay();
102
- var dialog = document.createElement('div');
103
- dialog.className = 'dialog-container';
104
- dialog.innerHTML =
105
- '<div class="dialog-box">' +
106
- '<div class="dialog-header">' +
107
- '<h3 class="dialog-title">' + escapeHtml(title || 'Confirm') + '</h3>' +
108
- '</div>' +
109
- '<div class="dialog-body">' +
110
- '<p class="dialog-message">' + escapeHtml(message).replace(/\n/g, '<br>') + '</p>' +
111
- '</div>' +
112
- '<div class="dialog-footer">' +
113
- '<button class="dialog-btn dialog-btn-secondary" data-action="cancel">Cancel</button>' +
114
- '<button class="dialog-btn dialog-btn-primary dialog-btn-danger" data-action="confirm">Confirm</button>' +
115
- '</div>' +
116
- '</div>';
117
-
118
- var cancelBtn = dialog.querySelector('[data-action="cancel"]');
119
- var confirmBtn = dialog.querySelector('[data-action="confirm"]');
120
-
121
- cancelBtn.addEventListener('click', function() {
122
- closeDialog(dialog, overlay);
123
- resolve(false);
124
- });
125
-
126
- confirmBtn.addEventListener('click', function() {
127
- closeDialog(dialog, overlay);
128
- resolve(true);
129
- });
130
-
131
- overlay.querySelector('.dialog-backdrop').addEventListener('click', function() {
132
- closeDialog(dialog, overlay);
133
- resolve(false);
134
- });
135
-
136
- document.addEventListener('keydown', function handler(e) {
137
- if (e.key === 'Escape') {
138
- document.removeEventListener('keydown', handler);
139
- closeDialog(dialog, overlay);
140
- resolve(false);
141
- } else if (e.key === 'Enter') {
142
- document.removeEventListener('keydown', handler);
143
- closeDialog(dialog, overlay);
144
- resolve(true);
145
- }
146
- });
147
-
148
- showDialog(dialog, overlay);
149
- });
150
- },
151
-
152
- prompt: function(message, defaultValue, title) {
153
- return new Promise(function(resolve) {
154
- var overlay = createOverlay();
155
- var dialog = document.createElement('div');
156
- dialog.className = 'dialog-container';
157
- dialog.innerHTML =
158
- '<div class="dialog-box">' +
159
- '<div class="dialog-header">' +
160
- '<h3 class="dialog-title">' + escapeHtml(title || 'Input') + '</h3>' +
161
- '</div>' +
162
- '<div class="dialog-body">' +
163
- '<label class="dialog-label">' + escapeHtml(message) + '</label>' +
164
- '<input type="text" class="dialog-input" value="' + escapeHtml(defaultValue || '') + '">' +
165
- '</div>' +
166
- '<div class="dialog-footer">' +
167
- '<button class="dialog-btn dialog-btn-secondary" data-action="cancel">Cancel</button>' +
168
- '<button class="dialog-btn dialog-btn-primary" data-action="ok">OK</button>' +
169
- '</div>' +
170
- '</div>';
171
-
172
- var input = dialog.querySelector('.dialog-input');
173
- var cancelBtn = dialog.querySelector('[data-action="cancel"]');
174
- var okBtn = dialog.querySelector('[data-action="ok"]');
175
-
176
- cancelBtn.addEventListener('click', function() {
177
- closeDialog(dialog, overlay);
178
- resolve(null);
179
- });
180
-
181
- okBtn.addEventListener('click', function() {
182
- closeDialog(dialog, overlay);
183
- resolve(input.value);
184
- });
185
-
186
- input.addEventListener('keydown', function(e) {
187
- if (e.key === 'Enter') {
188
- closeDialog(dialog, overlay);
189
- resolve(input.value);
190
- }
191
- });
192
-
193
- overlay.querySelector('.dialog-backdrop').addEventListener('click', function() {
194
- closeDialog(dialog, overlay);
195
- resolve(null);
196
- });
197
-
198
- document.addEventListener('keydown', function handler(e) {
199
- if (e.key === 'Escape') {
200
- document.removeEventListener('keydown', handler);
201
- closeDialog(dialog, overlay);
202
- resolve(null);
203
- }
204
- });
205
-
206
- showDialog(dialog, overlay);
207
- });
208
- },
209
-
210
- showProgress: function(config) {
211
- var overlay = createOverlay();
212
- var dialog = document.createElement('div');
213
- dialog.className = 'dialog-container';
214
- dialog.innerHTML =
215
- '<div class="dialog-box dialog-box-progress">' +
216
- '<div class="dialog-header">' +
217
- '<h3 class="dialog-title">' + escapeHtml(config.title || 'Please wait') + '</h3>' +
218
- '</div>' +
219
- '<div class="dialog-body">' +
220
- '<p class="dialog-message progress-message">' + escapeHtml(config.message || 'Loading...') + '</p>' +
221
- '<div class="dialog-progress-bar">' +
222
- '<div class="dialog-progress-fill" style="width: 0%"></div>' +
223
- '</div>' +
224
- '<p class="dialog-progress-percent">0%</p>' +
225
- '</div>' +
226
- '</div>';
227
-
228
- showDialog(dialog, overlay);
229
-
230
- var progressFill = dialog.querySelector('.dialog-progress-fill');
231
- var progressPercent = dialog.querySelector('.dialog-progress-percent');
232
- var progressMessage = dialog.querySelector('.progress-message');
233
-
234
- return {
235
- update: function(percent, message) {
236
- progressFill.style.width = percent + '%';
237
- progressPercent.textContent = Math.round(percent) + '%';
238
- if (message) progressMessage.textContent = message;
239
- },
240
- close: function() {
241
- closeDialog(dialog, overlay);
242
- }
243
- };
244
- },
245
-
246
- showToast: function(message, type, duration) {
247
- var existing = document.querySelector('.toast-notification');
248
- if (existing) existing.remove();
249
-
250
- var toast = document.createElement('div');
251
- toast.className = 'toast-notification toast-' + (type || 'info');
252
- toast.innerHTML = '<span class="toast-message">' + escapeHtml(message) + '</span>';
253
- document.body.appendChild(toast);
254
-
255
- requestAnimationFrame(function() {
256
- toast.classList.add('visible');
257
- });
258
-
259
- setTimeout(function() {
260
- toast.classList.remove('visible');
261
- setTimeout(function() { if (toast.parentNode) toast.remove(); }, 300);
262
- }, duration || 3000);
263
- },
264
-
265
- closeAll: closeAllDialogs
266
- };
267
- })();
1
+ (function() {
2
+ var activeDialogs = [];
3
+ var dialogZIndex = 10000;
4
+
5
+ function escapeHtml(text) {
6
+ if (typeof window._escHtml === 'function') return window._escHtml(text);
7
+ var d = document.createElement('div'); d.textContent = text; return d.innerHTML;
8
+ }
9
+
10
+ function createOverlay() {
11
+ var overlay = document.createElement('div');
12
+ overlay.className = 'dialog-overlay';
13
+ overlay.innerHTML = '<div class="dialog-backdrop"></div>';
14
+ return overlay;
15
+ }
16
+
17
+ function showDialog(dialog, overlay) {
18
+ dialogZIndex++;
19
+ if (overlay) {
20
+ overlay.style.zIndex = dialogZIndex;
21
+ document.body.appendChild(overlay);
22
+ }
23
+ dialog.style.zIndex = dialogZIndex + 1;
24
+ document.body.appendChild(dialog);
25
+ activeDialogs.push({ dialog: dialog, overlay: overlay });
26
+ requestAnimationFrame(function() {
27
+ dialog.classList.add('visible');
28
+ if (overlay) overlay.classList.add('visible');
29
+ var input = dialog.querySelector('input, textarea');
30
+ if (input) input.focus();
31
+ else {
32
+ var btn = dialog.querySelector('.dialog-btn-primary');
33
+ if (btn) btn.focus();
34
+ }
35
+ });
36
+ }
37
+
38
+ function closeDialog(dialog, overlay) {
39
+ dialog.classList.remove('visible');
40
+ if (overlay) overlay.classList.remove('visible');
41
+ setTimeout(function() {
42
+ if (dialog.parentNode) dialog.remove();
43
+ if (overlay && overlay.parentNode) overlay.remove();
44
+ }, 200);
45
+ activeDialogs = activeDialogs.filter(function(d) { return d.dialog !== dialog; });
46
+ }
47
+
48
+ function closeAllDialogs() {
49
+ activeDialogs.forEach(function(d) { closeDialog(d.dialog, d.overlay); });
50
+ }
51
+
52
+ window._dialogCore = { escapeHtml: escapeHtml, createOverlay: createOverlay, showDialog: showDialog, closeDialog: closeDialog, closeAllDialogs: closeAllDialogs };
53
+ })();
@@ -0,0 +1,76 @@
1
+
2
+ Object.assign(ImageLoader.prototype, {
3
+ createImageElement(imagePath, options = {}) {
4
+ const container = document.createElement('div');
5
+ container.className = 'image-container';
6
+ container.dataset.imagePath = imagePath;
7
+ container.style.cssText = `
8
+ display: flex;
9
+ flex-direction: column;
10
+ gap: 0.5rem;
11
+ padding: 0.75rem;
12
+ border-radius: 0.375rem;
13
+ background: var(--color-bg-secondary);
14
+ border: 1px solid var(--color-border);
15
+ `;
16
+
17
+ const placeholder = document.createElement('div');
18
+ placeholder.className = 'image-placeholder';
19
+ placeholder.style.cssText = `
20
+ background: linear-gradient(90deg, var(--color-bg-tertiary) 25%, var(--color-bg-secondary) 50%, var(--color-bg-tertiary) 75%);
21
+ background-size: 200% 100%;
22
+ animation: loading 1.5s infinite;
23
+ border-radius: 0.375rem;
24
+ aspect-ratio: 16/9;
25
+ display: flex;
26
+ align-items: center;
27
+ justify-content: center;
28
+ color: var(--color-text-secondary);
29
+ font-size: 0.875rem;
30
+ `;
31
+ placeholder.innerHTML = 'Loading image...';
32
+ placeholder.dataset.path = imagePath;
33
+
34
+ const img = document.createElement('img');
35
+ img.className = 'lazy-image';
36
+ img.alt = imagePath;
37
+ img.style.cssText = `
38
+ max-width: 100%;
39
+ max-height: ${this.config.maxImageDisplaySize};
40
+ border-radius: 0.375rem;
41
+ display: none;
42
+ `;
43
+ img.dataset.src = imagePath;
44
+
45
+ const caption = document.createElement('div');
46
+ caption.className = 'image-caption';
47
+ caption.style.cssText = `
48
+ font-size: 0.75rem;
49
+ color: var(--color-text-secondary);
50
+ word-break: break-all;
51
+ font-family: 'Monaco', 'Menlo', monospace;
52
+ `;
53
+ caption.textContent = imagePath;
54
+
55
+ container.appendChild(placeholder);
56
+ container.appendChild(img);
57
+ container.appendChild(caption);
58
+
59
+ img.addEventListener('load', () => {
60
+ placeholder.style.display = 'none';
61
+ img.style.display = 'block';
62
+ });
63
+
64
+ img.addEventListener('error', () => {
65
+ placeholder.textContent = 'Failed to load image';
66
+ placeholder.style.background = 'var(--color-bg-error)';
67
+ placeholder.style.color = 'var(--color-text-error)';
68
+ });
69
+
70
+ if (this.intersectionObserver) {
71
+ this.intersectionObserver.observe(container);
72
+ }
73
+
74
+ return container;
75
+ }
76
+ });
@@ -89,80 +89,6 @@ class ImageLoader {
89
89
  return images;
90
90
  }
91
91
 
92
- createImageElement(imagePath, options = {}) {
93
- const container = document.createElement('div');
94
- container.className = 'image-container';
95
- container.dataset.imagePath = imagePath;
96
- container.style.cssText = `
97
- display: flex;
98
- flex-direction: column;
99
- gap: 0.5rem;
100
- padding: 0.75rem;
101
- border-radius: 0.375rem;
102
- background: var(--color-bg-secondary);
103
- border: 1px solid var(--color-border);
104
- `;
105
-
106
- const placeholder = document.createElement('div');
107
- placeholder.className = 'image-placeholder';
108
- placeholder.style.cssText = `
109
- background: linear-gradient(90deg, var(--color-bg-tertiary) 25%, var(--color-bg-secondary) 50%, var(--color-bg-tertiary) 75%);
110
- background-size: 200% 100%;
111
- animation: loading 1.5s infinite;
112
- border-radius: 0.375rem;
113
- aspect-ratio: 16/9;
114
- display: flex;
115
- align-items: center;
116
- justify-content: center;
117
- color: var(--color-text-secondary);
118
- font-size: 0.875rem;
119
- `;
120
- placeholder.innerHTML = 'Loading image...';
121
- placeholder.dataset.path = imagePath;
122
-
123
- const img = document.createElement('img');
124
- img.className = 'lazy-image';
125
- img.alt = imagePath;
126
- img.style.cssText = `
127
- max-width: 100%;
128
- max-height: ${this.config.maxImageDisplaySize};
129
- border-radius: 0.375rem;
130
- display: none;
131
- `;
132
- img.dataset.src = imagePath;
133
-
134
- const caption = document.createElement('div');
135
- caption.className = 'image-caption';
136
- caption.style.cssText = `
137
- font-size: 0.75rem;
138
- color: var(--color-text-secondary);
139
- word-break: break-all;
140
- font-family: 'Monaco', 'Menlo', monospace;
141
- `;
142
- caption.textContent = imagePath;
143
-
144
- container.appendChild(placeholder);
145
- container.appendChild(img);
146
- container.appendChild(caption);
147
-
148
- img.addEventListener('load', () => {
149
- placeholder.style.display = 'none';
150
- img.style.display = 'block';
151
- });
152
-
153
- img.addEventListener('error', () => {
154
- placeholder.textContent = 'Failed to load image';
155
- placeholder.style.background = 'var(--color-bg-error)';
156
- placeholder.style.color = 'var(--color-text-error)';
157
- });
158
-
159
- if (this.intersectionObserver) {
160
- this.intersectionObserver.observe(container);
161
- }
162
-
163
- return container;
164
- }
165
-
166
92
  initIntersectionObserver() {
167
93
  this.intersectionObserver = new IntersectionObserver(
168
94
  (entries) => {
@@ -0,0 +1,72 @@
1
+ Object.assign(SyntaxHighlighter.prototype, {
2
+ async highlight(code, language = 'plaintext') {
3
+ if (!code) return '';
4
+ if (this.config.lazyLoad) {
5
+ try {
6
+ await this.ensureLoaded();
7
+ } catch (error) {
8
+ console.warn('Prism loading failed, returning unformatted code');
9
+ return this.escapeHtml(code);
10
+ }
11
+ }
12
+ const cacheKey = `${language}:${code}`;
13
+ if (this.config.enableCache && this.highlightCache.has(cacheKey)) return this.highlightCache.get(cacheKey);
14
+ let highlighted;
15
+ try {
16
+ if (typeof Prism !== 'undefined' && Prism.languages[language]) {
17
+ highlighted = Prism.highlight(code, Prism.languages[language], language);
18
+ } else {
19
+ highlighted = this.escapeHtml(code);
20
+ }
21
+ } catch (error) {
22
+ console.error('Highlight error:', error);
23
+ highlighted = this.escapeHtml(code);
24
+ }
25
+ if (this.config.enableCache) {
26
+ this.highlightCache.set(cacheKey, highlighted);
27
+ if (this.highlightCache.size > this.config.maxCacheSize) {
28
+ const firstKey = this.highlightCache.keys().next().value;
29
+ this.highlightCache.delete(firstKey);
30
+ }
31
+ }
32
+ return highlighted;
33
+ },
34
+
35
+ async createHighlightedElement(code, language = 'plaintext') {
36
+ const pre = document.createElement('pre');
37
+ const code_el = document.createElement('code');
38
+ code_el.className = `language-${language}`;
39
+ if (this.config.lazyLoad) {
40
+ try {
41
+ await this.ensureLoaded();
42
+ const highlighted = await this.highlight(code, language);
43
+ code_el.innerHTML = highlighted;
44
+ } catch (error) {
45
+ code_el.textContent = code;
46
+ }
47
+ } else {
48
+ code_el.textContent = code;
49
+ }
50
+ pre.appendChild(code_el);
51
+ return pre;
52
+ },
53
+
54
+ async highlightElement(element) {
55
+ if (!element || !element.querySelector('code')) return;
56
+ if (this.config.lazyLoad) {
57
+ try {
58
+ await this.ensureLoaded();
59
+ } catch (error) {
60
+ console.warn('Prism loading failed, skipping highlighting');
61
+ return;
62
+ }
63
+ }
64
+ if (typeof Prism !== 'undefined') {
65
+ try {
66
+ Prism.highlightElement(element.querySelector('code'));
67
+ } catch (error) {
68
+ console.error('Element highlighting error:', error);
69
+ }
70
+ }
71
+ }
72
+ });
@@ -81,90 +81,6 @@ class SyntaxHighlighter {
81
81
  }
82
82
  }
83
83
 
84
- async highlight(code, language = 'plaintext') {
85
- if (!code) return '';
86
-
87
- if (this.config.lazyLoad) {
88
- try {
89
- await this.ensureLoaded();
90
- } catch (error) {
91
- console.warn('Prism loading failed, returning unformatted code');
92
- return this.escapeHtml(code);
93
- }
94
- }
95
-
96
- const cacheKey = `${language}:${code}`;
97
- if (this.config.enableCache && this.highlightCache.has(cacheKey)) {
98
- return this.highlightCache.get(cacheKey);
99
- }
100
-
101
- let highlighted;
102
- try {
103
- if (typeof Prism !== 'undefined' && Prism.languages[language]) {
104
- highlighted = Prism.highlight(code, Prism.languages[language], language);
105
- } else {
106
- highlighted = this.escapeHtml(code);
107
- }
108
- } catch (error) {
109
- console.error('Highlight error:', error);
110
- highlighted = this.escapeHtml(code);
111
- }
112
-
113
- if (this.config.enableCache) {
114
- this.highlightCache.set(cacheKey, highlighted);
115
-
116
- if (this.highlightCache.size > this.config.maxCacheSize) {
117
- const firstKey = this.highlightCache.keys().next().value;
118
- this.highlightCache.delete(firstKey);
119
- }
120
- }
121
-
122
- return highlighted;
123
- }
124
-
125
- async createHighlightedElement(code, language = 'plaintext') {
126
- const pre = document.createElement('pre');
127
- const code_el = document.createElement('code');
128
-
129
- code_el.className = `language-${language}`;
130
-
131
- if (this.config.lazyLoad) {
132
- try {
133
- await this.ensureLoaded();
134
- const highlighted = await this.highlight(code, language);
135
- code_el.innerHTML = highlighted;
136
- } catch (error) {
137
- code_el.textContent = code;
138
- }
139
- } else {
140
- code_el.textContent = code;
141
- }
142
-
143
- pre.appendChild(code_el);
144
- return pre;
145
- }
146
-
147
- async highlightElement(element) {
148
- if (!element || !element.querySelector('code')) return;
149
-
150
- if (this.config.lazyLoad) {
151
- try {
152
- await this.ensureLoaded();
153
- } catch (error) {
154
- console.warn('Prism loading failed, skipping highlighting');
155
- return;
156
- }
157
- }
158
-
159
- if (typeof Prism !== 'undefined') {
160
- try {
161
- Prism.highlightElement(element.querySelector('code'));
162
- } catch (error) {
163
- console.error('Element highlighting error:', error);
164
- }
165
- }
166
- }
167
-
168
84
  detectLanguage(code) {
169
85
  if (!code) return 'plaintext';
170
86
 
@@ -1,118 +0,0 @@
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;