sera-ai 1.0.13 → 1.0.15

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.mjs CHANGED
@@ -689,7 +689,7 @@ var useAudioRecovery = (reprocessSession) => {
689
689
  const store = transaction.objectStore(STORE_NAME);
690
690
  const session = {
691
691
  id: sessionId,
692
- audioChunks: [],
692
+ audioChunks: {},
693
693
  metadata: {
694
694
  ...metadata,
695
695
  timestamp: Date.now(),
@@ -2692,9 +2692,11 @@ var createAudioProcessorWorker = () => {
2692
2692
  }
2693
2693
 
2694
2694
  if (event.data.command === "resetUploadChunk") {
2695
+ // Only reset the upload flags, NOT the buffer
2696
+ // The buffer is cleared after chunk data is extracted and sent
2695
2697
  this._uploadChunk = false;
2696
2698
  this._uploadingChunk = false;
2697
- this._buffer = [];
2699
+ // NOTE: Do NOT clear buffer here - it's cleared in the chunk upload logic after data is sent
2698
2700
  }
2699
2701
 
2700
2702
  if (event.data.command === "pause") {
@@ -2842,6 +2844,7 @@ var useAudioRecorder = ({
2842
2844
  const [selectedModel, setSelectedModel] = React3__default.useState("new-large");
2843
2845
  const [isRetryingSession, setIsRetryingSession] = React3__default.useState(false);
2844
2846
  const [showRetrySessionPrompt, setShowRetrySessionPrompt] = React3__default.useState(false);
2847
+ const sessionHasFailedChunkRef = React3__default.useRef(false);
2845
2848
  const effectiveApiKey = apiKey;
2846
2849
  const {
2847
2850
  convertTranscriptionResponse,
@@ -2867,8 +2870,8 @@ var useAudioRecorder = ({
2867
2870
  markSessionComplete,
2868
2871
  markSessionFailed,
2869
2872
  retrySession,
2873
+ deleteSession,
2870
2874
  getFailedSession,
2871
- hasFailedSession,
2872
2875
  clearFailedSessions
2873
2876
  } = useAudioRecovery_default(async (audioChunks, metadata) => {
2874
2877
  try {
@@ -3031,15 +3034,6 @@ var useAudioRecorder = ({
3031
3034
  console.log("Speciality set, microphone validation will happen on recording start");
3032
3035
  }
3033
3036
  }, [speciality]);
3034
- React3__default.useEffect(() => {
3035
- const checkFailedSessions = async () => {
3036
- const failedSession = await hasFailedSession();
3037
- if (failedSession) {
3038
- setShowRetrySessionPrompt(true);
3039
- }
3040
- };
3041
- checkFailedSessions();
3042
- }, [hasFailedSession]);
3043
3037
  const clearAllSessions = React3__default.useCallback(async () => {
3044
3038
  await clearFailedSessions();
3045
3039
  setShowRetrySessionPrompt(false);
@@ -3054,8 +3048,8 @@ var useAudioRecorder = ({
3054
3048
  isFinalChunk,
3055
3049
  sequence,
3056
3050
  audioDataLength: audioData?.length,
3057
- requestFormat: selectedFormatRef.current
3058
- // Log the request format
3051
+ requestFormat: selectedFormatRef.current,
3052
+ sessionHasFailed: sessionHasFailedChunkRef.current
3059
3053
  });
3060
3054
  processorRef.current?.port.postMessage({ command: "resetUploadChunk" });
3061
3055
  if (audioData && localSessionIdRef.current && !retry) {
@@ -3069,6 +3063,14 @@ var useAudioRecorder = ({
3069
3063
  } else {
3070
3064
  console.log(`[DB] Skipping IndexedDB save: audioData=${!!audioData}, sessionId=${localSessionIdRef.current}, retry=${retry}`);
3071
3065
  }
3066
+ if (sessionHasFailedChunkRef.current && !retry) {
3067
+ console.log(`[SKIP] Session has failed chunk - skipping server upload for sequence ${sequence}, continuing to record audio`);
3068
+ if (isFinalChunk) {
3069
+ setShowRetrySessionPrompt(true);
3070
+ setIsProcessing(false);
3071
+ }
3072
+ return;
3073
+ }
3072
3074
  try {
3073
3075
  const data = await pRetry(
3074
3076
  async (attemptNumber) => {
@@ -3317,11 +3319,13 @@ var useAudioRecorder = ({
3317
3319
  }
3318
3320
  const isAbortError = err instanceof Error && err.name === "AbortError";
3319
3321
  const statusCode = err instanceof Error && err.message.includes("HTTP") ? parseInt(err.message.split("HTTP")[1].trim()) : null;
3320
- if (localSessionIdRef.current) {
3322
+ if (localSessionIdRef.current && !retry) {
3323
+ sessionHasFailedChunkRef.current = true;
3321
3324
  await markSessionFailed(
3322
3325
  localSessionIdRef.current,
3323
3326
  err instanceof Error ? err.message : "Unknown error"
3324
3327
  );
3328
+ console.log(`[FAIL] Chunk ${sequence} failed - session marked as failed, will continue recording audio locally`);
3325
3329
  if (isFinalChunk) {
3326
3330
  setShowRetrySessionPrompt(true);
3327
3331
  }
@@ -3387,16 +3391,26 @@ var useAudioRecorder = ({
3387
3391
  const success = await retrySession(failedSession.id);
3388
3392
  if (success) {
3389
3393
  console.log(`Successfully retried session ${failedSession.id}`);
3394
+ await deleteSession(failedSession.id);
3395
+ console.log(`Deleted session ${failedSession.id} from IndexedDB after successful retry`);
3390
3396
  setError(null);
3397
+ setShowRetrySessionPrompt(false);
3398
+ } else {
3399
+ console.log(`Retry failed for session ${failedSession.id} - keeping retry UI visible`);
3400
+ setShowRetrySessionPrompt(true);
3401
+ setError("Retry failed. Please check your connection and try again.");
3391
3402
  }
3403
+ } else {
3404
+ setShowRetrySessionPrompt(false);
3392
3405
  }
3393
3406
  } catch (error2) {
3394
3407
  console.error("Error retrying failed sessions:", error2);
3395
3408
  setError("Failed to retry sessions. Please try again.");
3409
+ setShowRetrySessionPrompt(true);
3396
3410
  } finally {
3397
3411
  setIsRetryingSession(false);
3398
3412
  }
3399
- }, [retrySession, getFailedSession]);
3413
+ }, [retrySession, getFailedSession, deleteSession]);
3400
3414
  const startRecording = React3__default.useCallback(async () => {
3401
3415
  try {
3402
3416
  setError(null);
@@ -3412,16 +3426,12 @@ var useAudioRecorder = ({
3412
3426
  sequenceCounterRef.current = 0;
3413
3427
  nextExpectedSequenceRef.current = 0;
3414
3428
  receivedTranscriptionsRef.current.clear();
3429
+ sessionHasFailedChunkRef.current = false;
3430
+ chunkQueueRef.current = [];
3415
3431
  const localSessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
3416
3432
  localSessionIdRef.current = localSessionId;
3417
3433
  sessionIdRef.current = null;
3418
3434
  console.log("Created local session ID for IndexedDB:", localSessionId);
3419
- await createSession(localSessionId, {
3420
- patientId,
3421
- patientName: patientName || void 0,
3422
- patientHistory: patientHistory || void 0,
3423
- speciality
3424
- });
3425
3435
  }
3426
3436
  const stream = await navigator.mediaDevices.getUserMedia({
3427
3437
  audio: {
@@ -3445,6 +3455,15 @@ var useAudioRecorder = ({
3445
3455
  });
3446
3456
  }
3447
3457
  const audioContext = new (window.AudioContext || window.webkitAudioContext)();
3458
+ if (!isPaused && localSessionIdRef.current) {
3459
+ await createSession(localSessionIdRef.current, {
3460
+ patientId,
3461
+ patientName: patientName || void 0,
3462
+ patientHistory: patientHistory || void 0,
3463
+ speciality,
3464
+ sampleRate: audioContext.sampleRate
3465
+ });
3466
+ }
3448
3467
  const processorUrl = createAudioProcessorWorker();
3449
3468
  await audioContext.audioWorklet.addModule(processorUrl);
3450
3469
  URL.revokeObjectURL(processorUrl);
@@ -3550,6 +3569,14 @@ var useAudioRecorder = ({
3550
3569
  }, 500);
3551
3570
  }
3552
3571
  setIsRecording(false);
3572
+ console.log("\u{1F50D} Stop recording - checking for failed session:", {
3573
+ hasSessionFailed: sessionHasFailedChunkRef.current,
3574
+ localSessionId: localSessionIdRef.current
3575
+ });
3576
+ if (sessionHasFailedChunkRef.current && localSessionIdRef.current) {
3577
+ console.log("\u26A0\uFE0F Recording stopped with failed session - showing retry UI");
3578
+ setShowRetrySessionPrompt(true);
3579
+ }
3553
3580
  }, [uploadChunkInterval]);
3554
3581
  React3__default.useEffect(() => {
3555
3582
  const handleDeviceChange = async () => {
@@ -3623,13 +3650,14 @@ var useAudioRecorder = ({
3623
3650
  }, 47e3);
3624
3651
  setUploadChunkInterval(intervalId);
3625
3652
  }, [isPaused]);
3626
- const processNextChunkInQueue = React3__default.useCallback(() => {
3627
- if (isProcessingQueueRef.current || chunkQueueRef.current.length === 0) return;
3653
+ const processNextChunkInQueue = React3__default.useCallback(async () => {
3654
+ if (isProcessingQueueRef.current || chunkQueueRef.current.length === 0) {
3655
+ return;
3656
+ }
3628
3657
  const { chunk, isFinal, sequence, isPaused: isPaused2 = false } = chunkQueueRef.current.shift();
3629
3658
  console.log(`[QUEUE] Processing chunk ${sequence} from queue, remaining: ${chunkQueueRef.current.length}`);
3630
3659
  isProcessingQueueRef.current = true;
3631
3660
  uploadChunkToServer(chunk, isFinal, sequence, false, isPaused2).finally(() => {
3632
- console.log(`[QUEUE] Finished processing chunk ${sequence}`);
3633
3661
  isProcessingQueueRef.current = false;
3634
3662
  processNextChunkInQueue();
3635
3663
  });
@@ -3638,7 +3666,11 @@ var useAudioRecorder = ({
3638
3666
  (audioData, isFinalChunk, sequence, isPausedChunk = false) => {
3639
3667
  console.log(`[QUEUE] Enqueuing ${isFinalChunk ? "FINAL" : isPausedChunk ? "PAUSED" : "regular"} chunk ${sequence}, samples: ${audioData?.length || 0}, queue size: ${chunkQueueRef.current.length}`);
3640
3668
  if (isFinalChunk) {
3641
- setIsProcessing(true);
3669
+ if (!sessionHasFailedChunkRef.current) {
3670
+ setIsProcessing(true);
3671
+ } else {
3672
+ console.log("\u26A0\uFE0F Session has failed - skipping processing state (IndexedDB save only)");
3673
+ }
3642
3674
  }
3643
3675
  chunkQueueRef.current.push({
3644
3676
  chunk: audioData,