@longline/aqua-ui 1.0.196 → 1.0.198

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 (69) 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/SpeechManager.d.ts +12 -0
  18. package/hooks/useAssemblyAIRecorder/SpeechManager.js +26 -0
  19. package/hooks/useAssemblyAIRecorder/index.d.ts +1 -0
  20. package/hooks/useAssemblyAIRecorder/index.js +1 -0
  21. package/hooks/useAssemblyAIRecorder/stories/AssemblyAudioInput.d.ts +6 -0
  22. package/hooks/useAssemblyAIRecorder/stories/AssemblyAudioInput.js +17 -0
  23. package/hooks/useAssemblyAIRecorder/useAssemblyAIRecorder.d.ts +27 -0
  24. package/hooks/useAssemblyAIRecorder/useAssemblyAIRecorder.js +294 -0
  25. package/hooks/useOpenAIRecorder/index.d.ts +1 -0
  26. package/hooks/useOpenAIRecorder/index.js +1 -0
  27. package/hooks/useOpenAIRecorder/old/AudioInputNoHook.d.ts +8 -0
  28. package/hooks/useOpenAIRecorder/old/AudioInputNoHook.js +192 -0
  29. package/hooks/useOpenAIRecorder/old/AudioInputStandardMedia.d.ts +5 -0
  30. package/hooks/useOpenAIRecorder/old/AudioInputStandardMedia.js +170 -0
  31. package/hooks/useOpenAIRecorder/stories/OpenAIAudioInput.d.ts +8 -0
  32. package/hooks/useOpenAIRecorder/stories/OpenAIAudioInput.js +17 -0
  33. package/hooks/useOpenAIRecorder/useOpenAIRecorder.d.ts +22 -0
  34. package/hooks/useOpenAIRecorder/useOpenAIRecorder.js +223 -0
  35. package/hooks/useOpenAIStream/index.d.ts +1 -0
  36. package/hooks/useOpenAIStream/index.js +1 -0
  37. package/hooks/useOpenAIStream/stories/OpenAIStreamInput.d.ts +23 -0
  38. package/hooks/useOpenAIStream/stories/OpenAIStreamInput.js +66 -0
  39. package/hooks/useOpenAIStream/useOpenAIStream.d.ts +14 -0
  40. package/hooks/useOpenAIStream/useOpenAIStream.js +159 -0
  41. package/inputs/Editor/Editor.d.ts +13 -3
  42. package/inputs/Editor/Editor.js +27 -24
  43. package/inputs/Editor/buttons/CodeBlockButton.js +1 -1
  44. package/inputs/Editor/buttons/OpenAIButton.d.ts +25 -0
  45. package/inputs/Editor/buttons/OpenAIButton.js +208 -0
  46. package/inputs/Editor/buttons/OpenAIMenu.d.ts +9 -0
  47. package/inputs/Editor/buttons/OpenAIMenu.js +42 -0
  48. package/inputs/Editor/buttons/SpeechButton.d.ts +8 -0
  49. package/inputs/Editor/buttons/SpeechButton.js +31 -0
  50. package/inputs/Editor/extensions/MarkElement.d.ts +9 -0
  51. package/inputs/Editor/extensions/MarkElement.js +35 -0
  52. package/inputs/Editor/extensions/StreamNode.d.ts +3 -0
  53. package/inputs/Editor/extensions/StreamNode.js +33 -0
  54. package/inputs/Editor/menu/MenuBar.d.ts +14 -3
  55. package/inputs/Editor/menu/MenuBar.js +14 -15
  56. package/inputs/Editor/menu/MenuButton.d.ts +7 -1
  57. package/inputs/Editor/menu/MenuButton.js +4 -5
  58. package/inputs/Editor/menu/MenuSeparator.js +1 -1
  59. package/package.json +10 -3
  60. package/svg/editor/index.d.ts +1 -1
  61. package/svg/editor/index.js +1 -1
  62. package/svg/icons/index.d.ts +1 -0
  63. package/svg/icons/index.js +1 -0
  64. package/inputs/Editor/AudioVisualizer.d.ts +0 -7
  65. package/inputs/Editor/AudioVisualizer.js +0 -81
  66. package/inputs/Editor/SpeechRecognizer.d.ts +0 -9
  67. package/inputs/Editor/SpeechRecognizer.js +0 -39
  68. package/inputs/Editor/buttons/RecordButton.d.ts +0 -9
  69. package/inputs/Editor/buttons/RecordButton.js +0 -8
@@ -0,0 +1,17 @@
1
+ import React, { useState } from 'react';
2
+ import { useAssemblyAIRecorder } from '../useAssemblyAIRecorder';
3
+ var AssemblyAudioInput = function (props) {
4
+ var _a = useState('initial'), transcript = _a[0], setTranscript = _a[1];
5
+ var _b = useAssemblyAIRecorder(props.url, setTranscript), recordingStatus = _b.recordingStatus, toggleRecording = _b.toggleRecording;
6
+ var getLabel = function (recordingStatus) {
7
+ switch (recordingStatus) {
8
+ case 'connecting': return "Connecting";
9
+ case 'recording': return "Recording";
10
+ default: return "Idle";
11
+ }
12
+ };
13
+ return (React.createElement("div", null,
14
+ React.createElement("button", { onClick: toggleRecording }, getLabel(recordingStatus)),
15
+ React.createElement("p", { style: { border: 'solid 1px blue', minHeight: '200px', color: 'white' } }, transcript)));
16
+ };
17
+ export { AssemblyAudioInput };
@@ -0,0 +1,27 @@
1
+ type TranscriptCallback = (text: string) => void;
2
+ type TRecordingStatus = 'idle' | 'connecting' | 'recording';
3
+ /**
4
+ * useAssemblyAIRecorder
5
+ *
6
+ * A custom React hook for real-time audio transcription using AssemblyAI's
7
+ * streaming API.
8
+ *
9
+ * When recording is toggled on, it takes a few moments to establish a web
10
+ * socket connection. During this time, the recording status will be
11
+ * `'connecting'`.
12
+ *
13
+ * @param url - Back-end URL for obtaining temporary token
14
+ * @param onTranscript - Callback that receives updated transcript text.
15
+ * @returns An object containing:
16
+ * - recordingStatus: 'idle' | 'connecting' | 'recording'
17
+ * - toggleRecording: function to start or stop recording
18
+ *
19
+ * The `SpeechManager` singleton is use to keep track of which
20
+ * AssemblyAIRecorder is currently recording; when a new recording is started,
21
+ * the current one is stopped.
22
+ */
23
+ declare const useAssemblyAIRecorder: (url: string, onTranscript: TranscriptCallback) => {
24
+ recordingStatus: TRecordingStatus;
25
+ toggleRecording: () => Promise<void>;
26
+ };
27
+ export { useAssemblyAIRecorder, TRecordingStatus };
@@ -0,0 +1,294 @@
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
+ import { SpeechManager } from './SpeechManager';
39
+ //
40
+ // The audio processing needs a worklet that runs in a separate thread.
41
+ // Ordinarily such a worklet would have to reside in a separate JavaScript
42
+ // file, not part of the Webpack bundle, that gets interpreted here using
43
+ // audioContext.audioWorklet.addModule. However, we can place the JavaScript
44
+ // code in a template string, compile that into a blob, and then use that
45
+ // instead. This way, no separate JS file needs to be deployed with AquaUI.
46
+ //
47
+ 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";
48
+ /**
49
+ * useAssemblyAIRecorder
50
+ *
51
+ * A custom React hook for real-time audio transcription using AssemblyAI's
52
+ * streaming API.
53
+ *
54
+ * When recording is toggled on, it takes a few moments to establish a web
55
+ * socket connection. During this time, the recording status will be
56
+ * `'connecting'`.
57
+ *
58
+ * @param url - Back-end URL for obtaining temporary token
59
+ * @param onTranscript - Callback that receives updated transcript text.
60
+ * @returns An object containing:
61
+ * - recordingStatus: 'idle' | 'connecting' | 'recording'
62
+ * - toggleRecording: function to start or stop recording
63
+ *
64
+ * The `SpeechManager` singleton is use to keep track of which
65
+ * AssemblyAIRecorder is currently recording; when a new recording is started,
66
+ * the current one is stopped.
67
+ */
68
+ var useAssemblyAIRecorder = function (url, onTranscript) {
69
+ // State to track the current recording status
70
+ var _a = useState('idle'), recordingStatus = _a[0], setRecordingStatus = _a[1];
71
+ // WebSocket connection to AssemblyAI
72
+ var wsRef = useRef(null);
73
+ // Audio context used for audio capture and processing
74
+ var audioContextRef = useRef(null);
75
+ // Queue of raw audio samples
76
+ var audioBufferQueueRef = useRef(new Int16Array(0));
77
+ // Active audio stream from the microphone
78
+ var streamRef = useRef(null);
79
+ // Stores ordered transcripts from AssemblyAI (by turn_order)
80
+ var turnsRef = useRef({});
81
+ /**
82
+ * Utility to merge two Int16Array buffers
83
+ */
84
+ var mergeBuffers = function (lhs, rhs) {
85
+ var merged = new Int16Array(lhs.length + rhs.length);
86
+ merged.set(lhs, 0);
87
+ merged.set(rhs, lhs.length);
88
+ return merged;
89
+ };
90
+ /**
91
+ * Requests permission and returns the user's microphone stream
92
+ */
93
+ var requestPermission = function () { return __awaiter(void 0, void 0, void 0, function () {
94
+ var stream;
95
+ return __generator(this, function (_a) {
96
+ switch (_a.label) {
97
+ case 0: return [4 /*yield*/, navigator.mediaDevices.getUserMedia({ audio: true })];
98
+ case 1:
99
+ stream = _a.sent();
100
+ streamRef.current = stream;
101
+ return [2 /*return*/, stream];
102
+ }
103
+ });
104
+ }); };
105
+ /**
106
+ * Begins recording audio using the AudioWorkletProcessor defined in
107
+ * audio-processor.js
108
+ *
109
+ * The onAudioCallback function is called with raw 16kHz 16-bit mono
110
+ * PCM data.
111
+ */
112
+ var startRecording = function (onAudioCallback) { return __awaiter(void 0, void 0, void 0, function () {
113
+ var stream, audioContext, source, scriptUrl, node;
114
+ return __generator(this, function (_a) {
115
+ switch (_a.label) {
116
+ case 0: return [4 /*yield*/, requestPermission()];
117
+ case 1:
118
+ stream = _a.sent();
119
+ audioContext = new AudioContext({
120
+ // AssemblyAI requires a sample rate of 16000. Browserscan will
121
+ // decide their own sample rate. Firefox will not allow 16000.
122
+ // For that reason, let the browser set the sample rate to whatever
123
+ // it wants, then have the AudioWorklet do the resampling.
124
+ // sampleRate: 16000 <--- Leave off
125
+ latencyHint: 'balanced',
126
+ });
127
+ audioContextRef.current = audioContext;
128
+ source = audioContext.createMediaStreamSource(stream);
129
+ scriptUrl = URL.createObjectURL(new Blob([moduleScript], { type: "text/javascript" }));
130
+ return [4 /*yield*/, audioContext.audioWorklet.addModule(scriptUrl)];
131
+ case 2:
132
+ _a.sent();
133
+ node = new AudioWorkletNode(audioContext, 'audio-processor');
134
+ source.connect(node);
135
+ node.connect(audioContext.destination); // Required to keep node alive in some browsers
136
+ // Handle incoming audio from the AudioWorkletProcessor
137
+ node.port.onmessage = function (event) {
138
+ var currentBuffer = new Int16Array(event.data.audio_data);
139
+ var bufferQueue = audioBufferQueueRef.current;
140
+ audioBufferQueueRef.current = mergeBuffers(bufferQueue, currentBuffer);
141
+ var bufferDuration = (audioBufferQueueRef.current.length / audioContext.sampleRate) * 1000;
142
+ // When we have at least 100ms of audio, send it to AssemblyAI
143
+ if (bufferDuration >= 100) {
144
+ var totalSamples = Math.floor(audioContext.sampleRate * 0.1); // 100ms worth
145
+ var finalBuffer = new Uint8Array(audioBufferQueueRef.current.subarray(0, totalSamples).buffer);
146
+ audioBufferQueueRef.current = audioBufferQueueRef.current.subarray(totalSamples);
147
+ onAudioCallback(finalBuffer);
148
+ }
149
+ };
150
+ return [2 /*return*/];
151
+ }
152
+ });
153
+ }); };
154
+ /**
155
+ * Stops all active recording resources and resets buffers
156
+ */
157
+ var stopRecording = function () {
158
+ var _a, _b;
159
+ // Stop all audio input tracks
160
+ (_a = streamRef.current) === null || _a === void 0 ? void 0 : _a.getTracks().forEach(function (track) { return track.stop(); });
161
+ streamRef.current = null;
162
+ // Close the audio context
163
+ (_b = audioContextRef.current) === null || _b === void 0 ? void 0 : _b.close();
164
+ audioContextRef.current = null;
165
+ // Clear audio buffer
166
+ audioBufferQueueRef.current = new Int16Array(0);
167
+ };
168
+ /**
169
+ * Fetches a temporary token for the AssemblyAI WebSocket API
170
+ */
171
+ var fetchToken = function () { return __awaiter(void 0, void 0, void 0, function () {
172
+ var response, data, err_1;
173
+ return __generator(this, function (_a) {
174
+ switch (_a.label) {
175
+ case 0:
176
+ _a.trys.push([0, 3, , 4]);
177
+ return [4 /*yield*/, fetch(url)];
178
+ case 1:
179
+ response = _a.sent();
180
+ return [4 /*yield*/, response.json()];
181
+ case 2:
182
+ data = _a.sent();
183
+ return [2 /*return*/, data.token || null];
184
+ case 3:
185
+ err_1 = _a.sent();
186
+ console.error('Failed to fetch token', err_1);
187
+ return [2 /*return*/, null];
188
+ case 4: return [2 /*return*/];
189
+ }
190
+ });
191
+ }); };
192
+ /**
193
+ * Gracefully terminates the WebSocket and recording session
194
+ */
195
+ var cleanup = function () {
196
+ var _a, _b;
197
+ try {
198
+ // Attempt to tell AssemblyAI we're done
199
+ (_a = wsRef.current) === null || _a === void 0 ? void 0 : _a.send(JSON.stringify({ type: 'Terminate' }));
200
+ }
201
+ catch (e) {
202
+ // If socket already closed, ignore
203
+ }
204
+ (_b = wsRef.current) === null || _b === void 0 ? void 0 : _b.close();
205
+ wsRef.current = null;
206
+ stopRecording();
207
+ setRecordingStatus('idle');
208
+ };
209
+ /**
210
+ * Toggles the recording state: start or stop
211
+ */
212
+ var toggleRecording = useCallback(function () { return __awaiter(void 0, void 0, void 0, function () {
213
+ var token, endpoint, ws;
214
+ return __generator(this, function (_a) {
215
+ switch (_a.label) {
216
+ case 0:
217
+ if (recordingStatus === 'recording' || recordingStatus === 'connecting') {
218
+ cleanup();
219
+ return [2 /*return*/];
220
+ }
221
+ // Turn off any other speech recording through the SpeechManager singleton.
222
+ // Register this stream as the current one.
223
+ console.log("START SESSION");
224
+ SpeechManager.startNewSession(function () {
225
+ cleanup();
226
+ });
227
+ // Update state to show we are connecting
228
+ setRecordingStatus('connecting');
229
+ return [4 /*yield*/, fetchToken()];
230
+ case 1:
231
+ token = _a.sent();
232
+ if (!token) {
233
+ console.error('Failed to get temp token');
234
+ setRecordingStatus('idle');
235
+ return [2 /*return*/];
236
+ }
237
+ endpoint = "wss://streaming.assemblyai.com/v3/ws?sample_rate=16000&formatted_finals=true&token=".concat(token);
238
+ ws = new WebSocket(endpoint);
239
+ wsRef.current = ws;
240
+ // Reset transcription state
241
+ turnsRef.current = {};
242
+ // WebSocket connection established
243
+ ws.onopen = function () {
244
+ console.log('WebSocket connected.');
245
+ // Start recording and streaming chunks
246
+ startRecording(function (audioChunk) {
247
+ if (ws.readyState === WebSocket.OPEN) {
248
+ ws.send(audioChunk);
249
+ }
250
+ else {
251
+ console.warn("Tried to send audio, but WebSocket is not open.");
252
+ }
253
+ });
254
+ // We're officially recording now
255
+ setRecordingStatus('recording');
256
+ };
257
+ // Handle messages from AssemblyAI
258
+ ws.onmessage = function (event) {
259
+ var msg = JSON.parse(event.data);
260
+ if (msg.type === 'Turn') {
261
+ var turn_order = msg.turn_order, transcript = msg.transcript;
262
+ turnsRef.current[turn_order] = transcript;
263
+ // Reassemble the transcript in order
264
+ var ordered = Object.keys(turnsRef.current)
265
+ .sort(function (a, b) { return Number(a) - Number(b); })
266
+ .map(function (k) { return turnsRef.current[k]; })
267
+ .join(' ');
268
+ onTranscript(ordered);
269
+ }
270
+ };
271
+ ws.onerror = function (err) {
272
+ console.error('WebSocket error:', err);
273
+ };
274
+ ws.onclose = function () {
275
+ console.log('WebSocket closed.');
276
+ };
277
+ return [2 /*return*/];
278
+ }
279
+ });
280
+ }); }, [recordingStatus, onTranscript]);
281
+ /**
282
+ * Ensures everything is cleaned up if the component using this hook unmounts
283
+ */
284
+ useEffect(function () {
285
+ return function () {
286
+ SpeechManager.stopCurrentSession();
287
+ };
288
+ }, []);
289
+ return {
290
+ recordingStatus: recordingStatus, // 'idle' | 'connecting' | 'recording'
291
+ toggleRecording: toggleRecording,
292
+ };
293
+ };
294
+ 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 };