@longline/aqua-ui 1.0.196 → 1.0.197

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.
Files changed (66) hide show
  1. package/aquaui-sprites.svg +1 -1
  2. package/containers/List/ListRow.d.ts +5 -0
  3. package/containers/List/ListRow.js +3 -3
  4. package/containers/Openable/Openable.d.ts +26 -0
  5. package/containers/Openable/Openable.js +76 -0
  6. package/containers/Openable/index.d.ts +1 -0
  7. package/containers/Openable/index.js +1 -0
  8. package/controls/Fab/Fab.d.ts +7 -1
  9. package/controls/Fab/Fab.js +21 -4
  10. package/controls/SpeechRecognizer/EditorRegistry.d.ts +8 -0
  11. package/controls/SpeechRecognizer/EditorRegistry.js +24 -0
  12. package/controls/SpeechRecognizer/SpeechRecognizer.d.ts +3 -0
  13. package/controls/SpeechRecognizer/SpeechRecognizer.js +118 -0
  14. package/controls/SpeechRecognizer/index.d.ts +1 -0
  15. package/controls/SpeechRecognizer/index.js +1 -0
  16. package/formatters/CountryFormatter/Countries.js +0 -1
  17. package/hooks/useAssemblyAIRecorder/index.d.ts +1 -0
  18. package/hooks/useAssemblyAIRecorder/index.js +1 -0
  19. package/hooks/useAssemblyAIRecorder/stories/AssemblyAudioInput.d.ts +6 -0
  20. package/hooks/useAssemblyAIRecorder/stories/AssemblyAudioInput.js +17 -0
  21. package/hooks/useAssemblyAIRecorder/useAssemblyAIRecorder.d.ts +23 -0
  22. package/hooks/useAssemblyAIRecorder/useAssemblyAIRecorder.js +283 -0
  23. package/hooks/useOpenAIRecorder/index.d.ts +1 -0
  24. package/hooks/useOpenAIRecorder/index.js +1 -0
  25. package/hooks/useOpenAIRecorder/old/AudioInputNoHook.d.ts +8 -0
  26. package/hooks/useOpenAIRecorder/old/AudioInputNoHook.js +192 -0
  27. package/hooks/useOpenAIRecorder/old/AudioInputStandardMedia.d.ts +5 -0
  28. package/hooks/useOpenAIRecorder/old/AudioInputStandardMedia.js +170 -0
  29. package/hooks/useOpenAIRecorder/stories/OpenAIAudioInput.d.ts +8 -0
  30. package/hooks/useOpenAIRecorder/stories/OpenAIAudioInput.js +17 -0
  31. package/hooks/useOpenAIRecorder/useOpenAIRecorder.d.ts +22 -0
  32. package/hooks/useOpenAIRecorder/useOpenAIRecorder.js +223 -0
  33. package/hooks/useOpenAIStream/index.d.ts +1 -0
  34. package/hooks/useOpenAIStream/index.js +1 -0
  35. package/hooks/useOpenAIStream/stories/OpenAIStreamInput.d.ts +23 -0
  36. package/hooks/useOpenAIStream/stories/OpenAIStreamInput.js +66 -0
  37. package/hooks/useOpenAIStream/useOpenAIStream.d.ts +14 -0
  38. package/hooks/useOpenAIStream/useOpenAIStream.js +159 -0
  39. package/inputs/Editor/Editor.d.ts +8 -2
  40. package/inputs/Editor/Editor.js +25 -22
  41. package/inputs/Editor/buttons/CodeBlockButton.js +1 -1
  42. package/inputs/Editor/buttons/OpenAIButton.d.ts +25 -0
  43. package/inputs/Editor/buttons/OpenAIButton.js +208 -0
  44. package/inputs/Editor/buttons/OpenAIMenu.d.ts +9 -0
  45. package/inputs/Editor/buttons/OpenAIMenu.js +42 -0
  46. package/inputs/Editor/buttons/SpeechButton.d.ts +8 -0
  47. package/inputs/Editor/buttons/SpeechButton.js +31 -0
  48. package/inputs/Editor/extensions/MarkElement.d.ts +9 -0
  49. package/inputs/Editor/extensions/MarkElement.js +35 -0
  50. package/inputs/Editor/extensions/StreamNode.d.ts +3 -0
  51. package/inputs/Editor/extensions/StreamNode.js +33 -0
  52. package/inputs/Editor/menu/MenuBar.d.ts +6 -3
  53. package/inputs/Editor/menu/MenuBar.js +9 -11
  54. package/inputs/Editor/menu/MenuButton.d.ts +7 -1
  55. package/inputs/Editor/menu/MenuButton.js +4 -5
  56. package/package.json +10 -3
  57. package/svg/editor/index.d.ts +1 -1
  58. package/svg/editor/index.js +1 -1
  59. package/svg/icons/index.d.ts +1 -0
  60. package/svg/icons/index.js +1 -0
  61. package/inputs/Editor/AudioVisualizer.d.ts +0 -7
  62. package/inputs/Editor/AudioVisualizer.js +0 -81
  63. package/inputs/Editor/SpeechRecognizer.d.ts +0 -9
  64. package/inputs/Editor/SpeechRecognizer.js +0 -39
  65. package/inputs/Editor/buttons/RecordButton.d.ts +0 -9
  66. package/inputs/Editor/buttons/RecordButton.js +0 -8
@@ -0,0 +1,283 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
12
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ import { useRef, useState, useEffect, useCallback } from 'react';
38
+ //
39
+ // The audio processing needs a worklet that runs in a separate thread.
40
+ // Ordinarily such a worklet would have to reside in a separate JavaScript
41
+ // file, not part of the Webpack bundle, that gets interpreted here using
42
+ // audioContext.audioWorklet.addModule. However, we can place the JavaScript
43
+ // code in a template string, compile that into a blob, and then use that
44
+ // instead. This way, no separate JS file needs to be deployed with AquaUI.
45
+ //
46
+ var moduleScript = /* js */ "\nconst MAX_16BIT_INT = 32767\nclass AudioProcessor extends AudioWorkletProcessor {\n constructor() {\n super();\n\n // Input is browser sample rate (typically 48000 Hz in Firefox)\n // This variable is globally available inside the AudioWorkletProcessor\n // scope.\n this.inputSampleRate = sampleRate;\n console.log('Detected input sample rate:', sampleRate);\n this.targetSampleRate = 16000;\n this.ratio = this.inputSampleRate / this.targetSampleRate;\n\n // Buffer of input samples for resampling\n this.buffer = [];\n\n // Number of samples per output chunk\n this.chunkSize = 1600; // ~100ms at 16kHz\n }\n\n /**\n * Resample the input buffer to the target sample rate using linear interpolation.\n */\n resample(inputBuffer) {\n const outputLength = Math.floor(inputBuffer.length / this.ratio);\n const output = new Float32Array(outputLength);\n\n for (let i = 0; i < outputLength; i++) {\n const idx = i * this.ratio;\n const idxInt = Math.floor(idx);\n const idxFrac = idx - idxInt;\n\n const sample1 = inputBuffer[idxInt] || 0;\n const sample2 = inputBuffer[idxInt + 1] || 0;\n output[i] = sample1 + (sample2 - sample1) * idxFrac;\n }\n\n return output;\n }\n\n /**\n * Converts Float32 PCM [-1, 1] to 16-bit signed PCM\n */\n floatTo16BitPCM(float32Array) {\n const int16Array = new Int16Array(float32Array.length);\n for (let i = 0; i < float32Array.length; i++) {\n let s = Math.max(-1, Math.min(1, float32Array[i]));\n int16Array[i] = s < 0 ? s * 0x8000 : s * 0x7FFF;\n }\n return int16Array;\n }\n\n /**\n * Called automatically by browser on each audio frame (128 samples per channel).\n */\n process(inputs, outputs, parameters) {\n try {\n const input = inputs[0];\n if (!input || input.length === 0) return true;\n\n // Read from mono channel\n const channelData = input[0];\n this.buffer.push(...channelData);\n\n // Only process if we have enough input to produce a full output chunk\n const neededInputSamples = this.chunkSize * this.ratio;\n if (this.buffer.length >= neededInputSamples) {\n const inputForChunk = this.buffer.slice(0, neededInputSamples);\n const resampled = this.resample(inputForChunk);\n const pcm = this.floatTo16BitPCM(resampled);\n\n this.port.postMessage({ audio_data: pcm }); // Send Int16Array to main thread\n\n // Remove consumed input samples\n this.buffer = this.buffer.slice(neededInputSamples);\n }\n\n return true; // Keep processor alive\n } catch (error) {\n console.error(error)\n return false\n }\n }\n}\n\nregisterProcessor('audio-processor', AudioProcessor);\n";
47
+ /**
48
+ * useAssemblyAIRecorder
49
+ *
50
+ * A custom React hook for real-time audio transcription using AssemblyAI's
51
+ * streaming API.
52
+ *
53
+ * When recording is toggled on, it takes a few moments to establish a web
54
+ * socket connection. During this time, the recording status will be
55
+ * `'connecting'`.
56
+ *
57
+ * @param url - Back-end URL for obtaining temporary token
58
+ * @param onTranscript - Callback that receives updated transcript text.
59
+ * @returns An object containing:
60
+ * - recordingStatus: 'idle' | 'connecting' | 'recording'
61
+ * - toggleRecording: function to start or stop recording
62
+ */
63
+ var useAssemblyAIRecorder = function (url, onTranscript) {
64
+ // State to track the current recording status
65
+ var _a = useState('idle'), recordingStatus = _a[0], setRecordingStatus = _a[1];
66
+ // WebSocket connection to AssemblyAI
67
+ var wsRef = useRef(null);
68
+ // Audio context used for audio capture and processing
69
+ var audioContextRef = useRef(null);
70
+ // Queue of raw audio samples
71
+ var audioBufferQueueRef = useRef(new Int16Array(0));
72
+ // Active audio stream from the microphone
73
+ var streamRef = useRef(null);
74
+ // Stores ordered transcripts from AssemblyAI (by turn_order)
75
+ var turnsRef = useRef({});
76
+ /**
77
+ * Utility to merge two Int16Array buffers
78
+ */
79
+ var mergeBuffers = function (lhs, rhs) {
80
+ var merged = new Int16Array(lhs.length + rhs.length);
81
+ merged.set(lhs, 0);
82
+ merged.set(rhs, lhs.length);
83
+ return merged;
84
+ };
85
+ /**
86
+ * Requests permission and returns the user's microphone stream
87
+ */
88
+ var requestPermission = function () { return __awaiter(void 0, void 0, void 0, function () {
89
+ var stream;
90
+ return __generator(this, function (_a) {
91
+ switch (_a.label) {
92
+ case 0: return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
93
+ case 1:
94
+ stream = _a.sent();
95
+ streamRef.current = stream;
96
+ return [2 /*return*/, stream];
97
+ }
98
+ });
99
+ }); };
100
+ /**
101
+ * Begins recording audio using the AudioWorkletProcessor defined in
102
+ * audio-processor.js
103
+ *
104
+ * The onAudioCallback function is called with raw 16kHz 16-bit mono
105
+ * PCM data.
106
+ */
107
+ var startRecording = function (onAudioCallback) { return __awaiter(void 0, void 0, void 0, function () {
108
+ var stream, audioContext, source, scriptUrl, node;
109
+ return __generator(this, function (_a) {
110
+ switch (_a.label) {
111
+ case 0: return [4 /*yield*/, requestPermission()];
112
+ case 1:
113
+ stream = _a.sent();
114
+ audioContext = new AudioContext({
115
+ // AssemblyAI requires a sample rate of 16000. Browserscan will
116
+ // decide their own sample rate. Firefox will not allow 16000.
117
+ // For that reason, let the browser set the sample rate to whatever
118
+ // it wants, then have the AudioWorklet do the resampling.
119
+ // sampleRate: 16000 <--- Leave off
120
+ latencyHint: 'balanced',
121
+ });
122
+ audioContextRef.current = audioContext;
123
+ source = audioContext.createMediaStreamSource(stream);
124
+ scriptUrl = URL.createObjectURL(new Blob([moduleScript], { type: "text/javascript" }));
125
+ return [4 /*yield*/, audioContext.audioWorklet.addModule(scriptUrl)];
126
+ case 2:
127
+ _a.sent();
128
+ node = new AudioWorkletNode(audioContext, 'audio-processor');
129
+ source.connect(node);
130
+ node.connect(audioContext.destination); // Required to keep node alive in some browsers
131
+ // Handle incoming audio from the AudioWorkletProcessor
132
+ node.port.onmessage = function (event) {
133
+ var currentBuffer = new Int16Array(event.data.audio_data);
134
+ var bufferQueue = audioBufferQueueRef.current;
135
+ audioBufferQueueRef.current = mergeBuffers(bufferQueue, currentBuffer);
136
+ var bufferDuration = (audioBufferQueueRef.current.length / audioContext.sampleRate) * 1000;
137
+ // When we have at least 100ms of audio, send it to AssemblyAI
138
+ if (bufferDuration >= 100) {
139
+ var totalSamples = Math.floor(audioContext.sampleRate * 0.1); // 100ms worth
140
+ var finalBuffer = new Uint8Array(audioBufferQueueRef.current.subarray(0, totalSamples).buffer);
141
+ audioBufferQueueRef.current = audioBufferQueueRef.current.subarray(totalSamples);
142
+ onAudioCallback(finalBuffer);
143
+ }
144
+ };
145
+ return [2 /*return*/];
146
+ }
147
+ });
148
+ }); };
149
+ /**
150
+ * Stops all active recording resources and resets buffers
151
+ */
152
+ var stopRecording = function () {
153
+ var _a, _b;
154
+ // Stop all audio input tracks
155
+ (_a = streamRef.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach(function (track) { return track.stop(); });
156
+ streamRef.current = null;
157
+ // Close the audio context
158
+ (_b = audioContextRef.current) === null || _b === void 0 ? void 0 : _b.close();
159
+ audioContextRef.current = null;
160
+ // Clear audio buffer
161
+ audioBufferQueueRef.current = new Int16Array(0);
162
+ };
163
+ /**
164
+ * Fetches a temporary token for the AssemblyAI WebSocket API
165
+ */
166
+ var fetchToken = function () { return __awaiter(void 0, void 0, void 0, function () {
167
+ var response, data, err_1;
168
+ return __generator(this, function (_a) {
169
+ switch (_a.label) {
170
+ case 0:
171
+ _a.trys.push([0, 3, , 4]);
172
+ return [4 /*yield*/, fetch(url)];
173
+ case 1:
174
+ response = _a.sent();
175
+ return [4 /*yield*/, response.json()];
176
+ case 2:
177
+ data = _a.sent();
178
+ return [2 /*return*/, data.token || null];
179
+ case 3:
180
+ err_1 = _a.sent();
181
+ console.error('Failed to fetch token', err_1);
182
+ return [2 /*return*/, null];
183
+ case 4: return [2 /*return*/];
184
+ }
185
+ });
186
+ }); };
187
+ /**
188
+ * Gracefully terminates the WebSocket and recording session
189
+ */
190
+ var cleanup = function () {
191
+ var _a, _b;
192
+ try {
193
+ // Attempt to tell AssemblyAI we're done
194
+ (_a = wsRef.current) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({ type: 'Terminate' }));
195
+ }
196
+ catch (e) {
197
+ // If socket already closed, ignore
198
+ }
199
+ (_b = wsRef.current) === null || _b === void 0 ? void 0 : _b.close();
200
+ wsRef.current = null;
201
+ stopRecording();
202
+ setRecordingStatus('idle');
203
+ };
204
+ /**
205
+ * Toggles the recording state: start or stop
206
+ */
207
+ var toggleRecording = useCallback(function () { return __awaiter(void 0, void 0, void 0, function () {
208
+ var token, endpoint, ws;
209
+ return __generator(this, function (_a) {
210
+ switch (_a.label) {
211
+ case 0:
212
+ if (recordingStatus === 'recording' || recordingStatus === 'connecting') {
213
+ cleanup();
214
+ return [2 /*return*/];
215
+ }
216
+ // Update state to show we are connecting
217
+ setRecordingStatus('connecting');
218
+ return [4 /*yield*/, fetchToken()];
219
+ case 1:
220
+ token = _a.sent();
221
+ if (!token) {
222
+ alert('Failed to get temp token');
223
+ setRecordingStatus('idle');
224
+ return [2 /*return*/];
225
+ }
226
+ endpoint = "wss://streaming.assemblyai.com/v3/ws?sample_rate=16000&formatted_finals=true&token=".concat(token);
227
+ ws = new WebSocket(endpoint);
228
+ wsRef.current = ws;
229
+ // Reset transcription state
230
+ turnsRef.current = {};
231
+ // WebSocket connection established
232
+ ws.onopen = function () {
233
+ console.log('WebSocket connected.');
234
+ // Start recording and streaming chunks
235
+ startRecording(function (audioChunk) {
236
+ if (ws.readyState === WebSocket.OPEN) {
237
+ ws.send(audioChunk);
238
+ }
239
+ else {
240
+ console.warn("Tried to send audio, but WebSocket is not open.");
241
+ }
242
+ });
243
+ // We're officially recording now
244
+ setRecordingStatus('recording');
245
+ };
246
+ // Handle messages from AssemblyAI
247
+ ws.onmessage = function (event) {
248
+ var msg = JSON.parse(event.data);
249
+ if (msg.type === 'Turn') {
250
+ var turn_order = msg.turn_order, transcript = msg.transcript;
251
+ turnsRef.current[turn_order] = transcript;
252
+ // Reassemble the transcript in order
253
+ var ordered = Object.keys(turnsRef.current)
254
+ .sort(function (a, b) { return Number(a) - Number(b); })
255
+ .map(function (k) { return turnsRef.current[k]; })
256
+ .join(' ');
257
+ onTranscript(ordered);
258
+ }
259
+ };
260
+ ws.onerror = function (err) {
261
+ console.error('WebSocket error:', err);
262
+ };
263
+ ws.onclose = function () {
264
+ console.log('WebSocket closed.');
265
+ };
266
+ return [2 /*return*/];
267
+ }
268
+ });
269
+ }); }, [recordingStatus, onTranscript]);
270
+ /**
271
+ * Ensures everything is cleaned up if the component using this hook unmounts
272
+ */
273
+ useEffect(function () {
274
+ return function () {
275
+ cleanup();
276
+ };
277
+ }, []);
278
+ return {
279
+ recordingStatus: recordingStatus, // 'idle' | 'connecting' | 'recording'
280
+ toggleRecording: toggleRecording,
281
+ };
282
+ };
283
+ export { useAssemblyAIRecorder };
@@ -0,0 +1 @@
1
+ export { useOpenAIRecorder } from './useOpenAIRecorder';
@@ -0,0 +1 @@
1
+ export { useOpenAIRecorder } from './useOpenAIRecorder';
@@ -0,0 +1,8 @@
1
+ import * as React from 'react';
2
+ interface IProps {
3
+ }
4
+ /**
5
+ * The extended media recorder allows converting to .wav format.
6
+ */
7
+ declare const AudioInput: (props: IProps) => React.JSX.Element;
8
+ export { AudioInput };
@@ -0,0 +1,192 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ var __generator = (this && this.__generator) || function (thisArg, body) {
11
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
12
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
13
+ function verb(n) { return function (v) { return step([n, v]); }; }
14
+ function step(op) {
15
+ if (f) throw new TypeError("Generator is already executing.");
16
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
17
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
18
+ if (y = 0, t) op = [op[0] & 2, t.value];
19
+ switch (op[0]) {
20
+ case 0: case 1: t = op; break;
21
+ case 4: _.label++; return { value: op[1], done: false };
22
+ case 5: _.label++; y = op[1]; op = [0]; continue;
23
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
24
+ default:
25
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
26
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
27
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
28
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
29
+ if (t[2]) _.ops.pop();
30
+ _.trys.pop(); continue;
31
+ }
32
+ op = body.call(thisArg, _);
33
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
34
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
35
+ }
36
+ };
37
+ import * as React from 'react';
38
+ import { MediaRecorder, register } from 'extendable-media-recorder';
39
+ import { connect } from 'extendable-media-recorder-wav-encoder';
40
+ import { Fab } from '../../../controls/Fab';
41
+ import { SVG } from '../../../svg';
42
+ /**
43
+ * The extended media recorder allows converting to .wav format.
44
+ */
45
+ var AudioInput = function (props) {
46
+ var _a = React.useState(false), isRecording = _a[0], setIsRecording = _a[1];
47
+ var _b = React.useState(''), transcript = _b[0], setTranscript = _b[1];
48
+ var mediaRecorderRef = React.useRef(null);
49
+ var streamRef = React.useRef(null);
50
+ var streamIdRef = React.useRef(Date.now().toString()); // Unique per session
51
+ var sourceRef = React.useRef(null);
52
+ var header;
53
+ var startRecording = function () { return __awaiter(void 0, void 0, void 0, function () {
54
+ var _a, _b, mediaRecorder, err_1;
55
+ return __generator(this, function (_c) {
56
+ switch (_c.label) {
57
+ case 0:
58
+ _c.trys.push([0, 4, , 5]);
59
+ _a = register;
60
+ return [4 /*yield*/, connect()];
61
+ case 1: return [4 /*yield*/, _a.apply(void 0, [_c.sent()])];
62
+ case 2:
63
+ _c.sent();
64
+ _b = streamRef;
65
+ return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
66
+ case 3:
67
+ _b.current = _c.sent();
68
+ mediaRecorder = new MediaRecorder(streamRef.current, { mimeType: 'audio/wav' });
69
+ mediaRecorder.ondataavailable = function (event) { return __awaiter(void 0, void 0, void 0, function () {
70
+ var blob, formData, content, error_1;
71
+ return __generator(this, function (_a) {
72
+ switch (_a.label) {
73
+ case 0:
74
+ if (!(event.data.size > 0)) return [3 /*break*/, 8];
75
+ blob = event.data;
76
+ formData = new FormData();
77
+ if (!(header === undefined)) return [3 /*break*/, 2];
78
+ return [4 /*yield*/, blob.arrayBuffer()];
79
+ case 1:
80
+ header = (_a.sent()).slice(0, 44);
81
+ return [3 /*break*/, 4];
82
+ case 2: return [4 /*yield*/, blob.arrayBuffer()];
83
+ case 3:
84
+ content = _a.sent();
85
+ blob = new Blob([header, content], { type: blob.type });
86
+ _a.label = 4;
87
+ case 4:
88
+ formData.append('audio', blob);
89
+ formData.append('stream_id', streamIdRef.current);
90
+ _a.label = 5;
91
+ case 5:
92
+ _a.trys.push([5, 7, , 8]);
93
+ return [4 /*yield*/, fetch('http://api.flow/api/ai/upload-audio-chunk', {
94
+ method: 'POST',
95
+ body: formData,
96
+ })];
97
+ case 6:
98
+ _a.sent();
99
+ return [3 /*break*/, 8];
100
+ case 7:
101
+ error_1 = _a.sent();
102
+ console.error('Upload failed:', error_1);
103
+ return [3 /*break*/, 8];
104
+ case 8: return [2 /*return*/];
105
+ }
106
+ });
107
+ }); };
108
+ mediaRecorderRef.current = mediaRecorder;
109
+ mediaRecorder.start(20000); // 4-second chunks
110
+ return [3 /*break*/, 5];
111
+ case 4:
112
+ err_1 = _c.sent();
113
+ console.error('Microphone access error:', err_1);
114
+ setIsRecording(false);
115
+ return [3 /*break*/, 5];
116
+ case 5: return [2 /*return*/];
117
+ }
118
+ });
119
+ }); };
120
+ React.useEffect(function () {
121
+ var _a, _b, _c;
122
+ if (isRecording) {
123
+ startRecording();
124
+ }
125
+ else {
126
+ if (((_a = mediaRecorderRef.current) === null || _a === void 0 ? void 0 : _a.state) !== 'inactive') {
127
+ (_b = mediaRecorderRef.current) === null || _b === void 0 ? void 0 : _b.stop();
128
+ }
129
+ (_c = streamRef.current) === null || _c === void 0 ? void 0 : _c.getTracks().forEach(function (track) { return track.stop(); });
130
+ }
131
+ ;
132
+ }, [isRecording]);
133
+ var readyStateToString = function (state) {
134
+ switch (state) {
135
+ case EventSource.CLOSED: return "CLOSED";
136
+ case EventSource.OPEN: return "OPEN";
137
+ case EventSource.CONNECTING: return "CONNECTING";
138
+ default:
139
+ return "UNKNOWN";
140
+ }
141
+ };
142
+ var eventPhaseToString = function (phase) {
143
+ switch (phase) {
144
+ case Event.NONE: return "NONE";
145
+ case Event.CAPTURING_PHASE: return "CAPTURING_PHASE";
146
+ case Event.AT_TARGET: return "AT_TARGET";
147
+ case Event.BUBBLING_PHASE: return "BUBBLING_PHASE";
148
+ default:
149
+ return "UNKNOWN";
150
+ }
151
+ };
152
+ React.useEffect(function () {
153
+ if (isRecording) {
154
+ sourceRef.current = new EventSource("http://api.flow/api/ai/transcribe-stream/".concat(streamIdRef.current));
155
+ sourceRef.current.onmessage = function (event) {
156
+ console.log("Message:", event.data);
157
+ console.log("ReadyState:", readyStateToString(sourceRef.current.readyState));
158
+ setTranscript(function (prev) { return prev + event.data + '\n'; });
159
+ };
160
+ sourceRef.current.onerror = function (err) {
161
+ console.error('SSE error:', err);
162
+ console.log('EventPhase:', eventPhaseToString(err.eventPhase));
163
+ console.log('ReadyState:', readyStateToString(sourceRef.current.readyState));
164
+ sourceRef.current.close();
165
+ };
166
+ }
167
+ return function () { var _a; return (_a = sourceRef.current) === null || _a === void 0 ? void 0 : _a.close(); };
168
+ }, [isRecording]);
169
+ var start = function () {
170
+ setTranscript('');
171
+ streamIdRef.current = Date.now().toString(); // Reset for new session
172
+ setIsRecording(true);
173
+ };
174
+ var stop = function () {
175
+ var _a;
176
+ setIsRecording(false);
177
+ if (((_a = mediaRecorderRef.current) === null || _a === void 0 ? void 0 : _a.state) !== 'inactive') {
178
+ mediaRecorderRef.current.stop();
179
+ }
180
+ };
181
+ var handleToggle = function () {
182
+ if (isRecording) {
183
+ stop();
184
+ }
185
+ else {
186
+ start();
187
+ }
188
+ };
189
+ return (React.createElement("div", null,
190
+ React.createElement(Fab, { title: "Record", active: isRecording, icon: SVG.Icons.Microphone, onClick: handleToggle })));
191
+ };
192
+ export { AudioInput };
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+ interface IProps {
3
+ }
4
+ declare const AudioInput: (props: IProps) => React.JSX.Element;
5
+ export { AudioInput };