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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scribe-widget",
3
- "version": "1.0.12",
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.5",
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={reset} /> : null;
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={reset} />;
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
- log('Polling for completion...');
188
- const finalResult = await clientRef.current.pollForCompletion(undefined, {
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
  }
@@ -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
+ }
package/src/types.ts CHANGED
@@ -7,7 +7,8 @@ export type WidgetState =
7
7
  | 'paused'
8
8
  | 'processing'
9
9
  | 'results'
10
- | 'error';
10
+ | 'error'
11
+ | 'polling_error';
11
12
 
12
13
  export interface ScribeWidgetConfig {
13
14
  accessToken?: string;