sera-ai 1.0.12 → 1.0.14
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/dist/index.js +60 -31
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +60 -31
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -713,7 +713,7 @@ var useAudioRecovery = (reprocessSession) => {
|
|
|
713
713
|
const store = transaction.objectStore(STORE_NAME);
|
|
714
714
|
const session = {
|
|
715
715
|
id: sessionId,
|
|
716
|
-
audioChunks:
|
|
716
|
+
audioChunks: {},
|
|
717
717
|
metadata: {
|
|
718
718
|
...metadata,
|
|
719
719
|
timestamp: Date.now(),
|
|
@@ -2716,9 +2716,11 @@ var createAudioProcessorWorker = () => {
|
|
|
2716
2716
|
}
|
|
2717
2717
|
|
|
2718
2718
|
if (event.data.command === "resetUploadChunk") {
|
|
2719
|
+
// Only reset the upload flags, NOT the buffer
|
|
2720
|
+
// The buffer is cleared after chunk data is extracted and sent
|
|
2719
2721
|
this._uploadChunk = false;
|
|
2720
2722
|
this._uploadingChunk = false;
|
|
2721
|
-
|
|
2723
|
+
// NOTE: Do NOT clear buffer here - it's cleared in the chunk upload logic after data is sent
|
|
2722
2724
|
}
|
|
2723
2725
|
|
|
2724
2726
|
if (event.data.command === "pause") {
|
|
@@ -2866,6 +2868,7 @@ var useAudioRecorder = ({
|
|
|
2866
2868
|
const [selectedModel, setSelectedModel] = React3__namespace.default.useState("new-large");
|
|
2867
2869
|
const [isRetryingSession, setIsRetryingSession] = React3__namespace.default.useState(false);
|
|
2868
2870
|
const [showRetrySessionPrompt, setShowRetrySessionPrompt] = React3__namespace.default.useState(false);
|
|
2871
|
+
const sessionHasFailedChunkRef = React3__namespace.default.useRef(false);
|
|
2869
2872
|
const effectiveApiKey = apiKey;
|
|
2870
2873
|
const {
|
|
2871
2874
|
convertTranscriptionResponse,
|
|
@@ -2892,7 +2895,6 @@ var useAudioRecorder = ({
|
|
|
2892
2895
|
markSessionFailed,
|
|
2893
2896
|
retrySession,
|
|
2894
2897
|
getFailedSession,
|
|
2895
|
-
hasFailedSession,
|
|
2896
2898
|
clearFailedSessions
|
|
2897
2899
|
} = useAudioRecovery_default(async (audioChunks, metadata) => {
|
|
2898
2900
|
try {
|
|
@@ -3055,15 +3057,6 @@ var useAudioRecorder = ({
|
|
|
3055
3057
|
console.log("Speciality set, microphone validation will happen on recording start");
|
|
3056
3058
|
}
|
|
3057
3059
|
}, [speciality]);
|
|
3058
|
-
React3__namespace.default.useEffect(() => {
|
|
3059
|
-
const checkFailedSessions = async () => {
|
|
3060
|
-
const failedSession = await hasFailedSession();
|
|
3061
|
-
if (failedSession) {
|
|
3062
|
-
setShowRetrySessionPrompt(true);
|
|
3063
|
-
}
|
|
3064
|
-
};
|
|
3065
|
-
checkFailedSessions();
|
|
3066
|
-
}, [hasFailedSession]);
|
|
3067
3060
|
const clearAllSessions = React3__namespace.default.useCallback(async () => {
|
|
3068
3061
|
await clearFailedSessions();
|
|
3069
3062
|
setShowRetrySessionPrompt(false);
|
|
@@ -3078,17 +3071,28 @@ var useAudioRecorder = ({
|
|
|
3078
3071
|
isFinalChunk,
|
|
3079
3072
|
sequence,
|
|
3080
3073
|
audioDataLength: audioData?.length,
|
|
3081
|
-
requestFormat: selectedFormatRef.current
|
|
3082
|
-
|
|
3074
|
+
requestFormat: selectedFormatRef.current,
|
|
3075
|
+
sessionHasFailed: sessionHasFailedChunkRef.current
|
|
3083
3076
|
});
|
|
3084
3077
|
processorRef.current?.port.postMessage({ command: "resetUploadChunk" });
|
|
3085
3078
|
if (audioData && localSessionIdRef.current && !retry) {
|
|
3086
3079
|
try {
|
|
3080
|
+
console.log(`[DB] Saving chunk ${sequence} to IndexedDB session ${localSessionIdRef.current}`);
|
|
3087
3081
|
await appendAudioToSession(localSessionIdRef.current, audioData, sequence);
|
|
3088
|
-
console.log(`[
|
|
3082
|
+
console.log(`[DB] \u2713 Successfully saved audio chunk ${sequence} to IndexedDB (${audioData.length} samples)`);
|
|
3089
3083
|
} catch (error2) {
|
|
3090
|
-
console.error(`[
|
|
3084
|
+
console.error(`[DB] \u2717 Failed to save audio chunk ${sequence} to IndexedDB:`, error2);
|
|
3091
3085
|
}
|
|
3086
|
+
} else {
|
|
3087
|
+
console.log(`[DB] Skipping IndexedDB save: audioData=${!!audioData}, sessionId=${localSessionIdRef.current}, retry=${retry}`);
|
|
3088
|
+
}
|
|
3089
|
+
if (sessionHasFailedChunkRef.current && !retry) {
|
|
3090
|
+
console.log(`[SKIP] Session has failed chunk - skipping server upload for sequence ${sequence}, continuing to record audio`);
|
|
3091
|
+
if (isFinalChunk) {
|
|
3092
|
+
setShowRetrySessionPrompt(true);
|
|
3093
|
+
setIsProcessing(false);
|
|
3094
|
+
}
|
|
3095
|
+
return;
|
|
3092
3096
|
}
|
|
3093
3097
|
try {
|
|
3094
3098
|
const data = await pRetry__default.default(
|
|
@@ -3338,11 +3342,13 @@ var useAudioRecorder = ({
|
|
|
3338
3342
|
}
|
|
3339
3343
|
const isAbortError = err instanceof Error && err.name === "AbortError";
|
|
3340
3344
|
const statusCode = err instanceof Error && err.message.includes("HTTP") ? parseInt(err.message.split("HTTP")[1].trim()) : null;
|
|
3341
|
-
if (localSessionIdRef.current) {
|
|
3345
|
+
if (localSessionIdRef.current && !retry) {
|
|
3346
|
+
sessionHasFailedChunkRef.current = true;
|
|
3342
3347
|
await markSessionFailed(
|
|
3343
3348
|
localSessionIdRef.current,
|
|
3344
3349
|
err instanceof Error ? err.message : "Unknown error"
|
|
3345
3350
|
);
|
|
3351
|
+
console.log(`[FAIL] Chunk ${sequence} failed - session marked as failed, will continue recording audio locally`);
|
|
3346
3352
|
if (isFinalChunk) {
|
|
3347
3353
|
setShowRetrySessionPrompt(true);
|
|
3348
3354
|
}
|
|
@@ -3433,16 +3439,12 @@ var useAudioRecorder = ({
|
|
|
3433
3439
|
sequenceCounterRef.current = 0;
|
|
3434
3440
|
nextExpectedSequenceRef.current = 0;
|
|
3435
3441
|
receivedTranscriptionsRef.current.clear();
|
|
3442
|
+
sessionHasFailedChunkRef.current = false;
|
|
3443
|
+
chunkQueueRef.current = [];
|
|
3436
3444
|
const localSessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
3437
3445
|
localSessionIdRef.current = localSessionId;
|
|
3438
3446
|
sessionIdRef.current = null;
|
|
3439
3447
|
console.log("Created local session ID for IndexedDB:", localSessionId);
|
|
3440
|
-
await createSession(localSessionId, {
|
|
3441
|
-
patientId,
|
|
3442
|
-
patientName: patientName || void 0,
|
|
3443
|
-
patientHistory: patientHistory || void 0,
|
|
3444
|
-
speciality
|
|
3445
|
-
});
|
|
3446
3448
|
}
|
|
3447
3449
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
3448
3450
|
audio: {
|
|
@@ -3466,6 +3468,15 @@ var useAudioRecorder = ({
|
|
|
3466
3468
|
});
|
|
3467
3469
|
}
|
|
3468
3470
|
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
|
|
3471
|
+
if (!isPaused && localSessionIdRef.current) {
|
|
3472
|
+
await createSession(localSessionIdRef.current, {
|
|
3473
|
+
patientId,
|
|
3474
|
+
patientName: patientName || void 0,
|
|
3475
|
+
patientHistory: patientHistory || void 0,
|
|
3476
|
+
speciality,
|
|
3477
|
+
sampleRate: audioContext.sampleRate
|
|
3478
|
+
});
|
|
3479
|
+
}
|
|
3469
3480
|
const processorUrl = createAudioProcessorWorker();
|
|
3470
3481
|
await audioContext.audioWorklet.addModule(processorUrl);
|
|
3471
3482
|
URL.revokeObjectURL(processorUrl);
|
|
@@ -3481,16 +3492,19 @@ var useAudioRecorder = ({
|
|
|
3481
3492
|
totalRecordingTime,
|
|
3482
3493
|
lastAudioTime
|
|
3483
3494
|
} = event.data;
|
|
3484
|
-
const sequence = sequenceCounterRef.current++;
|
|
3485
3495
|
if (command === "finalChunk" && audioBuffer) {
|
|
3496
|
+
const sequence = sequenceCounterRef.current++;
|
|
3486
3497
|
const audioArray = new Float32Array(audioBuffer);
|
|
3487
|
-
console.log(`
|
|
3498
|
+
console.log(`[RECEIVE] Final chunk: ${audioArray.length} samples, sequence: ${sequence}`);
|
|
3488
3499
|
enqueueChunk(audioArray, true, sequence);
|
|
3489
|
-
} else if (command === "
|
|
3500
|
+
} else if (command === "chunk" && audioBuffer) {
|
|
3501
|
+
const sequence = sequenceCounterRef.current++;
|
|
3490
3502
|
const audioArray = new Float32Array(audioBuffer);
|
|
3503
|
+
console.log(`[RECEIVE] Chunk: ${audioArray.length} samples, sequence: ${sequence}`);
|
|
3491
3504
|
enqueueChunk(audioArray, false, sequence);
|
|
3492
3505
|
} else if (command === "pauseChunk" && audioBuffer) {
|
|
3493
|
-
|
|
3506
|
+
const sequence = sequenceCounterRef.current++;
|
|
3507
|
+
console.log(`[RECEIVE] Pause chunk with audioBuffer, sequence: ${sequence}`);
|
|
3494
3508
|
enqueueChunk(new Float32Array(audioBuffer), false, sequence, true);
|
|
3495
3509
|
} else if (command === "audioLevel") {
|
|
3496
3510
|
setAudioLevel(level);
|
|
@@ -3568,6 +3582,14 @@ var useAudioRecorder = ({
|
|
|
3568
3582
|
}, 500);
|
|
3569
3583
|
}
|
|
3570
3584
|
setIsRecording(false);
|
|
3585
|
+
console.log("\u{1F50D} Stop recording - checking for failed session:", {
|
|
3586
|
+
hasSessionFailed: sessionHasFailedChunkRef.current,
|
|
3587
|
+
localSessionId: localSessionIdRef.current
|
|
3588
|
+
});
|
|
3589
|
+
if (sessionHasFailedChunkRef.current && localSessionIdRef.current) {
|
|
3590
|
+
console.log("\u26A0\uFE0F Recording stopped with failed session - showing retry UI");
|
|
3591
|
+
setShowRetrySessionPrompt(true);
|
|
3592
|
+
}
|
|
3571
3593
|
}, [uploadChunkInterval]);
|
|
3572
3594
|
React3__namespace.default.useEffect(() => {
|
|
3573
3595
|
const handleDeviceChange = async () => {
|
|
@@ -3641,9 +3663,12 @@ var useAudioRecorder = ({
|
|
|
3641
3663
|
}, 47e3);
|
|
3642
3664
|
setUploadChunkInterval(intervalId);
|
|
3643
3665
|
}, [isPaused]);
|
|
3644
|
-
const processNextChunkInQueue = React3__namespace.default.useCallback(() => {
|
|
3645
|
-
if (isProcessingQueueRef.current || chunkQueueRef.current.length === 0)
|
|
3666
|
+
const processNextChunkInQueue = React3__namespace.default.useCallback(async () => {
|
|
3667
|
+
if (isProcessingQueueRef.current || chunkQueueRef.current.length === 0) {
|
|
3668
|
+
return;
|
|
3669
|
+
}
|
|
3646
3670
|
const { chunk, isFinal, sequence, isPaused: isPaused2 = false } = chunkQueueRef.current.shift();
|
|
3671
|
+
console.log(`[QUEUE] Processing chunk ${sequence} from queue, remaining: ${chunkQueueRef.current.length}`);
|
|
3647
3672
|
isProcessingQueueRef.current = true;
|
|
3648
3673
|
uploadChunkToServer(chunk, isFinal, sequence, false, isPaused2).finally(() => {
|
|
3649
3674
|
isProcessingQueueRef.current = false;
|
|
@@ -3652,9 +3677,13 @@ var useAudioRecorder = ({
|
|
|
3652
3677
|
}, [uploadChunkToServer, isLoaded]);
|
|
3653
3678
|
const enqueueChunk = React3__namespace.default.useCallback(
|
|
3654
3679
|
(audioData, isFinalChunk, sequence, isPausedChunk = false) => {
|
|
3680
|
+
console.log(`[QUEUE] Enqueuing ${isFinalChunk ? "FINAL" : isPausedChunk ? "PAUSED" : "regular"} chunk ${sequence}, samples: ${audioData?.length || 0}, queue size: ${chunkQueueRef.current.length}`);
|
|
3655
3681
|
if (isFinalChunk) {
|
|
3656
|
-
|
|
3657
|
-
|
|
3682
|
+
if (!sessionHasFailedChunkRef.current) {
|
|
3683
|
+
setIsProcessing(true);
|
|
3684
|
+
} else {
|
|
3685
|
+
console.log("\u26A0\uFE0F Session has failed - skipping processing state (IndexedDB save only)");
|
|
3686
|
+
}
|
|
3658
3687
|
}
|
|
3659
3688
|
chunkQueueRef.current.push({
|
|
3660
3689
|
chunk: audioData,
|