@omniloy/sofia-sdk 1.0.10 → 1.0.11

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.
@@ -5145,7 +5145,7 @@ const {
5145
5145
  create: create$1,
5146
5146
  } = axios;
5147
5147
 
5148
- const version$2 = "1.0.10";
5148
+ const version$2 = "1.0.11";
5149
5149
 
5150
5150
  const handleApiError$1 = async (error) => {
5151
5151
  return Promise.reject(error);
@@ -11078,6 +11078,141 @@ const useVadCoverage = () => {
11078
11078
  };
11079
11079
  };
11080
11080
 
11081
+ const FAMILIES = [
11082
+ "recording",
11083
+ "activity",
11084
+ "report",
11085
+ "lifecycle"
11086
+ ];
11087
+ const familyOf = (name) => {
11088
+ const prefix = name.split(".")[0];
11089
+ return FAMILIES.includes(prefix) ? prefix : "lifecycle";
11090
+ };
11091
+
11092
+ const compileSubscription = (patterns) => {
11093
+ if (!patterns || patterns.length === 0) return () => false;
11094
+ if (patterns.includes("*")) return () => true;
11095
+ const exact = /* @__PURE__ */ new Set();
11096
+ const families = /* @__PURE__ */ new Set();
11097
+ for (const pattern of patterns) {
11098
+ if (pattern.endsWith(".*")) families.add(pattern.slice(0, -2));
11099
+ else exact.add(pattern);
11100
+ }
11101
+ if (families.size === 0) return (name) => exact.has(name);
11102
+ return (name) => {
11103
+ if (exact.has(name)) return true;
11104
+ const dot = name.indexOf(".");
11105
+ return dot > 0 && families.has(name.slice(0, dot));
11106
+ };
11107
+ };
11108
+ const subscriptionKey = (patterns) => patterns ? patterns.join("|") : "";
11109
+
11110
+ const QUEUE_CAP = 200;
11111
+ let handler = null;
11112
+ let matches = () => false;
11113
+ let sessionId = null;
11114
+ let seq = 0;
11115
+ let queue$1 = [];
11116
+ let draining = false;
11117
+ let handlerFailed = false;
11118
+ let registrations = 0;
11119
+ const drain = () => {
11120
+ draining = false;
11121
+ const batch = queue$1;
11122
+ queue$1 = [];
11123
+ const current = handler;
11124
+ if (!current) return;
11125
+ for (const event of batch) {
11126
+ try {
11127
+ current(event);
11128
+ } catch {
11129
+ if (!handlerFailed) {
11130
+ handlerFailed = true;
11131
+ logger.warn(
11132
+ "[SdkEvents] onEvent handler threw; further failures suppressed"
11133
+ );
11134
+ }
11135
+ }
11136
+ }
11137
+ };
11138
+ const schedule = () => {
11139
+ if (draining) return;
11140
+ draining = true;
11141
+ queueMicrotask(drain);
11142
+ };
11143
+ const SdkEventBus = {
11144
+ /**
11145
+ * Installs the host handler. Returns an unsubscribe. Events that matched
11146
+ * the subscription but arrived before a handler existed are queued and
11147
+ * delivered on the next microtask. That window is real: React runs child
11148
+ * effects before parent ones, so a provider below `Omniscribe` can emit
11149
+ * after `setSubscription` and before this call.
11150
+ */
11151
+ setHandler(next) {
11152
+ registrations += 1;
11153
+ if (next && handler && registrations > 1) {
11154
+ logger.warn(
11155
+ "[SdkEvents] a second onEvent handler was registered. The SDK supports one <Omniscribe> per page; the newest handler wins."
11156
+ );
11157
+ }
11158
+ handler = next;
11159
+ if (next && queue$1.length > 0) schedule();
11160
+ return () => {
11161
+ if (handler === next) handler = null;
11162
+ };
11163
+ },
11164
+ setSubscription(patterns) {
11165
+ matches = compileSubscription(patterns);
11166
+ },
11167
+ setSessionId(next) {
11168
+ sessionId = next;
11169
+ },
11170
+ /**
11171
+ * Cheap guard for callers on a hot path who would otherwise build a
11172
+ * payload for nobody.
11173
+ */
11174
+ wants(name) {
11175
+ return matches(name);
11176
+ },
11177
+ /**
11178
+ * Queues an event for delivery on the next microtask.
11179
+ *
11180
+ * Never throws. Deferring delivery does three things at once: host code
11181
+ * never runs inside React's render phase (where a `setState` would
11182
+ * throw), a slow handler cannot block `socket.onmessage` on the audio
11183
+ * path, and a handler that itself triggers an event cannot recurse —
11184
+ * the re-entrant call only appends to a queue the current drain has
11185
+ * already taken. One shared drain, not one microtask per event, so `seq`
11186
+ * ordering is preserved.
11187
+ */
11188
+ emit(name, payload, level = "info") {
11189
+ if (!matches(name)) return;
11190
+ queue$1.push({
11191
+ name,
11192
+ family: familyOf(name),
11193
+ level,
11194
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
11195
+ seq: ++seq,
11196
+ sdkVersion: version$2,
11197
+ sessionId,
11198
+ payload
11199
+ });
11200
+ if (queue$1.length > QUEUE_CAP) queue$1.shift();
11201
+ if (handler) schedule();
11202
+ },
11203
+ /** Test-only: restores the module to its initial state. */
11204
+ __resetForTests() {
11205
+ handler = null;
11206
+ matches = () => false;
11207
+ sessionId = null;
11208
+ seq = 0;
11209
+ queue$1 = [];
11210
+ draining = false;
11211
+ handlerFailed = false;
11212
+ registrations = 0;
11213
+ }
11214
+ };
11215
+
11081
11216
  const SettingsContext = React__default.createContext(
11082
11217
  void 0
11083
11218
  );
@@ -11118,6 +11253,7 @@ const SettingsProvider = ({
11118
11253
  DEFAULT_MINOR_AGE_THRESHOLD
11119
11254
  );
11120
11255
  const [isLoading, setIsLoading] = React__default.useState(true);
11256
+ const hasEmittedReadyRef = React__default.useRef(false);
11121
11257
  const isInitializedRef = React__default.useRef(false);
11122
11258
  const applySettings = React__default.useCallback(
11123
11259
  (parsed) => {
@@ -11170,6 +11306,10 @@ const SettingsProvider = ({
11170
11306
  SettingsCache.getInstance().clear();
11171
11307
  } finally {
11172
11308
  setIsLoading(false);
11309
+ if (!hasEmittedReadyRef.current) {
11310
+ hasEmittedReadyRef.current = true;
11311
+ SdkEventBus.emit("lifecycle.ready", {});
11312
+ }
11173
11313
  }
11174
11314
  }, [toolArgs, templateId, predefinedLanguage, applySettings]);
11175
11315
  const reloadSettings = React__default.useCallback(async () => {
@@ -17282,8 +17422,147 @@ const evaluateMinorRule = ({
17282
17422
 
17283
17423
  const postAppEvent = (event) => requester.post("/app-events", event).catch(() => void 0);
17284
17424
 
17425
+ const ACTIVITY_THROTTLE_MS = 5e3;
17426
+ const lastEmittedAt = /* @__PURE__ */ new Map();
17427
+ const shouldEmit = (key) => {
17428
+ const now = Date.now();
17429
+ const previous = lastEmittedAt.get(key);
17430
+ if (previous !== void 0 && now - previous < ACTIVITY_THROTTLE_MS) {
17431
+ return false;
17432
+ }
17433
+ lastEmittedAt.set(key, now);
17434
+ return true;
17435
+ };
17436
+ const emitInteraction = (kind) => {
17437
+ if (!SdkEventBus.wants("activity.interaction")) return;
17438
+ if (!shouldEmit(`interaction:${kind}`)) return;
17439
+ SdkEventBus.emit("activity.interaction", { kind });
17440
+ };
17441
+ const emitTyping = (surface) => {
17442
+ if (!SdkEventBus.wants("activity.typing")) return;
17443
+ if (!shouldEmit(`typing:${surface}`)) return;
17444
+ SdkEventBus.emit("activity.typing", { surface });
17445
+ };
17446
+
17447
+ let open$1 = false;
17448
+ const openReportBracket = () => {
17449
+ if (open$1) return;
17450
+ open$1 = true;
17451
+ SdkEventBus.emit("report.generation_started", {});
17452
+ };
17453
+ const closeReportBracket = (ok) => {
17454
+ if (!open$1) return;
17455
+ open$1 = false;
17456
+ SdkEventBus.emit("report.settled", { ok });
17457
+ };
17458
+
17459
+ const RECORDING_HEARTBEAT_MS = 3e4;
17460
+ let lastBeatAt = null;
17461
+ const emitRecordingHeartbeat = () => {
17462
+ if (!SdkEventBus.wants("recording.heartbeat")) return;
17463
+ const now = Date.now();
17464
+ if (lastBeatAt !== null && now - lastBeatAt < RECORDING_HEARTBEAT_MS) return;
17465
+ lastBeatAt = now;
17466
+ SdkEventBus.emit("recording.heartbeat", {});
17467
+ };
17468
+ const resetRecordingHeartbeat = () => {
17469
+ lastBeatAt = null;
17470
+ };
17471
+
17472
+ const INTERACTION_KIND = {
17473
+ recording_button: "recording",
17474
+ chat_mic_button: "chat",
17475
+ attach_file_button: "chat",
17476
+ remove_file_button: "chat",
17477
+ edit_message_button: "chat",
17478
+ cancel_edit_message_button: "chat",
17479
+ send_edit_message_button: "chat",
17480
+ copy_human_message_button: "chat",
17481
+ copy_ai_message_button: "chat",
17482
+ settings_button: "settings",
17483
+ settings_back_button: "settings",
17484
+ settings_section_button: "settings",
17485
+ select_audio_environment: "settings",
17486
+ compile_summary_button: "report",
17487
+ regenerate_summary_button: "report",
17488
+ generate_extras_button: "report",
17489
+ expand_transcription_button: "transcript",
17490
+ history_thread_item: "history",
17491
+ close_widget_button: "widget",
17492
+ main_menu_button: "widget",
17493
+ play_panel_button: "widget"
17494
+ };
17495
+ const CLICK_FALLBACK_KIND = "widget";
17496
+ const RECORDING_MODES = ["consultation", "dictation"];
17497
+ const AUDIO_LOSS_CAUSES = ["mic", "network", "server"];
17498
+ const readNumber = (payload, key) => {
17499
+ const value = payload?.[key];
17500
+ return typeof value === "number" && Number.isFinite(value) ? value : 0;
17501
+ };
17502
+ const readMode = (payload) => {
17503
+ const value = payload?.mode;
17504
+ return typeof value === "string" && RECORDING_MODES.includes(value) ? value : "consultation";
17505
+ };
17506
+ const readCause = (payload) => {
17507
+ const value = payload?.cause;
17508
+ return typeof value === "string" && AUDIO_LOSS_CAUSES.includes(value) ? value : "server";
17509
+ };
17510
+ const fanOutAppEvent = (input) => {
17511
+ try {
17512
+ const { event_type, event_name, payload } = input;
17513
+ if (event_type === "click") {
17514
+ emitInteraction(INTERACTION_KIND[event_name] ?? CLICK_FALLBACK_KIND);
17515
+ }
17516
+ switch (event_name) {
17517
+ case "recording_started":
17518
+ resetRecordingHeartbeat();
17519
+ SdkEventBus.emit("recording.started", { mode: readMode(payload) });
17520
+ break;
17521
+ case "recording_stopped":
17522
+ SdkEventBus.emit("recording.stopped", {
17523
+ mode: readMode(payload),
17524
+ durationSeconds: readNumber(payload, "duration_seconds")
17525
+ });
17526
+ break;
17527
+ // The mic/network emitter reports the length of the gap once audio
17528
+ // recovers. The server-close emitter has no gap to measure — capture
17529
+ // is torn down on the spot — and reports the recording position
17530
+ // instead, so fall back to that rather than hand the host a silent 0.
17531
+ case "audio_lost":
17532
+ SdkEventBus.emit(
17533
+ "recording.audio_lost",
17534
+ {
17535
+ cause: readCause(payload),
17536
+ durationSeconds: readNumber(payload, "duration_seconds") || readNumber(payload, "recording_position_seconds")
17537
+ },
17538
+ "warn"
17539
+ );
17540
+ break;
17541
+ case "mic_disconnected":
17542
+ SdkEventBus.emit("recording.microphone_disconnected", {}, "warn");
17543
+ break;
17544
+ // A report request is in flight. The host must suppress its idle
17545
+ // timer until report.settled, or it will close the widget during
17546
+ // generation — which looks exactly like idleness and loses the note.
17547
+ // openReportBracket ignores a second click while one is in flight, so
17548
+ // the pair stays balanced.
17549
+ case "compile_summary_button":
17550
+ case "regenerate_summary_button":
17551
+ openReportBracket();
17552
+ break;
17553
+ case "close_widget_button":
17554
+ SdkEventBus.emit("lifecycle.closed", {});
17555
+ break;
17556
+ default:
17557
+ break;
17558
+ }
17559
+ } catch {
17560
+ }
17561
+ };
17562
+
17285
17563
  const EventTracker = {
17286
17564
  track(input) {
17565
+ fanOutAppEvent(input);
17287
17566
  const event = {
17288
17567
  event_type: input.event_type,
17289
17568
  event_name: input.event_name,
@@ -21051,6 +21330,7 @@ const useAudioProcessor = ({
21051
21330
  );
21052
21331
  socket.send(audioData16kHz);
21053
21332
  lastSendAtRef.current = Date.now();
21333
+ emitRecordingHeartbeat();
21054
21334
  }
21055
21335
  };
21056
21336
  audioTracks.forEach((t) => {
@@ -22540,6 +22820,7 @@ const useReportPreviewState = ({
22540
22820
  }, []);
22541
22821
  const setScalarEdit = React__default.useCallback(
22542
22822
  (entryKey, value) => {
22823
+ emitTyping("note");
22543
22824
  setEdits((prev) => {
22544
22825
  const next = new Map(prev);
22545
22826
  next.set(entryKey, value);
@@ -22550,6 +22831,7 @@ const useReportPreviewState = ({
22550
22831
  );
22551
22832
  const setRowFieldEdit = React__default.useCallback(
22552
22833
  (entryKey, rowIndex, fieldKey, value) => {
22834
+ emitTyping("note");
22553
22835
  setEdits((prev) => {
22554
22836
  const next = new Map(prev);
22555
22837
  next.set(rowFieldEditKey(entryKey, rowIndex, fieldKey), value);
@@ -22559,6 +22841,7 @@ const useReportPreviewState = ({
22559
22841
  []
22560
22842
  );
22561
22843
  const setScalarGapValue = React__default.useCallback((key, value) => {
22844
+ emitTyping("note");
22562
22845
  setFilledScalarGaps((prev) => {
22563
22846
  const next = new Map(prev);
22564
22847
  next.set(key, value);
@@ -24458,6 +24741,7 @@ const useReportGeneration = (options) => {
24458
24741
  } finally {
24459
24742
  setState((prev) => ({ ...prev, generating: false }));
24460
24743
  regenerateInflightRef.current = false;
24744
+ closeReportBracket(false);
24461
24745
  }
24462
24746
  }, [
24463
24747
  abortIfBlocked,
@@ -24498,6 +24782,7 @@ const useReportGeneration = (options) => {
24498
24782
  handleReport?.(result);
24499
24783
  }
24500
24784
  trackEvent("event", "summary_compiled");
24785
+ closeReportBracket(true);
24501
24786
  if (patientId && doctorId) {
24502
24787
  await saveDayData({
24503
24788
  patientId,
@@ -24688,6 +24973,7 @@ const useReportGeneration = (options) => {
24688
24973
  }
24689
24974
  } finally {
24690
24975
  generateInflightRef.current = false;
24976
+ closeReportBracket(false);
24691
24977
  setUserPendingClick(false);
24692
24978
  const finalPid = appointmentData?.patientId;
24693
24979
  const finalDid = appointmentData?.doctorId;
@@ -151315,122 +151601,108 @@ const ChatFooter = ({
151315
151601
  ]
151316
151602
  }
151317
151603
  ),
151318
- /* @__PURE__ */ jsxs(
151319
- "form",
151320
- {
151321
- onSubmit,
151322
- className: "omniscribe_chat-view-combined-footer-row",
151323
- children: [
151324
- /* @__PURE__ */ jsx$2(
151325
- "input",
151326
- {
151327
- ref: fileInputRef,
151328
- type: "file",
151329
- multiple: true,
151330
- accept: "image/*,.pdf",
151331
- onChange: onFileSelect,
151332
- style: { display: "none" }
151333
- }
151334
- ),
151335
- /* @__PURE__ */ jsxs(
151336
- "div",
151337
- {
151338
- className: `omniscribe_chat-view-combined-input-container${uploadedFiles.length > 0 ? " omniscribe_chat-view-combined-input-container--with-files" : ""}`,
151339
- children: [
151340
- uploadedFiles.length > 0 && /* @__PURE__ */ jsx$2("div", { className: "omniscribe_chat-view-combined-files-row", children: /* @__PURE__ */ jsx$2(FilePreview, { files: uploadedFiles, onRemove: onFileRemove }) }),
151341
- /* @__PURE__ */ jsxs("div", { className: "omniscribe_chat-view-combined-input-row", children: [
151342
- /* @__PURE__ */ jsx$2(
151343
- Tooltip,
151604
+ /* @__PURE__ */ jsxs("div", { className: "omniscribe_chat-view-combined-footer-row", children: [
151605
+ /* @__PURE__ */ jsx$2(
151606
+ "input",
151607
+ {
151608
+ ref: fileInputRef,
151609
+ type: "file",
151610
+ multiple: true,
151611
+ accept: "image/*,.pdf",
151612
+ onChange: onFileSelect,
151613
+ style: { display: "none" }
151614
+ }
151615
+ ),
151616
+ /* @__PURE__ */ jsxs(
151617
+ "div",
151618
+ {
151619
+ className: `omniscribe_chat-view-combined-input-container${uploadedFiles.length > 0 ? " omniscribe_chat-view-combined-input-container--with-files" : ""}`,
151620
+ children: [
151621
+ uploadedFiles.length > 0 && /* @__PURE__ */ jsx$2("div", { className: "omniscribe_chat-view-combined-files-row", children: /* @__PURE__ */ jsx$2(FilePreview, { files: uploadedFiles, onRemove: onFileRemove }) }),
151622
+ /* @__PURE__ */ jsxs("div", { className: "omniscribe_chat-view-combined-input-row", children: [
151623
+ /* @__PURE__ */ jsx$2(
151624
+ Tooltip,
151625
+ {
151626
+ message: formatMessage({ id: "Attach file" }),
151627
+ side: "top",
151628
+ children: /* @__PURE__ */ jsx$2(
151629
+ "button",
151344
151630
  {
151345
- message: formatMessage({ id: "Attach file" }),
151346
- side: "top",
151631
+ type: "button",
151632
+ className: "omniscribe_chat-view-combined-clip-icon",
151633
+ onClick: () => {
151634
+ trackEvent("click", "attach_file_button");
151635
+ onAttachClick();
151636
+ },
151637
+ disabled: uploadedFiles.length >= MAX_FILES,
151638
+ "aria-label": formatMessage({ id: "Attach file" }),
151347
151639
  children: /* @__PURE__ */ jsx$2(
151348
- "button",
151640
+ "svg",
151349
151641
  {
151350
- type: "button",
151351
- className: "omniscribe_chat-view-combined-clip-icon",
151352
- onClick: () => {
151353
- trackEvent("click", "attach_file_button");
151354
- onAttachClick();
151355
- },
151356
- disabled: uploadedFiles.length >= MAX_FILES,
151357
- "aria-label": formatMessage({ id: "Attach file" }),
151358
- children: /* @__PURE__ */ jsx$2(
151359
- "svg",
151360
- {
151361
- width: "12",
151362
- height: "12",
151363
- viewBox: "0 0 24 24",
151364
- fill: "none",
151365
- stroke: "currentColor",
151366
- strokeWidth: "2",
151367
- strokeLinecap: "round",
151368
- strokeLinejoin: "round",
151369
- children: /* @__PURE__ */ jsx$2("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" })
151370
- }
151371
- )
151642
+ width: "12",
151643
+ height: "12",
151644
+ viewBox: "0 0 24 24",
151645
+ fill: "none",
151646
+ stroke: "currentColor",
151647
+ strokeWidth: "2",
151648
+ strokeLinecap: "round",
151649
+ strokeLinejoin: "round",
151650
+ children: /* @__PURE__ */ jsx$2("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" })
151372
151651
  }
151373
151652
  )
151374
151653
  }
151375
- ),
151376
- /* @__PURE__ */ jsx$2(
151377
- Textarea,
151378
- {
151379
- ref: textareaRef,
151380
- rows: 1,
151381
- "data-testid": "sofia-chat-input",
151382
- value: inputValue,
151383
- variant: "secondary",
151384
- onChange: onInputChange,
151385
- onKeyDown: (e) => {
151386
- if (e.key === "Enter" && !e.shiftKey && !e.metaKey) {
151387
- e.preventDefault();
151388
- const el = e.target;
151389
- const form = el?.closest("form");
151390
- form?.requestSubmit();
151391
- }
151392
- },
151393
- maxLength: inputMaxLength,
151394
- className: "omniscribe_chat-view-combined-textarea no-scrollbar text-p",
151395
- placeholder: formatMessage({
151396
- id: "What do you need to know?"
151397
- })
151398
- }
151399
- ),
151400
- isLoading ? /* @__PURE__ */ jsx$2(
151401
- "button",
151402
- {
151403
- type: "button",
151404
- "data-testid": "sofia-chat-stop",
151405
- onClick: onStop,
151406
- className: "omniscribe_chat-view-combined-send-btn omniscribe_chat-view-combined-stop-btn",
151407
- title: formatMessage({ id: "Stop" }),
151408
- children: /* @__PURE__ */ jsx$2(SquareIcon, { width: 10, height: 10 })
151409
- }
151410
- ) : inputValue.length > 0 ? /* @__PURE__ */ jsx$2(
151411
- "button",
151412
- {
151413
- type: "button",
151414
- "data-testid": "sofia-chat-send",
151415
- onClick: (e) => {
151416
- const form = e.currentTarget.closest(
151417
- "form"
151418
- );
151419
- form?.requestSubmit();
151420
- },
151421
- className: "omniscribe_chat-view-combined-send-btn",
151422
- disabled: isLoading || !inputIsValid,
151423
- title: formatMessage({ id: "Send" }),
151424
- children: /* @__PURE__ */ jsx$2(SendArrowIcon, { width: 16, height: 16 })
151654
+ )
151655
+ }
151656
+ ),
151657
+ /* @__PURE__ */ jsx$2(
151658
+ Textarea,
151659
+ {
151660
+ ref: textareaRef,
151661
+ rows: 1,
151662
+ "data-testid": "sofia-chat-input",
151663
+ value: inputValue,
151664
+ variant: "secondary",
151665
+ onChange: onInputChange,
151666
+ onKeyDown: (e) => {
151667
+ if (e.key === "Enter" && !e.shiftKey && !e.metaKey) {
151668
+ e.preventDefault();
151669
+ onSubmit(e);
151425
151670
  }
151426
- ) : /* @__PURE__ */ jsx$2(MicrophoneButton, { onTextUpdate: onTranscriptionUpdate })
151427
- ] })
151428
- ]
151429
- }
151430
- )
151431
- ]
151432
- }
151433
- )
151671
+ },
151672
+ maxLength: inputMaxLength,
151673
+ className: "omniscribe_chat-view-combined-textarea no-scrollbar text-p",
151674
+ placeholder: formatMessage({
151675
+ id: "What do you need to know?"
151676
+ })
151677
+ }
151678
+ ),
151679
+ isLoading ? /* @__PURE__ */ jsx$2(
151680
+ "button",
151681
+ {
151682
+ type: "button",
151683
+ "data-testid": "sofia-chat-stop",
151684
+ onClick: onStop,
151685
+ className: "omniscribe_chat-view-combined-send-btn omniscribe_chat-view-combined-stop-btn",
151686
+ title: formatMessage({ id: "Stop" }),
151687
+ children: /* @__PURE__ */ jsx$2(SquareIcon, { width: 10, height: 10 })
151688
+ }
151689
+ ) : inputValue.length > 0 ? /* @__PURE__ */ jsx$2(
151690
+ "button",
151691
+ {
151692
+ type: "button",
151693
+ "data-testid": "sofia-chat-send",
151694
+ onClick: onSubmit,
151695
+ className: "omniscribe_chat-view-combined-send-btn",
151696
+ disabled: isLoading || !inputIsValid,
151697
+ title: formatMessage({ id: "Send" }),
151698
+ children: /* @__PURE__ */ jsx$2(SendArrowIcon, { width: 16, height: 16 })
151699
+ }
151700
+ ) : /* @__PURE__ */ jsx$2(MicrophoneButton, { onTextUpdate: onTranscriptionUpdate })
151701
+ ] })
151702
+ ]
151703
+ }
151704
+ )
151705
+ ] })
151434
151706
  ] })
151435
151707
  ] });
151436
151708
  };
@@ -152083,6 +152355,7 @@ const ChatView = () => {
152083
152355
  appointmentData?.doctorId
152084
152356
  ]);
152085
152357
  const handleInputChange = (e) => {
152358
+ emitTyping("chat");
152086
152359
  input.onChange(e);
152087
152360
  setInputIsValid(
152088
152361
  InputValidator.validate(e.target.value, CHAT_INPUT_VALIDATION).isValid
@@ -156492,6 +156765,9 @@ const SessionProvider = ({ children }) => {
156492
156765
  prevPatientIdRef.current = patientId;
156493
156766
  setSessionId(patientId ? generateUuid() : null);
156494
156767
  }, [patientId]);
156768
+ useEffect(() => {
156769
+ SdkEventBus.setSessionId(sessionId);
156770
+ }, [sessionId]);
156495
156771
  return /* @__PURE__ */ jsx$2(SessionContext.Provider, { value: { sessionId }, children });
156496
156772
  };
156497
156773
 
@@ -156711,7 +156987,9 @@ const Omniscribe = ({
156711
156987
  onReportApply,
156712
156988
  updateTemplate,
156713
156989
  templateExtras,
156714
- handleExtras
156990
+ handleExtras,
156991
+ onEvent,
156992
+ eventSubscriptions
156715
156993
  }) => {
156716
156994
  const templateFields = template ?? toolsargs;
156717
156995
  const effectiveBaseUrl = React.useMemo(
@@ -156760,6 +157038,28 @@ const Omniscribe = ({
156760
157038
  React.useEffect(() => {
156761
157039
  logger.setDebugMode(debug || false);
156762
157040
  }, [debug]);
157041
+ const onEventRef = React.useRef(onEvent);
157042
+ onEventRef.current = onEvent;
157043
+ const hasEventHandler = onEvent !== void 0;
157044
+ const subscriptionsRef = React.useRef(
157045
+ eventSubscriptions
157046
+ );
157047
+ subscriptionsRef.current = eventSubscriptions;
157048
+ const subscriptions = subscriptionKey(eventSubscriptions);
157049
+ React.useEffect(() => {
157050
+ SdkEventBus.setSubscription(subscriptionsRef.current);
157051
+ }, [subscriptions]);
157052
+ React.useEffect(() => {
157053
+ if (!hasEventHandler) return;
157054
+ return SdkEventBus.setHandler((event) => onEventRef.current?.(event));
157055
+ }, [hasEventHandler]);
157056
+ React.useEffect(() => {
157057
+ if (hasEventHandler && !eventSubscriptions?.length) {
157058
+ logger.warn(
157059
+ '[Sofia SDK] onEvent was provided without eventSubscriptions, so no events will be delivered. Pass e.g. ["recording.*", "activity.*"].'
157060
+ );
157061
+ }
157062
+ }, [hasEventHandler, eventSubscriptions]);
156763
157063
  React.useEffect(() => {
156764
157064
  if (apikey && apikey.trim() !== "") {
156765
157065
  setEncryptionSeed(apikey);