agentgui 1.0.917 → 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 (52) hide show
  1. package/database-schema.js +0 -58
  2. package/lib/db-queries-cleanup.js +0 -12
  3. package/lib/db-queries-del.js +0 -1
  4. package/lib/db-queries.js +0 -4
  5. package/lib/http-handler.js +1 -10
  6. package/lib/plugins/database-plugin.js +1 -1
  7. package/lib/process-message.js +2 -2
  8. package/lib/provider-config.js +0 -16
  9. package/lib/recovery.js +2 -12
  10. package/lib/routes-agent-actions.js +2 -58
  11. package/lib/routes-debug.js +1 -7
  12. package/lib/routes-registry.js +5 -17
  13. package/lib/server-startup.js +1 -59
  14. package/lib/stream-event-handler.js +1 -3
  15. package/lib/ws-handlers-session2.js +2 -23
  16. package/lib/ws-handlers-util.js +106 -175
  17. package/lib/ws-legacy-handlers.js +1 -104
  18. package/lib/ws-setup.js +1 -3
  19. package/package.json +1 -15
  20. package/server.js +9 -26
  21. package/test.js +1 -21
  22. package/ecosystem.config.cjs +0 -22
  23. package/lib/checkpoint-manager.js +0 -182
  24. package/lib/db-queries-tools.js +0 -131
  25. package/lib/db-queries-voice.js +0 -85
  26. package/lib/oauth-codex.js +0 -164
  27. package/lib/oauth-common.js +0 -92
  28. package/lib/oauth-gemini.js +0 -199
  29. package/lib/plugins/auth-plugin.js +0 -132
  30. package/lib/plugins/speech-plugin.js +0 -72
  31. package/lib/plugins/tools-plugin.js +0 -120
  32. package/lib/pm2-manager.js +0 -170
  33. package/lib/routes-oauth.js +0 -105
  34. package/lib/routes-speech.js +0 -173
  35. package/lib/routes-tools.js +0 -184
  36. package/lib/speech-manager.js +0 -200
  37. package/lib/speech.js +0 -50
  38. package/lib/tool-install-machine.js +0 -157
  39. package/lib/tool-manager.js +0 -98
  40. package/lib/tool-provisioner.js +0 -98
  41. package/lib/tool-spawner.js +0 -163
  42. package/lib/tool-version-check.js +0 -196
  43. package/lib/tool-version-fetch.js +0 -68
  44. package/lib/ws-handlers-oauth.js +0 -76
  45. package/static/js/agent-auth-oauth.js +0 -159
  46. package/static/js/pm2-monitor.js +0 -151
  47. package/static/js/stt-handler.js +0 -147
  48. package/static/js/tool-install-machine.js +0 -155
  49. package/static/js/tools-manager-ui.js +0 -124
  50. package/static/js/tools-manager.js +0 -172
  51. package/static/js/voice-machine.js +0 -145
  52. 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
- })();