sera-ai 1.0.28 → 1.0.29
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 +185 -411
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +185 -411
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -151,12 +151,12 @@ var useFFmpegConverter = () => {
|
|
|
151
151
|
const [statusMessage, setStatusMessage] = React3.useState("");
|
|
152
152
|
const loadFFmpeg = React3.useCallback(async () => {
|
|
153
153
|
if (ffmpegInstance) {
|
|
154
|
-
console.log("[
|
|
154
|
+
console.log("[SERA] FFmpeg already loaded, skipping");
|
|
155
155
|
setFfmpegLoaded(true);
|
|
156
156
|
return true;
|
|
157
157
|
}
|
|
158
158
|
if (ffmpegLoadingPromise) {
|
|
159
|
-
console.log("[
|
|
159
|
+
console.log("[SERA] FFmpeg already loading, waiting for existing promise");
|
|
160
160
|
const result = await ffmpegLoadingPromise;
|
|
161
161
|
if (result) {
|
|
162
162
|
setFfmpegLoaded(true);
|
|
@@ -166,7 +166,7 @@ var useFFmpegConverter = () => {
|
|
|
166
166
|
ffmpegLoadingPromise = (async () => {
|
|
167
167
|
try {
|
|
168
168
|
const cdnCorePath = "https://unpkg.com/@ffmpeg/core@0.11.0/dist/ffmpeg-core.js";
|
|
169
|
-
console.log(
|
|
169
|
+
console.log(`[SERA] FFmpeg loading from CDN | corePath=${cdnCorePath}`);
|
|
170
170
|
setStatusMessage("Loading FFmpeg...");
|
|
171
171
|
const ffmpeg$1 = ffmpeg.createFFmpeg({
|
|
172
172
|
log: false,
|
|
@@ -180,10 +180,10 @@ var useFFmpegConverter = () => {
|
|
|
180
180
|
setFfmpegLoaded(true);
|
|
181
181
|
setIsLoaded(true);
|
|
182
182
|
setStatusMessage("");
|
|
183
|
-
console.log("[
|
|
183
|
+
console.log("[SERA] FFmpeg WASM loaded successfully from CDN");
|
|
184
184
|
return true;
|
|
185
185
|
} catch (err) {
|
|
186
|
-
console.error("
|
|
186
|
+
console.error("[SERA] FFmpeg loading failed:", err);
|
|
187
187
|
setError("Failed to load FFmpeg");
|
|
188
188
|
setStatusMessage("");
|
|
189
189
|
ffmpegLoadingPromise = null;
|
|
@@ -253,13 +253,13 @@ var useFFmpegConverter = () => {
|
|
|
253
253
|
if (!ffmpegInstance || !ffmpegLoaded) {
|
|
254
254
|
const loaded = await loadFFmpeg();
|
|
255
255
|
if (!loaded) {
|
|
256
|
-
console.error("
|
|
256
|
+
console.error("[SERA] FFmpeg loading failed for FLAC conversion");
|
|
257
257
|
return wavFile;
|
|
258
258
|
}
|
|
259
259
|
}
|
|
260
260
|
const ffmpeg$1 = ffmpegInstance;
|
|
261
261
|
if (!ffmpeg$1) {
|
|
262
|
-
console.error("FFmpeg not available");
|
|
262
|
+
console.error("[SERA] FFmpeg not available for FLAC conversion");
|
|
263
263
|
return wavFile;
|
|
264
264
|
}
|
|
265
265
|
try {
|
|
@@ -293,7 +293,7 @@ var useFFmpegConverter = () => {
|
|
|
293
293
|
const flacFile = new File([flacBlob], flacFileName, { type: "audio/flac" });
|
|
294
294
|
setProgress(100);
|
|
295
295
|
setStatusMessage("FLAC conversion complete");
|
|
296
|
-
console.log(`[
|
|
296
|
+
console.log(`[SERA] FLAC conversion complete | inputSize=${wavFile.size}, outputSize=${flacFile.size}, reduction=${Math.round((1 - flacFile.size / wavFile.size) * 100)}%`);
|
|
297
297
|
setTimeout(() => {
|
|
298
298
|
setIsConverting(false);
|
|
299
299
|
setProgress(0);
|
|
@@ -301,7 +301,7 @@ var useFFmpegConverter = () => {
|
|
|
301
301
|
}, 500);
|
|
302
302
|
return flacFile;
|
|
303
303
|
} catch (err) {
|
|
304
|
-
console.error("FLAC conversion failed:", err);
|
|
304
|
+
console.error("[SERA] FLAC conversion failed:", err);
|
|
305
305
|
setError("FLAC conversion failed");
|
|
306
306
|
setIsConverting(false);
|
|
307
307
|
setProgress(0);
|
|
@@ -318,7 +318,7 @@ var useFFmpegConverter = () => {
|
|
|
318
318
|
}
|
|
319
319
|
const maxFileSize = 50 * 1024 * 1024;
|
|
320
320
|
if (file.size > maxFileSize) {
|
|
321
|
-
console.warn(`[
|
|
321
|
+
console.warn(`[SERA] Silence removal skipped, file too large | size=${file.size}`);
|
|
322
322
|
return file;
|
|
323
323
|
}
|
|
324
324
|
try {
|
|
@@ -326,12 +326,12 @@ var useFFmpegConverter = () => {
|
|
|
326
326
|
setError(null);
|
|
327
327
|
setProgress(0);
|
|
328
328
|
setStatusMessage("Removing silence...");
|
|
329
|
-
console.log(`[
|
|
329
|
+
console.log(`[SERA] Silence removal started | size=${file.size}, name=${file.name}`);
|
|
330
330
|
if (!ffmpegInstance) {
|
|
331
331
|
setStatusMessage("Loading FFmpeg for silence removal...");
|
|
332
332
|
const loaded = await loadFFmpeg();
|
|
333
333
|
if (!loaded || !ffmpegInstance) {
|
|
334
|
-
console.error("[
|
|
334
|
+
console.error("[SERA] FFmpeg loading failed for silence removal");
|
|
335
335
|
setIsConverting(false);
|
|
336
336
|
setProgress(0);
|
|
337
337
|
setStatusMessage("");
|
|
@@ -345,7 +345,7 @@ var useFFmpegConverter = () => {
|
|
|
345
345
|
const wavData = await ffmpeg.fetchFile(file);
|
|
346
346
|
ffmpegInstance.FS("writeFile", inputFileName, wavData);
|
|
347
347
|
const originalSize = file.size;
|
|
348
|
-
console.log(`[
|
|
348
|
+
console.log(`[SERA] Silence removal input written | size=${originalSize}`);
|
|
349
349
|
setProgress(30);
|
|
350
350
|
setStatusMessage("Analyzing and removing silence...");
|
|
351
351
|
await ffmpegInstance.run(
|
|
@@ -364,11 +364,11 @@ var useFFmpegConverter = () => {
|
|
|
364
364
|
ffmpegInstance.FS("unlink", inputFileName);
|
|
365
365
|
ffmpegInstance.FS("unlink", outputFileName);
|
|
366
366
|
} catch (cleanupErr) {
|
|
367
|
-
console.warn("[
|
|
367
|
+
console.warn("[SERA] Silence removal cleanup warning:", cleanupErr);
|
|
368
368
|
}
|
|
369
369
|
const processedSize = outputData.length;
|
|
370
370
|
const reductionPercent = Math.round((1 - processedSize / originalSize) * 100);
|
|
371
|
-
console.log(`[
|
|
371
|
+
console.log(`[SERA] Silence removal complete | inputSize=${originalSize}, outputSize=${processedSize}, reduction=${reductionPercent}%`);
|
|
372
372
|
const processedFile = new File(
|
|
373
373
|
[new Uint8Array(outputData)],
|
|
374
374
|
file.name,
|
|
@@ -383,7 +383,7 @@ var useFFmpegConverter = () => {
|
|
|
383
383
|
}, 500);
|
|
384
384
|
return processedFile;
|
|
385
385
|
} catch (err) {
|
|
386
|
-
console.error("[
|
|
386
|
+
console.error("[SERA] Silence removal failed:", err);
|
|
387
387
|
setError("Silence removal failed");
|
|
388
388
|
setIsConverting(false);
|
|
389
389
|
setProgress(0);
|
|
@@ -1089,7 +1089,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1089
1089
|
let sessionId = "";
|
|
1090
1090
|
let metadata = {};
|
|
1091
1091
|
let speciality = "";
|
|
1092
|
-
console.log(
|
|
1092
|
+
console.log(`[SERA] Parsing HL7 transcription | segments=${segments.length}`);
|
|
1093
1093
|
for (const segment of segments) {
|
|
1094
1094
|
switch (segment.type) {
|
|
1095
1095
|
case "MSH":
|
|
@@ -1131,7 +1131,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1131
1131
|
const parsedClassification = JSON.parse(classificationJson);
|
|
1132
1132
|
Object.assign(classifiedInfo, parsedClassification);
|
|
1133
1133
|
} catch (e) {
|
|
1134
|
-
console.error("Failed to parse classification JSON:", e);
|
|
1134
|
+
console.error("[SERA] Failed to parse HL7 classification JSON:", e);
|
|
1135
1135
|
}
|
|
1136
1136
|
}
|
|
1137
1137
|
}
|
|
@@ -1182,7 +1182,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1182
1182
|
}
|
|
1183
1183
|
};
|
|
1184
1184
|
} catch (error) {
|
|
1185
|
-
console.error("HL7 transcription conversion error:", error);
|
|
1185
|
+
console.error("[SERA] HL7 transcription conversion error:", error);
|
|
1186
1186
|
throw new Error(
|
|
1187
1187
|
`Failed to convert HL7 transcription data: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1188
1188
|
);
|
|
@@ -1195,7 +1195,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1195
1195
|
try {
|
|
1196
1196
|
const segments = parseHL7(hl7Data);
|
|
1197
1197
|
const specialities = [];
|
|
1198
|
-
console.log(
|
|
1198
|
+
console.log(`[SERA] Parsing HL7 specialities | segments=${segments.length}`);
|
|
1199
1199
|
let currentSpeciality = null;
|
|
1200
1200
|
for (const segment of segments) {
|
|
1201
1201
|
switch (segment.type) {
|
|
@@ -1237,7 +1237,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1237
1237
|
specialities.push(specialityData);
|
|
1238
1238
|
}
|
|
1239
1239
|
} catch (e) {
|
|
1240
|
-
console.error("Failed to parse legacy specialities JSON data:", e);
|
|
1240
|
+
console.error("[SERA] Failed to parse legacy specialities JSON data:", e);
|
|
1241
1241
|
}
|
|
1242
1242
|
}
|
|
1243
1243
|
}
|
|
@@ -1250,10 +1250,10 @@ var useHL7FHIRConverter = () => {
|
|
|
1250
1250
|
}
|
|
1251
1251
|
specialities.push(currentSpeciality);
|
|
1252
1252
|
}
|
|
1253
|
-
console.log(
|
|
1253
|
+
console.log(`[SERA] Parsed specialities | count=${specialities.length}`);
|
|
1254
1254
|
return { specialities };
|
|
1255
1255
|
} catch (error) {
|
|
1256
|
-
console.error("HL7 specialities conversion error:", error);
|
|
1256
|
+
console.error("[SERA] HL7 specialities conversion error:", error);
|
|
1257
1257
|
throw new Error(
|
|
1258
1258
|
`Failed to convert HL7 specialities data: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1259
1259
|
);
|
|
@@ -1300,8 +1300,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1300
1300
|
classification = conclusionData.classifiedInfo;
|
|
1301
1301
|
}
|
|
1302
1302
|
} catch (parseError) {
|
|
1303
|
-
console.error("
|
|
1304
|
-
console.log("Raw conclusion:", resource.conclusion);
|
|
1303
|
+
console.error("[SERA] Failed to parse DiagnosticReport conclusion:", parseError);
|
|
1305
1304
|
if (!classification["Clinical Summary"]) {
|
|
1306
1305
|
classification["Clinical Summary"] = [];
|
|
1307
1306
|
}
|
|
@@ -1320,16 +1319,13 @@ var useHL7FHIRConverter = () => {
|
|
|
1320
1319
|
break;
|
|
1321
1320
|
case "Composition":
|
|
1322
1321
|
if (resource.section && Array.isArray(resource.section) && resource.section.length > 0) {
|
|
1323
|
-
console.log(
|
|
1322
|
+
console.log(`[SERA] Processing FHIR Composition | sections=${resource.section.length}`);
|
|
1324
1323
|
for (const section of resource.section) {
|
|
1325
1324
|
if (section.title === "Transcription" && section.text?.div) {
|
|
1326
1325
|
const textContent = section.text.div.replace(/<[^>]*>/g, "");
|
|
1327
1326
|
transcription += (transcription ? "\n" : "") + textContent;
|
|
1328
|
-
console.log(
|
|
1327
|
+
console.log(`[SERA] Extracted transcription text | chars=${textContent.length}`);
|
|
1329
1328
|
} else if (section.title === "Medical Classification") {
|
|
1330
|
-
console.log(
|
|
1331
|
-
"\u2139\uFE0F Skipping Medical Classification section (handled by DiagnosticReport)"
|
|
1332
|
-
);
|
|
1333
1329
|
}
|
|
1334
1330
|
}
|
|
1335
1331
|
} else if (resource.text?.div) {
|
|
@@ -1338,10 +1334,8 @@ var useHL7FHIRConverter = () => {
|
|
|
1338
1334
|
}
|
|
1339
1335
|
if (resource.identifier) {
|
|
1340
1336
|
const identifiers = Array.isArray(resource.identifier) ? resource.identifier : [resource.identifier];
|
|
1341
|
-
console.log(`\u{1F4CB} Found ${identifiers.length} identifier(s)`);
|
|
1342
1337
|
for (let j = 0; j < identifiers.length; j++) {
|
|
1343
1338
|
const identifier = identifiers[j];
|
|
1344
|
-
console.log(` [${j}] system: ${identifier.system}, value: ${identifier.value}`);
|
|
1345
1339
|
if (identifier.system === "http://nuxera.ai/session-identifier" && identifier.value) {
|
|
1346
1340
|
sessionId = identifier.value;
|
|
1347
1341
|
break;
|
|
@@ -1350,10 +1344,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1350
1344
|
}
|
|
1351
1345
|
if (!sessionId && resource.id) {
|
|
1352
1346
|
sessionId = resource.id;
|
|
1353
|
-
console.log(
|
|
1354
|
-
"\u26A0\uFE0F [Priority 2] Using Composition.id as fallback session ID:",
|
|
1355
|
-
sessionId
|
|
1356
|
-
);
|
|
1347
|
+
console.log(`[SERA] Using Composition.id as fallback sessionId | sessionId=${sessionId}`);
|
|
1357
1348
|
}
|
|
1358
1349
|
break;
|
|
1359
1350
|
case "Task":
|
|
@@ -1361,10 +1352,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1361
1352
|
for (const identifier of resource.identifier) {
|
|
1362
1353
|
if (identifier.system === "http://nuxera.ai/session-id" && identifier.value) {
|
|
1363
1354
|
sessionId = identifier.value;
|
|
1364
|
-
console.log(
|
|
1365
|
-
"\u2705 [Priority 3] Extracted session ID from Task.identifier:",
|
|
1366
|
-
sessionId
|
|
1367
|
-
);
|
|
1355
|
+
console.log(`[SERA] Extracted sessionId from Task.identifier | sessionId=${sessionId}`);
|
|
1368
1356
|
break;
|
|
1369
1357
|
}
|
|
1370
1358
|
}
|
|
@@ -1374,15 +1362,15 @@ var useHL7FHIRConverter = () => {
|
|
|
1374
1362
|
}
|
|
1375
1363
|
if (!sessionId && fhirData.resourceType === "Bundle" && fhirData.identifier?.value) {
|
|
1376
1364
|
sessionId = fhirData.identifier.value;
|
|
1377
|
-
console.log(
|
|
1365
|
+
console.log(`[SERA] Using Bundle.identifier as fallback sessionId | sessionId=${sessionId}`);
|
|
1378
1366
|
}
|
|
1379
1367
|
if (!sessionId && fhirData.resourceType === "Bundle" && fhirData.id) {
|
|
1380
1368
|
sessionId = fhirData.id;
|
|
1381
|
-
console.log(
|
|
1369
|
+
console.log(`[SERA] Using Bundle.id as fallback sessionId | sessionId=${sessionId}`);
|
|
1382
1370
|
}
|
|
1383
1371
|
if (!sessionId) {
|
|
1384
1372
|
sessionId = `session_${Date.now()}`;
|
|
1385
|
-
console.warn(
|
|
1373
|
+
console.warn(`[SERA] No sessionId found in FHIR response, generated | sessionId=${sessionId}`);
|
|
1386
1374
|
}
|
|
1387
1375
|
return {
|
|
1388
1376
|
transcription: transcription || "No transcription found in FHIR data",
|
|
@@ -1395,7 +1383,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1395
1383
|
}
|
|
1396
1384
|
};
|
|
1397
1385
|
} catch (error) {
|
|
1398
|
-
console.error("
|
|
1386
|
+
console.error("[SERA] FHIR transcription conversion error:", error);
|
|
1399
1387
|
throw new Error(
|
|
1400
1388
|
`Failed to convert FHIR transcription data: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
1401
1389
|
);
|
|
@@ -1441,7 +1429,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1441
1429
|
} catch (error) {
|
|
1442
1430
|
const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
|
|
1443
1431
|
setConversionError(errorMessage);
|
|
1444
|
-
console.error("Transcription conversion failed:", error);
|
|
1432
|
+
console.error("[SERA] Transcription conversion failed:", error);
|
|
1445
1433
|
return {
|
|
1446
1434
|
transcription: "Conversion failed - see raw response",
|
|
1447
1435
|
classification: { "Conversion Error": [errorMessage] },
|
|
@@ -1537,7 +1525,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1537
1525
|
)}|||||F|||${timestamp}`
|
|
1538
1526
|
);
|
|
1539
1527
|
const hl7Message = hl7Lines.join("\r\n") + "\r\n";
|
|
1540
|
-
console.log(
|
|
1528
|
+
console.log(`[SERA] HL7 transcription request constructed | size=${hl7Message.length}`);
|
|
1541
1529
|
const formData = new FormData();
|
|
1542
1530
|
const hl7Blob = new Blob([hl7Message], { type: "text/plain" });
|
|
1543
1531
|
formData.append("hl7_request", hl7Blob, "transcription_request.hl7");
|
|
@@ -1792,10 +1780,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1792
1780
|
});
|
|
1793
1781
|
}
|
|
1794
1782
|
}
|
|
1795
|
-
console.log(
|
|
1796
|
-
"Constructed FHIR Transcription Request Bundle:",
|
|
1797
|
-
JSON.stringify(fhirBundle, null, 2)
|
|
1798
|
-
);
|
|
1783
|
+
console.log(`[SERA] FHIR transcription request constructed | entries=${fhirBundle.entry.length}`);
|
|
1799
1784
|
const formData = new FormData();
|
|
1800
1785
|
const fhirBlob = new Blob([JSON.stringify(fhirBundle, null, 2)], {
|
|
1801
1786
|
type: "application/fhir+json"
|
|
@@ -1807,10 +1792,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1807
1792
|
}
|
|
1808
1793
|
formData.append("sequence", requestData.sequence.toString());
|
|
1809
1794
|
formData.append("isFinalChunk", requestData.isFinalChunk.toString());
|
|
1810
|
-
console.log(
|
|
1811
|
-
"Created FHIR FormData with session ID:",
|
|
1812
|
-
requestData.sessionId || "none (first call)"
|
|
1813
|
-
);
|
|
1795
|
+
console.log(`[SERA] FHIR FormData created | sessionId=${requestData.sessionId || "none"}`);
|
|
1814
1796
|
return formData;
|
|
1815
1797
|
},
|
|
1816
1798
|
[formatFHIRDate, mapFHIRGender]
|
|
@@ -1882,16 +1864,7 @@ var useHL7FHIRConverter = () => {
|
|
|
1882
1864
|
`OBX|${obxSequence++}|TX|REQUEST_TYPE^Request Type||DICTATION|||||F|||${timestamp}`
|
|
1883
1865
|
);
|
|
1884
1866
|
const hl7Message = hl7Lines.join("\r\n") + "\r\n";
|
|
1885
|
-
console.log(
|
|
1886
|
-
console.log("Request Data:", requestData);
|
|
1887
|
-
console.log("Audio File:", {
|
|
1888
|
-
name: audioFile.name,
|
|
1889
|
-
size: audioFile.size,
|
|
1890
|
-
type: audioFile.type
|
|
1891
|
-
});
|
|
1892
|
-
console.log("HL7 Message:");
|
|
1893
|
-
console.log(hl7Message);
|
|
1894
|
-
console.log("=== End Debug ===");
|
|
1867
|
+
console.log(`[SERA] HL7 dictation request constructed | size=${hl7Message.length}, audioFile=${audioFile.name}, audioSize=${audioFile.size}`);
|
|
1895
1868
|
const formData = new FormData();
|
|
1896
1869
|
formData.append("audio", audioFile);
|
|
1897
1870
|
const hl7Blob = new Blob([hl7Message], { type: "text/plain; charset=utf-8" });
|
|
@@ -2046,15 +2019,7 @@ var useHL7FHIRConverter = () => {
|
|
|
2046
2019
|
}
|
|
2047
2020
|
]
|
|
2048
2021
|
};
|
|
2049
|
-
console.log(
|
|
2050
|
-
console.log("Request Data:", requestData);
|
|
2051
|
-
console.log("Audio File:", {
|
|
2052
|
-
name: audioFile.name,
|
|
2053
|
-
size: audioFile.size,
|
|
2054
|
-
type: audioFile.type
|
|
2055
|
-
});
|
|
2056
|
-
console.log("FHIR Bundle:", JSON.stringify(fhirBundle, null, 2));
|
|
2057
|
-
console.log("=== End Debug ===");
|
|
2022
|
+
console.log(`[SERA] FHIR dictation request constructed | entries=${fhirBundle.entry.length}, audioFile=${audioFile.name}, audioSize=${audioFile.size}`);
|
|
2058
2023
|
const formData = new FormData();
|
|
2059
2024
|
formData.append("audio", audioFile);
|
|
2060
2025
|
const fhirBlob = new Blob([JSON.stringify(fhirBundle, null, 2)], {
|
|
@@ -2070,7 +2035,7 @@ var useHL7FHIRConverter = () => {
|
|
|
2070
2035
|
try {
|
|
2071
2036
|
const segments = parseHL7(hl7Data);
|
|
2072
2037
|
let dictationResponse = {};
|
|
2073
|
-
console.log(
|
|
2038
|
+
console.log(`[SERA] Parsing HL7 dictation | segments=${segments.length}`);
|
|
2074
2039
|
for (const segment of segments) {
|
|
2075
2040
|
switch (segment.type) {
|
|
2076
2041
|
case "MSH":
|
|
@@ -2165,7 +2130,7 @@ var useHL7FHIRConverter = () => {
|
|
|
2165
2130
|
);
|
|
2166
2131
|
}
|
|
2167
2132
|
} catch (e) {
|
|
2168
|
-
console.warn("Could not calculate processing time:", e);
|
|
2133
|
+
console.warn("[SERA] Could not calculate processing time:", e);
|
|
2169
2134
|
}
|
|
2170
2135
|
}
|
|
2171
2136
|
const finalResponse = {
|
|
@@ -2175,10 +2140,10 @@ var useHL7FHIRConverter = () => {
|
|
|
2175
2140
|
language: dictationResponse.language,
|
|
2176
2141
|
processingTimes: dictationResponse.processingTimes
|
|
2177
2142
|
};
|
|
2178
|
-
console.log(
|
|
2143
|
+
console.log(`[SERA] HL7 dictation converted | sessionId=${finalResponse.sessionId}, dictationLength=${finalResponse.dictation.length}`);
|
|
2179
2144
|
return finalResponse;
|
|
2180
2145
|
} catch (error) {
|
|
2181
|
-
console.error("HL7 dictation conversion error:", error);
|
|
2146
|
+
console.error("[SERA] HL7 dictation conversion error:", error);
|
|
2182
2147
|
throw new Error(
|
|
2183
2148
|
`Failed to convert HL7 dictation data: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2184
2149
|
);
|
|
@@ -2191,20 +2156,16 @@ var useHL7FHIRConverter = () => {
|
|
|
2191
2156
|
let dictationResponse = {};
|
|
2192
2157
|
let parsedData = fhirData;
|
|
2193
2158
|
if (typeof fhirData === "string") {
|
|
2194
|
-
console.log("\u{1F4DD} Response is a string, parsing JSON...");
|
|
2195
2159
|
try {
|
|
2196
2160
|
parsedData = JSON.parse(fhirData);
|
|
2197
|
-
console.log("\u2705 JSON parsed successfully");
|
|
2198
2161
|
} catch (e) {
|
|
2199
|
-
console.error("
|
|
2162
|
+
console.error("[SERA] Failed to parse FHIR dictation JSON string:", e);
|
|
2200
2163
|
throw new Error("Invalid JSON response from server");
|
|
2201
2164
|
}
|
|
2202
2165
|
}
|
|
2203
|
-
console.log("Parsing FHIR dictation data:", parsedData);
|
|
2204
2166
|
let resources = [];
|
|
2205
2167
|
if (parsedData.resourceType === "Bundle" && parsedData.entry) {
|
|
2206
2168
|
resources = parsedData.entry.map((entry) => entry.resource);
|
|
2207
|
-
console.log(`\u{1F4E6} Found ${resources.length} resources in Bundle`);
|
|
2208
2169
|
} else if (parsedData.resourceType) {
|
|
2209
2170
|
resources = [parsedData];
|
|
2210
2171
|
} else if (parsedData.resource) {
|
|
@@ -2212,26 +2173,17 @@ var useHL7FHIRConverter = () => {
|
|
|
2212
2173
|
}
|
|
2213
2174
|
for (let i = 0; i < resources.length; i++) {
|
|
2214
2175
|
const resource = resources[i];
|
|
2215
|
-
console.log(`
|
|
2216
|
-
--- Processing resource [${i}]: ${resource?.resourceType || "unknown"} ---`);
|
|
2217
2176
|
if (!resource || !resource.resourceType) continue;
|
|
2218
2177
|
switch (resource.resourceType) {
|
|
2219
2178
|
case "MessageHeader":
|
|
2220
2179
|
dictationResponse.sessionId = resource.id;
|
|
2221
|
-
console.log("\u2705 Session ID from MessageHeader:", resource.id);
|
|
2222
2180
|
break;
|
|
2223
2181
|
case "Media":
|
|
2224
|
-
console.log("\u{1F3AF} Found Media resource for dictation");
|
|
2225
2182
|
if (resource.extension && Array.isArray(resource.extension)) {
|
|
2226
|
-
console.log(`\u{1F4CB} Processing ${resource.extension.length} extensions`);
|
|
2227
2183
|
for (const ext of resource.extension) {
|
|
2228
|
-
console.log(
|
|
2229
|
-
` Extension URL: ${ext.url}, valueString: ${ext.valueString || ext.valueInteger}`
|
|
2230
|
-
);
|
|
2231
2184
|
switch (ext.url) {
|
|
2232
2185
|
case "http://nuxera.ai/extensions/transcription":
|
|
2233
2186
|
dictationResponse.dictation = ext.valueString || "";
|
|
2234
|
-
console.log("\u2705\u2705 Extracted dictation text:", dictationResponse.dictation);
|
|
2235
2187
|
break;
|
|
2236
2188
|
case "http://nuxera.ai/extensions/audio-size":
|
|
2237
2189
|
if (!dictationResponse.processingTimes) {
|
|
@@ -2241,17 +2193,12 @@ var useHL7FHIRConverter = () => {
|
|
|
2241
2193
|
if (sizeMatch) {
|
|
2242
2194
|
dictationResponse.processingTimes.audioSizeMB = parseFloat(sizeMatch[1]);
|
|
2243
2195
|
}
|
|
2244
|
-
console.log(
|
|
2245
|
-
"\u2705 Extracted audio size:",
|
|
2246
|
-
dictationResponse.processingTimes.audioSizeMB
|
|
2247
|
-
);
|
|
2248
2196
|
break;
|
|
2249
2197
|
case "http://nuxera.ai/extensions/processing-timestamp":
|
|
2250
2198
|
if (!dictationResponse.processingTimes) {
|
|
2251
2199
|
dictationResponse.processingTimes = {};
|
|
2252
2200
|
}
|
|
2253
2201
|
dictationResponse.processingTimes.requestTimestamp = ext.valueInteger;
|
|
2254
|
-
console.log("\u2705 Extracted processing timestamp:", ext.valueInteger);
|
|
2255
2202
|
break;
|
|
2256
2203
|
case "http://nuxera.ai/extensions/confidence":
|
|
2257
2204
|
dictationResponse.confidence = ext.valueDecimal || ext.valueInteger;
|
|
@@ -2276,11 +2223,9 @@ var useHL7FHIRConverter = () => {
|
|
|
2276
2223
|
}
|
|
2277
2224
|
if (!dictationResponse.sessionId && resource.identifier && resource.identifier[0]) {
|
|
2278
2225
|
dictationResponse.sessionId = resource.identifier[0].value;
|
|
2279
|
-
console.log("\u2705 Session ID from Media.identifier:", dictationResponse.sessionId);
|
|
2280
2226
|
}
|
|
2281
2227
|
if (!dictationResponse.sessionId && resource.id) {
|
|
2282
2228
|
dictationResponse.sessionId = resource.id;
|
|
2283
|
-
console.log("\u2705 Session ID from Media.id:", dictationResponse.sessionId);
|
|
2284
2229
|
}
|
|
2285
2230
|
if (resource.createdDateTime && dictationResponse.processingTimes) {
|
|
2286
2231
|
dictationResponse.processingTimes.processedAt = resource.createdDateTime;
|
|
@@ -2369,14 +2314,9 @@ var useHL7FHIRConverter = () => {
|
|
|
2369
2314
|
dictationResponse.processingTimes.totalProcessingMs = Math.round(
|
|
2370
2315
|
processedAtMs - requestMs
|
|
2371
2316
|
);
|
|
2372
|
-
console.log(
|
|
2373
|
-
"\u2705 Calculated total processing time:",
|
|
2374
|
-
dictationResponse.processingTimes.totalProcessingMs,
|
|
2375
|
-
"ms"
|
|
2376
|
-
);
|
|
2377
2317
|
}
|
|
2378
2318
|
} catch (e) {
|
|
2379
|
-
console.warn("Could not calculate processing time:", e);
|
|
2319
|
+
console.warn("[SERA] Could not calculate FHIR dictation processing time:", e);
|
|
2380
2320
|
}
|
|
2381
2321
|
}
|
|
2382
2322
|
const finalResponse = {
|
|
@@ -2386,10 +2326,10 @@ var useHL7FHIRConverter = () => {
|
|
|
2386
2326
|
language: dictationResponse.language,
|
|
2387
2327
|
processingTimes: dictationResponse.processingTimes
|
|
2388
2328
|
};
|
|
2389
|
-
console.log(
|
|
2329
|
+
console.log(`[SERA] FHIR dictation converted | sessionId=${finalResponse.sessionId}, dictationLength=${finalResponse.dictation.length}`);
|
|
2390
2330
|
return finalResponse;
|
|
2391
2331
|
} catch (error) {
|
|
2392
|
-
console.error("
|
|
2332
|
+
console.error("[SERA] FHIR dictation conversion error:", error);
|
|
2393
2333
|
throw new Error(
|
|
2394
2334
|
`Failed to convert FHIR dictation data: ${error instanceof Error ? error.message : "Unknown error"}`
|
|
2395
2335
|
);
|
|
@@ -2423,7 +2363,7 @@ var useHL7FHIRConverter = () => {
|
|
|
2423
2363
|
} catch (error) {
|
|
2424
2364
|
const errorMessage = error instanceof Error ? error.message : "Unknown conversion error";
|
|
2425
2365
|
setConversionError(errorMessage);
|
|
2426
|
-
console.error("Dictation conversion failed:", error);
|
|
2366
|
+
console.error("[SERA] Dictation conversion failed:", error);
|
|
2427
2367
|
return {
|
|
2428
2368
|
dictation: "",
|
|
2429
2369
|
sessionId: null,
|
|
@@ -2498,12 +2438,7 @@ var useHL7FHIRConverter = () => {
|
|
|
2498
2438
|
)}|||||F|||${timestamp}`
|
|
2499
2439
|
);
|
|
2500
2440
|
const hl7Message = hl7Lines.join("\r\n") + "\r\n";
|
|
2501
|
-
console.log(
|
|
2502
|
-
console.log("Operation:", operation);
|
|
2503
|
-
console.log("API Key Data:", apiKeyData);
|
|
2504
|
-
console.log("HL7 Message:");
|
|
2505
|
-
console.log(hl7Message);
|
|
2506
|
-
console.log("=== End Debug ===");
|
|
2441
|
+
console.log(`[SERA] HL7 API key request constructed | operation=${operation}, size=${hl7Message.length}`);
|
|
2507
2442
|
return hl7Message;
|
|
2508
2443
|
},
|
|
2509
2444
|
[escapeHL7]
|
|
@@ -2663,11 +2598,7 @@ var useHL7FHIRConverter = () => {
|
|
|
2663
2598
|
});
|
|
2664
2599
|
}
|
|
2665
2600
|
}
|
|
2666
|
-
console.log(
|
|
2667
|
-
console.log("Operation:", operation);
|
|
2668
|
-
console.log("API Key Data:", apiKeyData);
|
|
2669
|
-
console.log("FHIR Bundle:", JSON.stringify(fhirBundle, null, 2));
|
|
2670
|
-
console.log("=== End Debug ===");
|
|
2601
|
+
console.log(`[SERA] FHIR API key request constructed | operation=${operation}, entries=${fhirBundle.entry.length}`);
|
|
2671
2602
|
return JSON.stringify(fhirBundle);
|
|
2672
2603
|
},
|
|
2673
2604
|
[]
|
|
@@ -2844,9 +2775,6 @@ var createAudioProcessorWorker = () => {
|
|
|
2844
2775
|
});
|
|
2845
2776
|
this._audioLevelCheckInterval = 0;
|
|
2846
2777
|
|
|
2847
|
-
// Debug: Log audio capture status every few seconds
|
|
2848
|
-
if (this._audioLevelCheckInterval % 1000 === 0) {
|
|
2849
|
-
}
|
|
2850
2778
|
}
|
|
2851
2779
|
|
|
2852
2780
|
if (audioLevel > this._audioThreshold) {
|
|
@@ -2902,7 +2830,6 @@ var createAudioProcessorWorker = () => {
|
|
|
2902
2830
|
},
|
|
2903
2831
|
[flat.buffer]
|
|
2904
2832
|
);
|
|
2905
|
-
console.log('[UPLOAD] Sending chunk: ' + (this._bufferSize / this._sampleRate).toFixed(1) + 's');
|
|
2906
2833
|
// Clear buffer after upload
|
|
2907
2834
|
this._buffer = [];
|
|
2908
2835
|
this._bufferSize = 0;
|
|
@@ -2987,31 +2914,17 @@ var useAudioRecorder = ({
|
|
|
2987
2914
|
clearFailedSessions
|
|
2988
2915
|
} = useAudioRecovery_default(async (audioChunks, metadata) => {
|
|
2989
2916
|
try {
|
|
2990
|
-
console.log("\u{1F504} Retry callback started with audio chunks:", {
|
|
2991
|
-
chunksCount: audioChunks.length,
|
|
2992
|
-
totalSamples: audioChunks.reduce((sum, chunk) => sum + chunk.length, 0),
|
|
2993
|
-
chunkDetails: audioChunks.map((chunk, idx) => ({
|
|
2994
|
-
index: idx,
|
|
2995
|
-
length: chunk.length,
|
|
2996
|
-
hasData: chunk.length > 0
|
|
2997
|
-
}))
|
|
2998
|
-
});
|
|
2999
2917
|
if (audioChunks.length === 0) {
|
|
3000
2918
|
throw new Error("No audio chunks provided for retry");
|
|
3001
2919
|
}
|
|
3002
2920
|
const combinedAudio = combineAudioChunks(audioChunks);
|
|
3003
|
-
console.log(
|
|
3004
|
-
combinedLength: combinedAudio.length,
|
|
3005
|
-
hasAudio: combinedAudio.length > 0
|
|
3006
|
-
});
|
|
2921
|
+
console.log(`[SERA] Recovery retry | chunks=${audioChunks.length}, samples=${combinedAudio.length}`);
|
|
3007
2922
|
if (combinedAudio.length === 0) {
|
|
3008
2923
|
throw new Error("Combined audio is empty");
|
|
3009
2924
|
}
|
|
3010
2925
|
if (!uploadChunkToServerRef.current) {
|
|
3011
2926
|
throw new Error("Upload function not yet initialized");
|
|
3012
2927
|
}
|
|
3013
|
-
const newSessionId = `session_retry_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
3014
|
-
console.log("\u{1F194} Generated new retry session ID:", newSessionId);
|
|
3015
2928
|
await uploadChunkToServerRef.current(
|
|
3016
2929
|
combinedAudio,
|
|
3017
2930
|
true,
|
|
@@ -3020,9 +2933,9 @@ var useAudioRecorder = ({
|
|
|
3020
2933
|
false,
|
|
3021
2934
|
metadata.sampleRate
|
|
3022
2935
|
);
|
|
3023
|
-
console.log("[
|
|
2936
|
+
console.log("[SERA] Recovery retry completed successfully");
|
|
3024
2937
|
} catch (error2) {
|
|
3025
|
-
console.error("[
|
|
2938
|
+
console.error("[SERA] Recovery retry failed:", error2);
|
|
3026
2939
|
throw error2;
|
|
3027
2940
|
}
|
|
3028
2941
|
});
|
|
@@ -3049,7 +2962,6 @@ var useAudioRecorder = ({
|
|
|
3049
2962
|
const ffmpegLoadedRef = React3__namespace.default.useRef(ffmpegLoaded);
|
|
3050
2963
|
React3__namespace.default.useEffect(() => {
|
|
3051
2964
|
ffmpegLoadedRef.current = ffmpegLoaded;
|
|
3052
|
-
console.log(`\u{1F504} ffmpegLoadedRef updated to: ${ffmpegLoaded}`);
|
|
3053
2965
|
}, [ffmpegLoaded]);
|
|
3054
2966
|
React3__namespace.default.useEffect(() => {
|
|
3055
2967
|
selectedModelRef.current = selectedModel;
|
|
@@ -3064,11 +2976,6 @@ var useAudioRecorder = ({
|
|
|
3064
2976
|
selectedFormatRef.current = selectedFormat;
|
|
3065
2977
|
}, [selectedFormat]);
|
|
3066
2978
|
React3__namespace.default.useEffect(() => {
|
|
3067
|
-
console.log("Triggering onTranscriptionUpdate with:", {
|
|
3068
|
-
alreadyDoneTranscription,
|
|
3069
|
-
sessionId: sessionIdRef.current,
|
|
3070
|
-
localSessionId: localSessionIdRef.current
|
|
3071
|
-
});
|
|
3072
2979
|
if (alreadyDoneTranscription.length > 0) {
|
|
3073
2980
|
onTranscriptionUpdate(
|
|
3074
2981
|
alreadyDoneTranscription,
|
|
@@ -3076,30 +2983,21 @@ var useAudioRecorder = ({
|
|
|
3076
2983
|
);
|
|
3077
2984
|
}
|
|
3078
2985
|
}, [alreadyDoneTranscription]);
|
|
3079
|
-
React3__namespace.default.useEffect(() => {
|
|
3080
|
-
console.log("Speciality changed in useAudioRecorder:", speciality);
|
|
3081
|
-
}, [speciality]);
|
|
3082
2986
|
React3__namespace.default.useEffect(() => {
|
|
3083
2987
|
let isMounted = true;
|
|
3084
2988
|
if (!ffmpegLoadedRef.current) {
|
|
3085
2989
|
(async () => {
|
|
3086
|
-
console.log("Initializing FFmpeg WASM\u2026");
|
|
3087
2990
|
try {
|
|
3088
2991
|
const ok = await loadFFmpeg();
|
|
3089
|
-
if (isMounted) {
|
|
3090
|
-
console.log("FFmpeg
|
|
3091
|
-
if (ok) {
|
|
3092
|
-
console.log("FFmpeg WASM initialized successfully for audio recorder");
|
|
3093
|
-
}
|
|
2992
|
+
if (isMounted && ok) {
|
|
2993
|
+
console.log("[SERA] FFmpeg WASM initialized");
|
|
3094
2994
|
}
|
|
3095
2995
|
} catch (error2) {
|
|
3096
2996
|
if (isMounted) {
|
|
3097
|
-
console.error("FFmpeg WASM initialization failed:", error2);
|
|
2997
|
+
console.error("[SERA] FFmpeg WASM initialization failed:", error2);
|
|
3098
2998
|
}
|
|
3099
2999
|
}
|
|
3100
3000
|
})();
|
|
3101
|
-
} else {
|
|
3102
|
-
console.log("FFmpeg WASM already initialized, skipping");
|
|
3103
3001
|
}
|
|
3104
3002
|
return () => {
|
|
3105
3003
|
isMounted = false;
|
|
@@ -3123,12 +3021,11 @@ var useAudioRecorder = ({
|
|
|
3123
3021
|
const track = audioTracks[0];
|
|
3124
3022
|
const settings = track.getSettings();
|
|
3125
3023
|
setCurrentDeviceId(settings.deviceId || null);
|
|
3126
|
-
console.log();
|
|
3127
3024
|
}
|
|
3128
3025
|
testStream.getTracks().forEach((track) => track.stop());
|
|
3129
3026
|
return true;
|
|
3130
3027
|
} catch (error2) {
|
|
3131
|
-
console.error("Microphone validation failed:", error2);
|
|
3028
|
+
console.error("[SERA] Microphone validation failed:", error2);
|
|
3132
3029
|
if (error2 instanceof Error) {
|
|
3133
3030
|
let errorMessage = "";
|
|
3134
3031
|
if (error2.name === "NotFoundError" || error2.name === "DevicesNotFoundError") {
|
|
@@ -3151,9 +3048,6 @@ var useAudioRecorder = ({
|
|
|
3151
3048
|
}
|
|
3152
3049
|
}, [currentDeviceId]);
|
|
3153
3050
|
React3__namespace.default.useEffect(() => {
|
|
3154
|
-
if (speciality) {
|
|
3155
|
-
console.log("Speciality set, microphone validation will happen on recording start");
|
|
3156
|
-
}
|
|
3157
3051
|
}, [speciality]);
|
|
3158
3052
|
const clearAllSessions = React3__namespace.default.useCallback(async () => {
|
|
3159
3053
|
await clearFailedSessions();
|
|
@@ -3162,30 +3056,16 @@ var useAudioRecorder = ({
|
|
|
3162
3056
|
const uploadChunkToServer = React3__namespace.default.useCallback(
|
|
3163
3057
|
async (audioData, isFinalChunk, sequence, retry = false, isPausedChunk = false, sampleRateOverride) => {
|
|
3164
3058
|
const currentFfmpegLoaded = ffmpegLoadedRef.current;
|
|
3165
|
-
console.log(
|
|
3166
|
-
ffmpegLoaded: currentFfmpegLoaded,
|
|
3167
|
-
silenceRemovalEnabled: removeSilenceRef.current,
|
|
3168
|
-
hasRemoveSilenceFunction: typeof removeSilence === "function",
|
|
3169
|
-
isFinalChunk,
|
|
3170
|
-
sequence,
|
|
3171
|
-
audioDataLength: audioData?.length,
|
|
3172
|
-
requestFormat: selectedFormatRef.current,
|
|
3173
|
-
sessionHasFailed: sessionHasFailedChunkRef.current
|
|
3174
|
-
});
|
|
3059
|
+
console.log(`[SERA] Step 6: Upload chunk | sequence=${sequence}, isFinal=${isFinalChunk}, samples=${audioData?.length || 0}, format=${selectedFormatRef.current}`);
|
|
3175
3060
|
processorRef.current?.port.postMessage({ command: "resetUploadChunk" });
|
|
3176
3061
|
if (audioData && localSessionIdRef.current && !retry) {
|
|
3177
3062
|
try {
|
|
3178
|
-
console.log(`[DB] Saving chunk ${sequence} to IndexedDB session ${localSessionIdRef.current}`);
|
|
3179
3063
|
await appendAudioToSession(localSessionIdRef.current, audioData, sequence);
|
|
3180
|
-
console.log(`[DB] \u2713 Successfully saved audio chunk ${sequence} to IndexedDB (${audioData.length} samples)`);
|
|
3181
3064
|
} catch (error2) {
|
|
3182
|
-
console.error(`[
|
|
3065
|
+
console.error(`[SERA] IndexedDB save failed for chunk ${sequence}:`, error2);
|
|
3183
3066
|
}
|
|
3184
|
-
} else {
|
|
3185
|
-
console.log(`[DB] Skipping IndexedDB save: audioData=${!!audioData}, sessionId=${localSessionIdRef.current}, retry=${retry}`);
|
|
3186
3067
|
}
|
|
3187
3068
|
if (sessionHasFailedChunkRef.current && !retry) {
|
|
3188
|
-
console.log(`[SKIP] Session has failed chunk - skipping server upload for sequence ${sequence}, continuing to record audio`);
|
|
3189
3069
|
if (isFinalChunk) {
|
|
3190
3070
|
setShowRetrySessionPrompt(true);
|
|
3191
3071
|
setIsProcessing(false);
|
|
@@ -3193,151 +3073,96 @@ var useAudioRecorder = ({
|
|
|
3193
3073
|
return;
|
|
3194
3074
|
}
|
|
3195
3075
|
try {
|
|
3196
|
-
|
|
3197
|
-
|
|
3198
|
-
|
|
3199
|
-
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
if (amplitude > maxAmplitude) maxAmplitude = amplitude;
|
|
3219
|
-
if (amplitude > 1e-3) {
|
|
3220
|
-
nonZeroSamples++;
|
|
3221
|
-
}
|
|
3222
|
-
}
|
|
3223
|
-
const audioPercentage = nonZeroSamples / audioData.length;
|
|
3224
|
-
console.log(
|
|
3225
|
-
`[AUDIO] Audio stats: maxAmplitude=${maxAmplitude.toFixed(4)}, audioContent=${(audioPercentage * 100).toFixed(2)}%, sequence=${sequence}, isFinal=${isFinalChunk}`
|
|
3226
|
-
);
|
|
3227
|
-
console.log(`[WAV] Converting to WAV with sample rate: ${currentSampleRate}Hz`);
|
|
3228
|
-
const timestamp = Date.now();
|
|
3229
|
-
const fileName = `audio-chunk-${timestamp}.wav`;
|
|
3230
|
-
let wavFile = await convertToWav(audioData, currentSampleRate, fileName);
|
|
3231
|
-
if (!wavFile) {
|
|
3232
|
-
throw new Error("WAV conversion failed through FFmpeg");
|
|
3233
|
-
}
|
|
3234
|
-
console.log(`[INFO] Original WAV file: ${wavFile.size} bytes, ${wavFile.name}`);
|
|
3235
|
-
if (currentFfmpegLoaded && removeSilenceRef.current) {
|
|
3236
|
-
try {
|
|
3237
|
-
console.log("[SILENCE] Attempting to remove silence from audio chunk...");
|
|
3238
|
-
const processedFile = await removeSilence(wavFile);
|
|
3239
|
-
if (processedFile) {
|
|
3240
|
-
console.log(
|
|
3241
|
-
`[SUCCESS] Silence removed successfully: ${processedFile.size} bytes (was ${wavFile.size} bytes)`
|
|
3242
|
-
);
|
|
3243
|
-
if (processedFile.size < 1e3) {
|
|
3244
|
-
console.warn(
|
|
3245
|
-
`[WARN] Processed file very small (${processedFile.size} bytes), using original file`
|
|
3246
|
-
);
|
|
3247
|
-
} else {
|
|
3248
|
-
wavFile = processedFile;
|
|
3249
|
-
}
|
|
3250
|
-
} else {
|
|
3251
|
-
console.warn("Silence removal returned null, using original file");
|
|
3252
|
-
}
|
|
3253
|
-
} catch (silenceError) {
|
|
3254
|
-
console.warn("Silence removal failed, using original file:", silenceError);
|
|
3255
|
-
}
|
|
3256
|
-
} else {
|
|
3257
|
-
console.log("[SILENCE] Silence removal disabled or not loaded");
|
|
3258
|
-
}
|
|
3259
|
-
let audioFile = wavFile;
|
|
3260
|
-
try {
|
|
3261
|
-
console.log("[FLAC] Converting WAV to FLAC...");
|
|
3262
|
-
const flacFile = await convertToFlac(wavFile);
|
|
3263
|
-
if (flacFile && flacFile.type === "audio/flac") {
|
|
3264
|
-
audioFile = flacFile;
|
|
3265
|
-
console.log(
|
|
3266
|
-
`[FLAC] Conversion successful: ${wavFile.size} bytes WAV \u2192 ${flacFile.size} bytes FLAC`
|
|
3267
|
-
);
|
|
3268
|
-
} else {
|
|
3269
|
-
console.warn("[FLAC] Conversion failed, using WAV file");
|
|
3270
|
-
}
|
|
3271
|
-
} catch (flacError) {
|
|
3272
|
-
console.warn("[FLAC] Conversion error, using WAV file:", flacError);
|
|
3076
|
+
if (!audioData || audioData.length === 0) {
|
|
3077
|
+
if (retry) {
|
|
3078
|
+
throw new pRetry.AbortError("No audio data provided for retry");
|
|
3079
|
+
}
|
|
3080
|
+
throw new Error("No audio data provided");
|
|
3081
|
+
}
|
|
3082
|
+
const currentSampleRate = sampleRateOverride ?? recordingSampleRateRef.current ?? audioContextRef.current?.sampleRate;
|
|
3083
|
+
if (!isValidSampleRate(currentSampleRate)) {
|
|
3084
|
+
throw new InvalidSampleRateError(currentSampleRate);
|
|
3085
|
+
}
|
|
3086
|
+
console.log(`[SERA] Step 7: Audio preparation | ${audioData.length} samples, ${(audioData.length / currentSampleRate).toFixed(1)}s, ${currentSampleRate}Hz`);
|
|
3087
|
+
const timestamp = Date.now();
|
|
3088
|
+
const fileName = `audio-chunk-${timestamp}.wav`;
|
|
3089
|
+
let wavFile = await convertToWav(audioData, currentSampleRate, fileName);
|
|
3090
|
+
if (!wavFile) {
|
|
3091
|
+
throw new Error("WAV conversion failed through FFmpeg");
|
|
3092
|
+
}
|
|
3093
|
+
if (currentFfmpegLoaded && removeSilenceRef.current) {
|
|
3094
|
+
try {
|
|
3095
|
+
const processedFile = await removeSilence(wavFile);
|
|
3096
|
+
if (processedFile && processedFile.size >= 1e3) {
|
|
3097
|
+
wavFile = processedFile;
|
|
3273
3098
|
}
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3099
|
+
} catch (silenceError) {
|
|
3100
|
+
console.warn("[SERA] Silence removal failed, using original file:", silenceError);
|
|
3101
|
+
}
|
|
3102
|
+
}
|
|
3103
|
+
let audioFile = wavFile;
|
|
3104
|
+
try {
|
|
3105
|
+
const flacFile = await convertToFlac(wavFile);
|
|
3106
|
+
if (flacFile && flacFile.type === "audio/flac") {
|
|
3107
|
+
audioFile = flacFile;
|
|
3108
|
+
}
|
|
3109
|
+
} catch (flacError) {
|
|
3110
|
+
console.warn("[SERA] FLAC conversion failed, using WAV file:", flacError);
|
|
3111
|
+
}
|
|
3112
|
+
console.log(`[SERA] Step 8: Audio file ready | ${audioFile.name}, ${audioFile.size} bytes`);
|
|
3113
|
+
const patientDetailsPayload = patientDetails;
|
|
3114
|
+
const requestData = {
|
|
3115
|
+
sessionId: retry ? void 0 : sessionIdRef.current || void 0,
|
|
3116
|
+
model: selectedModelRef.current,
|
|
3117
|
+
doctorName,
|
|
3118
|
+
patientDetails: patientDetailsPayload,
|
|
3119
|
+
removeSilence: removeSilenceRef.current,
|
|
3120
|
+
skipDiarization: skipDiarizationRef.current,
|
|
3121
|
+
isFinalChunk,
|
|
3122
|
+
isPaused: isPausedChunk,
|
|
3123
|
+
sequence,
|
|
3124
|
+
speciality,
|
|
3125
|
+
retry
|
|
3126
|
+
};
|
|
3127
|
+
let formData;
|
|
3128
|
+
switch (selectedFormatRef.current) {
|
|
3129
|
+
case "hl7":
|
|
3130
|
+
formData = createHL7TranscriptionRequest(audioFile, requestData);
|
|
3131
|
+
break;
|
|
3132
|
+
case "fhir":
|
|
3133
|
+
formData = createFHIRTranscriptionRequest(audioFile, requestData);
|
|
3134
|
+
break;
|
|
3135
|
+
case "json":
|
|
3136
|
+
default:
|
|
3137
|
+
formData = new FormData();
|
|
3138
|
+
if (retry) {
|
|
3139
|
+
formData.append("retry", "true");
|
|
3140
|
+
} else if (sessionIdRef.current) {
|
|
3141
|
+
formData.append("sessionId", sessionIdRef.current);
|
|
3281
3142
|
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
patientDetails
|
|
3288
|
-
removeSilence: removeSilenceRef.current,
|
|
3289
|
-
skipDiarization: skipDiarizationRef.current,
|
|
3290
|
-
isFinalChunk,
|
|
3291
|
-
isPaused: isPausedChunk,
|
|
3292
|
-
sequence,
|
|
3293
|
-
speciality,
|
|
3294
|
-
retry
|
|
3295
|
-
};
|
|
3296
|
-
let formData;
|
|
3297
|
-
let contentType;
|
|
3298
|
-
switch (selectedFormatRef.current) {
|
|
3299
|
-
case "hl7":
|
|
3300
|
-
formData = createHL7TranscriptionRequest(audioFile, requestData);
|
|
3301
|
-
contentType = "multipart/form-data; hl7-request=true";
|
|
3302
|
-
console.log("Created HL7-formatted request");
|
|
3303
|
-
console.log("HL7 FormData entries:", Array.from(formData.entries()));
|
|
3304
|
-
break;
|
|
3305
|
-
case "fhir":
|
|
3306
|
-
formData = createFHIRTranscriptionRequest(audioFile, requestData);
|
|
3307
|
-
contentType = "multipart/form-data; fhir-request=true";
|
|
3308
|
-
console.log("Created FHIR-formatted request");
|
|
3309
|
-
console.log("FHIR FormData entries:", Array.from(formData.entries()));
|
|
3310
|
-
break;
|
|
3311
|
-
case "json":
|
|
3312
|
-
default:
|
|
3313
|
-
formData = new FormData();
|
|
3314
|
-
if (retry) {
|
|
3315
|
-
formData.append("retry", "true");
|
|
3316
|
-
} else if (sessionIdRef.current) {
|
|
3317
|
-
formData.append("sessionId", sessionIdRef.current);
|
|
3318
|
-
}
|
|
3319
|
-
formData.append("audio", audioFile);
|
|
3320
|
-
formData.append("model", selectedModelRef.current);
|
|
3321
|
-
formData.append("doctorName", doctorName);
|
|
3322
|
-
if (patientHistory) formData.append("patientHistory", patientHistory);
|
|
3323
|
-
if (patientDetailsPayload) {
|
|
3324
|
-
formData.append("patientDetails", JSON.stringify(patientDetailsPayload));
|
|
3325
|
-
}
|
|
3326
|
-
formData.append("removeSilence", removeSilenceRef.current.toString());
|
|
3327
|
-
formData.append("skipDiarization", skipDiarizationRef.current.toString());
|
|
3328
|
-
formData.append("isFinalChunk", isFinalChunk.toString());
|
|
3329
|
-
formData.append("isPaused", isPausedChunk.toString());
|
|
3330
|
-
formData.append("sequence", sequence.toString());
|
|
3331
|
-
formData.append("speciality", speciality);
|
|
3332
|
-
console.log("Created JSON-formatted request");
|
|
3333
|
-
break;
|
|
3143
|
+
formData.append("audio", audioFile);
|
|
3144
|
+
formData.append("model", selectedModelRef.current);
|
|
3145
|
+
formData.append("doctorName", doctorName);
|
|
3146
|
+
if (patientHistory) formData.append("patientHistory", patientHistory);
|
|
3147
|
+
if (patientDetailsPayload) {
|
|
3148
|
+
formData.append("patientDetails", JSON.stringify(patientDetailsPayload));
|
|
3334
3149
|
}
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3150
|
+
formData.append("removeSilence", removeSilenceRef.current.toString());
|
|
3151
|
+
formData.append("skipDiarization", skipDiarizationRef.current.toString());
|
|
3152
|
+
formData.append("isFinalChunk", isFinalChunk.toString());
|
|
3153
|
+
formData.append("isPaused", isPausedChunk.toString());
|
|
3154
|
+
formData.append("sequence", sequence.toString());
|
|
3155
|
+
formData.append("speciality", speciality);
|
|
3156
|
+
break;
|
|
3157
|
+
}
|
|
3158
|
+
const headers = {
|
|
3159
|
+
"x-api-key": effectiveApiKey || "",
|
|
3160
|
+
"x-response-format": selectedFormatRef.current,
|
|
3161
|
+
"x-request-format": selectedFormatRef.current
|
|
3162
|
+
};
|
|
3163
|
+
const data = await pRetry__default.default(
|
|
3164
|
+
async (attemptNumber) => {
|
|
3165
|
+
console.log(`[SERA] Step 9: Sending to server | sequence=${sequence}, attempt=${attemptNumber}`);
|
|
3341
3166
|
const response = await fetch(`${apiBaseUrl}/api/transcribe`, {
|
|
3342
3167
|
method: "POST",
|
|
3343
3168
|
headers,
|
|
@@ -3345,11 +3170,7 @@ var useAudioRecorder = ({
|
|
|
3345
3170
|
});
|
|
3346
3171
|
if (!response.ok) {
|
|
3347
3172
|
const errorText = await response.text();
|
|
3348
|
-
console.error(
|
|
3349
|
-
status: response.status,
|
|
3350
|
-
statusText: response.statusText,
|
|
3351
|
-
body: errorText
|
|
3352
|
-
});
|
|
3173
|
+
console.error(`[SERA] Server error | status=${response.status}, body=${errorText}`);
|
|
3353
3174
|
let errorMessage = `HTTP ${response.status}`;
|
|
3354
3175
|
try {
|
|
3355
3176
|
const errorData = JSON.parse(errorText);
|
|
@@ -3370,30 +3191,20 @@ var useAudioRecorder = ({
|
|
|
3370
3191
|
let responseData;
|
|
3371
3192
|
if (selectedFormatRef.current === "json") {
|
|
3372
3193
|
responseData = await response.json();
|
|
3373
|
-
console.log("Parsed JSON response:", responseData);
|
|
3374
3194
|
} else if (selectedFormatRef.current === "hl7") {
|
|
3375
3195
|
responseData = await response.text();
|
|
3376
|
-
console.log("Received HL7 response:", responseData);
|
|
3377
3196
|
} else if (selectedFormatRef.current === "fhir") {
|
|
3378
3197
|
responseData = await response.json();
|
|
3379
|
-
console.log("Received FHIR response:", responseData);
|
|
3380
3198
|
} else {
|
|
3381
3199
|
const responseText = await response.text();
|
|
3382
3200
|
try {
|
|
3383
3201
|
responseData = JSON.parse(responseText);
|
|
3384
|
-
console.log("Fallback: Parsed as JSON:", responseData);
|
|
3385
3202
|
} catch {
|
|
3386
3203
|
responseData = responseText;
|
|
3387
|
-
console.log("Fallback: Using as text:", responseData);
|
|
3388
3204
|
}
|
|
3389
3205
|
}
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
selectedFormatRef.current
|
|
3393
|
-
);
|
|
3394
|
-
console.log("Original response:", responseData);
|
|
3395
|
-
console.log("Converted response:", convertedData);
|
|
3396
|
-
return convertedData;
|
|
3206
|
+
console.log(`[SERA] Step 10: Server response received | sequence=${sequence}`);
|
|
3207
|
+
return responseData;
|
|
3397
3208
|
},
|
|
3398
3209
|
{
|
|
3399
3210
|
retries: 3,
|
|
@@ -3402,13 +3213,7 @@ var useAudioRecorder = ({
|
|
|
3402
3213
|
maxTimeout: 1e4,
|
|
3403
3214
|
randomize: true,
|
|
3404
3215
|
onFailedAttempt: (error2) => {
|
|
3405
|
-
console.warn(
|
|
3406
|
-
`[WARN] Transcribe attempt ${error2.attemptNumber} failed for sequence ${sequence}:`,
|
|
3407
|
-
{
|
|
3408
|
-
error: error2,
|
|
3409
|
-
retriesLeft: error2.retriesLeft
|
|
3410
|
-
}
|
|
3411
|
-
);
|
|
3216
|
+
console.warn(`[SERA] Attempt ${error2.attemptNumber} failed for sequence ${sequence} | retriesLeft=${error2.retriesLeft}`);
|
|
3412
3217
|
if (error2.retriesLeft > 0) {
|
|
3413
3218
|
setError(
|
|
3414
3219
|
`Network issue detected. Retrying... (${error2.retriesLeft} attempts remaining)`
|
|
@@ -3417,27 +3222,23 @@ var useAudioRecorder = ({
|
|
|
3417
3222
|
}
|
|
3418
3223
|
}
|
|
3419
3224
|
);
|
|
3225
|
+
const convertedData = convertTranscriptionResponse(data, selectedFormatRef.current);
|
|
3420
3226
|
if (conversionError) {
|
|
3421
3227
|
clearError();
|
|
3422
3228
|
}
|
|
3423
3229
|
if (error && error.includes("Retrying")) {
|
|
3424
3230
|
setError(null);
|
|
3425
3231
|
}
|
|
3426
|
-
if (!
|
|
3232
|
+
if (!convertedData) {
|
|
3427
3233
|
throw new Error("No data received from transcription server");
|
|
3428
3234
|
}
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
data.sessionId
|
|
3434
|
-
);
|
|
3435
|
-
sessionIdRef.current = data.sessionId;
|
|
3436
|
-
} else if (!retry && data.sessionId && !sessionIdRef.current) {
|
|
3437
|
-
console.log("[SUCCESS] Received initial server session ID:", data.sessionId);
|
|
3438
|
-
sessionIdRef.current = data.sessionId;
|
|
3235
|
+
if (retry && convertedData.sessionId) {
|
|
3236
|
+
sessionIdRef.current = convertedData.sessionId;
|
|
3237
|
+
} else if (!retry && convertedData.sessionId && !sessionIdRef.current) {
|
|
3238
|
+
sessionIdRef.current = convertedData.sessionId;
|
|
3439
3239
|
}
|
|
3440
|
-
|
|
3240
|
+
console.log(`[SERA] Step 11: Transcription received | sequence=${sequence}, sessionId=${sessionIdRef.current}`);
|
|
3241
|
+
receivedTranscriptionsRef.current.set(sequence, convertedData.transcription);
|
|
3441
3242
|
while (receivedTranscriptionsRef.current.has(nextExpectedSequenceRef.current)) {
|
|
3442
3243
|
const t = receivedTranscriptionsRef.current.get(nextExpectedSequenceRef.current);
|
|
3443
3244
|
setAlreadyDoneTranscription(t);
|
|
@@ -3445,17 +3246,21 @@ var useAudioRecorder = ({
|
|
|
3445
3246
|
nextExpectedSequenceRef.current++;
|
|
3446
3247
|
}
|
|
3447
3248
|
if (isFinalChunk) {
|
|
3249
|
+
console.log(`[SERA] Step 12: Final chunk complete - transcription done | sessionId=${sessionIdRef.current}`);
|
|
3448
3250
|
setTranscriptionDone(true);
|
|
3449
|
-
onTranscriptionComplete(
|
|
3251
|
+
onTranscriptionComplete(
|
|
3252
|
+
convertedData.transcription,
|
|
3253
|
+
convertedData.classifiedInfo,
|
|
3254
|
+
sessionIdRef.current
|
|
3255
|
+
);
|
|
3450
3256
|
if (localSessionIdRef.current) {
|
|
3451
3257
|
await markSessionComplete(localSessionIdRef.current);
|
|
3452
3258
|
setShowRetrySessionPrompt(false);
|
|
3453
3259
|
}
|
|
3454
3260
|
}
|
|
3455
3261
|
} catch (err) {
|
|
3456
|
-
console.error(`[
|
|
3262
|
+
console.error(`[SERA] Upload failed for sequence ${sequence}:`, err);
|
|
3457
3263
|
if (conversionError) {
|
|
3458
|
-
console.error("Conversion error during upload:", conversionError);
|
|
3459
3264
|
setError(`Data conversion failed: ${conversionError}`);
|
|
3460
3265
|
}
|
|
3461
3266
|
const isAbortError = err instanceof Error && err.name === "AbortError";
|
|
@@ -3466,7 +3271,7 @@ var useAudioRecorder = ({
|
|
|
3466
3271
|
localSessionIdRef.current,
|
|
3467
3272
|
err instanceof Error ? err.message : "Unknown error"
|
|
3468
3273
|
);
|
|
3469
|
-
console.
|
|
3274
|
+
console.warn(`[SERA] Chunk ${sequence} failed - continuing to record audio locally`);
|
|
3470
3275
|
if (isFinalChunk) {
|
|
3471
3276
|
setShowRetrySessionPrompt(true);
|
|
3472
3277
|
}
|
|
@@ -3537,18 +3342,17 @@ var useAudioRecorder = ({
|
|
|
3537
3342
|
if (failedSession) {
|
|
3538
3343
|
const success = await retrySession(failedSession.id);
|
|
3539
3344
|
if (success) {
|
|
3540
|
-
console.log(`Successfully retried session ${failedSession.id}`);
|
|
3541
3345
|
await deleteSession(failedSession.id);
|
|
3542
|
-
console.log(`
|
|
3346
|
+
console.log(`[SERA] Session retry succeeded | sessionId=${failedSession.id}`);
|
|
3543
3347
|
setError(null);
|
|
3544
3348
|
retrySucceeded = true;
|
|
3545
3349
|
} else {
|
|
3546
|
-
console.
|
|
3350
|
+
console.warn(`[SERA] Session retry failed | sessionId=${failedSession.id}`);
|
|
3547
3351
|
setError("Retry failed. Please check your connection and try again.");
|
|
3548
3352
|
}
|
|
3549
3353
|
}
|
|
3550
3354
|
} catch (error2) {
|
|
3551
|
-
console.error("Error retrying failed
|
|
3355
|
+
console.error("[SERA] Error retrying failed session:", error2);
|
|
3552
3356
|
setError("Failed to retry sessions. Please try again.");
|
|
3553
3357
|
} finally {
|
|
3554
3358
|
setIsRetryingSession(false);
|
|
@@ -3581,7 +3385,7 @@ var useAudioRecorder = ({
|
|
|
3581
3385
|
const localSessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
3582
3386
|
localSessionIdRef.current = localSessionId;
|
|
3583
3387
|
sessionIdRef.current = null;
|
|
3584
|
-
console.log(
|
|
3388
|
+
console.log(`[SERA] Step 1: New recording session created | localSessionId=${localSessionId}`);
|
|
3585
3389
|
}
|
|
3586
3390
|
const stream = await navigator.mediaDevices.getUserMedia({
|
|
3587
3391
|
audio: {
|
|
@@ -3597,9 +3401,8 @@ var useAudioRecorder = ({
|
|
|
3597
3401
|
const track = audioTracks[0];
|
|
3598
3402
|
const settings = track.getSettings();
|
|
3599
3403
|
setCurrentDeviceId(settings.deviceId || null);
|
|
3600
|
-
console.log("Recording started with local session:", localSessionIdRef.current);
|
|
3601
3404
|
track.addEventListener("ended", () => {
|
|
3602
|
-
console.
|
|
3405
|
+
console.warn("[SERA] Microphone disconnected during recording");
|
|
3603
3406
|
setError("Microphone disconnected. Session saved - please reconnect and retry.");
|
|
3604
3407
|
stopRecording();
|
|
3605
3408
|
});
|
|
@@ -3626,38 +3429,26 @@ var useAudioRecorder = ({
|
|
|
3626
3429
|
level,
|
|
3627
3430
|
silentDuration,
|
|
3628
3431
|
hasDetectedAudio,
|
|
3629
|
-
isInitialPhase
|
|
3630
|
-
totalRecordingTime,
|
|
3631
|
-
lastAudioTime
|
|
3432
|
+
isInitialPhase
|
|
3632
3433
|
} = event.data;
|
|
3633
3434
|
if (command === "finalChunk" && audioBuffer) {
|
|
3634
3435
|
const sequence = sequenceCounterRef.current++;
|
|
3635
3436
|
const audioArray = new Float32Array(audioBuffer);
|
|
3636
|
-
console.log(`[
|
|
3437
|
+
console.log(`[SERA] Step 4: Final chunk received | sequence=${sequence}, samples=${audioArray.length}`);
|
|
3637
3438
|
enqueueChunk(audioArray, true, sequence);
|
|
3638
3439
|
} else if (command === "chunk" && audioBuffer) {
|
|
3639
3440
|
const sequence = sequenceCounterRef.current++;
|
|
3640
3441
|
const audioArray = new Float32Array(audioBuffer);
|
|
3641
|
-
console.log(`[
|
|
3442
|
+
console.log(`[SERA] Step 4: Chunk received | sequence=${sequence}, samples=${audioArray.length}`);
|
|
3642
3443
|
enqueueChunk(audioArray, false, sequence);
|
|
3643
3444
|
} else if (command === "pauseChunk" && audioBuffer) {
|
|
3644
3445
|
const sequence = sequenceCounterRef.current++;
|
|
3645
|
-
console.log(`[RECEIVE] Pause chunk with audioBuffer, sequence: ${sequence}`);
|
|
3646
3446
|
enqueueChunk(new Float32Array(audioBuffer), false, sequence, true);
|
|
3647
3447
|
} else if (command === "audioLevel") {
|
|
3648
3448
|
setAudioLevel(level);
|
|
3649
3449
|
} else if (command === "prolongedSilence") {
|
|
3650
|
-
console.
|
|
3651
|
-
`Prolonged silence detected: ${Math.round(silentDuration)}s silent, ${Math.round(
|
|
3652
|
-
totalRecordingTime
|
|
3653
|
-
)}s total, last audio ${Math.round(lastAudioTime)}s ago`
|
|
3654
|
-
);
|
|
3450
|
+
console.warn(`[SERA] Prolonged silence: ${Math.round(silentDuration)}s`);
|
|
3655
3451
|
} else if (command === "noAudioDetected") {
|
|
3656
|
-
console.log(
|
|
3657
|
-
`No audio detected: ${Math.round(
|
|
3658
|
-
silentDuration
|
|
3659
|
-
)}s silent, hasDetectedAudio: ${hasDetectedAudio}, isInitialPhase: ${isInitialPhase}`
|
|
3660
|
-
);
|
|
3661
3452
|
setNoAudioDetected(true);
|
|
3662
3453
|
let errorMessage;
|
|
3663
3454
|
if (isInitialPhase && !hasDetectedAudio) {
|
|
@@ -3677,7 +3468,7 @@ var useAudioRecorder = ({
|
|
|
3677
3468
|
source.connect(processor);
|
|
3678
3469
|
audioContextRef.current = audioContext;
|
|
3679
3470
|
recordingSampleRateRef.current = audioContext.sampleRate;
|
|
3680
|
-
console.log(`[
|
|
3471
|
+
console.log(`[SERA] Step 2: Recording started | sampleRate=${audioContext.sampleRate}Hz`);
|
|
3681
3472
|
processorRef.current = processor;
|
|
3682
3473
|
setIsRecording(true);
|
|
3683
3474
|
const intervalId = window.setInterval(() => {
|
|
@@ -3685,7 +3476,7 @@ var useAudioRecorder = ({
|
|
|
3685
3476
|
}, 47e3);
|
|
3686
3477
|
setUploadChunkInterval(intervalId);
|
|
3687
3478
|
} catch (err) {
|
|
3688
|
-
console.error("Recording start failed:", err);
|
|
3479
|
+
console.error("[SERA] Recording start failed:", err);
|
|
3689
3480
|
}
|
|
3690
3481
|
}, [
|
|
3691
3482
|
validateMicrophoneAccess,
|
|
@@ -3697,13 +3488,12 @@ var useAudioRecorder = ({
|
|
|
3697
3488
|
currentDeviceId
|
|
3698
3489
|
]);
|
|
3699
3490
|
const stopRecording = React3__namespace.default.useCallback(async () => {
|
|
3700
|
-
console.log("
|
|
3491
|
+
console.log("[SERA] Step 3: Stop recording requested");
|
|
3701
3492
|
if (uploadChunkInterval) {
|
|
3702
3493
|
clearInterval(uploadChunkInterval);
|
|
3703
3494
|
setUploadChunkInterval(null);
|
|
3704
3495
|
}
|
|
3705
3496
|
if (processorRef.current) {
|
|
3706
|
-
console.log("Stopping recording and sending final chunk --> ", isPaused ? "paused" : "final");
|
|
3707
3497
|
processorRef.current.port.postMessage({ command: "stop" });
|
|
3708
3498
|
setTimeout(async () => {
|
|
3709
3499
|
if (processorRef.current) {
|
|
@@ -3721,18 +3511,13 @@ var useAudioRecorder = ({
|
|
|
3721
3511
|
}, 500);
|
|
3722
3512
|
}
|
|
3723
3513
|
setIsRecording(false);
|
|
3724
|
-
console.log("\u{1F50D} Stop recording - checking for failed session:", {
|
|
3725
|
-
hasSessionFailed: sessionHasFailedChunkRef.current,
|
|
3726
|
-
localSessionId: localSessionIdRef.current
|
|
3727
|
-
});
|
|
3728
3514
|
if (sessionHasFailedChunkRef.current && localSessionIdRef.current) {
|
|
3729
|
-
console.
|
|
3515
|
+
console.warn("[SERA] Recording stopped with failed session - showing retry UI");
|
|
3730
3516
|
setShowRetrySessionPrompt(true);
|
|
3731
3517
|
}
|
|
3732
3518
|
}, [uploadChunkInterval]);
|
|
3733
3519
|
React3__namespace.default.useEffect(() => {
|
|
3734
3520
|
const handleDeviceChange = async () => {
|
|
3735
|
-
console.log("Audio device change detected");
|
|
3736
3521
|
try {
|
|
3737
3522
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
3738
3523
|
const audioInputDevices = devices.filter((device) => device.kind === "audioinput");
|
|
@@ -3751,7 +3536,7 @@ var useAudioRecorder = ({
|
|
|
3751
3536
|
}
|
|
3752
3537
|
}
|
|
3753
3538
|
} catch (error2) {
|
|
3754
|
-
console.error("Device change detection failed:", error2);
|
|
3539
|
+
console.error("[SERA] Device change detection failed:", error2);
|
|
3755
3540
|
setError("Unable to detect audio devices. Please check your microphone permissions.");
|
|
3756
3541
|
}
|
|
3757
3542
|
};
|
|
@@ -3771,7 +3556,7 @@ var useAudioRecorder = ({
|
|
|
3771
3556
|
}, 1e3);
|
|
3772
3557
|
}
|
|
3773
3558
|
} catch (error2) {
|
|
3774
|
-
console.error("Device selection failed:", error2);
|
|
3559
|
+
console.error("[SERA] Device selection failed:", error2);
|
|
3775
3560
|
setError("Failed to switch to selected microphone.");
|
|
3776
3561
|
}
|
|
3777
3562
|
},
|
|
@@ -3807,7 +3592,7 @@ var useAudioRecorder = ({
|
|
|
3807
3592
|
return;
|
|
3808
3593
|
}
|
|
3809
3594
|
const { chunk, isFinal, sequence, isPaused: isPaused2 = false } = chunkQueueRef.current.shift();
|
|
3810
|
-
console.log(`[
|
|
3595
|
+
console.log(`[SERA] Step 5: Queue processing | sequence=${sequence}, isFinal=${isFinal}, queued=${chunkQueueRef.current.length}`);
|
|
3811
3596
|
isProcessingQueueRef.current = true;
|
|
3812
3597
|
uploadChunkToServer(chunk, isFinal, sequence, false, isPaused2).finally(() => {
|
|
3813
3598
|
isProcessingQueueRef.current = false;
|
|
@@ -3816,12 +3601,9 @@ var useAudioRecorder = ({
|
|
|
3816
3601
|
}, [uploadChunkToServer, isLoaded]);
|
|
3817
3602
|
const enqueueChunk = React3__namespace.default.useCallback(
|
|
3818
3603
|
(audioData, isFinalChunk, sequence, isPausedChunk = false) => {
|
|
3819
|
-
console.log(`[QUEUE] Enqueuing ${isFinalChunk ? "FINAL" : isPausedChunk ? "PAUSED" : "regular"} chunk ${sequence}, samples: ${audioData?.length || 0}, queue size: ${chunkQueueRef.current.length}`);
|
|
3820
3604
|
if (isFinalChunk) {
|
|
3821
3605
|
if (!sessionHasFailedChunkRef.current) {
|
|
3822
3606
|
setIsProcessing(true);
|
|
3823
|
-
} else {
|
|
3824
|
-
console.log("\u26A0\uFE0F Session has failed - skipping processing state (IndexedDB save only)");
|
|
3825
3607
|
}
|
|
3826
3608
|
}
|
|
3827
3609
|
chunkQueueRef.current.push({
|
|
@@ -4531,21 +4313,13 @@ var AudioRecorder = ({
|
|
|
4531
4313
|
patientDetails,
|
|
4532
4314
|
selectedFormat,
|
|
4533
4315
|
onTranscriptionUpdate: (text, sessionId) => {
|
|
4534
|
-
console.log(
|
|
4316
|
+
console.log(`[SERA] Transcription update received | sessionId=${sessionId}, textLength=${text.length}`);
|
|
4535
4317
|
if (text.length > 0) {
|
|
4536
|
-
console.log("Transcription update:", text, sessionId);
|
|
4537
4318
|
onTranscriptionUpdate && onTranscriptionUpdate(text, sessionId);
|
|
4538
4319
|
}
|
|
4539
4320
|
},
|
|
4540
4321
|
onTranscriptionComplete: (text, classification, sessionId) => {
|
|
4541
|
-
console.log(
|
|
4542
|
-
"onTranscriptionComplete called with text:",
|
|
4543
|
-
text,
|
|
4544
|
-
"classification:",
|
|
4545
|
-
classification,
|
|
4546
|
-
"sessionId:",
|
|
4547
|
-
sessionId
|
|
4548
|
-
);
|
|
4322
|
+
console.log(`[SERA] Transcription complete | sessionId=${sessionId}, textLength=${text.length}, hasClassification=${!!classification}`);
|
|
4549
4323
|
onTranscriptionComplete && onTranscriptionComplete(text, classification, sessionId);
|
|
4550
4324
|
}
|
|
4551
4325
|
});
|
|
@@ -4604,7 +4378,7 @@ var AudioRecorder = ({
|
|
|
4604
4378
|
] }) });
|
|
4605
4379
|
}
|
|
4606
4380
|
if (isMicrophoneError) {
|
|
4607
|
-
console.log("
|
|
4381
|
+
console.log("[SERA] Showing microphone error UI");
|
|
4608
4382
|
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: [
|
|
4609
4383
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertTriangle, { className: "h-5 w-5 text-red-400" }) }),
|
|
4610
4384
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "ml-3 flex-1", children: [
|
|
@@ -4614,7 +4388,7 @@ var AudioRecorder = ({
|
|
|
4614
4388
|
"button",
|
|
4615
4389
|
{
|
|
4616
4390
|
onClick: () => {
|
|
4617
|
-
console.log("
|
|
4391
|
+
console.log("[SERA] Microphone check again requested");
|
|
4618
4392
|
validateMicrophoneAccess();
|
|
4619
4393
|
},
|
|
4620
4394
|
className: "bg-red-600 hover:bg-red-700 text-white px-3 py-1 rounded text-sm transition-colors",
|