agentgui 1.0.526 → 1.0.528
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/lib/claude-runner.js +15 -2
- package/lib/speech.js +48 -1
- package/package.json +1 -1
- package/server.js +20 -2
- package/static/js/tools-manager.js +65 -18
- package/static/js/voice.js +43 -10
package/lib/claude-runner.js
CHANGED
|
@@ -7,6 +7,9 @@ function getSpawnOptions(cwd, additionalOptions = {}) {
|
|
|
7
7
|
if (isWindows) {
|
|
8
8
|
options.shell = true;
|
|
9
9
|
}
|
|
10
|
+
if (!options.env) {
|
|
11
|
+
options.env = { ...process.env };
|
|
12
|
+
}
|
|
10
13
|
return options;
|
|
11
14
|
}
|
|
12
15
|
|
|
@@ -50,6 +53,7 @@ class AgentRunner {
|
|
|
50
53
|
this.adapterCommand = config.adapterCommand || null;
|
|
51
54
|
this.adapterArgs = config.adapterArgs || [];
|
|
52
55
|
this.npxPackage = config.npxPackage || null;
|
|
56
|
+
this.spawnEnv = config.spawnEnv || {};
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
defaultBuildArgs(prompt, config) {
|
|
@@ -81,7 +85,11 @@ class AgentRunner {
|
|
|
81
85
|
} = config;
|
|
82
86
|
|
|
83
87
|
const args = this.buildArgs(prompt, config);
|
|
84
|
-
const
|
|
88
|
+
const spawnOpts = getSpawnOptions(cwd);
|
|
89
|
+
if (Object.keys(this.spawnEnv).length > 0) {
|
|
90
|
+
spawnOpts.env = { ...spawnOpts.env, ...this.spawnEnv };
|
|
91
|
+
}
|
|
92
|
+
const proc = spawn(this.command, args, spawnOpts);
|
|
85
93
|
|
|
86
94
|
if (config.onPid) {
|
|
87
95
|
try { config.onPid(proc.pid); } catch (e) {}
|
|
@@ -256,7 +264,11 @@ class AgentRunner {
|
|
|
256
264
|
args = [...resolved.prefixArgs, ...this.buildArgs(prompt, config)];
|
|
257
265
|
}
|
|
258
266
|
|
|
259
|
-
const
|
|
267
|
+
const spawnOpts = getSpawnOptions(cwd);
|
|
268
|
+
if (Object.keys(this.spawnEnv).length > 0) {
|
|
269
|
+
spawnOpts.env = { ...spawnOpts.env, ...this.spawnEnv };
|
|
270
|
+
}
|
|
271
|
+
const proc = spawn(cmd, args, spawnOpts);
|
|
260
272
|
|
|
261
273
|
if (config.onPid) {
|
|
262
274
|
try { config.onPid(proc.pid); } catch (e) {}
|
|
@@ -568,6 +580,7 @@ registry.register({
|
|
|
568
580
|
protocol: 'direct',
|
|
569
581
|
supportsStdin: true,
|
|
570
582
|
supportedFeatures: ['streaming', 'resume', 'system-prompt', 'permissions-skip'],
|
|
583
|
+
spawnEnv: { MAX_THINKING_TOKENS: '0' },
|
|
571
584
|
|
|
572
585
|
buildArgs(prompt, config) {
|
|
573
586
|
const {
|
package/lib/speech.js
CHANGED
|
@@ -1,4 +1,51 @@
|
|
|
1
1
|
import { createRequire } from 'module';
|
|
2
2
|
const require = createRequire(import.meta.url);
|
|
3
3
|
const speech = require('webtalk/speech');
|
|
4
|
-
|
|
4
|
+
|
|
5
|
+
const ttsMemCache = new Map();
|
|
6
|
+
const TTS_CACHE_MAX_BYTES = 10 * 1024 * 1024;
|
|
7
|
+
let ttsCacheBytes = 0;
|
|
8
|
+
|
|
9
|
+
function ttsCacheKey(text, voiceId) {
|
|
10
|
+
return (voiceId || 'default') + ':' + text;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function ttsCacheGet(key) {
|
|
14
|
+
return ttsMemCache.get(key) || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function ttsCacheSet(key, wav) {
|
|
18
|
+
if (ttsMemCache.has(key)) return;
|
|
19
|
+
const size = wav ? wav.length : 0;
|
|
20
|
+
while (ttsCacheBytes + size > TTS_CACHE_MAX_BYTES && ttsMemCache.size > 0) {
|
|
21
|
+
const oldest = ttsMemCache.keys().next().value;
|
|
22
|
+
const old = ttsMemCache.get(oldest);
|
|
23
|
+
ttsCacheBytes -= old ? old.length : 0;
|
|
24
|
+
ttsMemCache.delete(oldest);
|
|
25
|
+
}
|
|
26
|
+
ttsMemCache.set(key, wav);
|
|
27
|
+
ttsCacheBytes += size;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function synthesizeWithCache(text, voiceId) {
|
|
31
|
+
const key = ttsCacheKey(text, voiceId);
|
|
32
|
+
const cached = ttsCacheGet(key);
|
|
33
|
+
if (cached) return cached;
|
|
34
|
+
const wav = await speech.synthesize(text, voiceId);
|
|
35
|
+
ttsCacheSet(key, wav);
|
|
36
|
+
return wav;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const transcribe = speech.transcribe;
|
|
40
|
+
export const synthesize = synthesizeWithCache;
|
|
41
|
+
export const synthesizeStream = speech.synthesizeStream;
|
|
42
|
+
export const getSTT = speech.getSTT;
|
|
43
|
+
export const getStatus = speech.getStatus;
|
|
44
|
+
export const getVoices = speech.getVoices;
|
|
45
|
+
export const preloadTTS = speech.preloadTTS;
|
|
46
|
+
export { ttsCacheKey, ttsCacheGet, ttsCacheSet };
|
|
47
|
+
export const splitSentences = speech.splitSentences;
|
|
48
|
+
export const resetSTTError = speech.resetSTTError;
|
|
49
|
+
export const clearCorruptedSTTCache = speech.clearCorruptedSTTCache;
|
|
50
|
+
export const getSttOptions = speech.getSttOptions;
|
|
51
|
+
export const VOICE_DIRS = speech.VOICE_DIRS;
|
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -64,7 +64,7 @@ const voiceCacheManager = {
|
|
|
64
64
|
this.generating.set(cacheKey, true);
|
|
65
65
|
try {
|
|
66
66
|
const speech = await getSpeech();
|
|
67
|
-
const audioBlob = await speech.
|
|
67
|
+
const audioBlob = await speech.synthesize(text, 'default');
|
|
68
68
|
const saved = queries.saveVoiceCache(conversationId, text, audioBlob);
|
|
69
69
|
const totalSize = queries.getVoiceCacheSize(conversationId);
|
|
70
70
|
if (totalSize > this.maxCacheSize) {
|
|
@@ -240,14 +240,15 @@ function flushTTSaccumulator(key, conversationId, sessionId) {
|
|
|
240
240
|
}
|
|
241
241
|
}
|
|
242
242
|
if (voices.size === 0) return;
|
|
243
|
-
const cacheKey = speech.ttsCacheKey(text, vid);
|
|
244
243
|
for (const vid of voices) {
|
|
244
|
+
const cacheKey = speech.ttsCacheKey(text, vid);
|
|
245
245
|
const cached = speech.ttsCacheGet(cacheKey);
|
|
246
246
|
if (cached) {
|
|
247
247
|
pushTTSAudio(cacheKey, cached, conversationId, sessionId, vid);
|
|
248
248
|
continue;
|
|
249
249
|
}
|
|
250
250
|
speech.synthesize(text, vid).then(wav => {
|
|
251
|
+
if (speech.ttsCacheSet) speech.ttsCacheSet(cacheKey, wav);
|
|
251
252
|
pushTTSAudio(cacheKey, wav, conversationId, sessionId, vid);
|
|
252
253
|
}).catch(() => {});
|
|
253
254
|
}
|
|
@@ -2880,6 +2881,23 @@ const server = http.createServer(async (req, res) => {
|
|
|
2880
2881
|
return;
|
|
2881
2882
|
}
|
|
2882
2883
|
|
|
2884
|
+
if (pathOnly.startsWith('/api/tts-cache/') && req.method === 'GET') {
|
|
2885
|
+
const cacheKey = decodeURIComponent(pathOnly.slice('/api/tts-cache/'.length));
|
|
2886
|
+
try {
|
|
2887
|
+
const speech = await getSpeech();
|
|
2888
|
+
const cached = speech.ttsCacheGet(cacheKey);
|
|
2889
|
+
if (cached) {
|
|
2890
|
+
res.writeHead(200, { 'Content-Type': 'audio/wav', 'Content-Length': cached.length, 'Cache-Control': 'public, max-age=3600' });
|
|
2891
|
+
res.end(cached);
|
|
2892
|
+
} else {
|
|
2893
|
+
sendJSON(req, res, 404, { error: 'not cached' });
|
|
2894
|
+
}
|
|
2895
|
+
} catch (err) {
|
|
2896
|
+
sendJSON(req, res, 500, { error: err.message });
|
|
2897
|
+
}
|
|
2898
|
+
return;
|
|
2899
|
+
}
|
|
2900
|
+
|
|
2883
2901
|
if (pathOnly === '/api/speech-status' && req.method === 'GET') {
|
|
2884
2902
|
try {
|
|
2885
2903
|
const { getStatus } = await getSpeech();
|
|
@@ -25,28 +25,39 @@
|
|
|
25
25
|
|
|
26
26
|
if (!autoSpeakToggle || !voiceSelector) return;
|
|
27
27
|
|
|
28
|
-
// Load saved preferences
|
|
29
28
|
var savedAutoSpeak = localStorage.getItem('toolsAutoSpeak') === 'true';
|
|
30
|
-
var savedVoice = localStorage.getItem('toolsVoice') || 'default';
|
|
31
|
-
|
|
32
29
|
autoSpeakToggle.checked = savedAutoSpeak;
|
|
33
|
-
voiceSelector.value = savedVoice;
|
|
34
30
|
|
|
35
|
-
// Listen for voice list updates
|
|
36
31
|
window.addEventListener('ws-message', function(e) {
|
|
37
32
|
var data = e.detail;
|
|
38
|
-
if (data && data.type === 'voice_list')
|
|
39
|
-
updateVoiceSelector(data.voices);
|
|
40
|
-
}
|
|
33
|
+
if (data && data.type === 'voice_list') updateVoiceSelector(data.voices);
|
|
41
34
|
});
|
|
42
35
|
|
|
43
|
-
|
|
36
|
+
function trySubscribeVsManager() {
|
|
37
|
+
if (window.wsManager && window.wsManager.subscribeToVoiceList) {
|
|
38
|
+
window.wsManager.subscribeToVoiceList(updateVoiceSelector);
|
|
39
|
+
} else {
|
|
40
|
+
var BASE = window.__BASE_URL || '';
|
|
41
|
+
fetch(BASE + '/api/voices').then(function(r) { return r.json(); }).then(function(d) {
|
|
42
|
+
if (d.ok && Array.isArray(d.voices)) updateVoiceSelector(d.voices);
|
|
43
|
+
}).catch(function() {});
|
|
44
|
+
setTimeout(function() {
|
|
45
|
+
if (window.wsManager && window.wsManager.subscribeToVoiceList) {
|
|
46
|
+
window.wsManager.subscribeToVoiceList(updateVoiceSelector);
|
|
47
|
+
}
|
|
48
|
+
}, 2000);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
trySubscribeVsManager();
|
|
52
|
+
|
|
44
53
|
autoSpeakToggle.addEventListener('change', function() {
|
|
45
54
|
localStorage.setItem('toolsAutoSpeak', this.checked);
|
|
55
|
+
if (window.voiceModule) window.voiceModule.setAutoSpeak(this.checked);
|
|
46
56
|
});
|
|
47
57
|
|
|
48
58
|
voiceSelector.addEventListener('change', function() {
|
|
49
59
|
localStorage.setItem('toolsVoice', this.value);
|
|
60
|
+
if (window.voiceModule) window.voiceModule.setVoice(this.value);
|
|
50
61
|
});
|
|
51
62
|
}
|
|
52
63
|
|
|
@@ -54,17 +65,40 @@
|
|
|
54
65
|
var voiceSelector = document.getElementById('toolsVoiceSelector');
|
|
55
66
|
if (!voiceSelector || !voices || !Array.isArray(voices)) return;
|
|
56
67
|
|
|
57
|
-
var currentValue = voiceSelector.value;
|
|
58
|
-
voiceSelector.innerHTML = '
|
|
68
|
+
var currentValue = voiceSelector.value || localStorage.getItem('toolsVoice') || 'default';
|
|
69
|
+
voiceSelector.innerHTML = '';
|
|
70
|
+
|
|
71
|
+
var builtIn = voices.filter(function(v) { return !v.isCustom; });
|
|
72
|
+
var custom = voices.filter(function(v) { return v.isCustom; });
|
|
73
|
+
|
|
74
|
+
if (builtIn.length) {
|
|
75
|
+
var grp1 = document.createElement('optgroup');
|
|
76
|
+
grp1.label = 'Built-in Voices';
|
|
77
|
+
builtIn.forEach(function(voice) {
|
|
78
|
+
var opt = document.createElement('option');
|
|
79
|
+
opt.value = voice.id;
|
|
80
|
+
var parts = [];
|
|
81
|
+
if (voice.gender && voice.gender !== 'custom') parts.push(voice.gender);
|
|
82
|
+
if (voice.accent && voice.accent !== 'custom') parts.push(voice.accent);
|
|
83
|
+
opt.textContent = voice.name + (parts.length ? ' (' + parts.join(', ') + ')' : '');
|
|
84
|
+
grp1.appendChild(opt);
|
|
85
|
+
});
|
|
86
|
+
voiceSelector.appendChild(grp1);
|
|
87
|
+
}
|
|
59
88
|
|
|
60
|
-
|
|
61
|
-
var
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
89
|
+
if (custom.length) {
|
|
90
|
+
var grp2 = document.createElement('optgroup');
|
|
91
|
+
grp2.label = 'Custom Voices';
|
|
92
|
+
custom.forEach(function(voice) {
|
|
93
|
+
var opt = document.createElement('option');
|
|
94
|
+
opt.value = voice.id;
|
|
95
|
+
opt.textContent = voice.name;
|
|
96
|
+
grp2.appendChild(opt);
|
|
97
|
+
});
|
|
98
|
+
voiceSelector.appendChild(grp2);
|
|
99
|
+
}
|
|
66
100
|
|
|
67
|
-
if (
|
|
101
|
+
if (voiceSelector.querySelector('option[value="' + currentValue + '"]')) {
|
|
68
102
|
voiceSelector.value = currentValue;
|
|
69
103
|
}
|
|
70
104
|
}
|
|
@@ -194,10 +228,23 @@
|
|
|
194
228
|
popup.classList.remove('open');
|
|
195
229
|
}
|
|
196
230
|
|
|
231
|
+
function isAutoSpeakOn() {
|
|
232
|
+
var toggle = document.getElementById('toolsAutoSpeakToggle');
|
|
233
|
+
return toggle ? toggle.checked : false;
|
|
234
|
+
}
|
|
235
|
+
|
|
197
236
|
function onWsMessage(e) {
|
|
198
237
|
var data = e.detail;
|
|
199
238
|
if (!data) return;
|
|
200
239
|
|
|
240
|
+
if (data.type === 'streaming_progress' && data.block && data.block.type === 'text' && data.block.text) {
|
|
241
|
+
if (isAutoSpeakOn() && (!data.blockRole || data.blockRole === 'assistant')) {
|
|
242
|
+
if (window.voiceModule && typeof window.voiceModule.speakText === 'function') {
|
|
243
|
+
window.voiceModule.speakText(data.block.text);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
201
248
|
if (data.type === 'tools_update_started') {
|
|
202
249
|
var updateTools = data.tools || [];
|
|
203
250
|
updateTools.forEach(function(toolId) {
|
package/static/js/voice.js
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
var _lastVoiceBlockText = null;
|
|
19
19
|
var _lastVoiceBlockTime = 0;
|
|
20
20
|
var _voiceBreakNext = false;
|
|
21
|
-
var selectedVoiceId = localStorage.getItem('voice-
|
|
21
|
+
var selectedVoiceId = localStorage.getItem('gmgui-voice-selection') || 'default';
|
|
22
22
|
var ttsAudioCache = new Map();
|
|
23
23
|
var TTS_CLIENT_CACHE_MAX = 50;
|
|
24
24
|
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
function setupVoiceSelector() {
|
|
34
34
|
var selector = document.getElementById('voiceSelector');
|
|
35
35
|
if (!selector) return;
|
|
36
|
-
var saved = localStorage.getItem('voice-
|
|
36
|
+
var saved = localStorage.getItem('gmgui-voice-selection');
|
|
37
37
|
if (saved) selectedVoiceId = saved;
|
|
38
38
|
if (window.wsManager) {
|
|
39
39
|
window.wsManager.subscribeToVoiceList(function(voices) {
|
|
@@ -66,8 +66,8 @@
|
|
|
66
66
|
});
|
|
67
67
|
selector.appendChild(grp2);
|
|
68
68
|
}
|
|
69
|
-
if (
|
|
70
|
-
selector.value =
|
|
69
|
+
if (selectedVoiceId && selector.querySelector('option[value="' + selectedVoiceId + '"]')) {
|
|
70
|
+
selector.value = selectedVoiceId;
|
|
71
71
|
}
|
|
72
72
|
});
|
|
73
73
|
return;
|
|
@@ -104,15 +104,15 @@
|
|
|
104
104
|
});
|
|
105
105
|
selector.appendChild(grp2);
|
|
106
106
|
}
|
|
107
|
-
if (
|
|
108
|
-
selector.value =
|
|
107
|
+
if (selectedVoiceId && selector.querySelector('option[value="' + selectedVoiceId + '"]')) {
|
|
108
|
+
selector.value = selectedVoiceId;
|
|
109
109
|
}
|
|
110
110
|
})
|
|
111
111
|
.catch(function(err) { console.error('[Voice] Failed to load voices:', err); });
|
|
112
112
|
}
|
|
113
113
|
selector.addEventListener('change', function() {
|
|
114
114
|
selectedVoiceId = selector.value;
|
|
115
|
-
localStorage.setItem('voice-
|
|
115
|
+
localStorage.setItem('gmgui-voice-selection', selectedVoiceId);
|
|
116
116
|
sendVoiceToServer();
|
|
117
117
|
});
|
|
118
118
|
}
|
|
@@ -211,14 +211,14 @@
|
|
|
211
211
|
function setupTTSToggle() {
|
|
212
212
|
var toggle = document.getElementById('voiceTTSToggle');
|
|
213
213
|
if (toggle) {
|
|
214
|
-
var saved = localStorage.getItem('
|
|
214
|
+
var saved = localStorage.getItem('gmgui-auto-speak');
|
|
215
215
|
if (saved !== null) {
|
|
216
216
|
ttsEnabled = saved === 'true';
|
|
217
217
|
toggle.checked = ttsEnabled;
|
|
218
218
|
}
|
|
219
219
|
toggle.addEventListener('change', function() {
|
|
220
220
|
ttsEnabled = toggle.checked;
|
|
221
|
-
localStorage.setItem('
|
|
221
|
+
localStorage.setItem('gmgui-auto-speak', ttsEnabled);
|
|
222
222
|
if (!ttsEnabled) stopSpeaking();
|
|
223
223
|
});
|
|
224
224
|
}
|
|
@@ -352,6 +352,10 @@
|
|
|
352
352
|
|
|
353
353
|
function speak(text) {
|
|
354
354
|
if (!ttsEnabled) return;
|
|
355
|
+
speakDirect(text);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function speakDirect(text) {
|
|
355
359
|
var clean = text.replace(/<[^>]*>/g, '').trim();
|
|
356
360
|
if (!clean) return;
|
|
357
361
|
var parts = [];
|
|
@@ -946,10 +950,39 @@
|
|
|
946
950
|
return text.replace(/[&<>"']/g, function(c) { return map[c]; });
|
|
947
951
|
}
|
|
948
952
|
|
|
953
|
+
function getAutoSpeak() {
|
|
954
|
+
return ttsEnabled;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
function setAutoSpeak(value) {
|
|
958
|
+
ttsEnabled = Boolean(value);
|
|
959
|
+
localStorage.setItem('gmgui-auto-speak', ttsEnabled);
|
|
960
|
+
var toggle = document.getElementById('voiceTTSToggle');
|
|
961
|
+
if (toggle) toggle.checked = ttsEnabled;
|
|
962
|
+
if (!ttsEnabled) stopSpeaking();
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
function getVoice() {
|
|
966
|
+
return selectedVoiceId;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
function setVoice(voiceId) {
|
|
970
|
+
selectedVoiceId = String(voiceId);
|
|
971
|
+
localStorage.setItem('gmgui-voice-selection', selectedVoiceId);
|
|
972
|
+
var selector = document.getElementById('voiceSelector');
|
|
973
|
+
if (selector) selector.value = selectedVoiceId;
|
|
974
|
+
sendVoiceToServer();
|
|
975
|
+
}
|
|
976
|
+
|
|
949
977
|
window.voiceModule = {
|
|
950
978
|
activate: activate,
|
|
951
979
|
deactivate: deactivate,
|
|
952
|
-
handleBlock: handleVoiceBlock
|
|
980
|
+
handleBlock: handleVoiceBlock,
|
|
981
|
+
getAutoSpeak: getAutoSpeak,
|
|
982
|
+
setAutoSpeak: setAutoSpeak,
|
|
983
|
+
getVoice: getVoice,
|
|
984
|
+
setVoice: setVoice,
|
|
985
|
+
speakText: speakDirect
|
|
953
986
|
};
|
|
954
987
|
|
|
955
988
|
if (document.readyState === 'loading') {
|