scribe-widget 1.0.12 → 1.0.14
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/scribe-widget.es.js +3002 -2971
- package/dist/scribe-widget.umd.js +236 -236
- package/package.json +2 -2
- package/src/App.tsx +20 -3
- package/src/components/PollingErrorState.tsx +26 -0
- package/src/hooks/useScribeSession.ts +53 -21
- package/src/styles/widget.css +52 -0
- package/src/types.ts +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scribe-widget",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "Floating panel widget for medical transcription using eka.scribe",
|
|
5
5
|
"main": "dist/scribe-widget.umd.js",
|
|
6
6
|
"module": "dist/scribe-widget.es.js",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"prepublishOnly": "npm run clean && npm run build"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"med-scribe-alliance-ts-sdk": "1.0.
|
|
17
|
+
"med-scribe-alliance-ts-sdk": "1.0.6",
|
|
18
18
|
"react": "^18.2.0",
|
|
19
19
|
"react-dom": "^18.2.0"
|
|
20
20
|
},
|
package/src/App.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useState, useMemo } from 'react';
|
|
1
|
+
import { useState, useMemo, useCallback } from 'react';
|
|
2
2
|
import { FloatingPanel } from './components/FloatingPanel';
|
|
3
3
|
import { ConfigState } from './components/ConfigState';
|
|
4
4
|
import { IdleState } from './components/IdleState';
|
|
@@ -7,6 +7,7 @@ import { RecordingState } from './components/RecordingState';
|
|
|
7
7
|
import { ProcessingState } from './components/ProcessingState';
|
|
8
8
|
import { ResultsState } from './components/ResultsState';
|
|
9
9
|
import { ErrorState } from './components/ErrorState';
|
|
10
|
+
import { PollingErrorState } from './components/PollingErrorState';
|
|
10
11
|
import { useScribeSession } from './hooks/useScribeSession';
|
|
11
12
|
import { ScribeWidgetConfig } from './types';
|
|
12
13
|
|
|
@@ -44,9 +45,16 @@ export function App({ config: initialConfig, onClose }: AppProps) {
|
|
|
44
45
|
pauseRecording,
|
|
45
46
|
resumeRecording,
|
|
46
47
|
stopRecording,
|
|
48
|
+
retryPolling,
|
|
47
49
|
reset,
|
|
48
50
|
} = useScribeSession(config);
|
|
49
51
|
|
|
52
|
+
// Start new recording - goes back to config screen
|
|
53
|
+
const handleStartNewRecording = useCallback(() => {
|
|
54
|
+
reset();
|
|
55
|
+
setCredentials(null);
|
|
56
|
+
}, [reset]);
|
|
57
|
+
|
|
50
58
|
if (isMinimized) {
|
|
51
59
|
return null;
|
|
52
60
|
}
|
|
@@ -90,10 +98,19 @@ export function App({ config: initialConfig, onClose }: AppProps) {
|
|
|
90
98
|
return <ProcessingState />;
|
|
91
99
|
|
|
92
100
|
case 'results':
|
|
93
|
-
return result ? <ResultsState result={result} onNewRecording={
|
|
101
|
+
return result ? <ResultsState result={result} onNewRecording={handleStartNewRecording} /> : null;
|
|
102
|
+
|
|
103
|
+
case 'polling_error':
|
|
104
|
+
return (
|
|
105
|
+
<PollingErrorState
|
|
106
|
+
message={errorMessage}
|
|
107
|
+
onRetry={retryPolling}
|
|
108
|
+
onStartNew={handleStartNewRecording}
|
|
109
|
+
/>
|
|
110
|
+
);
|
|
94
111
|
|
|
95
112
|
case 'error':
|
|
96
|
-
return <ErrorState message={errorMessage} onRetry={
|
|
113
|
+
return <ErrorState message={errorMessage} onRetry={handleStartNewRecording} />;
|
|
97
114
|
|
|
98
115
|
default:
|
|
99
116
|
return <IdleState onStartRecording={startRecording} />;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ErrorIcon } from './Icons';
|
|
2
|
+
|
|
3
|
+
interface PollingErrorStateProps {
|
|
4
|
+
message: string;
|
|
5
|
+
onRetry: () => void;
|
|
6
|
+
onStartNew: () => void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function PollingErrorState({ message, onRetry, onStartNew }: PollingErrorStateProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="polling-error-state">
|
|
12
|
+
<div className="error-icon">
|
|
13
|
+
<ErrorIcon />
|
|
14
|
+
</div>
|
|
15
|
+
<p className="error-message">{message}</p>
|
|
16
|
+
<div className="polling-error-actions">
|
|
17
|
+
<button className="retry-polling-btn" onClick={onRetry}>
|
|
18
|
+
Retry Fetching Results
|
|
19
|
+
</button>
|
|
20
|
+
<button className="start-new-btn" onClick={onStartNew}>
|
|
21
|
+
Start New Recording
|
|
22
|
+
</button>
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
@@ -11,6 +11,7 @@ interface UseScribeSessionReturn {
|
|
|
11
11
|
pauseRecording: () => void;
|
|
12
12
|
resumeRecording: () => void;
|
|
13
13
|
stopRecording: () => Promise<void>;
|
|
14
|
+
retryPolling: () => Promise<void>;
|
|
14
15
|
reset: () => void;
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -99,11 +100,50 @@ export function useScribeSession(config: ScribeWidgetConfig): UseScribeSessionRe
|
|
|
99
100
|
}
|
|
100
101
|
};
|
|
101
102
|
|
|
102
|
-
const showError = useCallback((message: string) => {
|
|
103
|
+
const showError = useCallback((message: string, isPollingError: boolean = false) => {
|
|
103
104
|
setErrorMessage(message);
|
|
104
|
-
setState('error');
|
|
105
|
+
setState(isPollingError ? 'polling_error' : 'error');
|
|
105
106
|
}, []);
|
|
106
107
|
|
|
108
|
+
// Polling function - extracted to be reusable for retry
|
|
109
|
+
const pollForResults = useCallback(async (): Promise<boolean> => {
|
|
110
|
+
if (!clientRef.current) {
|
|
111
|
+
showError('SDK not initialized. Please try again.', true);
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
setState('processing');
|
|
117
|
+
log('Polling for completion...');
|
|
118
|
+
|
|
119
|
+
const finalResult = await clientRef.current.pollForCompletion(undefined, {
|
|
120
|
+
maxAttempts: 60,
|
|
121
|
+
intervalMs: 2000,
|
|
122
|
+
onProgress: (status) => {
|
|
123
|
+
log('Status update:', status.status);
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
setResult(finalResult);
|
|
128
|
+
log('Final result:', finalResult);
|
|
129
|
+
|
|
130
|
+
setState('results');
|
|
131
|
+
|
|
132
|
+
if (config.onResult) {
|
|
133
|
+
config.onResult(finalResult);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return true;
|
|
137
|
+
} catch (error) {
|
|
138
|
+
log('Polling failed', error);
|
|
139
|
+
showError('Failed to fetch results. Please retry.', true);
|
|
140
|
+
if (config.onError && error instanceof Error) {
|
|
141
|
+
config.onError(error);
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
}, [config, log, showError]);
|
|
146
|
+
|
|
107
147
|
const startRecording = useCallback(async () => {
|
|
108
148
|
const permission = await checkMicrophonePermission();
|
|
109
149
|
|
|
@@ -130,7 +170,7 @@ export function useScribeSession(config: ScribeWidgetConfig): UseScribeSessionRe
|
|
|
130
170
|
setState('recording');
|
|
131
171
|
|
|
132
172
|
await clientRef.current.startRecording({
|
|
133
|
-
templates: ['eka_emr_template'],
|
|
173
|
+
templates: config.templates || ['eka_emr_template'],
|
|
134
174
|
languageHint: config.languageHint,
|
|
135
175
|
});
|
|
136
176
|
|
|
@@ -184,23 +224,8 @@ export function useScribeSession(config: ScribeWidgetConfig): UseScribeSessionRe
|
|
|
184
224
|
const endResponse = await clientRef.current.endRecording();
|
|
185
225
|
log('Recording ended', endResponse);
|
|
186
226
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
maxAttempts: 60,
|
|
190
|
-
intervalMs: 2000,
|
|
191
|
-
onProgress: (status) => {
|
|
192
|
-
log('Status update:', status.status);
|
|
193
|
-
},
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
setResult(finalResult);
|
|
197
|
-
log('Final result:', finalResult);
|
|
198
|
-
|
|
199
|
-
setState('results');
|
|
200
|
-
|
|
201
|
-
if (config.onResult) {
|
|
202
|
-
config.onResult(finalResult);
|
|
203
|
-
}
|
|
227
|
+
// Poll for results
|
|
228
|
+
await pollForResults();
|
|
204
229
|
} catch (error) {
|
|
205
230
|
log('Failed to stop recording', error);
|
|
206
231
|
showError('Failed to process recording. Please try again.');
|
|
@@ -208,7 +233,13 @@ export function useScribeSession(config: ScribeWidgetConfig): UseScribeSessionRe
|
|
|
208
233
|
config.onError(error);
|
|
209
234
|
}
|
|
210
235
|
}
|
|
211
|
-
}, [config, log, showError, stopTimer]);
|
|
236
|
+
}, [config, log, showError, stopTimer, pollForResults]);
|
|
237
|
+
|
|
238
|
+
// Retry polling - can be called when polling fails
|
|
239
|
+
const retryPolling = useCallback(async () => {
|
|
240
|
+
log('Retrying polling...');
|
|
241
|
+
await pollForResults();
|
|
242
|
+
}, [log, pollForResults]);
|
|
212
243
|
|
|
213
244
|
const reset = useCallback(() => {
|
|
214
245
|
setResult(null);
|
|
@@ -226,6 +257,7 @@ export function useScribeSession(config: ScribeWidgetConfig): UseScribeSessionRe
|
|
|
226
257
|
pauseRecording,
|
|
227
258
|
resumeRecording,
|
|
228
259
|
stopRecording,
|
|
260
|
+
retryPolling,
|
|
229
261
|
reset,
|
|
230
262
|
};
|
|
231
263
|
}
|
package/src/styles/widget.css
CHANGED
|
@@ -493,3 +493,55 @@
|
|
|
493
493
|
.config-submit-btn:hover {
|
|
494
494
|
background: #1d4ed8;
|
|
495
495
|
}
|
|
496
|
+
|
|
497
|
+
/* Polling Error State */
|
|
498
|
+
.polling-error-state {
|
|
499
|
+
display: flex;
|
|
500
|
+
flex-direction: column;
|
|
501
|
+
align-items: center;
|
|
502
|
+
gap: 12px;
|
|
503
|
+
padding: 20px;
|
|
504
|
+
text-align: center;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.polling-error-actions {
|
|
508
|
+
display: flex;
|
|
509
|
+
flex-direction: column;
|
|
510
|
+
gap: 8px;
|
|
511
|
+
width: 100%;
|
|
512
|
+
margin-top: 8px;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
.retry-polling-btn {
|
|
516
|
+
background: #2563eb;
|
|
517
|
+
color: white;
|
|
518
|
+
border: none;
|
|
519
|
+
padding: 12px 20px;
|
|
520
|
+
border-radius: 8px;
|
|
521
|
+
font-size: 14px;
|
|
522
|
+
font-weight: 500;
|
|
523
|
+
cursor: pointer;
|
|
524
|
+
transition: background 0.2s;
|
|
525
|
+
width: 100%;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
.retry-polling-btn:hover {
|
|
529
|
+
background: #1d4ed8;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.start-new-btn {
|
|
533
|
+
background: transparent;
|
|
534
|
+
color: #6b7280;
|
|
535
|
+
border: 1px solid #d1d5db;
|
|
536
|
+
padding: 10px 20px;
|
|
537
|
+
border-radius: 8px;
|
|
538
|
+
font-size: 13px;
|
|
539
|
+
cursor: pointer;
|
|
540
|
+
transition: all 0.2s;
|
|
541
|
+
width: 100%;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
.start-new-btn:hover {
|
|
545
|
+
background: #f3f4f6;
|
|
546
|
+
color: #374151;
|
|
547
|
+
}
|