agentgui 1.0.916 → 1.0.918

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.
Files changed (53) hide show
  1. package/CLAUDE.md +1 -382
  2. package/database-schema.js +0 -58
  3. package/lib/db-queries-cleanup.js +0 -12
  4. package/lib/db-queries-del.js +0 -1
  5. package/lib/db-queries.js +0 -4
  6. package/lib/http-handler.js +1 -10
  7. package/lib/plugins/database-plugin.js +1 -1
  8. package/lib/process-message.js +2 -2
  9. package/lib/provider-config.js +0 -16
  10. package/lib/recovery.js +2 -12
  11. package/lib/routes-agent-actions.js +2 -58
  12. package/lib/routes-debug.js +1 -7
  13. package/lib/routes-registry.js +5 -17
  14. package/lib/server-startup.js +1 -59
  15. package/lib/stream-event-handler.js +1 -3
  16. package/lib/ws-handlers-session2.js +2 -23
  17. package/lib/ws-handlers-util.js +106 -175
  18. package/lib/ws-legacy-handlers.js +1 -104
  19. package/lib/ws-setup.js +1 -3
  20. package/package.json +1 -15
  21. package/server.js +9 -26
  22. package/test.js +1 -21
  23. package/ecosystem.config.cjs +0 -22
  24. package/lib/checkpoint-manager.js +0 -182
  25. package/lib/db-queries-tools.js +0 -131
  26. package/lib/db-queries-voice.js +0 -85
  27. package/lib/oauth-codex.js +0 -164
  28. package/lib/oauth-common.js +0 -92
  29. package/lib/oauth-gemini.js +0 -199
  30. package/lib/plugins/auth-plugin.js +0 -132
  31. package/lib/plugins/speech-plugin.js +0 -72
  32. package/lib/plugins/tools-plugin.js +0 -120
  33. package/lib/pm2-manager.js +0 -170
  34. package/lib/routes-oauth.js +0 -105
  35. package/lib/routes-speech.js +0 -173
  36. package/lib/routes-tools.js +0 -184
  37. package/lib/speech-manager.js +0 -200
  38. package/lib/speech.js +0 -50
  39. package/lib/tool-install-machine.js +0 -157
  40. package/lib/tool-manager.js +0 -98
  41. package/lib/tool-provisioner.js +0 -98
  42. package/lib/tool-spawner.js +0 -163
  43. package/lib/tool-version-check.js +0 -196
  44. package/lib/tool-version-fetch.js +0 -68
  45. package/lib/ws-handlers-oauth.js +0 -76
  46. package/static/js/agent-auth-oauth.js +0 -159
  47. package/static/js/pm2-monitor.js +0 -151
  48. package/static/js/stt-handler.js +0 -147
  49. package/static/js/tool-install-machine.js +0 -155
  50. package/static/js/tools-manager-ui.js +0 -124
  51. package/static/js/tools-manager.js +0 -172
  52. package/static/js/voice-machine.js +0 -145
  53. package/static/js/voice.js +0 -134
@@ -1,172 +0,0 @@
1
- (function() {
2
- var btn = document.getElementById('toolsManagerBtn');
3
- var popup = document.getElementById('toolsPopup');
4
- var tools = [];
5
- var isRefreshing = false;
6
-
7
- var hasRefreshed = false;
8
- function init() {
9
- if (!btn || !popup) return;
10
- btn.style.display = 'flex';
11
- btn.addEventListener('click', function() { if (!hasRefreshed) { hasRefreshed = true; refresh(); } togglePopup(); });
12
- document.addEventListener('click', function(e) {
13
- if (!btn.contains(e.target) && !popup.contains(e.target)) closePopup();
14
- });
15
- window.addEventListener('ws-message', onWsMessage);
16
- initVoiceControls();
17
- }
18
-
19
- function initVoiceControls() {
20
- var autoSpeakToggle = document.getElementById('toolsAutoSpeakToggle');
21
- var voiceSelector = document.getElementById('toolsVoiceSelector');
22
- if (!autoSpeakToggle || !voiceSelector) return;
23
- autoSpeakToggle.checked = localStorage.getItem('toolsAutoSpeak') === 'true';
24
- window.addEventListener('ws-message', function(e) {
25
- var data = e.detail;
26
- if (data && data.type === 'voice_list') window.toolsManagerUI.updateVoiceSelector(data.voices);
27
- });
28
- function trySubscribe() {
29
- if (window.wsManager && window.wsManager.subscribeToVoiceList) {
30
- window.wsManager.subscribeToVoiceList(window.toolsManagerUI.updateVoiceSelector);
31
- } else {
32
- var BASE = window.__BASE_URL || '';
33
- fetch(BASE + '/api/voices').then(r => r.json()).then(d => {
34
- if (d.ok && Array.isArray(d.voices)) window.toolsManagerUI.updateVoiceSelector(d.voices);
35
- }).catch(() => {});
36
- setTimeout(function() {
37
- if (window.wsManager && window.wsManager.subscribeToVoiceList)
38
- window.wsManager.subscribeToVoiceList(window.toolsManagerUI.updateVoiceSelector);
39
- }, 2000);
40
- }
41
- }
42
- trySubscribe();
43
- autoSpeakToggle.addEventListener('change', function() {
44
- localStorage.setItem('toolsAutoSpeak', this.checked);
45
- if (window.voiceModule) window.voiceModule.setAutoSpeak(this.checked);
46
- });
47
- voiceSelector.addEventListener('change', function() {
48
- localStorage.setItem('toolsVoice', this.value);
49
- if (window.voiceModule) window.voiceModule.setVoice(this.value);
50
- });
51
- }
52
-
53
- function fetchTools() {
54
- window.wsClient.rpc('tools.list')
55
- .then(d => { tools = d.tools || []; syncMachineStates(); render(); })
56
- .catch(() => {
57
- fetch('/gm/api/tools').then(r => r.json()).then(d => { tools = d.tools || []; syncMachineStates(); render(); })
58
- .catch(e => console.error('[TOOLS-MGR]', e.message));
59
- });
60
- }
61
-
62
- function syncMachineStates() {
63
- if (!window.toolInstallMachineAPI) return;
64
- tools.forEach(function(tool) {
65
- var ms = window.toolInstallMachineAPI.getState(tool.id);
66
- if (ms !== 'installing' && ms !== 'updating') {
67
- if (tool.status === 'installed') window.toolInstallMachineAPI.send(tool.id, { type: 'SET_INSTALLED', installedVersion: tool.installedVersion, publishedVersion: tool.publishedVersion });
68
- else if (tool.status === 'needs_update') window.toolInstallMachineAPI.send(tool.id, { type: 'SET_NEEDS_UPDATE', installedVersion: tool.installedVersion, publishedVersion: tool.publishedVersion });
69
- else if (tool.status === 'failed') window.toolInstallMachineAPI.send(tool.id, { type: 'SET_FAILED', error: tool.error_message });
70
- }
71
- });
72
- }
73
-
74
- function install(toolId) {
75
- if (window.toolInstallMachineAPI && window.toolInstallMachineAPI.isLocked(toolId)) return;
76
- if (window.toolInstallMachineAPI) window.toolInstallMachineAPI.send(toolId, { type: 'INSTALL' });
77
- render();
78
- fetch('/gm/api/tools/' + toolId + '/install', { method: 'POST' }).then(r => r.json()).then(d => {
79
- if (!d.success) { alert('Install failed: ' + (d.error || 'Unknown error')); if (window.toolInstallMachineAPI) window.toolInstallMachineAPI.send(toolId, { type: 'FAILED', error: d.error }); render(); }
80
- }).catch(e => { alert('Install failed: ' + e.message); if (window.toolInstallMachineAPI) window.toolInstallMachineAPI.send(toolId, { type: 'FAILED', error: e.message }); render(); });
81
- }
82
-
83
- function update(toolId) {
84
- if (window.toolInstallMachineAPI && window.toolInstallMachineAPI.isLocked(toolId)) return;
85
- if (window.toolInstallMachineAPI) window.toolInstallMachineAPI.send(toolId, { type: 'UPDATE' });
86
- render();
87
- fetch('/gm/api/tools/' + toolId + '/update', { method: 'POST' }).then(r => r.json()).then(d => {
88
- if (!d.success) { alert('Update failed: ' + (d.error || 'Unknown error')); if (window.toolInstallMachineAPI) window.toolInstallMachineAPI.send(toolId, { type: 'FAILED', error: d.error }); render(); }
89
- }).catch(e => { alert('Update failed: ' + e.message); if (window.toolInstallMachineAPI) window.toolInstallMachineAPI.send(toolId, { type: 'FAILED', error: e.message }); render(); });
90
- }
91
-
92
- function onWsMessage(e) {
93
- var data = e.detail;
94
- if (!data) return;
95
- if (data.type === 'streaming_progress' && data.block && data.block.type === 'text' && data.block.text) {
96
- var toggle = document.getElementById('toolsAutoSpeakToggle');
97
- if (toggle && toggle.checked && (!data.blockRole || data.blockRole === 'assistant'))
98
- if (window.voiceModule) window.voiceModule.speakText(data.block.text);
99
- }
100
- var api = window.toolInstallMachineAPI, tid = data.toolId;
101
- if (data.type === 'tools_update_started') { (data.tools || []).forEach(t => api && api.send(t, { type: 'UPDATE' })); render(); }
102
- else if (data.type === 'tool_install_started' || data.type === 'tool_install_progress') { if (api) api.send(tid, { type: 'PROGRESS' }); render(); }
103
- else if (data.type === 'tool_update_progress') { if (api) api.send(tid, { type: 'PROGRESS' }); render(); }
104
- else if (data.type === 'tool_install_complete' || data.type === 'tool_update_complete') {
105
- var d = data.data || {};
106
- if (api) api.send(tid, { type: 'COMPLETE', version: d.version, installedVersion: d.installedVersion, publishedVersion: d.publishedVersion });
107
- var tool = tools.find(t => t.id === tid);
108
- if (tool) { tool.version = d.version || tool.version; tool.installedVersion = d.installedVersion || tool.installedVersion; tool.publishedVersion = d.publishedVersion || tool.publishedVersion; tool.isUpToDate = d.isUpToDate ?? false; tool.upgradeNeeded = d.upgradeNeeded ?? false; tool.hasUpdate = (d.upgradeNeeded && d.installed) ?? false; }
109
- render(); setTimeout(fetchTools, 1000);
110
- } else if (data.type === 'tool_install_failed' || data.type === 'tool_update_failed') {
111
- if (api) api.send(tid, { type: 'FAILED', error: (data.data || {}).error });
112
- var tool = tools.find(t => t.id === tid); if (tool) tool.error_message = (data.data || {}).error;
113
- render();
114
- } else if (data.type === 'tool_status_update') {
115
- var tool = tools.find(t => t.id === tid);
116
- if (tool && data.data && data.data.installed) {
117
- tool.installed = true; tool.isUpToDate = data.data.isUpToDate ?? true; tool.installedVersion = data.data.installedVersion || tool.installedVersion;
118
- if (api) api.send(tid, data.data.isUpToDate ? { type: 'SET_INSTALLED', installedVersion: tool.installedVersion } : { type: 'SET_NEEDS_UPDATE', installedVersion: tool.installedVersion });
119
- render();
120
- }
121
- } else if (data.type === 'tools_update_complete' || data.type === 'tools_refresh_complete') { isRefreshing = false; fetchTools(); }
122
- }
123
-
124
- function render() {
125
- var scroll = popup.querySelector('.tools-popup-scroll');
126
- if (!scroll) return;
127
- var h = window.webjsx && window.webjsx.createElement;
128
- var applyDiff = window.webjsx && window.webjsx.applyDiff;
129
- var ui = window.toolsManagerUI;
130
- if (!h || !applyDiff) { scroll.innerHTML = '<div class="tool-empty-state"><div class="tool-empty-state-text">Loading...</div></div>'; return; }
131
- if (!tools.length) {
132
- applyDiff(scroll, [h('div', { class: 'tool-empty-state', style: 'grid-column:1/-1;' }, h('div', { class: 'tool-empty-state-text' }, 'No tools available'))]);
133
- return;
134
- }
135
- var cli = tools.filter(t => t.category === 'cli');
136
- var plugin = tools.filter(t => t.category === 'plugin');
137
- var other = tools.filter(t => !t.category);
138
- var vnodes = [];
139
- if (cli.length) vnodes.push(h('div', { class: 'tool-section-header' }, 'CLI Agents'), ...cli.map(t => ui.renderToolCard(t, isRefreshing)).filter(Boolean));
140
- if (plugin.length) vnodes.push(h('div', { class: 'tool-section-header' }, 'GM Plugins'), ...plugin.map(t => ui.renderToolCard(t, isRefreshing)).filter(Boolean));
141
- if (other.length) vnodes.push(...other.map(t => ui.renderToolCard(t, isRefreshing)).filter(Boolean));
142
- applyDiff(scroll, vnodes);
143
- }
144
-
145
- function togglePopup(e) { if (e) e.stopPropagation(); if (!popup.classList.contains('open')) { isRefreshing = false; refresh(); } popup.classList.toggle('open'); }
146
- function closePopup() { popup.classList.remove('open'); }
147
- function refresh() { fetchTools(); }
148
-
149
- function updateAll() {
150
- var toUpdate = tools.filter(t => t.hasUpdate || t.status === 'needs_update' || t.status === 'failed');
151
- if (!toUpdate.length) { alert('All tools are up-to-date'); return; }
152
- toUpdate.forEach(t => { if (window.toolInstallMachineAPI) window.toolInstallMachineAPI.send(t.id, { type: 'UPDATE' }); });
153
- render();
154
- fetch('/gm/api/tools/update', { method: 'POST' }).then(r => r.json()).then(d => {
155
- if (!d.updating) { alert('Update started, but response unexpected'); toUpdate.forEach(t => { if (window.toolInstallMachineAPI) window.toolInstallMachineAPI.send(t.id, { type: 'FAILED', error: 'unexpected response' }); }); }
156
- }).catch(e => { alert('Update failed: ' + e.message); toUpdate.forEach(t => { if (window.toolInstallMachineAPI) window.toolInstallMachineAPI.send(t.id, { type: 'FAILED', error: e.message }); }); });
157
- }
158
-
159
- if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
160
- else init();
161
-
162
- window.toolsManager = {
163
- refresh: function() { isRefreshing = true; render(); fetch('/gm/api/tools/refresh-all', { method: 'POST' }).catch(e => console.error('[TOOLS-MGR]', e.message)); },
164
- install: function(toolId) { install(toolId); },
165
- update: function(toolId) { update(toolId); },
166
- updateAll: function() { updateAll(); },
167
- getAutoSpeak: function() { var t = document.getElementById('toolsAutoSpeakToggle'); return t ? t.checked : false; },
168
- getVoice: function() { var s = document.getElementById('toolsVoiceSelector'); return s ? s.value : 'default'; },
169
- setAutoSpeak: function(v) { var t = document.getElementById('toolsAutoSpeakToggle'); if (t) { t.checked = v; localStorage.setItem('toolsAutoSpeak', v); } },
170
- setVoice: function(v) { var s = document.getElementById('toolsVoiceSelector'); if (s && Array.from(s.options).some(o => o.value === v)) { s.value = v; localStorage.setItem('toolsVoice', v); } }
171
- };
172
- })();
@@ -1,145 +0,0 @@
1
- (function() {
2
- const { createMachine, createActor, assign } = XState;
3
-
4
- const MAX_FAILURES = 3;
5
-
6
- const voiceMachine = createMachine({
7
- id: 'voice',
8
- initial: 'idle',
9
- context: {
10
- queue: [],
11
- consecutiveFailures: 0,
12
- currentText: null,
13
- enabled: true,
14
- },
15
- states: {
16
- idle: {
17
- on: {
18
- SPEAK: {
19
- target: 'queued',
20
- guard: ({ context }) => context.enabled,
21
- actions: assign(({ context, event }) => ({
22
- queue: [...context.queue, event.text],
23
- })),
24
- },
25
- SET_ENABLED: {
26
- actions: assign(({ event }) => ({ enabled: event.enabled })),
27
- },
28
- },
29
- },
30
- queued: {
31
- always: [
32
- {
33
- guard: ({ context }) => context.queue.length > 0,
34
- target: 'speaking',
35
- actions: assign(({ context }) => ({
36
- currentText: context.queue[0],
37
- queue: context.queue.slice(1),
38
- })),
39
- },
40
- { target: 'idle' },
41
- ],
42
- on: {
43
- SPEAK: {
44
- guard: ({ context }) => context.enabled,
45
- actions: assign(({ context, event }) => ({
46
- queue: [...context.queue, event.text],
47
- })),
48
- },
49
- STOP: {
50
- target: 'idle',
51
- actions: assign({ queue: [], currentText: null }),
52
- },
53
- },
54
- },
55
- speaking: {
56
- on: {
57
- SPEAK: {
58
- guard: ({ context }) => context.enabled,
59
- actions: assign(({ context, event }) => ({
60
- queue: [...context.queue, event.text],
61
- })),
62
- },
63
- PLAYBACK_DONE: [
64
- {
65
- guard: ({ context }) => context.queue.length > 0,
66
- target: 'speaking',
67
- actions: assign(({ context }) => ({
68
- currentText: context.queue[0],
69
- queue: context.queue.slice(1),
70
- consecutiveFailures: 0,
71
- })),
72
- },
73
- {
74
- target: 'idle',
75
- actions: assign({ currentText: null, consecutiveFailures: 0 }),
76
- },
77
- ],
78
- PLAYBACK_ERROR: [
79
- {
80
- guard: ({ context }) => context.consecutiveFailures + 1 >= MAX_FAILURES,
81
- target: 'disabled',
82
- actions: assign({ queue: [], currentText: null, consecutiveFailures: MAX_FAILURES }),
83
- },
84
- {
85
- target: 'idle',
86
- actions: assign(({ context }) => ({
87
- consecutiveFailures: context.consecutiveFailures + 1,
88
- currentText: null,
89
- })),
90
- },
91
- ],
92
- STOP: {
93
- target: 'idle',
94
- actions: assign({ queue: [], currentText: null }),
95
- },
96
- SET_ENABLED: {
97
- actions: assign(({ event }) => ({ enabled: event.enabled })),
98
- },
99
- },
100
- },
101
- disabled: {
102
- on: {
103
- RESET: {
104
- target: 'idle',
105
- actions: assign({ consecutiveFailures: 0, queue: [], currentText: null }),
106
- },
107
- SET_ENABLED: {
108
- actions: assign(({ event }) => ({ enabled: event.enabled })),
109
- },
110
- },
111
- },
112
- },
113
- });
114
-
115
- const actor = createActor(voiceMachine);
116
- actor.start();
117
-
118
- function sendEvent(event) {
119
- actor.send(event);
120
- return actor.getSnapshot();
121
- }
122
-
123
- function getState() {
124
- return actor.getSnapshot().value;
125
- }
126
-
127
- function isActive() {
128
- return getState() === 'speaking' || getState() === 'queued';
129
- }
130
-
131
- function isDisabled() {
132
- return getState() === 'disabled';
133
- }
134
-
135
- function getContext() {
136
- return actor.getSnapshot().context;
137
- }
138
-
139
- function subscribe(fn) {
140
- return actor.subscribe(fn);
141
- }
142
-
143
- window.__voiceMachine = actor;
144
- window.voiceMachineAPI = { send: sendEvent, getState, isActive, isDisabled, getContext, subscribe };
145
- })();
@@ -1,134 +0,0 @@
1
- (function() {
2
- var BASE = window.__BASE_URL || '';
3
- var ttsEnabled = true;
4
- var currentAudio = null;
5
- var selectedVoiceId = localStorage.getItem('gmgui-voice-selection') || 'default';
6
- var ttsAudioCache = new Map();
7
- var TTS_CLIENT_CACHE_MAX = 50;
8
- var audioChunkQueue = [];
9
- var streamingSupported = true;
10
-
11
- window.addEventListener('ws-message', function(e) {
12
- var data = e.detail;
13
- if (!data) return;
14
- if (data.type === 'tts_audio' && data.audio && data.voiceId === selectedVoiceId) cacheTTSAudio(data.cacheKey, data.audio);
15
- if (data.type === 'sync_connected') sendVoiceToServer();
16
- });
17
-
18
- var api = function() { return window.voiceMachineAPI; };
19
-
20
- if (window.voiceMachineAPI) {
21
- window.voiceMachineAPI.subscribe(function(snapshot) {
22
- if (snapshot.value === 'speaking' && snapshot.context.currentText) {
23
- _doSpeak(snapshot.context.currentText);
24
- }
25
- });
26
- } else {
27
- document.addEventListener('DOMContentLoaded', function() {
28
- if (window.voiceMachineAPI) {
29
- window.voiceMachineAPI.subscribe(function(snapshot) {
30
- if (snapshot.value === 'speaking' && snapshot.context.currentText) {
31
- _doSpeak(snapshot.context.currentText);
32
- }
33
- });
34
- }
35
- });
36
- }
37
-
38
- function cacheTTSAudio(cacheKey, b64) {
39
- if (ttsAudioCache.size >= TTS_CLIENT_CACHE_MAX) ttsAudioCache.delete(ttsAudioCache.keys().next().value);
40
- var binary = atob(b64), bytes = new Uint8Array(binary.length);
41
- for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
42
- ttsAudioCache.set(cacheKey, new Blob([bytes], { type: 'audio/wav' }));
43
- }
44
-
45
- function playNextChunk() {
46
- if (!audioChunkQueue.length) {
47
- if (api()) api().send({ type: 'PLAYBACK_DONE' });
48
- return;
49
- }
50
- var blob = audioChunkQueue.shift(), url = URL.createObjectURL(blob);
51
- currentAudio = new Audio(url);
52
- var next = function() { URL.revokeObjectURL(url); currentAudio = null; playNextChunk(); };
53
- currentAudio.onended = next;
54
- currentAudio.onerror = function() { URL.revokeObjectURL(url); currentAudio = null; if (api()) api().send({ type: 'PLAYBACK_ERROR' }); };
55
- currentAudio.play().catch(function() { URL.revokeObjectURL(url); currentAudio = null; if (api()) api().send({ type: 'PLAYBACK_ERROR' }); });
56
- }
57
-
58
- function _doSpeak(text) {
59
- audioChunkQueue = [];
60
- var cacheKey = selectedVoiceId + ':' + text;
61
- var cached = ttsAudioCache.get(cacheKey);
62
- if (cached) { ttsAudioCache.delete(cacheKey); ttsAudioCache.set(cacheKey, cached); audioChunkQueue.push(cached); playNextChunk(); return; }
63
- function stream() {
64
- if (!streamingSupported) { nonStream(text); return; }
65
- fetch(BASE + '/api/tts-stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: text, voiceId: selectedVoiceId }) })
66
- .then(function(r) {
67
- if (!r.ok) { streamingSupported = false; throw 0; }
68
- var reader = r.body.getReader(), buf = new Uint8Array(0);
69
- function cat(a, b) { var c = new Uint8Array(a.length + b.length); c.set(a); c.set(b, a.length); return c; }
70
- function pump() { return reader.read().then(function(res) {
71
- if (res.done) { if (!audioChunkQueue.length) { if (api()) api().send({ type: 'PLAYBACK_DONE' }); } return; }
72
- buf = cat(buf, res.value);
73
- while (buf.length >= 4) {
74
- var len = new DataView(buf.buffer, buf.byteOffset, 4).getUint32(0, false);
75
- if (len > 10 * 1024 * 1024) { buf = new Uint8Array(0); if (api()) api().send({ type: 'PLAYBACK_ERROR' }); return; }
76
- if (buf.length < 4 + len) break;
77
- audioChunkQueue.push(new Blob([buf.slice(4, 4 + len)], { type: 'audio/wav' }));
78
- buf = buf.slice(4 + len);
79
- if (audioChunkQueue.length === 1) playNextChunk();
80
- }
81
- return pump();
82
- }); }
83
- return pump();
84
- }).catch(function() { nonStream(text); });
85
- }
86
- function nonStream(txt) {
87
- fetch(BASE + '/api/tts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: txt, voiceId: selectedVoiceId }) })
88
- .then(function(r) { if (!r.ok) throw 0; return r.arrayBuffer(); })
89
- .then(function(b) { audioChunkQueue.push(new Blob([b], { type: 'audio/wav' })); playNextChunk(); })
90
- .catch(function() { if (api()) api().send({ type: 'PLAYBACK_ERROR' }); });
91
- }
92
- stream();
93
- }
94
-
95
- function speakDirect(text) {
96
- if (!ttsEnabled) return;
97
- var vmApi = api();
98
- if (vmApi && vmApi.isDisabled()) return;
99
- var clean = text.replace(/<[^>]*>/g, '').trim();
100
- if (!clean) return;
101
- var parts = (typeof agentGUIClient !== 'undefined' && agentGUIClient && typeof agentGUIClient.parseMarkdownCodeBlocks === 'function')
102
- ? agentGUIClient.parseMarkdownCodeBlocks(clean) : [{ type: 'text', content: clean }];
103
- parts.forEach(function(p) {
104
- if (p.type !== 'code' && p.content.trim()) {
105
- if (vmApi) vmApi.send({ type: 'SPEAK', text: p.content.trim() });
106
- }
107
- });
108
- }
109
-
110
- function stopSpeaking() {
111
- audioChunkQueue = [];
112
- if (currentAudio) { currentAudio.pause(); currentAudio = null; }
113
- if (api()) api().send({ type: 'STOP' });
114
- }
115
-
116
- function sendVoiceToServer() {
117
- if (typeof agentGUIClient !== 'undefined' && agentGUIClient && agentGUIClient.wsManager && agentGUIClient.wsManager.isConnected)
118
- agentGUIClient.wsManager.sendMessage({ type: 'set_voice', voiceId: selectedVoiceId });
119
- }
120
-
121
- window.voiceModule = {
122
- getAutoSpeak: function() { return ttsEnabled; },
123
- setAutoSpeak: function(v) {
124
- ttsEnabled = Boolean(v);
125
- localStorage.setItem('gmgui-auto-speak', ttsEnabled);
126
- if (!ttsEnabled) stopSpeaking();
127
- if (api()) api().send({ type: 'SET_ENABLED', enabled: ttsEnabled });
128
- },
129
- getVoice: function() { return selectedVoiceId; },
130
- setVoice: function(id) { selectedVoiceId = String(id); localStorage.setItem('gmgui-voice-selection', selectedVoiceId); sendVoiceToServer(); },
131
- speakText: speakDirect,
132
- stopSpeaking: stopSpeaking
133
- };
134
- })();