sera-ai 1.0.7 → 1.0.9
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/README.md +145 -0
- package/dist/index.d.mts +15 -1
- package/dist/index.d.ts +15 -1
- package/dist/index.js +886 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +887 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2852,6 +2852,7 @@ var useAudioRecorder = ({
|
|
|
2852
2852
|
speciality,
|
|
2853
2853
|
patientId,
|
|
2854
2854
|
patientName,
|
|
2855
|
+
patientHistory,
|
|
2855
2856
|
selectedFormat = "json",
|
|
2856
2857
|
skipDiarization = true,
|
|
2857
2858
|
silenceRemoval = true,
|
|
@@ -3233,6 +3234,7 @@ var useAudioRecorder = ({
|
|
|
3233
3234
|
formData.append("model", selectedModelRef.current);
|
|
3234
3235
|
formData.append("doctorName", doctorName);
|
|
3235
3236
|
formData.append("patientName", patientName || "");
|
|
3237
|
+
if (patientHistory) formData.append("patientHistory", patientHistory);
|
|
3236
3238
|
if (patientId) formData.append("patientId", patientId.toString());
|
|
3237
3239
|
formData.append("removeSilence", removeSilenceRef.current.toString());
|
|
3238
3240
|
formData.append("skipDiarization", skipDiarizationRef.current.toString());
|
|
@@ -3414,6 +3416,7 @@ var useAudioRecorder = ({
|
|
|
3414
3416
|
skipDiarization,
|
|
3415
3417
|
selectedFormat,
|
|
3416
3418
|
patientName,
|
|
3419
|
+
patientHistory,
|
|
3417
3420
|
onTranscriptionComplete,
|
|
3418
3421
|
speciality,
|
|
3419
3422
|
removeSilence,
|
|
@@ -3472,6 +3475,7 @@ var useAudioRecorder = ({
|
|
|
3472
3475
|
await createSession(localSessionId, {
|
|
3473
3476
|
patientId,
|
|
3474
3477
|
patientName: patientName || void 0,
|
|
3478
|
+
patientHistory: patientHistory || void 0,
|
|
3475
3479
|
speciality
|
|
3476
3480
|
});
|
|
3477
3481
|
}
|
|
@@ -3570,6 +3574,7 @@ var useAudioRecorder = ({
|
|
|
3570
3574
|
createSession,
|
|
3571
3575
|
patientId,
|
|
3572
3576
|
patientName,
|
|
3577
|
+
patientHistory,
|
|
3573
3578
|
speciality,
|
|
3574
3579
|
currentDeviceId
|
|
3575
3580
|
]);
|
|
@@ -4360,6 +4365,7 @@ var AudioRecorder = ({
|
|
|
4360
4365
|
speciality,
|
|
4361
4366
|
patientId,
|
|
4362
4367
|
patientName,
|
|
4368
|
+
patientHistory,
|
|
4363
4369
|
onTranscriptionUpdate,
|
|
4364
4370
|
onTranscriptionComplete,
|
|
4365
4371
|
onSuccess,
|
|
@@ -4394,6 +4400,7 @@ var AudioRecorder = ({
|
|
|
4394
4400
|
speciality,
|
|
4395
4401
|
patientName,
|
|
4396
4402
|
patientId,
|
|
4403
|
+
patientHistory,
|
|
4397
4404
|
onTranscriptionUpdate: (text, sessionId) => {
|
|
4398
4405
|
console.log("onTranscriptionUpdate called with text:", text, "sessionId:", sessionId);
|
|
4399
4406
|
if (text.length > 0) {
|
|
@@ -5095,7 +5102,886 @@ var AudioDictation = ({
|
|
|
5095
5102
|
) });
|
|
5096
5103
|
};
|
|
5097
5104
|
var AudioDictation_default = AudioDictation;
|
|
5105
|
+
var createAudioCaptureWorker = () => {
|
|
5106
|
+
const workerCode = `
|
|
5107
|
+
class AudioCaptureProcessor extends AudioWorkletProcessor {
|
|
5108
|
+
constructor() {
|
|
5109
|
+
super();
|
|
5110
|
+
this._buffer = [];
|
|
5111
|
+
this._isStopped = false;
|
|
5112
|
+
this._isPaused = false;
|
|
5113
|
+
this._chunkReady = false;
|
|
5114
|
+
this._processingChunk = false;
|
|
5115
|
+
|
|
5116
|
+
this._audioLevelCheckInterval = 0;
|
|
5117
|
+
this._audioLevelCheckFrequency = 128;
|
|
5118
|
+
this._silentSampleCount = 0;
|
|
5119
|
+
this._maxSilentSamples = 44100 * 30;
|
|
5120
|
+
this._audioThreshold = 0.002;
|
|
5121
|
+
this._hasDetectedAudio = false;
|
|
5122
|
+
this._lastAudioTime = 0;
|
|
5123
|
+
this._recordingStartTime = Date.now();
|
|
5124
|
+
this._initialSilenceThreshold = 44100 * 10;
|
|
5125
|
+
this._isInitialPhase = true;
|
|
5126
|
+
this._bufferSize = 0;
|
|
5127
|
+
|
|
5128
|
+
this.port.onmessage = (event) => {
|
|
5129
|
+
if (event.data.command === "stop") {
|
|
5130
|
+
this._isStopped = true;
|
|
5131
|
+
this._sendFinalChunk();
|
|
5132
|
+
}
|
|
5133
|
+
|
|
5134
|
+
if (event.data.command === "getChunk") {
|
|
5135
|
+
this._chunkReady = true;
|
|
5136
|
+
}
|
|
5137
|
+
|
|
5138
|
+
if (event.data.command === "resetChunk") {
|
|
5139
|
+
this._chunkReady = false;
|
|
5140
|
+
this._processingChunk = false;
|
|
5141
|
+
this._buffer = [];
|
|
5142
|
+
this._bufferSize = 0;
|
|
5143
|
+
}
|
|
5144
|
+
|
|
5145
|
+
if (event.data.command === "pause") {
|
|
5146
|
+
this._isPaused = true;
|
|
5147
|
+
}
|
|
5148
|
+
|
|
5149
|
+
if (event.data.command === "resume") {
|
|
5150
|
+
this._isPaused = false;
|
|
5151
|
+
}
|
|
5152
|
+
};
|
|
5153
|
+
}
|
|
5154
|
+
|
|
5155
|
+
_sendFinalChunk() {
|
|
5156
|
+
if (this._buffer.length > 0) {
|
|
5157
|
+
const flat = this._flattenBuffer();
|
|
5158
|
+
this.port.postMessage({
|
|
5159
|
+
command: "finalChunk",
|
|
5160
|
+
audioBuffer: flat.buffer,
|
|
5161
|
+
duration: this._bufferSize / 44100
|
|
5162
|
+
}, [flat.buffer]);
|
|
5163
|
+
} else {
|
|
5164
|
+
// Send empty final chunk
|
|
5165
|
+
const emptyBuffer = new Float32Array(1000);
|
|
5166
|
+
this.port.postMessage({
|
|
5167
|
+
command: "finalChunk",
|
|
5168
|
+
audioBuffer: emptyBuffer.buffer,
|
|
5169
|
+
duration: 0
|
|
5170
|
+
}, [emptyBuffer.buffer]);
|
|
5171
|
+
}
|
|
5172
|
+
this._buffer = [];
|
|
5173
|
+
this._bufferSize = 0;
|
|
5174
|
+
}
|
|
5175
|
+
|
|
5176
|
+
_flattenBuffer() {
|
|
5177
|
+
let totalLength = 0;
|
|
5178
|
+
for (let i = 0; i < this._buffer.length; i++) {
|
|
5179
|
+
totalLength += this._buffer[i].length;
|
|
5180
|
+
}
|
|
5181
|
+
|
|
5182
|
+
const flat = new Float32Array(totalLength);
|
|
5183
|
+
let offset = 0;
|
|
5184
|
+
for (let i = 0; i < this._buffer.length; i++) {
|
|
5185
|
+
flat.set(this._buffer[i], offset);
|
|
5186
|
+
offset += this._buffer[i].length;
|
|
5187
|
+
}
|
|
5188
|
+
return flat;
|
|
5189
|
+
}
|
|
5190
|
+
|
|
5191
|
+
process(inputs, outputs) {
|
|
5192
|
+
if (this._isStopped || this._isPaused) {
|
|
5193
|
+
return true;
|
|
5194
|
+
}
|
|
5195
|
+
|
|
5196
|
+
const input = inputs[0];
|
|
5197
|
+
if (input && input.length > 0) {
|
|
5198
|
+
const samples = input[0];
|
|
5199
|
+
|
|
5200
|
+
// Calculate audio level
|
|
5201
|
+
let audioLevel = 0;
|
|
5202
|
+
for (let i = 0; i < samples.length; i++) {
|
|
5203
|
+
audioLevel += Math.abs(samples[i]);
|
|
5204
|
+
}
|
|
5205
|
+
audioLevel /= samples.length;
|
|
5206
|
+
|
|
5207
|
+
// Send audio level updates
|
|
5208
|
+
this._audioLevelCheckInterval++;
|
|
5209
|
+
if (this._audioLevelCheckInterval >= this._audioLevelCheckFrequency) {
|
|
5210
|
+
this.port.postMessage({
|
|
5211
|
+
command: "audioLevel",
|
|
5212
|
+
level: audioLevel,
|
|
5213
|
+
});
|
|
5214
|
+
this._audioLevelCheckInterval = 0;
|
|
5215
|
+
}
|
|
5216
|
+
|
|
5217
|
+
// Check for audio activity
|
|
5218
|
+
if (audioLevel > this._audioThreshold) {
|
|
5219
|
+
this._hasDetectedAudio = true;
|
|
5220
|
+
this._isInitialPhase = false;
|
|
5221
|
+
this._silentSampleCount = 0;
|
|
5222
|
+
this._lastAudioTime = Date.now();
|
|
5223
|
+
} else {
|
|
5224
|
+
this._silentSampleCount += samples.length;
|
|
5225
|
+
|
|
5226
|
+
if (this._isInitialPhase && this._silentSampleCount > this._initialSilenceThreshold) {
|
|
5227
|
+
this.port.postMessage({
|
|
5228
|
+
command: "noAudioDetected",
|
|
5229
|
+
message: "No audio input detected after 10 seconds. Please check your microphone."
|
|
5230
|
+
});
|
|
5231
|
+
return true;
|
|
5232
|
+
}
|
|
5233
|
+
}
|
|
5234
|
+
|
|
5235
|
+
// Buffer the audio
|
|
5236
|
+
this._buffer.push(new Float32Array(samples));
|
|
5237
|
+
this._bufferSize += samples.length;
|
|
5238
|
+
|
|
5239
|
+
// Send chunk if ready
|
|
5240
|
+
if (this._chunkReady && !this._processingChunk) {
|
|
5241
|
+
this._processingChunk = true;
|
|
5242
|
+
|
|
5243
|
+
const flat = this._flattenBuffer();
|
|
5244
|
+
this.port.postMessage({
|
|
5245
|
+
command: "chunk",
|
|
5246
|
+
audioBuffer: flat.buffer,
|
|
5247
|
+
duration: this._bufferSize / 44100,
|
|
5248
|
+
hasActivity: audioLevel > this._audioThreshold
|
|
5249
|
+
}, [flat.buffer]);
|
|
5250
|
+
|
|
5251
|
+
this._buffer = [];
|
|
5252
|
+
this._bufferSize = 0;
|
|
5253
|
+
this._chunkReady = false;
|
|
5254
|
+
this._processingChunk = false;
|
|
5255
|
+
}
|
|
5256
|
+
}
|
|
5257
|
+
|
|
5258
|
+
return true;
|
|
5259
|
+
}
|
|
5260
|
+
}
|
|
5261
|
+
|
|
5262
|
+
registerProcessor("audio-capture-processor", AudioCaptureProcessor);
|
|
5263
|
+
`;
|
|
5264
|
+
const blob = new Blob([workerCode], { type: "application/javascript" });
|
|
5265
|
+
return URL.createObjectURL(blob);
|
|
5266
|
+
};
|
|
5267
|
+
var useAudioCapture = ({
|
|
5268
|
+
onAudioChunk,
|
|
5269
|
+
onAudioComplete,
|
|
5270
|
+
onAudioFile,
|
|
5271
|
+
silenceRemoval = false,
|
|
5272
|
+
chunkDuration = 30,
|
|
5273
|
+
// Default 30 seconds per chunk
|
|
5274
|
+
format = "raw"
|
|
5275
|
+
}) => {
|
|
5276
|
+
const [isRecording, setIsRecording] = React3.useState(false);
|
|
5277
|
+
const [isPaused, setIsPaused] = React3.useState(false);
|
|
5278
|
+
const [isProcessing, setIsProcessing] = React3.useState(false);
|
|
5279
|
+
const [error, setError] = React3.useState(null);
|
|
5280
|
+
const [currentDeviceId, setCurrentDeviceId] = React3.useState(null);
|
|
5281
|
+
const [availableDevices, setAvailableDevices] = React3.useState([]);
|
|
5282
|
+
const [audioLevel, setAudioLevel] = React3.useState(0);
|
|
5283
|
+
const [noAudioDetected, setNoAudioDetected] = React3.useState(false);
|
|
5284
|
+
const [recordingDuration, setRecordingDuration] = React3.useState(0);
|
|
5285
|
+
const [totalChunks, setTotalChunks] = React3.useState(0);
|
|
5286
|
+
const mediaStreamRef = React3.useRef(null);
|
|
5287
|
+
const processorRef = React3.useRef(null);
|
|
5288
|
+
const audioContextRef = React3.useRef(null);
|
|
5289
|
+
const chunkIntervalRef = React3.useRef(null);
|
|
5290
|
+
const recordingStartTimeRef = React3.useRef(0);
|
|
5291
|
+
const durationIntervalRef = React3.useRef(null);
|
|
5292
|
+
const sequenceCounterRef = React3.useRef(0);
|
|
5293
|
+
const allAudioChunksRef = React3.useRef([]);
|
|
5294
|
+
const {
|
|
5295
|
+
removeSilence,
|
|
5296
|
+
isLoaded,
|
|
5297
|
+
isConverting,
|
|
5298
|
+
progress,
|
|
5299
|
+
statusMessage,
|
|
5300
|
+
convertToWav
|
|
5301
|
+
} = useFFmpegConverter_default();
|
|
5302
|
+
const validateMicrophoneAccess = React3.useCallback(async () => {
|
|
5303
|
+
try {
|
|
5304
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
5305
|
+
const audioInputDevices = devices.filter((device) => device.kind === "audioinput");
|
|
5306
|
+
setAvailableDevices(audioInputDevices);
|
|
5307
|
+
if (audioInputDevices.length === 0) {
|
|
5308
|
+
throw new Error("No microphone devices detected. Please connect a microphone.");
|
|
5309
|
+
}
|
|
5310
|
+
const testStream = await navigator.mediaDevices.getUserMedia({
|
|
5311
|
+
audio: {
|
|
5312
|
+
deviceId: currentDeviceId ? { exact: currentDeviceId } : void 0
|
|
5313
|
+
}
|
|
5314
|
+
});
|
|
5315
|
+
const audioTracks = testStream.getAudioTracks();
|
|
5316
|
+
if (audioTracks.length > 0) {
|
|
5317
|
+
const track = audioTracks[0];
|
|
5318
|
+
const settings = track.getSettings();
|
|
5319
|
+
setCurrentDeviceId(settings.deviceId || null);
|
|
5320
|
+
}
|
|
5321
|
+
testStream.getTracks().forEach((track) => track.stop());
|
|
5322
|
+
return true;
|
|
5323
|
+
} catch (error2) {
|
|
5324
|
+
console.error("Microphone validation failed:", error2);
|
|
5325
|
+
if (error2 instanceof Error) {
|
|
5326
|
+
let errorMessage = "";
|
|
5327
|
+
if (error2.name === "NotFoundError" || error2.name === "DevicesNotFoundError") {
|
|
5328
|
+
errorMessage = "No microphone devices found. Please connect a microphone.";
|
|
5329
|
+
} else if (error2.name === "NotAllowedError" || error2.name === "PermissionDeniedError") {
|
|
5330
|
+
errorMessage = "Microphone permission denied. Please allow access and try again.";
|
|
5331
|
+
} else if (error2.name === "NotReadableError" || error2.name === "TrackStartError") {
|
|
5332
|
+
errorMessage = "Microphone is busy or unavailable. Please close other applications using the microphone.";
|
|
5333
|
+
} else if (error2.name === "OverconstrainedError") {
|
|
5334
|
+
errorMessage = "Selected microphone device is not available. Please select a different device.";
|
|
5335
|
+
} else {
|
|
5336
|
+
errorMessage = `Microphone error: ${error2.message}`;
|
|
5337
|
+
}
|
|
5338
|
+
setError(errorMessage);
|
|
5339
|
+
} else {
|
|
5340
|
+
setError("Microphone access error occurred.");
|
|
5341
|
+
}
|
|
5342
|
+
return false;
|
|
5343
|
+
}
|
|
5344
|
+
}, [currentDeviceId]);
|
|
5345
|
+
const processAudioChunk = React3.useCallback(async (audioData, sequence, isFinal) => {
|
|
5346
|
+
try {
|
|
5347
|
+
setIsProcessing(true);
|
|
5348
|
+
let processedAudio = audioData;
|
|
5349
|
+
if (silenceRemoval && isLoaded && typeof removeSilence === "function") {
|
|
5350
|
+
console.log("Applying silence removal to audio chunk...");
|
|
5351
|
+
const tempFile = float32ToWavFile(audioData);
|
|
5352
|
+
const processedFile = await removeSilence(tempFile);
|
|
5353
|
+
if (processedFile) {
|
|
5354
|
+
const arrayBuffer = await processedFile.arrayBuffer();
|
|
5355
|
+
const dataView = new DataView(arrayBuffer);
|
|
5356
|
+
const samples = new Float32Array((arrayBuffer.byteLength - 44) / 2);
|
|
5357
|
+
for (let i = 0; i < samples.length; i++) {
|
|
5358
|
+
const int16 = dataView.getInt16(44 + i * 2, true);
|
|
5359
|
+
samples[i] = int16 / (int16 < 0 ? 32768 : 32767);
|
|
5360
|
+
}
|
|
5361
|
+
processedAudio = samples;
|
|
5362
|
+
}
|
|
5363
|
+
}
|
|
5364
|
+
if (!isFinal) {
|
|
5365
|
+
allAudioChunksRef.current.push(processedAudio);
|
|
5366
|
+
}
|
|
5367
|
+
if (onAudioChunk) {
|
|
5368
|
+
onAudioChunk(processedAudio, sequence, isFinal);
|
|
5369
|
+
}
|
|
5370
|
+
if (isFinal) {
|
|
5371
|
+
const totalLength = allAudioChunksRef.current.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
5372
|
+
const finalAudio = new Float32Array(totalLength);
|
|
5373
|
+
let offset = 0;
|
|
5374
|
+
for (const chunk of allAudioChunksRef.current) {
|
|
5375
|
+
finalAudio.set(chunk, offset);
|
|
5376
|
+
offset += chunk.length;
|
|
5377
|
+
}
|
|
5378
|
+
if (onAudioComplete) {
|
|
5379
|
+
onAudioComplete(finalAudio);
|
|
5380
|
+
}
|
|
5381
|
+
if (onAudioFile && format === "wav") {
|
|
5382
|
+
const wavFile = await createWavFile(finalAudio);
|
|
5383
|
+
onAudioFile(wavFile);
|
|
5384
|
+
} else if (onAudioFile && format === "raw") {
|
|
5385
|
+
const rawFile = createRawFile(finalAudio);
|
|
5386
|
+
onAudioFile(rawFile);
|
|
5387
|
+
}
|
|
5388
|
+
allAudioChunksRef.current = [];
|
|
5389
|
+
}
|
|
5390
|
+
} catch (error2) {
|
|
5391
|
+
console.error("Error processing audio chunk:", error2);
|
|
5392
|
+
setError(`Audio processing failed: ${error2 instanceof Error ? error2.message : "Unknown error"}`);
|
|
5393
|
+
} finally {
|
|
5394
|
+
setIsProcessing(false);
|
|
5395
|
+
}
|
|
5396
|
+
}, [silenceRemoval, isLoaded, removeSilence, onAudioChunk, onAudioComplete, onAudioFile, format]);
|
|
5397
|
+
const createWavFile = React3.useCallback(async (samples) => {
|
|
5398
|
+
if (isLoaded && typeof convertToWav === "function") {
|
|
5399
|
+
const result = await convertToWav(samples);
|
|
5400
|
+
return result || float32ToWavFile(samples);
|
|
5401
|
+
} else {
|
|
5402
|
+
return float32ToWavFile(samples);
|
|
5403
|
+
}
|
|
5404
|
+
}, [isLoaded, convertToWav]);
|
|
5405
|
+
const createRawFile = React3.useCallback((samples) => {
|
|
5406
|
+
const timestamp = Date.now();
|
|
5407
|
+
const filename = `audio-recording-${timestamp}.raw`;
|
|
5408
|
+
const buffer = new ArrayBuffer(samples.byteLength);
|
|
5409
|
+
const uint8Array = new Uint8Array(buffer);
|
|
5410
|
+
const sourceArray = new Uint8Array(samples.buffer, samples.byteOffset, samples.byteLength);
|
|
5411
|
+
uint8Array.set(sourceArray);
|
|
5412
|
+
return new File([buffer], filename, {
|
|
5413
|
+
type: "application/octet-stream",
|
|
5414
|
+
lastModified: timestamp
|
|
5415
|
+
});
|
|
5416
|
+
}, []);
|
|
5417
|
+
const float32ToWavFile = (samples) => {
|
|
5418
|
+
const sampleRate = audioContextRef.current?.sampleRate || 44100;
|
|
5419
|
+
const buffer = new ArrayBuffer(44 + samples.length * 2);
|
|
5420
|
+
const view = new DataView(buffer);
|
|
5421
|
+
const writeString = (offset2, str) => {
|
|
5422
|
+
for (let i = 0; i < str.length; i++) {
|
|
5423
|
+
view.setUint8(offset2 + i, str.charCodeAt(i));
|
|
5424
|
+
}
|
|
5425
|
+
};
|
|
5426
|
+
writeString(0, "RIFF");
|
|
5427
|
+
view.setUint32(4, 36 + samples.length * 2, true);
|
|
5428
|
+
writeString(8, "WAVE");
|
|
5429
|
+
writeString(12, "fmt ");
|
|
5430
|
+
view.setUint32(16, 16, true);
|
|
5431
|
+
view.setUint16(20, 1, true);
|
|
5432
|
+
view.setUint16(22, 1, true);
|
|
5433
|
+
view.setUint32(24, sampleRate, true);
|
|
5434
|
+
view.setUint32(28, sampleRate * 2, true);
|
|
5435
|
+
view.setUint16(32, 2, true);
|
|
5436
|
+
view.setUint16(34, 16, true);
|
|
5437
|
+
writeString(36, "data");
|
|
5438
|
+
view.setUint32(40, samples.length * 2, true);
|
|
5439
|
+
let offset = 44;
|
|
5440
|
+
for (let i = 0; i < samples.length; i++) {
|
|
5441
|
+
const sample = Math.max(-1, Math.min(1, samples[i]));
|
|
5442
|
+
view.setInt16(offset, sample < 0 ? sample * 32768 : sample * 32767, true);
|
|
5443
|
+
offset += 2;
|
|
5444
|
+
}
|
|
5445
|
+
const timestamp = Date.now();
|
|
5446
|
+
const filename = `audio-recording-${timestamp}.wav`;
|
|
5447
|
+
return new File([view], filename, {
|
|
5448
|
+
type: "audio/wav",
|
|
5449
|
+
lastModified: timestamp
|
|
5450
|
+
});
|
|
5451
|
+
};
|
|
5452
|
+
const startRecording = React3.useCallback(async () => {
|
|
5453
|
+
try {
|
|
5454
|
+
setError(null);
|
|
5455
|
+
setNoAudioDetected(false);
|
|
5456
|
+
const micValid = await validateMicrophoneAccess();
|
|
5457
|
+
if (!micValid) return;
|
|
5458
|
+
sequenceCounterRef.current = 0;
|
|
5459
|
+
allAudioChunksRef.current = [];
|
|
5460
|
+
setTotalChunks(0);
|
|
5461
|
+
setRecordingDuration(0);
|
|
5462
|
+
recordingStartTimeRef.current = Date.now();
|
|
5463
|
+
const stream = await navigator.mediaDevices.getUserMedia({
|
|
5464
|
+
audio: {
|
|
5465
|
+
deviceId: currentDeviceId ? { exact: currentDeviceId } : void 0,
|
|
5466
|
+
echoCancellation: true,
|
|
5467
|
+
noiseSuppression: true,
|
|
5468
|
+
autoGainControl: true
|
|
5469
|
+
}
|
|
5470
|
+
});
|
|
5471
|
+
mediaStreamRef.current = stream;
|
|
5472
|
+
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
5473
|
+
const processorUrl = createAudioCaptureWorker();
|
|
5474
|
+
await audioContext.audioWorklet.addModule(processorUrl);
|
|
5475
|
+
URL.revokeObjectURL(processorUrl);
|
|
5476
|
+
const processor = new AudioWorkletNode(audioContext, "audio-capture-processor");
|
|
5477
|
+
processor.port.onmessage = (event) => {
|
|
5478
|
+
if (event.data.command === "chunk") {
|
|
5479
|
+
const audioData = new Float32Array(event.data.audioBuffer);
|
|
5480
|
+
const sequence = sequenceCounterRef.current++;
|
|
5481
|
+
setTotalChunks(sequence + 1);
|
|
5482
|
+
processAudioChunk(audioData, sequence, false);
|
|
5483
|
+
} else if (event.data.command === "finalChunk") {
|
|
5484
|
+
const audioData = new Float32Array(event.data.audioBuffer);
|
|
5485
|
+
const sequence = sequenceCounterRef.current++;
|
|
5486
|
+
processAudioChunk(audioData, sequence, true);
|
|
5487
|
+
} else if (event.data.command === "audioLevel") {
|
|
5488
|
+
setAudioLevel(event.data.level);
|
|
5489
|
+
} else if (event.data.command === "noAudioDetected") {
|
|
5490
|
+
setNoAudioDetected(true);
|
|
5491
|
+
setError(event.data.message);
|
|
5492
|
+
}
|
|
5493
|
+
};
|
|
5494
|
+
const source = audioContext.createMediaStreamSource(stream);
|
|
5495
|
+
source.connect(processor);
|
|
5496
|
+
audioContextRef.current = audioContext;
|
|
5497
|
+
processorRef.current = processor;
|
|
5498
|
+
setIsRecording(true);
|
|
5499
|
+
const chunkIntervalId = window.setInterval(() => {
|
|
5500
|
+
processor.port.postMessage({ command: "getChunk" });
|
|
5501
|
+
}, chunkDuration * 1e3);
|
|
5502
|
+
chunkIntervalRef.current = chunkIntervalId;
|
|
5503
|
+
const durationIntervalId = window.setInterval(() => {
|
|
5504
|
+
const elapsed = (Date.now() - recordingStartTimeRef.current) / 1e3;
|
|
5505
|
+
setRecordingDuration(elapsed);
|
|
5506
|
+
}, 100);
|
|
5507
|
+
durationIntervalRef.current = durationIntervalId;
|
|
5508
|
+
} catch (err) {
|
|
5509
|
+
console.error("Recording start failed:", err);
|
|
5510
|
+
setError(err instanceof Error ? err.message : "Failed to start recording");
|
|
5511
|
+
}
|
|
5512
|
+
}, [validateMicrophoneAccess, currentDeviceId, chunkDuration, processAudioChunk]);
|
|
5513
|
+
const stopRecording = React3.useCallback(() => {
|
|
5514
|
+
console.log("Stopping recording...");
|
|
5515
|
+
if (chunkIntervalRef.current) {
|
|
5516
|
+
clearInterval(chunkIntervalRef.current);
|
|
5517
|
+
chunkIntervalRef.current = null;
|
|
5518
|
+
}
|
|
5519
|
+
if (durationIntervalRef.current) {
|
|
5520
|
+
clearInterval(durationIntervalRef.current);
|
|
5521
|
+
durationIntervalRef.current = null;
|
|
5522
|
+
}
|
|
5523
|
+
if (processorRef.current) {
|
|
5524
|
+
processorRef.current.port.postMessage({ command: "stop" });
|
|
5525
|
+
}
|
|
5526
|
+
if (mediaStreamRef.current) {
|
|
5527
|
+
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
|
|
5528
|
+
mediaStreamRef.current = null;
|
|
5529
|
+
}
|
|
5530
|
+
if (audioContextRef.current) {
|
|
5531
|
+
audioContextRef.current.close();
|
|
5532
|
+
audioContextRef.current = null;
|
|
5533
|
+
}
|
|
5534
|
+
processorRef.current = null;
|
|
5535
|
+
setIsRecording(false);
|
|
5536
|
+
}, []);
|
|
5537
|
+
const pauseRecording = React3.useCallback(() => {
|
|
5538
|
+
if (!isRecording || isPaused) return;
|
|
5539
|
+
if (chunkIntervalRef.current) {
|
|
5540
|
+
clearInterval(chunkIntervalRef.current);
|
|
5541
|
+
chunkIntervalRef.current = null;
|
|
5542
|
+
}
|
|
5543
|
+
if (durationIntervalRef.current) {
|
|
5544
|
+
clearInterval(durationIntervalRef.current);
|
|
5545
|
+
durationIntervalRef.current = null;
|
|
5546
|
+
}
|
|
5547
|
+
setIsPaused(true);
|
|
5548
|
+
if (processorRef.current) {
|
|
5549
|
+
processorRef.current.port.postMessage({ command: "pause" });
|
|
5550
|
+
}
|
|
5551
|
+
setIsRecording(false);
|
|
5552
|
+
}, [isRecording, isPaused]);
|
|
5553
|
+
const resumeRecording = React3.useCallback(() => {
|
|
5554
|
+
if (!isPaused) return;
|
|
5555
|
+
setIsPaused(false);
|
|
5556
|
+
setIsRecording(true);
|
|
5557
|
+
setNoAudioDetected(false);
|
|
5558
|
+
if (processorRef.current) {
|
|
5559
|
+
processorRef.current.port.postMessage({ command: "resume" });
|
|
5560
|
+
}
|
|
5561
|
+
const chunkIntervalId = window.setInterval(() => {
|
|
5562
|
+
processorRef.current?.port.postMessage({ command: "getChunk" });
|
|
5563
|
+
}, chunkDuration * 1e3);
|
|
5564
|
+
chunkIntervalRef.current = chunkIntervalId;
|
|
5565
|
+
const durationIntervalId = window.setInterval(() => {
|
|
5566
|
+
const elapsed = (Date.now() - recordingStartTimeRef.current) / 1e3;
|
|
5567
|
+
setRecordingDuration(elapsed);
|
|
5568
|
+
}, 100);
|
|
5569
|
+
durationIntervalRef.current = durationIntervalId;
|
|
5570
|
+
}, [isPaused, chunkDuration]);
|
|
5571
|
+
const selectMicrophone = React3.useCallback(async (deviceId) => {
|
|
5572
|
+
try {
|
|
5573
|
+
setCurrentDeviceId(deviceId);
|
|
5574
|
+
if (isRecording) {
|
|
5575
|
+
await stopRecording();
|
|
5576
|
+
await startRecording();
|
|
5577
|
+
}
|
|
5578
|
+
} catch (error2) {
|
|
5579
|
+
console.error("Device selection failed:", error2);
|
|
5580
|
+
setError("Failed to switch to selected microphone.");
|
|
5581
|
+
}
|
|
5582
|
+
}, [isRecording, stopRecording, startRecording]);
|
|
5583
|
+
return {
|
|
5584
|
+
mediaStreamRef,
|
|
5585
|
+
startRecording,
|
|
5586
|
+
stopRecording,
|
|
5587
|
+
pauseRecording,
|
|
5588
|
+
resumeRecording,
|
|
5589
|
+
isRecording,
|
|
5590
|
+
isPaused,
|
|
5591
|
+
isProcessing,
|
|
5592
|
+
error,
|
|
5593
|
+
availableDevices,
|
|
5594
|
+
currentDeviceId,
|
|
5595
|
+
selectMicrophone,
|
|
5596
|
+
validateMicrophoneAccess,
|
|
5597
|
+
audioLevel,
|
|
5598
|
+
noAudioDetected,
|
|
5599
|
+
isConverting,
|
|
5600
|
+
progress,
|
|
5601
|
+
statusMessage,
|
|
5602
|
+
recordingDuration,
|
|
5603
|
+
totalChunks
|
|
5604
|
+
};
|
|
5605
|
+
};
|
|
5606
|
+
var useAudioCapture_default = useAudioCapture;
|
|
5607
|
+
var tailwindStyles2 = `
|
|
5608
|
+
.space-y-4 > :not([hidden]) ~ :not([hidden]) { margin-top: 1rem; }
|
|
5609
|
+
.bg-orange-50 { background-color: rgb(255 247 237); }
|
|
5610
|
+
.bg-blue-900 { background-color: rgb(30 58 138); }
|
|
5611
|
+
.text-yellow-200 { color: rgb(254 240 138); }
|
|
5612
|
+
.hover\\:bg-blue-700:hover { background-color: rgb(29 78 216); }
|
|
5613
|
+
.dark .dark\\:bg-orange-900\\/20 { background-color: rgb(194 65 12 / 0.2); }
|
|
5614
|
+
.border { border-width: 1px; }
|
|
5615
|
+
.border-orange-200 { border-color: rgb(254 215 170); }
|
|
5616
|
+
.dark .dark\\:border-orange-800 { border-color: rgb(154 52 18); }
|
|
5617
|
+
.rounded-lg { border-radius: 0.5rem; }
|
|
5618
|
+
.p-4 { padding: 1rem; }
|
|
5619
|
+
.flex { display: flex; }
|
|
5620
|
+
.items-start { align-items: flex-start; }
|
|
5621
|
+
.items-center { align-items: center; }
|
|
5622
|
+
.justify-center { justify-content: center; }
|
|
5623
|
+
.justify-between { justify-content: space-between; }
|
|
5624
|
+
.flex-shrink-0 { flex-shrink: 0; }
|
|
5625
|
+
.h-5 { height: 1.25rem; }
|
|
5626
|
+
.w-5 { width: 1.25rem; }
|
|
5627
|
+
.text-orange-400 { color: rgb(251 146 60); }
|
|
5628
|
+
.ml-3 { margin-left: 0.75rem; }
|
|
5629
|
+
.mr-2 { margin-right: 0.5rem; }
|
|
5630
|
+
.flex-1 { flex: 1 1 0%; }
|
|
5631
|
+
.text-sm { font-size: 0.875rem; line-height: 1.25rem; }
|
|
5632
|
+
.font-medium { font-weight: 500; }
|
|
5633
|
+
.text-orange-800 { color: rgb(154 52 18); }
|
|
5634
|
+
.dark .dark\\:text-orange-200 { color: rgb(254 215 170); }
|
|
5635
|
+
.mt-2 { margin-top: 0.5rem; }
|
|
5636
|
+
.text-orange-700 { color: rgb(194 65 12); }
|
|
5637
|
+
.dark .dark\\:text-orange-300 { color: rgb(253 186 116); }
|
|
5638
|
+
.list-disc { list-style-type: disc; }
|
|
5639
|
+
.list-inside { list-style-position: inside; }
|
|
5640
|
+
.bg-red-50 { background-color: rgb(254 242 242); }
|
|
5641
|
+
.dark .dark\\:bg-red-900\\/20 { background-color: rgb(127 29 29 / 0.2); }
|
|
5642
|
+
.border-red-200 { border-color: rgb(254 202 202); }
|
|
5643
|
+
.dark .dark\\:border-red-800 { border-color: rgb(153 27 27); }
|
|
5644
|
+
.text-red-400 { color: rgb(248 113 113); }
|
|
5645
|
+
.text-red-800 { color: rgb(153 27 27); }
|
|
5646
|
+
.dark .dark\\:text-red-200 { color: rgb(254 202 202); }
|
|
5647
|
+
.text-red-700 { color: rgb(185 28 28); }
|
|
5648
|
+
.dark .dark\\:text-red-300 { color: rgb(252 165 165); }
|
|
5649
|
+
.mt-4 { margin-top: 1rem; }
|
|
5650
|
+
.bg-red-600 { background-color: rgb(220 38 38); }
|
|
5651
|
+
.hover\\:bg-red-700:hover { background-color: rgb(185 28 28); }
|
|
5652
|
+
.text-white { color: rgb(255 255 255); }
|
|
5653
|
+
.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
|
|
5654
|
+
.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
|
|
5655
|
+
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
|
|
5656
|
+
.px-4 { padding-left: 1rem; padding-right: 1rem; }
|
|
5657
|
+
.rounded { border-radius: 0.25rem; }
|
|
5658
|
+
.rounded-full { border-radius: 9999px; }
|
|
5659
|
+
.transition-colors { transition-property: color, background-color, border-color, text-decoration-color, fill, stroke; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; }
|
|
5660
|
+
.bg-yellow-50 { background-color: rgb(254 252 232); }
|
|
5661
|
+
.dark .dark\\:bg-yellow-900\\/20 { background-color: rgb(133 77 14 / 0.2); }
|
|
5662
|
+
.border-yellow-200 { border-color: rgb(254 240 138); }
|
|
5663
|
+
.dark .dark\\:border-yellow-800 { border-color: rgb(133 77 14); }
|
|
5664
|
+
.text-yellow-400 { color: rgb(250 204 21); }
|
|
5665
|
+
.text-yellow-800 { color: rgb(133 77 14); }
|
|
5666
|
+
.dark .dark\\:text-yellow-200 { color: rgb(254 240 138); }
|
|
5667
|
+
.text-yellow-700 { color: rgb(161 98 7); }
|
|
5668
|
+
.dark .dark\\:text-yellow-300 { color: rgb(253 224 71); }
|
|
5669
|
+
.space-x-3 > :not([hidden]) ~ :not([hidden]) { margin-left: 0.75rem; }
|
|
5670
|
+
.space-x-1 > :not([hidden]) ~ :not([hidden]) { margin-left: 0.25rem; }
|
|
5671
|
+
.space-x-2 > :not([hidden]) ~ :not([hidden]) { margin-left: 0.5rem; }
|
|
5672
|
+
.bg-yellow-600 { background-color: rgb(202 138 4); }
|
|
5673
|
+
.hover\\:bg-yellow-700:hover { background-color: rgb(161 98 7); }
|
|
5674
|
+
.disabled\\:opacity-50:disabled { opacity: 0.5; }
|
|
5675
|
+
.h-3 { height: 0.75rem; }
|
|
5676
|
+
.w-3 { width: 0.75rem; }
|
|
5677
|
+
.animate-spin { animation: spin 1s linear infinite; }
|
|
5678
|
+
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
|
|
5679
|
+
.bg-gray-600 { background-color: rgb(75 85 99); }
|
|
5680
|
+
.hover\\:bg-gray-700:hover { background-color: rgb(55 65 81); }
|
|
5681
|
+
.bg-blue-50 { background-color: rgb(239 246 255); }
|
|
5682
|
+
.dark .dark\\:bg-blue-900\\/20 { background-color: rgb(30 58 138 / 0.2); }
|
|
5683
|
+
.border-blue-200 { border-color: rgb(191 219 254); }
|
|
5684
|
+
.dark .dark\\:border-blue-800 { border-color: rgb(30 64 175); }
|
|
5685
|
+
.text-blue-600 { color: rgb(37 99 235); }
|
|
5686
|
+
.block { display: block; }
|
|
5687
|
+
.text-blue-700 { color: rgb(29 78 216); }
|
|
5688
|
+
.dark .dark\\:text-blue-300 { color: rgb(147 197 253); }
|
|
5689
|
+
.text-xs { font-size: 0.75rem; line-height: 1rem; }
|
|
5690
|
+
.dark .dark\\:text-blue-400 { color: rgb(96 165 250); }
|
|
5691
|
+
.mt-1 { margin-top: 0.25rem; }
|
|
5692
|
+
.bg-teal-600 { background-color: rgb(13 148 136); }
|
|
5693
|
+
.hover\\:bg-teal-700:hover { background-color: rgb(15 118 110); }
|
|
5694
|
+
.bg-green-600 { background-color: rgb(22 163 74); }
|
|
5695
|
+
.hover\\:bg-green-700:hover { background-color: rgb(21 128 61); }
|
|
5696
|
+
.bg-yellow-500 { background-color: rgb(234 179 8); }
|
|
5697
|
+
.hover\\:bg-yellow-600:hover { background-color: rgb(202 138 4); }
|
|
5698
|
+
.bg-gradient-to-r { background-image: linear-gradient(to right, var(--tw-gradient-stops)); }
|
|
5699
|
+
.from-purple-400 { --tw-gradient-from: #c084fc; --tw-gradient-to: rgb(192 132 252 / 0); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
|
|
5700
|
+
.to-blue-500 { --tw-gradient-to: #3b82f6; }
|
|
5701
|
+
.hover\\:from-purple-500:hover { --tw-gradient-from: #a855f7; --tw-gradient-to: rgb(168 85 247 / 0); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); }
|
|
5702
|
+
.hover\\:to-blue-600:hover { --tw-gradient-to: #2563eb; }
|
|
5703
|
+
.opacity-50 { opacity: 0.5; }
|
|
5704
|
+
.cursor-not-allowed { cursor: not-allowed; }
|
|
5705
|
+
.text-gray-600 { color: rgb(75 85 99); }
|
|
5706
|
+
.dark .dark\\:text-gray-300 { color: rgb(209 213 219); }
|
|
5707
|
+
.bg-gray-100 { background-color: rgb(243 244 246); }
|
|
5708
|
+
.dark .dark\\:bg-gray-800 { background-color: rgb(31 41 55); }
|
|
5709
|
+
.border-gray-200 { border-color: rgb(229 231 235); }
|
|
5710
|
+
.dark .dark\\:border-gray-700 { border-color: rgb(55 65 81); }
|
|
5711
|
+
.cursor-pointer { cursor: pointer; }
|
|
5712
|
+
.hover\\:bg-gray-200:hover { background-color: rgb(229 231 235); }
|
|
5713
|
+
.dark .dark\\:hover\\:bg-gray-700:hover { background-color: rgb(55 65 81); }
|
|
5714
|
+
`;
|
|
5715
|
+
var stylesInjected2 = false;
|
|
5716
|
+
var injectTailwindStyles2 = () => {
|
|
5717
|
+
if (!stylesInjected2 && typeof document !== "undefined") {
|
|
5718
|
+
const styleElement = document.createElement("style");
|
|
5719
|
+
styleElement.textContent = tailwindStyles2;
|
|
5720
|
+
document.head.appendChild(styleElement);
|
|
5721
|
+
stylesInjected2 = true;
|
|
5722
|
+
}
|
|
5723
|
+
};
|
|
5724
|
+
var AudioCapture = ({
|
|
5725
|
+
onAudioChunk,
|
|
5726
|
+
onAudioComplete,
|
|
5727
|
+
onAudioFile,
|
|
5728
|
+
silenceRemoval = false,
|
|
5729
|
+
chunkDuration = 30,
|
|
5730
|
+
format = "raw",
|
|
5731
|
+
showDownload = false,
|
|
5732
|
+
className = "",
|
|
5733
|
+
style
|
|
5734
|
+
}) => {
|
|
5735
|
+
React3__namespace.useEffect(() => {
|
|
5736
|
+
injectTailwindStyles2();
|
|
5737
|
+
}, []);
|
|
5738
|
+
const {
|
|
5739
|
+
mediaStreamRef,
|
|
5740
|
+
startRecording,
|
|
5741
|
+
stopRecording,
|
|
5742
|
+
pauseRecording,
|
|
5743
|
+
resumeRecording,
|
|
5744
|
+
isRecording,
|
|
5745
|
+
isPaused,
|
|
5746
|
+
isProcessing,
|
|
5747
|
+
error,
|
|
5748
|
+
availableDevices,
|
|
5749
|
+
currentDeviceId,
|
|
5750
|
+
selectMicrophone,
|
|
5751
|
+
validateMicrophoneAccess,
|
|
5752
|
+
noAudioDetected,
|
|
5753
|
+
isConverting,
|
|
5754
|
+
progress,
|
|
5755
|
+
statusMessage,
|
|
5756
|
+
recordingDuration,
|
|
5757
|
+
totalChunks
|
|
5758
|
+
} = useAudioCapture_default({
|
|
5759
|
+
onAudioChunk,
|
|
5760
|
+
onAudioComplete,
|
|
5761
|
+
onAudioFile,
|
|
5762
|
+
silenceRemoval,
|
|
5763
|
+
chunkDuration,
|
|
5764
|
+
format
|
|
5765
|
+
});
|
|
5766
|
+
const [isDisabled, setIsDisabled] = React3__namespace.useState(false);
|
|
5767
|
+
const [lastAudioFile, setLastAudioFile] = React3__namespace.useState(null);
|
|
5768
|
+
const [toast, setToast] = React3__namespace.useState({
|
|
5769
|
+
show: false,
|
|
5770
|
+
message: "",
|
|
5771
|
+
type: "success"
|
|
5772
|
+
});
|
|
5773
|
+
React3__namespace.useEffect(() => {
|
|
5774
|
+
if (onAudioFile) {
|
|
5775
|
+
const originalCallback = onAudioFile;
|
|
5776
|
+
onAudioFile = (file) => {
|
|
5777
|
+
setLastAudioFile(file);
|
|
5778
|
+
originalCallback(file);
|
|
5779
|
+
};
|
|
5780
|
+
}
|
|
5781
|
+
}, [onAudioFile]);
|
|
5782
|
+
const handleStopClick = () => {
|
|
5783
|
+
setIsDisabled(true);
|
|
5784
|
+
stopRecording();
|
|
5785
|
+
};
|
|
5786
|
+
const handleStartRecording = () => {
|
|
5787
|
+
setIsDisabled(false);
|
|
5788
|
+
setLastAudioFile(null);
|
|
5789
|
+
startRecording();
|
|
5790
|
+
};
|
|
5791
|
+
const handleDownload = () => {
|
|
5792
|
+
if (lastAudioFile) {
|
|
5793
|
+
const url = URL.createObjectURL(lastAudioFile);
|
|
5794
|
+
const a = document.createElement("a");
|
|
5795
|
+
a.href = url;
|
|
5796
|
+
a.download = lastAudioFile.name;
|
|
5797
|
+
document.body.appendChild(a);
|
|
5798
|
+
a.click();
|
|
5799
|
+
document.body.removeChild(a);
|
|
5800
|
+
URL.revokeObjectURL(url);
|
|
5801
|
+
setToast({
|
|
5802
|
+
show: true,
|
|
5803
|
+
message: `Downloaded ${lastAudioFile.name}`,
|
|
5804
|
+
type: "success"
|
|
5805
|
+
});
|
|
5806
|
+
}
|
|
5807
|
+
};
|
|
5808
|
+
React3__namespace.useEffect(() => {
|
|
5809
|
+
if (error) {
|
|
5810
|
+
setToast({ show: true, message: error, type: "error" });
|
|
5811
|
+
}
|
|
5812
|
+
}, [error]);
|
|
5813
|
+
const closeToast = () => {
|
|
5814
|
+
setToast({ ...toast, show: false });
|
|
5815
|
+
};
|
|
5816
|
+
const formatDuration = (seconds) => {
|
|
5817
|
+
const minutes = Math.floor(seconds / 60);
|
|
5818
|
+
const secs = Math.floor(seconds % 60);
|
|
5819
|
+
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
|
5820
|
+
};
|
|
5821
|
+
const isMicrophoneError = error && (error.toLowerCase().includes("microphone") || error.toLowerCase().includes("not found") || error.toLowerCase().includes("no audio") || error.toLowerCase().includes("devices not found") || error.toLowerCase().includes("access denied") || error.toLowerCase().includes("permission") || error.toLowerCase().includes("not allowed") || error.toLowerCase().includes("busy") || error.toLowerCase().includes("media devices not supported") || availableDevices.length === 0 || noAudioDetected);
|
|
5822
|
+
if (noAudioDetected || error && error.includes("No audio input detected")) {
|
|
5823
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-orange-50 dark:bg-orange-900/20 border border-orange-200 dark:border-orange-800 rounded-lg p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start", children: [
|
|
5824
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-5 w-5 text-orange-400" }) }),
|
|
5825
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-3 flex-1", children: [
|
|
5826
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-orange-800 dark:text-orange-200", children: "No Audio Input Detected" }),
|
|
5827
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 text-sm text-orange-700 dark:text-orange-300", children: [
|
|
5828
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { children: error }),
|
|
5829
|
+
/* @__PURE__ */ jsxRuntime.jsxs("ul", { className: "mt-2 list-disc list-inside", children: [
|
|
5830
|
+
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "Check if your microphone is properly connected" }),
|
|
5831
|
+
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "Ensure you're speaking close enough to the microphone" }),
|
|
5832
|
+
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "Try adjusting your microphone volume settings" }),
|
|
5833
|
+
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "Test your microphone in other applications" }),
|
|
5834
|
+
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "Make sure your browser has microphone permissions" }),
|
|
5835
|
+
/* @__PURE__ */ jsxRuntime.jsx("li", { children: "Please reload the page and try again" })
|
|
5836
|
+
] })
|
|
5837
|
+
] })
|
|
5838
|
+
] })
|
|
5839
|
+
] }) });
|
|
5840
|
+
}
|
|
5841
|
+
if (isMicrophoneError) {
|
|
5842
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start", children: [
|
|
5843
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-5 w-5 text-red-400" }) }),
|
|
5844
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-3 flex-1", children: [
|
|
5845
|
+
/* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-sm font-medium text-red-800 dark:text-red-200", children: "Audio Input Issue" }),
|
|
5846
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 text-sm text-red-700 dark:text-red-300", children: /* @__PURE__ */ jsxRuntime.jsx("p", { children: error }) }),
|
|
5847
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5848
|
+
"button",
|
|
5849
|
+
{
|
|
5850
|
+
onClick: () => {
|
|
5851
|
+
validateMicrophoneAccess();
|
|
5852
|
+
},
|
|
5853
|
+
className: "bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded text-sm transition-colors",
|
|
5854
|
+
children: "Check Again"
|
|
5855
|
+
}
|
|
5856
|
+
) })
|
|
5857
|
+
] })
|
|
5858
|
+
] }) });
|
|
5859
|
+
}
|
|
5860
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
|
|
5861
|
+
toast.show && /* @__PURE__ */ jsxRuntime.jsx(Toast, { message: toast.message, type: toast.type, onClose: closeToast, duration: 3 }),
|
|
5862
|
+
(isRecording || isPaused || recordingDuration > 0) && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-gray-100 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-center", children: [
|
|
5863
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-600 dark:text-gray-300", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex space-x-4", children: [
|
|
5864
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
5865
|
+
"Duration: ",
|
|
5866
|
+
formatDuration(recordingDuration)
|
|
5867
|
+
] }),
|
|
5868
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
5869
|
+
"Chunks: ",
|
|
5870
|
+
totalChunks
|
|
5871
|
+
] }),
|
|
5872
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { children: [
|
|
5873
|
+
"Format: ",
|
|
5874
|
+
format.toUpperCase()
|
|
5875
|
+
] }),
|
|
5876
|
+
silenceRemoval && /* @__PURE__ */ jsxRuntime.jsx("span", { children: "Silence Removal: ON" })
|
|
5877
|
+
] }) }),
|
|
5878
|
+
showDownload && lastAudioFile && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5879
|
+
"button",
|
|
5880
|
+
{
|
|
5881
|
+
onClick: handleDownload,
|
|
5882
|
+
className: "flex items-center space-x-1 bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded text-sm transition-colors",
|
|
5883
|
+
children: [
|
|
5884
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Download, { className: "h-3 w-3" }),
|
|
5885
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Download" })
|
|
5886
|
+
]
|
|
5887
|
+
}
|
|
5888
|
+
)
|
|
5889
|
+
] }) }),
|
|
5890
|
+
(isProcessing || isConverting) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center space-x-2 bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 rounded-lg p-4", children: [
|
|
5891
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin h-5 w-5 text-blue-600" }),
|
|
5892
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
|
|
5893
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-sm font-medium text-blue-700 dark:text-blue-300", children: isConverting ? `Processing Audio... ${Math.round(progress)}%` : "Processing audio chunk..." }),
|
|
5894
|
+
statusMessage && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "block text-xs text-blue-600 dark:text-blue-400 mt-1", children: statusMessage })
|
|
5895
|
+
] })
|
|
5896
|
+
] }),
|
|
5897
|
+
isRecording && !isPaused && mediaStreamRef.current && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5898
|
+
"div",
|
|
5899
|
+
{
|
|
5900
|
+
className: `audio-recorder-container ${isRecording && !isPaused ? "glow-active" : ""}`,
|
|
5901
|
+
children: [
|
|
5902
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "edge-container", children: [
|
|
5903
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "edge edge-top" }),
|
|
5904
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "edge edge-right" }),
|
|
5905
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "edge edge-bottom" }),
|
|
5906
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "edge edge-left" })
|
|
5907
|
+
] }),
|
|
5908
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center items-center", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
5909
|
+
AudioVisualizerImproved,
|
|
5910
|
+
{
|
|
5911
|
+
mediaStream: mediaStreamRef.current,
|
|
5912
|
+
isRecording: isRecording && !isPaused,
|
|
5913
|
+
forceLight: false
|
|
5914
|
+
}
|
|
5915
|
+
) })
|
|
5916
|
+
]
|
|
5917
|
+
}
|
|
5918
|
+
),
|
|
5919
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex justify-center", children: isProcessing ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center space-x-2 bg-teal-600 hover:bg-teal-700 text-white py-2 px-4 rounded-full", children: [
|
|
5920
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "animate-spin h-5 w-5" }),
|
|
5921
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Processing..." })
|
|
5922
|
+
] }) : isRecording || isPaused ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex space-x-2", children: [
|
|
5923
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
5924
|
+
"button",
|
|
5925
|
+
{
|
|
5926
|
+
className: `flex items-center justify-center space-x-2 bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded-full transition-colors ${isDisabled ? "opacity-50 cursor-not-allowed" : ""}`,
|
|
5927
|
+
onClick: handleStopClick,
|
|
5928
|
+
disabled: isDisabled,
|
|
5929
|
+
children: [
|
|
5930
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, { className: "h-5 w-5" }),
|
|
5931
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Stop" })
|
|
5932
|
+
]
|
|
5933
|
+
}
|
|
5934
|
+
),
|
|
5935
|
+
!isPaused ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5936
|
+
"button",
|
|
5937
|
+
{
|
|
5938
|
+
className: "flex items-center justify-center space-x-2 bg-yellow-500 hover:bg-yellow-600 text-white py-2 px-4 rounded-full transition-colors",
|
|
5939
|
+
onClick: pauseRecording,
|
|
5940
|
+
children: [
|
|
5941
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Pause, { className: "h-5 w-5" }),
|
|
5942
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Pause" })
|
|
5943
|
+
]
|
|
5944
|
+
}
|
|
5945
|
+
) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5946
|
+
"button",
|
|
5947
|
+
{
|
|
5948
|
+
className: "flex items-center justify-center space-x-2 bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded-full transition-colors",
|
|
5949
|
+
onClick: resumeRecording,
|
|
5950
|
+
children: [
|
|
5951
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "h-5 w-5" }),
|
|
5952
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Resume" })
|
|
5953
|
+
]
|
|
5954
|
+
}
|
|
5955
|
+
)
|
|
5956
|
+
] }) : /* @__PURE__ */ jsxRuntime.jsxs(
|
|
5957
|
+
"button",
|
|
5958
|
+
{
|
|
5959
|
+
className: className ? `flex items-center justify-center space-x-2 py-2 px-4 rounded-full transition-colors ${className}` : "flex items-center justify-center space-x-2 bg-gradient-to-r from-purple-400 to-blue-500 hover:bg-gradient-to-r hover:from-purple-500 hover:to-blue-600 text-white py-2 px-4 rounded-full transition-colors",
|
|
5960
|
+
onClick: handleStartRecording,
|
|
5961
|
+
children: [
|
|
5962
|
+
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Mic, { className: "h-5 w-5" }),
|
|
5963
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { children: "Start Recording" })
|
|
5964
|
+
]
|
|
5965
|
+
}
|
|
5966
|
+
) }),
|
|
5967
|
+
availableDevices.length > 1 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4", children: [
|
|
5968
|
+
/* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2", children: "Select Microphone:" }),
|
|
5969
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
5970
|
+
"select",
|
|
5971
|
+
{
|
|
5972
|
+
value: currentDeviceId || "",
|
|
5973
|
+
onChange: (e) => selectMicrophone(e.target.value),
|
|
5974
|
+
className: "w-full p-2 border border-gray-200 dark:border-gray-700 rounded bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100",
|
|
5975
|
+
disabled: isRecording,
|
|
5976
|
+
children: availableDevices.map((device) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: device.deviceId, children: device.label || `Microphone ${device.deviceId.slice(0, 8)}` }, device.deviceId))
|
|
5977
|
+
}
|
|
5978
|
+
)
|
|
5979
|
+
] })
|
|
5980
|
+
] });
|
|
5981
|
+
};
|
|
5982
|
+
var AudioCapture_default = AudioCapture;
|
|
5098
5983
|
|
|
5984
|
+
exports.AudioCapture = AudioCapture_default;
|
|
5099
5985
|
exports.AudioDictation = AudioDictation_default;
|
|
5100
5986
|
exports.AudioRecorder = AudioRecorder_default;
|
|
5101
5987
|
//# sourceMappingURL=index.js.map
|