agentgui 1.0.822 → 1.0.823
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 +10 -0
- package/package.json +1 -1
- package/static/index.html +1 -1
- package/static/js/ui-components-rendering.js +61 -182
- package/static/js/ws-latency.js +88 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
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
|
+
|
|
1
11
|
## 2026-04-11
|
|
2
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
|
|
3
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
|
package/package.json
CHANGED
package/static/index.html
CHANGED
|
@@ -298,6 +298,7 @@
|
|
|
298
298
|
<script defer src="/gm/js/conv-sidebar-clone.js"></script>
|
|
299
299
|
<script defer src="/gm/lib/msgpackr.min.js"></script>
|
|
300
300
|
<script defer src="/gm/js/ws-core.js"></script>
|
|
301
|
+
<script defer src="/gm/js/ws-latency.js"></script>
|
|
301
302
|
<script defer src="/gm/js/websocket-manager.js"></script>
|
|
302
303
|
<script defer src="/gm/js/ws-client.js"></script>
|
|
303
304
|
<script defer src="/gm/js/syntax-highlighter.js"></script>
|
|
@@ -313,7 +314,6 @@
|
|
|
313
314
|
<script defer src="/gm/js/voice.js"></script>
|
|
314
315
|
<script defer src="/gm/js/pm2-monitor.js"></script>
|
|
315
316
|
<script defer src="/gm/js/event-filter-config.js"></script>
|
|
316
|
-
<script defer src="/gm/js/event-filter.js"></script>
|
|
317
317
|
<script defer src="/gm/js/client.js"></script>
|
|
318
318
|
<script defer src="/gm/js/features.js"></script>
|
|
319
319
|
<script defer src="/gm/js/agent-auth.js"></script>
|
|
@@ -1,183 +1,62 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
return false;
|
|
6
|
-
}
|
|
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
|
-
html += `<
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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>`;
|
|
1
|
+
Object.assign(UIComponents, {
|
|
2
|
+
escapeHtml(text) { return window._escHtml ? window._escHtml(String(text)) : String(text).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); },
|
|
3
|
+
|
|
4
|
+
copyToClipboard(text) {
|
|
5
|
+
return navigator.clipboard.writeText(text).catch(() => false);
|
|
6
|
+
},
|
|
7
|
+
|
|
8
|
+
downloadFile(data, filename, mimeType = 'text/plain') {
|
|
9
|
+
const blob = new Blob([data], { type: mimeType });
|
|
10
|
+
const url = URL.createObjectURL(blob);
|
|
11
|
+
const link = document.createElement('a');
|
|
12
|
+
link.href = url; link.download = filename;
|
|
13
|
+
document.body.appendChild(link); link.click(); document.body.removeChild(link);
|
|
14
|
+
URL.revokeObjectURL(url);
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
createInput(config = {}) {
|
|
18
|
+
const { type = 'text', name = '', label = '', placeholder = '', value = '', required = false } = config;
|
|
19
|
+
const container = document.createElement('div');
|
|
20
|
+
container.className = 'form-group mb-4';
|
|
21
|
+
let html = '';
|
|
22
|
+
if (label) html += `<label class="block text-sm font-medium mb-2">${UIComponents.escapeHtml(label)}</label>`;
|
|
23
|
+
html += `<input type="${type}" name="${name}" placeholder="${UIComponents.escapeHtml(placeholder)}" value="${UIComponents.escapeHtml(value)}" ${required ? 'required' : ''} class="input input-block input-solid" />`;
|
|
24
|
+
container.innerHTML = html;
|
|
25
|
+
return container;
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
createSelect(config = {}) {
|
|
29
|
+
const { name = '', label = '', options = [], value = '', required = false } = config;
|
|
30
|
+
const container = document.createElement('div');
|
|
31
|
+
container.className = 'form-group mb-4';
|
|
32
|
+
let html = '';
|
|
33
|
+
if (label) html += `<label class="block text-sm font-medium mb-2">${UIComponents.escapeHtml(label)}</label>`;
|
|
34
|
+
html += `<select name="${name}" ${required ? 'required' : ''} class="select select-block select-solid">${options.map(opt => `<option value="${opt.value}" ${opt.value === value ? 'selected' : ''}>${UIComponents.escapeHtml(opt.label)}</option>`).join('')}</select>`;
|
|
35
|
+
container.innerHTML = html;
|
|
36
|
+
return container;
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
createButtonGroup(config = {}) {
|
|
40
|
+
const { buttons = [], vertical = false } = config;
|
|
41
|
+
const container = document.createElement('div');
|
|
42
|
+
container.className = `button-group flex gap-2 ${vertical ? 'flex-col' : 'flex-row'}`;
|
|
43
|
+
buttons.forEach(btn => {
|
|
44
|
+
const button = document.createElement('button');
|
|
45
|
+
button.className = `btn btn-${btn.variant || 'secondary'} flex-${vertical ? '1' : 'none'}`;
|
|
46
|
+
button.textContent = btn.label;
|
|
47
|
+
if (btn.onClick) button.addEventListener('click', btn.onClick);
|
|
48
|
+
container.appendChild(button);
|
|
49
|
+
});
|
|
50
|
+
return container;
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
createBadge(config = {}) {
|
|
54
|
+
const { label = '', variant = 'default', size = 'medium' } = config;
|
|
55
|
+
const sizeClasses = { small: 'badge-sm', medium: 'badge-md', large: 'badge-lg' };
|
|
56
|
+
const variantClasses = { default: 'badge-flat', primary: 'badge-flat-primary', success: 'badge-flat-success', warning: 'badge-flat-warning', error: 'badge-flat-error' };
|
|
57
|
+
const badge = document.createElement('span');
|
|
58
|
+
badge.className = `badge ${sizeClasses[size] || sizeClasses.medium} ${variantClasses[variant] || variantClasses.default}`;
|
|
59
|
+
badge.textContent = label;
|
|
60
|
+
return badge;
|
|
84
61
|
}
|
|
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
|
-
};
|
|
62
|
+
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Object.assign(WebSocketManager.prototype, {
|
|
2
|
+
startHeartbeat() {
|
|
3
|
+
this.stopHeartbeat();
|
|
4
|
+
this.heartbeatTimer = setInterval(() => { this.ping(); }, this.config.heartbeatInterval);
|
|
5
|
+
},
|
|
6
|
+
|
|
7
|
+
stopHeartbeat() {
|
|
8
|
+
if (this.heartbeatTimer) { clearInterval(this.heartbeatTimer); this.heartbeatTimer = null; }
|
|
9
|
+
},
|
|
10
|
+
|
|
11
|
+
_reportLatency() {
|
|
12
|
+
this.emit('latency', { current: this.latency.current, avg: this.latency.avg, jitter: this.latency.jitter, quality: this.latency.quality, trend: this.latency.trend });
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
_handleVisibilityChange() {
|
|
16
|
+
if (document.visibilityState === 'visible' && !this.isConnected && !this.isManuallyDisconnected) this.connect().catch(() => {});
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
_handleOnline() {
|
|
20
|
+
if (!this.isConnected && !this.isManuallyDisconnected) this.connect().catch(() => {});
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
ping() {
|
|
24
|
+
if (!this.isConnected) return;
|
|
25
|
+
this.latency.pingCounter++;
|
|
26
|
+
const id = this.latency.pingCounter;
|
|
27
|
+
const pongTimer = setTimeout(() => {
|
|
28
|
+
this.latency.missedPongs++;
|
|
29
|
+
this.emit('latency', { current: null, avg: this.latency.avg, jitter: this.latency.jitter, quality: 'poor', missed: true });
|
|
30
|
+
if (this.latency.missedPongs >= 3) { this.onClose(); }
|
|
31
|
+
}, this.config.pongTimeout);
|
|
32
|
+
this.requestMap.set('ping:' + id, { type: 'ping', sentAt: Date.now(), pongTimer });
|
|
33
|
+
try {
|
|
34
|
+
this.ws.send(window._codec ? window._codec.encode({ type: 'ping', id }) : msgpackr.pack({ type: 'ping', id }));
|
|
35
|
+
} catch (_) { clearTimeout(pongTimer); this.requestMap.delete('ping:' + id); }
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
_handlePong(data) {
|
|
39
|
+
const key = 'ping:' + data.id;
|
|
40
|
+
const pending = this.requestMap.get(key);
|
|
41
|
+
if (!pending) return;
|
|
42
|
+
clearTimeout(pending.pongTimer);
|
|
43
|
+
this.requestMap.delete(key);
|
|
44
|
+
this.latency.missedPongs = 0;
|
|
45
|
+
const rtt = Date.now() - pending.sentAt;
|
|
46
|
+
this._recordLatency(rtt);
|
|
47
|
+
this._checkDegradation();
|
|
48
|
+
this._reportLatency();
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
_recordLatency(rtt) {
|
|
52
|
+
const samples = this.latency.samples;
|
|
53
|
+
samples.push(rtt);
|
|
54
|
+
if (samples.length > this.config.latencyWindowSize) samples.shift();
|
|
55
|
+
this.latency.current = rtt;
|
|
56
|
+
this.latency.avg = Math.round(samples.reduce((a, b) => a + b, 0) / samples.length);
|
|
57
|
+
const jitterSamples = samples.slice(1).map((v, i) => Math.abs(v - samples[i]));
|
|
58
|
+
this.latency.jitter = jitterSamples.length ? Math.round(jitterSamples.reduce((a, b) => a + b, 0) / jitterSamples.length) : 0;
|
|
59
|
+
this.latency.quality = this._qualityTier(this.latency.avg);
|
|
60
|
+
if (this._latencyEma === null) this._latencyEma = rtt;
|
|
61
|
+
else this._latencyEma = 0.3 * rtt + 0.7 * this._latencyEma;
|
|
62
|
+
this.latency.predicted = Math.round(this._latencyEma);
|
|
63
|
+
this.stats.avgLatency = this.latency.avg;
|
|
64
|
+
this._trendHistory.push(rtt);
|
|
65
|
+
if (this._trendHistory.length > 5) this._trendHistory.shift();
|
|
66
|
+
if (this._trendHistory.length >= 3) {
|
|
67
|
+
const h = this._trendHistory;
|
|
68
|
+
const slope = (h[h.length - 1] - h[0]) / h.length;
|
|
69
|
+
this.latency.trend = slope > 5 ? 'rising' : slope < -5 ? 'falling' : 'stable';
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
_qualityTier(avg) {
|
|
74
|
+
if (avg < 50) return 'excellent';
|
|
75
|
+
if (avg < 150) return 'good';
|
|
76
|
+
if (avg < 300) return 'fair';
|
|
77
|
+
return 'poor';
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
_checkDegradation() {
|
|
81
|
+
const tier = this.latency.quality;
|
|
82
|
+
const prev = this._lastQualityTier;
|
|
83
|
+
if (tier !== prev) {
|
|
84
|
+
this._lastQualityTier = tier;
|
|
85
|
+
this.emit('quality_change', { quality: tier, latency: this.latency.avg });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
});
|