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.
- package/CLAUDE.md +19 -8
- package/lib/tool-install-machine.js +157 -0
- package/lib/tool-manager.js +1 -2
- package/lib/tool-provisioner.js +6 -1
- package/lib/tool-spawner.js +24 -11
- package/lib/ws-handlers-util.js +5 -1
- package/package.json +1 -1
- package/server.js +24 -5
- package/static/index.html +5 -0
- package/static/js/client.js +5 -30
- package/static/js/conv-list-machine.js +137 -0
- package/static/js/conversations.js +39 -74
- package/static/js/prompt-machine.js +108 -0
- package/static/js/tool-install-machine.js +155 -0
- package/static/js/tools-manager-ui.js +119 -0
- package/static/js/tools-manager.js +164 -435
- package/static/js/voice-machine.js +145 -0
- package/static/js/voice.js +132 -119
|
@@ -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
|
+
})();
|
package/static/js/voice.js
CHANGED
|
@@ -1,119 +1,132 @@
|
|
|
1
|
-
(function() {
|
|
2
|
-
var BASE = window.__BASE_URL || '';
|
|
3
|
-
var ttsEnabled = true;
|
|
4
|
-
var
|
|
5
|
-
var
|
|
6
|
-
var
|
|
7
|
-
var
|
|
8
|
-
var
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
var
|
|
51
|
-
currentAudio
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
+
})();
|