agentgui 1.0.506 → 1.0.508

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.
@@ -254,23 +254,74 @@ export async function checkForUpdates(toolId) {
254
254
 
255
255
  const spawnBunxProc = (pkg, onProgress) => new Promise((resolve) => {
256
256
  const cmd = isWindows ? 'bunx.cmd' : 'bunx';
257
- const proc = spawn(cmd, [pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows });
258
257
  let completed = false, stderr = '', stdout = '';
259
- const timer = setTimeout(() => { if (!completed) { completed = true; try { proc.kill('SIGKILL'); } catch (_) {} resolve({ success: false, error: 'Timeout (5min)' }); }}, 300000);
260
- proc.stdout.on('data', (d) => { stdout += d.toString(); if (onProgress) onProgress({ type: 'progress', data: d.toString() }); });
261
- proc.stderr.on('data', (d) => { stderr += d.toString(); if (onProgress) onProgress({ type: 'error', data: d.toString() }); });
258
+ let lastDataTime = Date.now();
259
+ let proc;
260
+
261
+ try {
262
+ proc = spawn(cmd, [pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows });
263
+ } catch (err) {
264
+ return resolve({ success: false, error: `Failed to spawn bunx: ${err.message}` });
265
+ }
266
+
267
+ if (!proc) {
268
+ return resolve({ success: false, error: 'Failed to spawn bunx process' });
269
+ }
270
+
271
+ const timer = setTimeout(() => {
272
+ if (!completed) {
273
+ completed = true;
274
+ try { proc.kill('SIGKILL'); } catch (_) {}
275
+ resolve({ success: false, error: 'Timeout (5min)' });
276
+ }
277
+ }, 300000);
278
+
279
+ const heartbeatTimer = setInterval(() => {
280
+ if (completed) { clearInterval(heartbeatTimer); return; }
281
+ const timeSinceLastData = Date.now() - lastDataTime;
282
+ if (timeSinceLastData > 30000) {
283
+ console.warn(`[tool-manager] No output from bunx ${pkg} for ${timeSinceLastData}ms - process may be hung`);
284
+ }
285
+ }, 30000);
286
+
287
+ const onData = (d) => {
288
+ lastDataTime = Date.now();
289
+ if (onProgress) onProgress({ type: 'progress', data: d.toString() });
290
+ };
291
+
292
+ if (proc.stdout) proc.stdout.on('data', (d) => { stdout += d.toString(); onData(d); });
293
+ if (proc.stderr) proc.stderr.on('data', (d) => { stderr += d.toString(); onData(d); });
294
+
262
295
  proc.on('close', (code) => {
263
296
  clearTimeout(timer);
297
+ clearInterval(heartbeatTimer);
264
298
  if (completed) return;
265
299
  completed = true;
266
300
  const output = stdout + stderr;
267
- if (code === 0 || output.includes('upgraded') || output.includes('registered') || output.includes('Hooks registered')) {
301
+ const successPatterns = [
302
+ code === 0,
303
+ output.includes('upgraded'),
304
+ output.includes('registered'),
305
+ output.includes('Hooks registered'),
306
+ output.includes('successfully'),
307
+ output.includes('Done'),
308
+ code === 0 && !output.includes('error')
309
+ ];
310
+ if (successPatterns.some(p => p)) {
268
311
  resolve({ success: true, error: null });
269
312
  } else {
270
313
  resolve({ success: false, error: output.substring(0, 1000) || 'Failed' });
271
314
  }
272
315
  });
273
- proc.on('error', (err) => { clearTimeout(timer); if (!completed) { completed = true; resolve({ success: false, error: err.message }); }});
316
+
317
+ proc.on('error', (err) => {
318
+ clearTimeout(timer);
319
+ clearInterval(heartbeatTimer);
320
+ if (!completed) {
321
+ completed = true;
322
+ resolve({ success: false, error: `Process error: ${err.message}` });
323
+ }
324
+ });
274
325
  });
275
326
 
276
327
  export async function install(toolId, onProgress) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.506",
3
+ "version": "1.0.508",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -1844,11 +1844,27 @@ const server = http.createServer(async (req, res) => {
1844
1844
  }
1845
1845
  queries.updateToolStatus(toolId, { status: 'installing' });
1846
1846
  sendJSON(req, res, 200, { success: true, installing: true, estimatedTime: 60000 });
1847
+
1848
+ let installCompleted = false;
1849
+ const installTimeout = setTimeout(() => {
1850
+ if (!installCompleted) {
1851
+ installCompleted = true;
1852
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Install timeout after 6 minutes' });
1853
+ if (wsOptimizer && wsOptimizer.broadcast) {
1854
+ wsOptimizer.broadcast({ type: 'tool_install_failed', toolId, data: { success: false, error: 'Install timeout after 6 minutes' } });
1855
+ }
1856
+ queries.addToolInstallHistory(toolId, 'install', 'failed', 'Install timeout after 6 minutes');
1857
+ }
1858
+ }, 360000);
1859
+
1847
1860
  toolManager.install(toolId, (msg) => {
1848
1861
  if (wsOptimizer && wsOptimizer.broadcast) {
1849
1862
  wsOptimizer.broadcast({ type: 'tool_install_progress', toolId, data: msg });
1850
1863
  }
1851
1864
  }).then((result) => {
1865
+ clearTimeout(installTimeout);
1866
+ if (installCompleted) return;
1867
+ installCompleted = true;
1852
1868
  if (result.success) {
1853
1869
  queries.updateToolStatus(toolId, { status: 'installed', version: result.version, installed_at: Date.now() });
1854
1870
  if (wsOptimizer && wsOptimizer.broadcast) {
@@ -1862,6 +1878,16 @@ const server = http.createServer(async (req, res) => {
1862
1878
  }
1863
1879
  queries.addToolInstallHistory(toolId, 'install', 'failed', result.error);
1864
1880
  }
1881
+ }).catch((err) => {
1882
+ clearTimeout(installTimeout);
1883
+ if (installCompleted) return;
1884
+ installCompleted = true;
1885
+ const error = err?.message || 'Unknown error';
1886
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
1887
+ if (wsOptimizer && wsOptimizer.broadcast) {
1888
+ wsOptimizer.broadcast({ type: 'tool_install_failed', toolId, data: { success: false, error } });
1889
+ }
1890
+ queries.addToolInstallHistory(toolId, 'install', 'failed', error);
1865
1891
  });
1866
1892
  return;
1867
1893
  }
@@ -1881,11 +1907,27 @@ const server = http.createServer(async (req, res) => {
1881
1907
  }
1882
1908
  queries.updateToolStatus(toolId, { status: 'updating' });
1883
1909
  sendJSON(req, res, 200, { success: true, updating: true });
1910
+
1911
+ let updateCompleted = false;
1912
+ const updateTimeout = setTimeout(() => {
1913
+ if (!updateCompleted) {
1914
+ updateCompleted = true;
1915
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: 'Update timeout after 6 minutes' });
1916
+ if (wsOptimizer && wsOptimizer.broadcast) {
1917
+ wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: { success: false, error: 'Update timeout after 6 minutes' } });
1918
+ }
1919
+ queries.addToolInstallHistory(toolId, 'update', 'failed', 'Update timeout after 6 minutes');
1920
+ }
1921
+ }, 360000);
1922
+
1884
1923
  toolManager.update(toolId, body.targetVersion, (msg) => {
1885
1924
  if (wsOptimizer && wsOptimizer.broadcast) {
1886
1925
  wsOptimizer.broadcast({ type: 'tool_update_progress', toolId, data: msg });
1887
1926
  }
1888
1927
  }).then((result) => {
1928
+ clearTimeout(updateTimeout);
1929
+ if (updateCompleted) return;
1930
+ updateCompleted = true;
1889
1931
  if (result.success) {
1890
1932
  queries.updateToolStatus(toolId, { status: 'installed', version: result.version, installed_at: Date.now() });
1891
1933
  if (wsOptimizer && wsOptimizer.broadcast) {
@@ -1899,6 +1941,16 @@ const server = http.createServer(async (req, res) => {
1899
1941
  }
1900
1942
  queries.addToolInstallHistory(toolId, 'update', 'failed', result.error);
1901
1943
  }
1944
+ }).catch((err) => {
1945
+ clearTimeout(updateTimeout);
1946
+ if (updateCompleted) return;
1947
+ updateCompleted = true;
1948
+ const error = err?.message || 'Unknown error';
1949
+ queries.updateToolStatus(toolId, { status: 'failed', error_message: error });
1950
+ if (wsOptimizer && wsOptimizer.broadcast) {
1951
+ wsOptimizer.broadcast({ type: 'tool_update_failed', toolId, data: { success: false, error } });
1952
+ }
1953
+ queries.addToolInstallHistory(toolId, 'update', 'failed', error);
1902
1954
  });
1903
1955
  return;
1904
1956
  }
package/static/index.html CHANGED
@@ -3231,6 +3231,14 @@
3231
3231
  aria-label="Message input"
3232
3232
  rows="1"
3233
3233
  ></textarea>
3234
+ <button class="voice-mic-btn" id="chatMicBtn" title="Record voice input" aria-label="Voice input">
3235
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
3236
+ <path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"/>
3237
+ <path d="M19 10v2a7 7 0 0 1-14 0v-2"/>
3238
+ <line x1="12" y1="19" x2="12" y2="23"/>
3239
+ <line x1="8" y1="23" x2="16" y2="23"/>
3240
+ </svg>
3241
+ </button>
3234
3242
  <button class="inject-btn" id="injectBtn" title="Inject instructions into running agent" aria-label="Inject instructions">
3235
3243
  <svg viewBox="0 0 24 24" fill="currentColor" width="18" height="18">
3236
3244
  <path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z"/>
@@ -3288,6 +3296,7 @@
3288
3296
  <script defer src="/gm/js/terminal.js"></script>
3289
3297
  <script defer src="/gm/js/script-runner.js"></script>
3290
3298
  <script defer src="/gm/js/tools-manager.js"></script>
3299
+ <script defer src="/gm/js/stt-handler.js"></script>
3291
3300
  <script defer src="/gm/js/client.js"></script>
3292
3301
  <script type="module" src="/gm/js/voice.js"></script>
3293
3302
  <script defer src="/gm/js/features.js"></script>
@@ -394,6 +394,8 @@ class AgentGUIClient {
394
394
  this.ui.sendButton.addEventListener('click', () => this.startExecution());
395
395
  }
396
396
 
397
+ this.setupChatMicButton();
398
+
397
399
  this.ui.stopButton = document.getElementById('stopBtn');
398
400
  this.ui.injectButton = document.getElementById('injectBtn');
399
401
 
@@ -486,6 +488,52 @@ class AgentGUIClient {
486
488
  });
487
489
  }
488
490
 
491
+ setupChatMicButton() {
492
+ const chatMicBtn = document.getElementById('chatMicBtn');
493
+ if (!chatMicBtn) return;
494
+
495
+ let isRecording = false;
496
+
497
+ chatMicBtn.addEventListener('mousedown', async (e) => {
498
+ e.preventDefault();
499
+ if (isRecording) return;
500
+ isRecording = true;
501
+ chatMicBtn.classList.add('recording');
502
+ const result = await window.STTHandler.startRecording();
503
+ if (!result.success) {
504
+ isRecording = false;
505
+ chatMicBtn.classList.remove('recording');
506
+ alert('Microphone access denied: ' + result.error);
507
+ }
508
+ });
509
+
510
+ chatMicBtn.addEventListener('mouseup', async (e) => {
511
+ e.preventDefault();
512
+ if (!isRecording) return;
513
+ isRecording = false;
514
+ chatMicBtn.classList.remove('recording');
515
+ const result = await window.STTHandler.stopRecording();
516
+ if (result.success) {
517
+ if (this.ui.messageInput) {
518
+ this.ui.messageInput.value = result.text;
519
+ }
520
+ } else {
521
+ alert('Transcription failed: ' + result.error);
522
+ }
523
+ });
524
+
525
+ chatMicBtn.addEventListener('mouseleave', async (e) => {
526
+ if (isRecording) {
527
+ isRecording = false;
528
+ chatMicBtn.classList.remove('recording');
529
+ const result = await window.STTHandler.stopRecording();
530
+ if (result.success && this.ui.messageInput) {
531
+ this.ui.messageInput.value = result.text;
532
+ }
533
+ }
534
+ });
535
+ }
536
+
489
537
  /**
490
538
  * Connect to WebSocket
491
539
  */
@@ -0,0 +1,138 @@
1
+ (function() {
2
+ var BASE = window.__BASE_URL || '';
3
+ var isRecording = false;
4
+ var mediaStream = null;
5
+ var audioContext = null;
6
+ var workletNode = null;
7
+ var recordedChunks = [];
8
+ var TARGET_SAMPLE_RATE = 16000;
9
+
10
+ window.STTHandler = {
11
+ isRecording: function() { return isRecording; },
12
+
13
+ startRecording: async function() {
14
+ if (isRecording) return;
15
+ try {
16
+ mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
17
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
18
+ var source = audioContext.createMediaStreamSource(mediaStream);
19
+ recordedChunks = [];
20
+ await audioContext.audioWorklet.addModule(BASE + '/js/audio-recorder-processor.js');
21
+ workletNode = new AudioWorkletNode(audioContext, 'recorder-processor');
22
+ workletNode.port.onmessage = function(e) {
23
+ recordedChunks.push(e.data);
24
+ };
25
+ source.connect(workletNode);
26
+ isRecording = true;
27
+ return { success: true };
28
+ } catch (e) {
29
+ isRecording = false;
30
+ return { success: false, error: e.message };
31
+ }
32
+ },
33
+
34
+ stopRecording: async function() {
35
+ if (!isRecording) return { success: false, error: 'Not recording' };
36
+ isRecording = false;
37
+
38
+ if (workletNode) {
39
+ workletNode.port.postMessage('stop');
40
+ workletNode.disconnect();
41
+ workletNode = null;
42
+ }
43
+
44
+ if (mediaStream) {
45
+ mediaStream.getTracks().forEach(function(t) { t.stop(); });
46
+ mediaStream = null;
47
+ }
48
+
49
+ var sourceSampleRate = audioContext ? audioContext.sampleRate : 48000;
50
+ if (audioContext) {
51
+ audioContext.close().catch(function() {});
52
+ audioContext = null;
53
+ }
54
+
55
+ if (recordedChunks.length === 0) {
56
+ return { success: false, error: 'No audio recorded' };
57
+ }
58
+
59
+ var totalLen = 0;
60
+ for (var i = 0; i < recordedChunks.length; i++) totalLen += recordedChunks[i].length;
61
+ var merged = new Float32Array(totalLen);
62
+ var offset = 0;
63
+ for (var j = 0; j < recordedChunks.length; j++) {
64
+ merged.set(recordedChunks[j], offset);
65
+ offset += recordedChunks[j].length;
66
+ }
67
+ recordedChunks = [];
68
+
69
+ var resampled = resampleBuffer(merged, sourceSampleRate, TARGET_SAMPLE_RATE);
70
+ var wavBuffer = encodeWav(resampled, TARGET_SAMPLE_RATE);
71
+
72
+ try {
73
+ var resp = await fetch(BASE + '/api/stt', {
74
+ method: 'POST',
75
+ headers: { 'Content-Type': 'audio/wav' },
76
+ body: wavBuffer
77
+ });
78
+ var data = await resp.json();
79
+ if (data.text) {
80
+ return { success: true, text: data.text };
81
+ } else if (data.error) {
82
+ return { success: false, error: data.error };
83
+ } else {
84
+ return { success: false, error: 'No transcription returned' };
85
+ }
86
+ } catch (e) {
87
+ return { success: false, error: 'Transcription failed: ' + e.message };
88
+ }
89
+ }
90
+ };
91
+
92
+ function resampleBuffer(inputBuffer, fromRate, toRate) {
93
+ if (fromRate === toRate) return inputBuffer;
94
+ var ratio = fromRate / toRate;
95
+ var newLen = Math.round(inputBuffer.length / ratio);
96
+ var result = new Float32Array(newLen);
97
+ for (var i = 0; i < newLen; i++) {
98
+ var srcIdx = i * ratio;
99
+ var lo = Math.floor(srcIdx);
100
+ var hi = Math.min(lo + 1, inputBuffer.length - 1);
101
+ var frac = srcIdx - lo;
102
+ result[i] = inputBuffer[lo] * (1 - frac) + inputBuffer[hi] * frac;
103
+ }
104
+ return result;
105
+ }
106
+
107
+ function encodeWav(float32Audio, sampleRate) {
108
+ var numSamples = float32Audio.length;
109
+ var bytesPerSample = 2;
110
+ var dataSize = numSamples * bytesPerSample;
111
+ var buffer = new ArrayBuffer(44 + dataSize);
112
+ var view = new DataView(buffer);
113
+
114
+ function writeStr(off, str) {
115
+ for (var i = 0; i < str.length; i++) view.setUint8(off + i, str.charCodeAt(i));
116
+ }
117
+
118
+ writeStr(0, 'RIFF');
119
+ view.setUint32(4, 36 + dataSize, true);
120
+ writeStr(8, 'WAVE');
121
+ writeStr(12, 'fmt ');
122
+ view.setUint32(16, 16, true);
123
+ view.setUint16(20, 1, true);
124
+ view.setUint16(22, 1, true);
125
+ view.setUint32(24, sampleRate, true);
126
+ view.setUint32(28, sampleRate * bytesPerSample, true);
127
+ view.setUint16(32, bytesPerSample, true);
128
+ view.setUint16(34, 16, true);
129
+ writeStr(36, 'data');
130
+ view.setUint32(40, dataSize, true);
131
+
132
+ for (var i = 0; i < numSamples; i++) {
133
+ var s = Math.max(-1, Math.min(1, float32Audio[i]));
134
+ view.setInt16(44 + i * 2, s < 0 ? s * 32768 : s * 32767, true);
135
+ }
136
+ return buffer;
137
+ }
138
+ })();
@@ -271,49 +271,6 @@
271
271
  }
272
272
  }
273
273
 
274
- function resampleBuffer(inputBuffer, fromRate, toRate) {
275
- if (fromRate === toRate) return inputBuffer;
276
- var ratio = fromRate / toRate;
277
- var newLen = Math.round(inputBuffer.length / ratio);
278
- var result = new Float32Array(newLen);
279
- for (var i = 0; i < newLen; i++) {
280
- var srcIdx = i * ratio;
281
- var lo = Math.floor(srcIdx);
282
- var hi = Math.min(lo + 1, inputBuffer.length - 1);
283
- var frac = srcIdx - lo;
284
- result[i] = inputBuffer[lo] * (1 - frac) + inputBuffer[hi] * frac;
285
- }
286
- return result;
287
- }
288
-
289
- function encodeWav(float32Audio, sampleRate) {
290
- var numSamples = float32Audio.length;
291
- var bytesPerSample = 2;
292
- var dataSize = numSamples * bytesPerSample;
293
- var buffer = new ArrayBuffer(44 + dataSize);
294
- var view = new DataView(buffer);
295
- function writeStr(off, str) {
296
- for (var i = 0; i < str.length; i++) view.setUint8(off + i, str.charCodeAt(i));
297
- }
298
- writeStr(0, 'RIFF');
299
- view.setUint32(4, 36 + dataSize, true);
300
- writeStr(8, 'WAVE');
301
- writeStr(12, 'fmt ');
302
- view.setUint32(16, 16, true);
303
- view.setUint16(20, 1, true);
304
- view.setUint16(22, 1, true);
305
- view.setUint32(24, sampleRate, true);
306
- view.setUint32(28, sampleRate * bytesPerSample, true);
307
- view.setUint16(32, bytesPerSample, true);
308
- view.setUint16(34, 16, true);
309
- writeStr(36, 'data');
310
- view.setUint32(40, dataSize, true);
311
- for (var i = 0; i < numSamples; i++) {
312
- var s = Math.max(-1, Math.min(1, float32Audio[i]));
313
- view.setInt16(44 + i * 2, s < 0 ? s * 32768 : s * 32767, true);
314
- }
315
- return buffer;
316
- }
317
274
 
318
275
  async function startRecording() {
319
276
  if (isRecording) return;
@@ -326,23 +283,13 @@
326
283
  el.setAttribute('data-final', '');
327
284
  }
328
285
  }
329
- try {
330
- mediaStream = await navigator.mediaDevices.getUserMedia({ audio: true });
331
- audioContext = new (window.AudioContext || window.webkitAudioContext)();
332
- var source = audioContext.createMediaStreamSource(mediaStream);
333
- recordedChunks = [];
334
- await audioContext.audioWorklet.addModule(BASE + '/js/audio-recorder-processor.js');
335
- workletNode = new AudioWorkletNode(audioContext, 'recorder-processor');
336
- workletNode.port.onmessage = function(e) {
337
- recordedChunks.push(e.data);
338
- };
339
- source.connect(workletNode);
286
+ var result = await window.STTHandler.startRecording();
287
+ if (result.success) {
340
288
  isRecording = true;
341
289
  var micBtn = document.getElementById('voiceMicBtn');
342
290
  if (micBtn) micBtn.classList.add('recording');
343
- } catch (e) {
344
- isRecording = false;
345
- if (el) el.textContent = 'Mic access denied or unavailable: ' + e.message;
291
+ } else {
292
+ if (el) el.textContent = 'Mic access denied: ' + result.error;
346
293
  }
347
294
  }
348
295
 
@@ -352,24 +299,7 @@
352
299
  var micBtn = document.getElementById('voiceMicBtn');
353
300
  if (micBtn) micBtn.classList.remove('recording');
354
301
  var el = document.getElementById('voiceTranscript');
355
- if (workletNode) { workletNode.port.postMessage('stop'); workletNode.disconnect(); workletNode = null; }
356
- if (mediaStream) {
357
- mediaStream.getTracks().forEach(function(t) { t.stop(); });
358
- mediaStream = null;
359
- }
360
- var sourceSampleRate = audioContext ? audioContext.sampleRate : 48000;
361
- if (audioContext) { audioContext.close().catch(function() {}); audioContext = null; }
362
- if (recordedChunks.length === 0) return;
363
- var totalLen = 0;
364
- for (var i = 0; i < recordedChunks.length; i++) totalLen += recordedChunks[i].length;
365
- var merged = new Float32Array(totalLen);
366
- var offset = 0;
367
- for (var j = 0; j < recordedChunks.length; j++) {
368
- merged.set(recordedChunks[j], offset);
369
- offset += recordedChunks[j].length;
370
- }
371
- recordedChunks = [];
372
- var resampled = resampleBuffer(merged, sourceSampleRate, TARGET_SAMPLE_RATE);
302
+
373
303
  if (el) {
374
304
  if (el.value !== undefined) {
375
305
  el.value = 'Transcribing...';
@@ -377,46 +307,23 @@
377
307
  el.textContent = 'Transcribing...';
378
308
  }
379
309
  }
380
- try {
381
- var wavBuffer = encodeWav(resampled, TARGET_SAMPLE_RATE);
382
- var resp = await fetch(BASE + '/api/stt', {
383
- method: 'POST',
384
- headers: { 'Content-Type': 'audio/wav' },
385
- body: wavBuffer
386
- });
387
- var data = await resp.json();
388
- if (data.text) {
389
- if (el) {
390
- if (el.value !== undefined) {
391
- el.value = data.text;
392
- } else {
393
- el.textContent = data.text;
394
- el.setAttribute('data-final', data.text);
395
- }
396
- }
397
- } else if (data.error) {
398
- if (el) {
399
- if (el.value !== undefined) {
400
- el.value = 'Error: ' + data.error;
401
- } else {
402
- el.textContent = 'Error: ' + data.error;
403
- }
404
- }
405
- } else {
406
- if (el) {
407
- if (el.value !== undefined) {
408
- el.value = '';
409
- } else {
410
- el.textContent = '';
411
- }
310
+
311
+ var result = await window.STTHandler.stopRecording();
312
+ if (result.success) {
313
+ if (el) {
314
+ if (el.value !== undefined) {
315
+ el.value = result.text;
316
+ } else {
317
+ el.textContent = result.text;
318
+ el.setAttribute('data-final', result.text);
412
319
  }
413
320
  }
414
- } catch (e) {
321
+ } else {
415
322
  if (el) {
416
323
  if (el.value !== undefined) {
417
- el.value = 'Transcription failed: ' + e.message;
324
+ el.value = 'Error: ' + result.error;
418
325
  } else {
419
- el.textContent = 'Transcription failed: ' + e.message;
326
+ el.textContent = 'Error: ' + result.error;
420
327
  }
421
328
  }
422
329
  }