agentgui 1.0.728 → 1.0.730

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.
@@ -0,0 +1,145 @@
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,119 +1,132 @@
1
- (function() {
2
- var BASE = window.__BASE_URL || '';
3
- var ttsEnabled = true;
4
- var speechQueue = [];
5
- var isSpeaking = false;
6
- var currentAudio = null;
7
- var selectedVoiceId = localStorage.getItem('gmgui-voice-selection') || 'default';
8
- var ttsAudioCache = new Map();
9
- var TTS_CLIENT_CACHE_MAX = 50;
10
- var audioChunkQueue = [];
11
- var isPlayingChunk = false;
12
- var streamDone = false;
13
- var ttsConsecutiveFailures = 0;
14
- var TTS_MAX_FAILURES = 3;
15
- var ttsDisabledUntilReset = false;
16
- var streamingSupported = true;
17
-
18
- window.addEventListener('ws-message', function(e) {
19
- var data = e.detail;
20
- if (!data) return;
21
- if (data.type === 'tts_audio' && data.audio && data.voiceId === selectedVoiceId) cacheTTSAudio(data.cacheKey, data.audio);
22
- if (data.type === 'sync_connected') sendVoiceToServer();
23
- });
24
-
25
- function cacheTTSAudio(cacheKey, b64) {
26
- if (ttsAudioCache.size >= TTS_CLIENT_CACHE_MAX) ttsAudioCache.delete(ttsAudioCache.keys().next().value);
27
- var binary = atob(b64), bytes = new Uint8Array(binary.length);
28
- for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
29
- ttsAudioCache.set(cacheKey, new Blob([bytes], { type: 'audio/wav' }));
30
- }
31
-
32
- function speakDirect(text) {
33
- var clean = text.replace(/<[^>]*>/g, '').trim();
34
- if (!clean) return;
35
- var parts = (typeof agentGUIClient !== 'undefined' && agentGUIClient && typeof agentGUIClient.parseMarkdownCodeBlocks === 'function')
36
- ? agentGUIClient.parseMarkdownCodeBlocks(clean) : [{ type: 'text', content: clean }];
37
- parts.forEach(function(p) { if (p.type !== 'code' && p.content.trim()) speechQueue.push(p.content.trim()); });
38
- processQueue();
39
- }
40
-
41
- function playNextChunk() {
42
- if (!audioChunkQueue.length) {
43
- isPlayingChunk = false;
44
- if (streamDone) { isSpeaking = false; processQueue(); }
45
- return;
46
- }
47
- isPlayingChunk = true;
48
- var blob = audioChunkQueue.shift(), url = URL.createObjectURL(blob);
49
- currentAudio = new Audio(url);
50
- var next = function() { URL.revokeObjectURL(url); currentAudio = null; playNextChunk(); };
51
- currentAudio.onended = next; currentAudio.onerror = next;
52
- currentAudio.play().catch(next);
53
- }
54
-
55
- function processQueue() {
56
- if (isSpeaking || !speechQueue.length) return;
57
- if (ttsDisabledUntilReset) { speechQueue = []; return; }
58
- isSpeaking = true; streamDone = false;
59
- var text = speechQueue.shift();
60
- audioChunkQueue = []; isPlayingChunk = false;
61
- var cached = ttsAudioCache.get(selectedVoiceId + ':' + text);
62
- if (cached) { ttsConsecutiveFailures = 0; audioChunkQueue.push(cached); streamDone = true; if (!isPlayingChunk) playNextChunk(); return; }
63
- function ok() { ttsConsecutiveFailures = 0; }
64
- function fail() {
65
- if (++ttsConsecutiveFailures >= TTS_MAX_FAILURES) { ttsDisabledUntilReset = true; speechQueue = []; }
66
- streamDone = true; isSpeaking = false;
67
- if (!ttsDisabledUntilReset) processQueue();
68
- }
69
- function stream() {
70
- if (!streamingSupported) { nonStream(text); return; }
71
- fetch(BASE + '/api/tts-stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: text, voiceId: selectedVoiceId }) })
72
- .then(function(r) {
73
- if (!r.ok) { streamingSupported = false; throw 0; }
74
- var reader = r.body.getReader(), buf = new Uint8Array(0);
75
- function cat(a, b) { var c = new Uint8Array(a.length + b.length); c.set(a); c.set(b, a.length); return c; }
76
- function pump() { return reader.read().then(function(res) {
77
- if (res.done) { ok(); streamDone = true; if (!isPlayingChunk && !audioChunkQueue.length) { isSpeaking = false; processQueue(); } return; }
78
- buf = cat(buf, res.value);
79
- while (buf.length >= 4) {
80
- var len = new DataView(buf.buffer, buf.byteOffset, 4).getUint32(0, false);
81
- if (buf.length < 4 + len) break;
82
- audioChunkQueue.push(new Blob([buf.slice(4, 4 + len)], { type: 'audio/wav' }));
83
- buf = buf.slice(4 + len);
84
- if (!isPlayingChunk) playNextChunk();
85
- }
86
- return pump();
87
- }); }
88
- return pump();
89
- }).catch(function() { nonStream(text); });
90
- }
91
- function nonStream(txt) {
92
- fetch(BASE + '/api/tts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: txt, voiceId: selectedVoiceId }) })
93
- .then(function(r) { if (!r.ok) throw 0; return r.arrayBuffer(); })
94
- .then(function(b) { ok(); audioChunkQueue.push(new Blob([b], { type: 'audio/wav' })); streamDone = true; if (!isPlayingChunk) playNextChunk(); })
95
- .catch(fail);
96
- }
97
- stream();
98
- }
99
-
100
- function stopSpeaking() {
101
- speechQueue = []; audioChunkQueue = []; isPlayingChunk = false; isSpeaking = false;
102
- ttsConsecutiveFailures = 0; ttsDisabledUntilReset = false;
103
- if (currentAudio) { currentAudio.pause(); currentAudio = null; }
104
- }
105
-
106
- function sendVoiceToServer() {
107
- if (typeof agentGUIClient !== 'undefined' && agentGUIClient && agentGUIClient.wsManager && agentGUIClient.wsManager.isConnected)
108
- agentGUIClient.wsManager.sendMessage({ type: 'set_voice', voiceId: selectedVoiceId });
109
- }
110
-
111
- window.voiceModule = {
112
- getAutoSpeak: function() { return ttsEnabled; },
113
- setAutoSpeak: function(v) { ttsEnabled = Boolean(v); localStorage.setItem('gmgui-auto-speak', ttsEnabled); if (!ttsEnabled) stopSpeaking(); },
114
- getVoice: function() { return selectedVoiceId; },
115
- setVoice: function(id) { selectedVoiceId = String(id); localStorage.setItem('gmgui-voice-selection', selectedVoiceId); sendVoiceToServer(); },
116
- speakText: speakDirect,
117
- stopSpeaking: stopSpeaking
118
- };
119
- })();
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 cached = ttsAudioCache.get(selectedVoiceId + ':' + text);
61
+ if (cached) { audioChunkQueue.push(cached); playNextChunk(); return; }
62
+ function stream() {
63
+ if (!streamingSupported) { nonStream(text); return; }
64
+ fetch(BASE + '/api/tts-stream', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: text, voiceId: selectedVoiceId }) })
65
+ .then(function(r) {
66
+ if (!r.ok) { streamingSupported = false; throw 0; }
67
+ var reader = r.body.getReader(), buf = new Uint8Array(0);
68
+ function cat(a, b) { var c = new Uint8Array(a.length + b.length); c.set(a); c.set(b, a.length); return c; }
69
+ function pump() { return reader.read().then(function(res) {
70
+ if (res.done) { if (!audioChunkQueue.length) { if (api()) api().send({ type: 'PLAYBACK_DONE' }); } return; }
71
+ buf = cat(buf, res.value);
72
+ while (buf.length >= 4) {
73
+ var len = new DataView(buf.buffer, buf.byteOffset, 4).getUint32(0, false);
74
+ if (buf.length < 4 + len) break;
75
+ audioChunkQueue.push(new Blob([buf.slice(4, 4 + len)], { type: 'audio/wav' }));
76
+ buf = buf.slice(4 + len);
77
+ if (audioChunkQueue.length === 1) playNextChunk();
78
+ }
79
+ return pump();
80
+ }); }
81
+ return pump();
82
+ }).catch(function() { nonStream(text); });
83
+ }
84
+ function nonStream(txt) {
85
+ fetch(BASE + '/api/tts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text: txt, voiceId: selectedVoiceId }) })
86
+ .then(function(r) { if (!r.ok) throw 0; return r.arrayBuffer(); })
87
+ .then(function(b) { audioChunkQueue.push(new Blob([b], { type: 'audio/wav' })); playNextChunk(); })
88
+ .catch(function() { if (api()) api().send({ type: 'PLAYBACK_ERROR' }); });
89
+ }
90
+ stream();
91
+ }
92
+
93
+ function speakDirect(text) {
94
+ if (!ttsEnabled) return;
95
+ var vmApi = api();
96
+ if (vmApi && vmApi.isDisabled()) return;
97
+ var clean = text.replace(/<[^>]*>/g, '').trim();
98
+ if (!clean) return;
99
+ var parts = (typeof agentGUIClient !== 'undefined' && agentGUIClient && typeof agentGUIClient.parseMarkdownCodeBlocks === 'function')
100
+ ? agentGUIClient.parseMarkdownCodeBlocks(clean) : [{ type: 'text', content: clean }];
101
+ parts.forEach(function(p) {
102
+ if (p.type !== 'code' && p.content.trim()) {
103
+ if (vmApi) vmApi.send({ type: 'SPEAK', text: p.content.trim() });
104
+ }
105
+ });
106
+ }
107
+
108
+ function stopSpeaking() {
109
+ audioChunkQueue = [];
110
+ if (currentAudio) { currentAudio.pause(); currentAudio = null; }
111
+ if (api()) api().send({ type: 'STOP' });
112
+ }
113
+
114
+ function sendVoiceToServer() {
115
+ if (typeof agentGUIClient !== 'undefined' && agentGUIClient && agentGUIClient.wsManager && agentGUIClient.wsManager.isConnected)
116
+ agentGUIClient.wsManager.sendMessage({ type: 'set_voice', voiceId: selectedVoiceId });
117
+ }
118
+
119
+ window.voiceModule = {
120
+ getAutoSpeak: function() { return ttsEnabled; },
121
+ setAutoSpeak: function(v) {
122
+ ttsEnabled = Boolean(v);
123
+ localStorage.setItem('gmgui-auto-speak', ttsEnabled);
124
+ if (!ttsEnabled) stopSpeaking();
125
+ if (api()) api().send({ type: 'SET_ENABLED', enabled: ttsEnabled });
126
+ },
127
+ getVoice: function() { return selectedVoiceId; },
128
+ setVoice: function(id) { selectedVoiceId = String(id); localStorage.setItem('gmgui-voice-selection', selectedVoiceId); sendVoiceToServer(); },
129
+ speakText: speakDirect,
130
+ stopSpeaking: stopSpeaking
131
+ };
132
+ })();