@omniloy/sofia-sdk 1.0.4 → 1.0.6

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.
@@ -646,6 +646,74 @@
646
646
  color: var(--color-gray-600, #4b5563);
647
647
  }
648
648
 
649
+ /* Connection status pill - inline banner replacing toasts.
650
+ Three states: reconnecting (neutral), disconnected (red), restored (dark). */
651
+ .omniscribe_connection-pill {
652
+ display: inline-flex;
653
+ align-items: center;
654
+ gap: 6px;
655
+ padding: 4px 10px;
656
+ border-radius: 9999px;
657
+ white-space: nowrap;
658
+ flex-shrink: 0;
659
+ font-weight: 500;
660
+ margin-inline: 8px;
661
+ }
662
+
663
+ .omniscribe_connection-pill-icon {
664
+ display: inline-flex;
665
+ align-items: center;
666
+ justify-content: center;
667
+ }
668
+
669
+ .omniscribe_connection-pill-icon--spin {
670
+ animation: omniscribe-connection-spin 1s linear infinite;
671
+ }
672
+
673
+ @keyframes omniscribe-connection-spin {
674
+ from {
675
+ transform: rotate(0deg);
676
+ }
677
+ to {
678
+ transform: rotate(360deg);
679
+ }
680
+ }
681
+
682
+ .omniscribe_connection-pill--reconnecting {
683
+ background-color: var(--color-gray-50, #f9fafb);
684
+ border: 1px solid var(--color-gray-200, #e5e7eb);
685
+ color: var(--color-gray-700, #374151);
686
+ }
687
+
688
+ .omniscribe_connection-pill--disconnected {
689
+ background-color: #fdecec;
690
+ border: 1px solid #f8c7c7;
691
+ color: #b42318;
692
+ padding-right: 4px;
693
+ }
694
+
695
+ .omniscribe_connection-pill--restored {
696
+ background-color: #1f2937;
697
+ border: 1px solid #1f2937;
698
+ color: #ffffff;
699
+ }
700
+
701
+ .omniscribe_connection-pill-retry {
702
+ border-radius: 9999px !important;
703
+ gap: 4px;
704
+ padding-block: 3px !important;
705
+ padding-inline: 8px !important;
706
+ height: auto !important;
707
+ background-color: #d92d20 !important;
708
+ color: #ffffff !important;
709
+ border: none !important;
710
+ margin-left: 2px;
711
+ }
712
+
713
+ .omniscribe_connection-pill-retry:hover:not(:disabled) {
714
+ background-color: #b42318 !important;
715
+ }
716
+
649
717
  /* Generate Report Button - Figma style */
650
718
  .omniscribe_transcribe-generate-btn {
651
719
  border-radius: 12px !important;
@@ -900,6 +968,236 @@
900
968
  background-color: color-mix(in oklab, var(--report) 60%, transparent);
901
969
  }
902
970
  }
971
+ .omniscribe_modal-overlay {
972
+ position: absolute;
973
+ background-color: color-mix(in oklab, var(--black) 50%, transparent);
974
+ display: flex;
975
+ justify-content: center;
976
+ align-items: center;
977
+ z-index: 50;
978
+ height: 100vh;
979
+ width: 100vw;
980
+ right: 0;
981
+ bottom: calc(var(--spacing) * -5.2);
982
+ }
983
+
984
+ .omniscribe_modal-container {
985
+ position: relative;
986
+ background-color: var(--white);
987
+ z-index: 100;
988
+ border-radius: 12px;
989
+ border: solid 1px var(--light-border);
990
+ box-shadow: 0 25px 50px -12px rgb(0 0 0 / 0.25);
991
+ padding: calc(var(--spacing) * 5);
992
+ transition-duration: 300ms;
993
+ transition-property: opacity;
994
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
995
+ max-width: 90vw;
996
+ max-height: 90vh;
997
+ }
998
+ .omniscribe_modal-container-scroll {
999
+ overflow: auto;
1000
+ }
1001
+
1002
+ .omniscribe_modal-container-show {
1003
+ opacity: 100%;
1004
+ }
1005
+
1006
+ .omniscribe_modal-container-hide {
1007
+ opacity: 0%;
1008
+ }
1009
+
1010
+ .omniscribe_modal-subcontainer {
1011
+ display: flex;
1012
+ flex-direction: column;
1013
+ }
1014
+
1015
+ .omniscribe_modal-title-container {
1016
+ display: flex;
1017
+ flex-direction: row;
1018
+ align-items: center;
1019
+ justify-content: space-between;
1020
+ margin-bottom: calc(var(--spacing) * 2);
1021
+ }
1022
+
1023
+ .omniscribe_modal-title {
1024
+ font-size: var(--text-3xl); /* 1.875rem = 30px */
1025
+ line-height: var(--text-xl--line-height);
1026
+ font-weight: var(--font-weight-bold);
1027
+ margin: 0;
1028
+ }
1029
+
1030
+ .omniscribe_modal-button {
1031
+ border: none;
1032
+ background: transparent;
1033
+ line-height: var(--text-2xl--line-height);
1034
+ cursor: pointer;
1035
+ color: var(--black);
1036
+ }
1037
+
1038
+ @media (min-width: 768px) {
1039
+ .omniscribe_modal-container {
1040
+ max-width: 60vw;
1041
+ }
1042
+ }
1043
+
1044
+ /* Large screens: desktop */
1045
+ @media (min-width: 1024px) {
1046
+ .omniscribe_modal-container {
1047
+ max-width: 50vw;
1048
+ }
1049
+ }
1050
+
1051
+ /* Very large screens: large desktop and ultra-wide */
1052
+ @media (min-width: 1440px) {
1053
+ .omniscribe_modal-container {
1054
+ max-width: 40vw;
1055
+ }
1056
+ }
1057
+
1058
+ /* Intermediate breakpoint: large tablets */
1059
+ @media (min-width: 640px) and (max-width: 767px) {
1060
+ .omniscribe_modal-container {
1061
+ max-width: 70vw;
1062
+ }
1063
+ }
1064
+ /* The shared Modal overlay uses position: absolute anchored to its
1065
+ nearest positioned ancestor — which in our case is the chat footer,
1066
+ not the widget root. That puts the modal at the bottom of the widget.
1067
+ Override to position: fixed so the overlay covers the full viewport
1068
+ and the modal truly centers regardless of where in the React tree it
1069
+ was rendered. */
1070
+ .omniscribe_modal-overlay:has(.omniscribe_audio-cuts-modal) {
1071
+ position: fixed;
1072
+ inset: 0;
1073
+ width: 100%;
1074
+ height: 100%;
1075
+ top: 0;
1076
+ left: 0;
1077
+ bottom: 0;
1078
+ right: 0;
1079
+ }
1080
+
1081
+ .omniscribe_audio-cuts-modal {
1082
+ width: 480px;
1083
+ max-width: 90vw;
1084
+ padding: 24px !important;
1085
+ }
1086
+
1087
+ /* Override Modal's giant default h2 size for this dialog. */
1088
+ .omniscribe_audio-cuts-modal .omniscribe_modal-title-container {
1089
+ margin-bottom: 16px;
1090
+ align-items: flex-start;
1091
+ }
1092
+
1093
+ .omniscribe_audio-cuts-modal .omniscribe_modal-title {
1094
+ font-size: 18px;
1095
+ line-height: 1.3;
1096
+ font-weight: 700;
1097
+ letter-spacing: -0.01em;
1098
+ color: var(--color-gray-900, #0f172a);
1099
+ }
1100
+
1101
+ .omniscribe_audio-cuts-body {
1102
+ display: flex;
1103
+ flex-direction: column;
1104
+ gap: 16px;
1105
+ }
1106
+
1107
+ /* Summary card: warning icon + paragraph in a soft container. */
1108
+ .omniscribe_audio-cuts-summary {
1109
+ display: flex;
1110
+ align-items: flex-start;
1111
+ gap: 12px;
1112
+ }
1113
+
1114
+ .omniscribe_audio-cuts-summary-icon {
1115
+ display: inline-flex;
1116
+ align-items: center;
1117
+ justify-content: center;
1118
+ flex-shrink: 0;
1119
+ width: 32px;
1120
+ height: 32px;
1121
+ border-radius: 9999px;
1122
+ background-color: #fef3c7;
1123
+ color: #b45309;
1124
+ }
1125
+
1126
+ .omniscribe_audio-cuts-summary-text {
1127
+ margin: 0;
1128
+ font-size: 14px;
1129
+ line-height: 1.5;
1130
+ color: var(--color-gray-800, #1f2937);
1131
+ flex: 1;
1132
+ }
1133
+
1134
+ /* Cut list: rounded gray container, clock icon per row, no bullets. */
1135
+ .omniscribe_audio-cuts-list {
1136
+ margin: 0;
1137
+ padding: 12px 14px;
1138
+ border-radius: 12px;
1139
+ background-color: var(--color-gray-50, #f9fafb);
1140
+ border: 1px solid var(--color-gray-200, #e5e7eb);
1141
+ list-style: none;
1142
+ max-height: 240px;
1143
+ overflow-y: auto;
1144
+ display: flex;
1145
+ flex-direction: column;
1146
+ gap: 8px;
1147
+ }
1148
+
1149
+ .omniscribe_audio-cuts-item {
1150
+ display: flex;
1151
+ align-items: center;
1152
+ gap: 8px;
1153
+ color: var(--color-gray-700, #374151);
1154
+ font-variant-numeric: tabular-nums;
1155
+ font-size: 13px;
1156
+ line-height: 1.4;
1157
+ }
1158
+
1159
+ .omniscribe_audio-cuts-item-icon {
1160
+ display: inline-flex;
1161
+ align-items: center;
1162
+ justify-content: center;
1163
+ flex-shrink: 0;
1164
+ color: var(--color-gray-500, #6b7280);
1165
+ }
1166
+
1167
+ /* Action row: right-aligned, secondary as text-link, primary as blue pill. */
1168
+ .omniscribe_audio-cuts-actions {
1169
+ display: flex;
1170
+ justify-content: flex-end;
1171
+ align-items: center;
1172
+ gap: 16px;
1173
+ margin-top: 4px;
1174
+ }
1175
+
1176
+ .omniscribe_audio-cuts-secondary {
1177
+ background: transparent;
1178
+ border: none;
1179
+ cursor: pointer;
1180
+ padding: 8px 12px;
1181
+ font-size: 14px;
1182
+ font-weight: 500;
1183
+ color: var(--color-gray-700, #374151);
1184
+ border-radius: 9999px;
1185
+ transition: background-color 0.15s ease;
1186
+ }
1187
+
1188
+ .omniscribe_audio-cuts-secondary:hover {
1189
+ background-color: var(--color-gray-100, #f3f4f6);
1190
+ color: var(--color-gray-900, #0f172a);
1191
+ }
1192
+
1193
+ .omniscribe_audio-cuts-primary {
1194
+ border-radius: 9999px !important;
1195
+ gap: 8px;
1196
+ padding-block: 10px !important;
1197
+ padding-inline: 18px !important;
1198
+ height: auto !important;
1199
+ font-weight: 600;
1200
+ }
903
1201
  /* Draggable Collapsed Container */
904
1202
  .omniscribe_draggable-collapsed {
905
1203
  position: fixed;
@@ -3755,6 +4053,34 @@
3755
4053
  line-height: 1.7 !important;
3756
4054
  }
3757
4055
 
4056
+ /* Audio-loss divider rendered between segments where a recording cut
4057
+ occurred. Visually separates captured speech from the gap so the doctor
4058
+ can spot lost portions while reviewing the transcription. */
4059
+ .omniscribe_transcription-panel-cut {
4060
+ display: flex;
4061
+ align-items: center;
4062
+ gap: 8px;
4063
+ margin-block: 10px;
4064
+ padding-inline: 4px;
4065
+ }
4066
+
4067
+ .omniscribe_transcription-panel-cut-line {
4068
+ flex: 1;
4069
+ height: 1px;
4070
+ background-color: #fed7aa;
4071
+ }
4072
+
4073
+ .omniscribe_transcription-panel-cut-label {
4074
+ flex-shrink: 0;
4075
+ padding: 2px 10px;
4076
+ border-radius: 9999px;
4077
+ background-color: #fff7ed;
4078
+ border: 1px solid #fed7aa;
4079
+ color: #c2410c;
4080
+ font-weight: 600;
4081
+ font-variant-numeric: tabular-nums;
4082
+ }
4083
+
3758
4084
  /* Scrollbar styles */
3759
4085
  .omniscribe_transcription-panel-content::-webkit-scrollbar {
3760
4086
  width: 6px;
@@ -3,7 +3,7 @@ import { JSX } from 'react/jsx-runtime';
3
3
  import * as React_2 from 'react';
4
4
  import { VariantProps } from 'class-variance-authority';
5
5
 
6
- export declare function Button({ className, variant, size, asChild, isDisabled, isLoading, text, whitespace, rounded, ...props }: ButtonProps): JSX.Element;
6
+ export declare function Button({ className, variant, size, asChild, isDisabled, isLoading, text, whitespace, rounded, type, ...props }: ButtonProps): JSX.Element;
7
7
 
8
8
  declare type ButtonProps = React_2.ComponentProps<'button'> & VariantProps<typeof buttonVariants> & {
9
9
  asChild?: boolean;
@@ -57,9 +57,19 @@ export declare interface OmniscribeProps {
57
57
  wssurl: string;
58
58
  /** API key for authentication */
59
59
  apikey: string;
60
- /** Unique identifier of the healthcare professional */
60
+ /**
61
+ * Unique identifier of the healthcare professional.
62
+ * SECURITY: Must be an INTERNAL system / surrogate id, never an MRN,
63
+ * national id, or other directly-identifying value. It is sent to the
64
+ * backend (and used as LLM context) without anonymization.
65
+ */
61
66
  userid: string;
62
- /** Unique identifier of the patient */
67
+ /**
68
+ * Unique identifier of the patient.
69
+ * SECURITY: Must be an INTERNAL system / surrogate id, never an MRN,
70
+ * national id, or other directly-identifying value. It is sent to the
71
+ * backend (and used as LLM context) without anonymization.
72
+ */
63
73
  patientid: string;
64
74
  /** Template ID - enables report generation when provided with template */
65
75
  templateid?: string;
@@ -124,6 +134,8 @@ export declare interface OmniscribeProps {
124
134
  disablegenerate?: boolean;
125
135
  /** Enable debug logging */
126
136
  debug?: boolean;
137
+ /** Doctor's medical specialty at the time of the event. Optional. When provided, it is attached to every tracked event for analytics segmentation. */
138
+ userMedicalSpecialty?: string;
127
139
  /** Expose function to get last generated report */
128
140
  setGetLastReport?: (fn: () => Promise<object | undefined>) => void;
129
141
  }
@@ -143,7 +155,23 @@ declare type TDayData = {
143
155
  patientId: string;
144
156
  doctorId: string;
145
157
  date: string;
146
- report: object;
158
+ report: object | undefined;
159
+ /** Last incremental extraction result (not the final report).
160
+ * Used as previous_extraction context when Generate fallback is triggered. */
161
+ lastExtraction?: object;
162
+ /** Auto-generated report sitting in storage waiting for the user to
163
+ * click Generate. Cleared (set to undefined) once the user consumes it
164
+ * via deliverReport, at which point the same payload is promoted to
165
+ * the `report` field. Stored separately so a reload doesn't make the
166
+ * UI think the user has already consumed it (which would switch the
167
+ * button to Regenerate). */
168
+ autoPendingReport?: object;
169
+ /** ISO timestamp set when the user clicks Generate. Cleared once the
170
+ * report is delivered. Persisted so a page reload mid-generation can
171
+ * restore the loading-state UI ("Generating..." while waiting for
172
+ * pendingReport / storage / API to resolve) without the user needing
173
+ * to click again. */
174
+ pendingClickAt?: string;
147
175
  transcript: TProcessedSegment[];
148
176
  transcriptionId?: string;
149
177
  clinicalNoteId?: string;
@@ -170,9 +198,8 @@ declare type TProcessedSegment = {
170
198
  declare type TRenderReport = (report: object, selected: string) => React_2.JSX.Element[] | React_2.JSX.Element;
171
199
 
172
200
  declare type TSegmentText = {
173
- speaker: string;
201
+ speaker?: string;
174
202
  text: string;
175
- speaker_id: string;
176
203
  }[];
177
204
 
178
205
  export declare type TToastState = {