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.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
- this._buffer = [];
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,
@@ -2891,8 +2894,8 @@ var useAudioRecorder = ({
2891
2894
  markSessionComplete,
2892
2895
  markSessionFailed,
2893
2896
  retrySession,
2897
+ deleteSession,
2894
2898
  getFailedSession,
2895
- hasFailedSession,
2896
2899
  clearFailedSessions
2897
2900
  } = useAudioRecovery_default(async (audioChunks, metadata) => {
2898
2901
  try {
@@ -3055,15 +3058,6 @@ var useAudioRecorder = ({
3055
3058
  console.log("Speciality set, microphone validation will happen on recording start");
3056
3059
  }
3057
3060
  }, [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
3061
  const clearAllSessions = React3__namespace.default.useCallback(async () => {
3068
3062
  await clearFailedSessions();
3069
3063
  setShowRetrySessionPrompt(false);
@@ -3078,8 +3072,8 @@ var useAudioRecorder = ({
3078
3072
  isFinalChunk,
3079
3073
  sequence,
3080
3074
  audioDataLength: audioData?.length,
3081
- requestFormat: selectedFormatRef.current
3082
- // Log the request format
3075
+ requestFormat: selectedFormatRef.current,
3076
+ sessionHasFailed: sessionHasFailedChunkRef.current
3083
3077
  });
3084
3078
  processorRef.current?.port.postMessage({ command: "resetUploadChunk" });
3085
3079
  if (audioData && localSessionIdRef.current && !retry) {
@@ -3093,6 +3087,14 @@ var useAudioRecorder = ({
3093
3087
  } else {
3094
3088
  console.log(`[DB] Skipping IndexedDB save: audioData=${!!audioData}, sessionId=${localSessionIdRef.current}, retry=${retry}`);
3095
3089
  }
3090
+ if (sessionHasFailedChunkRef.current && !retry) {
3091
+ console.log(`[SKIP] Session has failed chunk - skipping server upload for sequence ${sequence}, continuing to record audio`);
3092
+ if (isFinalChunk) {
3093
+ setShowRetrySessionPrompt(true);
3094
+ setIsProcessing(false);
3095
+ }
3096
+ return;
3097
+ }
3096
3098
  try {
3097
3099
  const data = await pRetry__default.default(
3098
3100
  async (attemptNumber) => {
@@ -3341,11 +3343,13 @@ var useAudioRecorder = ({
3341
3343
  }
3342
3344
  const isAbortError = err instanceof Error && err.name === "AbortError";
3343
3345
  const statusCode = err instanceof Error && err.message.includes("HTTP") ? parseInt(err.message.split("HTTP")[1].trim()) : null;
3344
- if (localSessionIdRef.current) {
3346
+ if (localSessionIdRef.current && !retry) {
3347
+ sessionHasFailedChunkRef.current = true;
3345
3348
  await markSessionFailed(
3346
3349
  localSessionIdRef.current,
3347
3350
  err instanceof Error ? err.message : "Unknown error"
3348
3351
  );
3352
+ console.log(`[FAIL] Chunk ${sequence} failed - session marked as failed, will continue recording audio locally`);
3349
3353
  if (isFinalChunk) {
3350
3354
  setShowRetrySessionPrompt(true);
3351
3355
  }
@@ -3411,16 +3415,26 @@ var useAudioRecorder = ({
3411
3415
  const success = await retrySession(failedSession.id);
3412
3416
  if (success) {
3413
3417
  console.log(`Successfully retried session ${failedSession.id}`);
3418
+ await deleteSession(failedSession.id);
3419
+ console.log(`Deleted session ${failedSession.id} from IndexedDB after successful retry`);
3414
3420
  setError(null);
3421
+ setShowRetrySessionPrompt(false);
3422
+ } else {
3423
+ console.log(`Retry failed for session ${failedSession.id} - keeping retry UI visible`);
3424
+ setShowRetrySessionPrompt(true);
3425
+ setError("Retry failed. Please check your connection and try again.");
3415
3426
  }
3427
+ } else {
3428
+ setShowRetrySessionPrompt(false);
3416
3429
  }
3417
3430
  } catch (error2) {
3418
3431
  console.error("Error retrying failed sessions:", error2);
3419
3432
  setError("Failed to retry sessions. Please try again.");
3433
+ setShowRetrySessionPrompt(true);
3420
3434
  } finally {
3421
3435
  setIsRetryingSession(false);
3422
3436
  }
3423
- }, [retrySession, getFailedSession]);
3437
+ }, [retrySession, getFailedSession, deleteSession]);
3424
3438
  const startRecording = React3__namespace.default.useCallback(async () => {
3425
3439
  try {
3426
3440
  setError(null);
@@ -3436,16 +3450,12 @@ var useAudioRecorder = ({
3436
3450
  sequenceCounterRef.current = 0;
3437
3451
  nextExpectedSequenceRef.current = 0;
3438
3452
  receivedTranscriptionsRef.current.clear();
3453
+ sessionHasFailedChunkRef.current = false;
3454
+ chunkQueueRef.current = [];
3439
3455
  const localSessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
3440
3456
  localSessionIdRef.current = localSessionId;
3441
3457
  sessionIdRef.current = null;
3442
3458
  console.log("Created local session ID for IndexedDB:", localSessionId);
3443
- await createSession(localSessionId, {
3444
- patientId,
3445
- patientName: patientName || void 0,
3446
- patientHistory: patientHistory || void 0,
3447
- speciality
3448
- });
3449
3459
  }
3450
3460
  const stream = await navigator.mediaDevices.getUserMedia({
3451
3461
  audio: {
@@ -3469,6 +3479,15 @@ var useAudioRecorder = ({
3469
3479
  });
3470
3480
  }
3471
3481
  const audioContext = new (window.AudioContext || window.webkitAudioContext)();
3482
+ if (!isPaused && localSessionIdRef.current) {
3483
+ await createSession(localSessionIdRef.current, {
3484
+ patientId,
3485
+ patientName: patientName || void 0,
3486
+ patientHistory: patientHistory || void 0,
3487
+ speciality,
3488
+ sampleRate: audioContext.sampleRate
3489
+ });
3490
+ }
3472
3491
  const processorUrl = createAudioProcessorWorker();
3473
3492
  await audioContext.audioWorklet.addModule(processorUrl);
3474
3493
  URL.revokeObjectURL(processorUrl);
@@ -3574,6 +3593,14 @@ var useAudioRecorder = ({
3574
3593
  }, 500);
3575
3594
  }
3576
3595
  setIsRecording(false);
3596
+ console.log("\u{1F50D} Stop recording - checking for failed session:", {
3597
+ hasSessionFailed: sessionHasFailedChunkRef.current,
3598
+ localSessionId: localSessionIdRef.current
3599
+ });
3600
+ if (sessionHasFailedChunkRef.current && localSessionIdRef.current) {
3601
+ console.log("\u26A0\uFE0F Recording stopped with failed session - showing retry UI");
3602
+ setShowRetrySessionPrompt(true);
3603
+ }
3577
3604
  }, [uploadChunkInterval]);
3578
3605
  React3__namespace.default.useEffect(() => {
3579
3606
  const handleDeviceChange = async () => {
@@ -3647,13 +3674,14 @@ var useAudioRecorder = ({
3647
3674
  }, 47e3);
3648
3675
  setUploadChunkInterval(intervalId);
3649
3676
  }, [isPaused]);
3650
- const processNextChunkInQueue = React3__namespace.default.useCallback(() => {
3651
- if (isProcessingQueueRef.current || chunkQueueRef.current.length === 0) return;
3677
+ const processNextChunkInQueue = React3__namespace.default.useCallback(async () => {
3678
+ if (isProcessingQueueRef.current || chunkQueueRef.current.length === 0) {
3679
+ return;
3680
+ }
3652
3681
  const { chunk, isFinal, sequence, isPaused: isPaused2 = false } = chunkQueueRef.current.shift();
3653
3682
  console.log(`[QUEUE] Processing chunk ${sequence} from queue, remaining: ${chunkQueueRef.current.length}`);
3654
3683
  isProcessingQueueRef.current = true;
3655
3684
  uploadChunkToServer(chunk, isFinal, sequence, false, isPaused2).finally(() => {
3656
- console.log(`[QUEUE] Finished processing chunk ${sequence}`);
3657
3685
  isProcessingQueueRef.current = false;
3658
3686
  processNextChunkInQueue();
3659
3687
  });
@@ -3662,7 +3690,11 @@ var useAudioRecorder = ({
3662
3690
  (audioData, isFinalChunk, sequence, isPausedChunk = false) => {
3663
3691
  console.log(`[QUEUE] Enqueuing ${isFinalChunk ? "FINAL" : isPausedChunk ? "PAUSED" : "regular"} chunk ${sequence}, samples: ${audioData?.length || 0}, queue size: ${chunkQueueRef.current.length}`);
3664
3692
  if (isFinalChunk) {
3665
- setIsProcessing(true);
3693
+ if (!sessionHasFailedChunkRef.current) {
3694
+ setIsProcessing(true);
3695
+ } else {
3696
+ console.log("\u26A0\uFE0F Session has failed - skipping processing state (IndexedDB save only)");
3697
+ }
3666
3698
  }
3667
3699
  chunkQueueRef.current.push({
3668
3700
  chunk: audioData,