agentgui 1.0.184 → 1.0.186
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/package.json +1 -1
- package/server.js +22 -14
- package/static/js/audio-recorder-processor.js +18 -0
- package/static/js/client.js +5 -1
- package/static/js/voice.js +13 -9
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -536,30 +536,30 @@ const server = http.createServer(async (req, res) => {
|
|
|
536
536
|
return;
|
|
537
537
|
}
|
|
538
538
|
|
|
539
|
-
if (
|
|
539
|
+
if (pathOnly === '/api/agents' && req.method === 'GET') {
|
|
540
540
|
sendJSON(req, res, 200, { agents: discoveredAgents });
|
|
541
541
|
return;
|
|
542
542
|
}
|
|
543
543
|
|
|
544
544
|
|
|
545
|
-
if (
|
|
545
|
+
if (pathOnly === '/api/import/claude-code' && req.method === 'GET') {
|
|
546
546
|
const result = queries.importClaudeCodeConversations();
|
|
547
547
|
sendJSON(req, res, 200, { imported: result });
|
|
548
548
|
return;
|
|
549
549
|
}
|
|
550
550
|
|
|
551
|
-
if (
|
|
551
|
+
if (pathOnly === '/api/discover/claude-code' && req.method === 'GET') {
|
|
552
552
|
const discovered = queries.discoverClaudeCodeConversations();
|
|
553
553
|
sendJSON(req, res, 200, { discovered });
|
|
554
554
|
return;
|
|
555
555
|
}
|
|
556
556
|
|
|
557
|
-
if (
|
|
557
|
+
if (pathOnly === '/api/home' && req.method === 'GET') {
|
|
558
558
|
sendJSON(req, res, 200, { home: os.homedir(), cwd: STARTUP_CWD });
|
|
559
559
|
return;
|
|
560
560
|
}
|
|
561
561
|
|
|
562
|
-
if (
|
|
562
|
+
if (pathOnly === '/api/stt' && req.method === 'POST') {
|
|
563
563
|
try {
|
|
564
564
|
const chunks = [];
|
|
565
565
|
for await (const chunk of req) chunks.push(chunk);
|
|
@@ -578,7 +578,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
578
578
|
return;
|
|
579
579
|
}
|
|
580
580
|
|
|
581
|
-
if (
|
|
581
|
+
if (pathOnly === '/api/voices' && req.method === 'GET') {
|
|
582
582
|
try {
|
|
583
583
|
const { getVoices } = await getSpeech();
|
|
584
584
|
sendJSON(req, res, 200, { ok: true, voices: getVoices() });
|
|
@@ -588,7 +588,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
588
588
|
return;
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
-
if (
|
|
591
|
+
if (pathOnly === '/api/tts' && req.method === 'POST') {
|
|
592
592
|
try {
|
|
593
593
|
const body = await parseBody(req);
|
|
594
594
|
const text = body.text || '';
|
|
@@ -615,7 +615,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
615
615
|
return;
|
|
616
616
|
}
|
|
617
617
|
|
|
618
|
-
if (
|
|
618
|
+
if (pathOnly === '/api/tts-stream' && req.method === 'POST') {
|
|
619
619
|
try {
|
|
620
620
|
const body = await parseBody(req);
|
|
621
621
|
const text = body.text || '';
|
|
@@ -653,7 +653,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
653
653
|
return;
|
|
654
654
|
}
|
|
655
655
|
|
|
656
|
-
if (
|
|
656
|
+
if (pathOnly === '/api/speech-status' && req.method === 'GET') {
|
|
657
657
|
try {
|
|
658
658
|
const { getStatus } = await getSpeech();
|
|
659
659
|
sendJSON(req, res, 200, getStatus());
|
|
@@ -663,7 +663,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
663
663
|
return;
|
|
664
664
|
}
|
|
665
665
|
|
|
666
|
-
if (
|
|
666
|
+
if (pathOnly === '/api/folders' && req.method === 'POST') {
|
|
667
667
|
const body = await parseBody(req);
|
|
668
668
|
const folderPath = body.path || STARTUP_CWD;
|
|
669
669
|
try {
|
|
@@ -1172,10 +1172,18 @@ wss.on('connection', (ws, req) => {
|
|
|
1172
1172
|
timestamp: Date.now()
|
|
1173
1173
|
}));
|
|
1174
1174
|
} else if (data.type === 'unsubscribe') {
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1175
|
+
if (data.sessionId) {
|
|
1176
|
+
ws.subscriptions.delete(data.sessionId);
|
|
1177
|
+
const idx = subscriptionIndex.get(data.sessionId);
|
|
1178
|
+
if (idx) { idx.delete(ws); if (idx.size === 0) subscriptionIndex.delete(data.sessionId); }
|
|
1179
|
+
}
|
|
1180
|
+
if (data.conversationId) {
|
|
1181
|
+
const key = `conv-${data.conversationId}`;
|
|
1182
|
+
ws.subscriptions.delete(key);
|
|
1183
|
+
const idx = subscriptionIndex.get(key);
|
|
1184
|
+
if (idx) { idx.delete(ws); if (idx.size === 0) subscriptionIndex.delete(key); }
|
|
1185
|
+
}
|
|
1186
|
+
debugLog(`[WebSocket] Client ${ws.clientId} unsubscribed from ${data.sessionId || data.conversationId}`);
|
|
1179
1187
|
} else if (data.type === 'get_subscriptions') {
|
|
1180
1188
|
ws.send(JSON.stringify({
|
|
1181
1189
|
type: 'subscriptions',
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class RecorderProcessor extends AudioWorkletProcessor {
|
|
2
|
+
constructor() {
|
|
3
|
+
super();
|
|
4
|
+
this._stopped = false;
|
|
5
|
+
this.port.onmessage = (e) => {
|
|
6
|
+
if (e.data === 'stop') this._stopped = true;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
process(inputs) {
|
|
10
|
+
if (this._stopped) return false;
|
|
11
|
+
const input = inputs[0];
|
|
12
|
+
if (input && input[0] && input[0].length > 0) {
|
|
13
|
+
this.port.postMessage(new Float32Array(input[0]));
|
|
14
|
+
}
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
registerProcessor('recorder-processor', RecorderProcessor);
|
package/static/js/client.js
CHANGED
|
@@ -1335,7 +1335,11 @@ class AgentGUIClient {
|
|
|
1335
1335
|
try {
|
|
1336
1336
|
this.cacheCurrentConversation();
|
|
1337
1337
|
this.stopChunkPolling();
|
|
1338
|
-
|
|
1338
|
+
var prevId = this.state.currentConversation?.id;
|
|
1339
|
+
if (prevId && prevId !== conversationId) {
|
|
1340
|
+
if (this.wsManager.isConnected && !this.state.streamingConversations.has(prevId)) {
|
|
1341
|
+
this.wsManager.sendMessage({ type: 'unsubscribe', conversationId: prevId });
|
|
1342
|
+
}
|
|
1339
1343
|
this.state.currentSession = null;
|
|
1340
1344
|
}
|
|
1341
1345
|
|
package/static/js/voice.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
var currentAudio = null;
|
|
10
10
|
var mediaStream = null;
|
|
11
11
|
var audioContext = null;
|
|
12
|
-
var
|
|
12
|
+
var workletNode = null;
|
|
13
13
|
var recordedChunks = [];
|
|
14
14
|
var TARGET_SAMPLE_RATE = 16000;
|
|
15
15
|
var spokenChunks = new Set();
|
|
@@ -208,14 +208,13 @@
|
|
|
208
208
|
mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
209
209
|
audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
210
210
|
var source = audioContext.createMediaStreamSource(mediaStream);
|
|
211
|
-
scriptNode = audioContext.createScriptProcessor(4096, 1, 1);
|
|
212
211
|
recordedChunks = [];
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
212
|
+
await audioContext.audioWorklet.addModule(BASE + '/js/audio-recorder-processor.js');
|
|
213
|
+
workletNode = new AudioWorkletNode(audioContext, 'recorder-processor');
|
|
214
|
+
workletNode.port.onmessage = function(e) {
|
|
215
|
+
recordedChunks.push(e.data);
|
|
216
216
|
};
|
|
217
|
-
source.connect(
|
|
218
|
-
scriptNode.connect(audioContext.destination);
|
|
217
|
+
source.connect(workletNode);
|
|
219
218
|
isRecording = true;
|
|
220
219
|
var micBtn = document.getElementById('voiceMicBtn');
|
|
221
220
|
if (micBtn) micBtn.classList.add('recording');
|
|
@@ -231,7 +230,7 @@
|
|
|
231
230
|
var micBtn = document.getElementById('voiceMicBtn');
|
|
232
231
|
if (micBtn) micBtn.classList.remove('recording');
|
|
233
232
|
var el = document.getElementById('voiceTranscript');
|
|
234
|
-
if (
|
|
233
|
+
if (workletNode) { workletNode.port.postMessage('stop'); workletNode.disconnect(); workletNode = null; }
|
|
235
234
|
if (mediaStream) {
|
|
236
235
|
mediaStream.getTracks().forEach(function(t) { t.stop(); });
|
|
237
236
|
mediaStream = null;
|
|
@@ -321,6 +320,7 @@
|
|
|
321
320
|
var TTS_MAX_FAILURES = 3;
|
|
322
321
|
var ttsDisabledUntilReset = false;
|
|
323
322
|
var streamingSupported = true;
|
|
323
|
+
var streamingFailedAt = 0;
|
|
324
324
|
|
|
325
325
|
function playNextChunk() {
|
|
326
326
|
if (audioChunkQueue.length === 0) {
|
|
@@ -392,7 +392,8 @@
|
|
|
392
392
|
}
|
|
393
393
|
|
|
394
394
|
function tryStreaming() {
|
|
395
|
-
if (!streamingSupported) { tryNonStreaming(text); return; }
|
|
395
|
+
if (!streamingSupported && (Date.now() - streamingFailedAt < 30000)) { tryNonStreaming(text); return; }
|
|
396
|
+
streamingSupported = true;
|
|
396
397
|
fetch(BASE + '/api/tts-stream', {
|
|
397
398
|
method: 'POST',
|
|
398
399
|
headers: { 'Content-Type': 'application/json' },
|
|
@@ -400,6 +401,7 @@
|
|
|
400
401
|
}).then(function(resp) {
|
|
401
402
|
if (!resp.ok) {
|
|
402
403
|
streamingSupported = false;
|
|
404
|
+
streamingFailedAt = Date.now();
|
|
403
405
|
throw new Error('TTS stream failed: ' + resp.status);
|
|
404
406
|
}
|
|
405
407
|
var reader = resp.body.getReader();
|
|
@@ -583,9 +585,11 @@
|
|
|
583
585
|
}
|
|
584
586
|
if (!voiceActive) return;
|
|
585
587
|
if (data.type === 'streaming_progress' && data.block) {
|
|
588
|
+
if (data.conversationId && data.conversationId !== currentConversationId) return;
|
|
586
589
|
handleVoiceBlock(data.block, true);
|
|
587
590
|
}
|
|
588
591
|
if (data.type === 'streaming_start') {
|
|
592
|
+
if (data.conversationId && data.conversationId !== currentConversationId) return;
|
|
589
593
|
spokenChunks = new Set();
|
|
590
594
|
}
|
|
591
595
|
});
|