scribe-widget 1.0.15 → 1.0.17

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.15",
3
+ "version": "1.0.17",
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",
package/src/App.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { useState, useMemo, useCallback } from 'react';
1
+ import { useState, useMemo, useCallback, useEffect } from 'react';
2
2
  import { FloatingPanel } from './components/FloatingPanel';
3
3
  import { ConfigState } from './components/ConfigState';
4
4
  import { IdleState } from './components/IdleState';
@@ -41,6 +41,7 @@ export function App({ config: initialConfig, onClose }: AppProps) {
41
41
  elapsedTime,
42
42
  result,
43
43
  errorMessage,
44
+ initializeSDK,
44
45
  startRecording,
45
46
  pauseRecording,
46
47
  resumeRecording,
@@ -49,12 +50,25 @@ export function App({ config: initialConfig, onClose }: AppProps) {
49
50
  reset,
50
51
  } = useScribeSession(config);
51
52
 
53
+ // Initialize SDK when credentials are set
54
+ useEffect(() => {
55
+ if (credentials?.baseUrl) {
56
+ initializeSDK();
57
+ }
58
+ }, [credentials, initializeSDK]);
59
+
52
60
  // Start new recording - goes back to config screen
53
61
  const handleStartNewRecording = useCallback(() => {
54
62
  reset();
55
63
  setCredentials(null);
56
64
  }, [reset]);
57
65
 
66
+ // Handle EMR selection
67
+ const handleSelectEMR = useCallback((emrId: string) => {
68
+ console.log('Selected EMR:', emrId);
69
+ // TODO: Handle EMR selection logic
70
+ }, []);
71
+
58
72
  if (isMinimized) {
59
73
  return null;
60
74
  }
@@ -98,7 +112,13 @@ export function App({ config: initialConfig, onClose }: AppProps) {
98
112
  return <ProcessingState />;
99
113
 
100
114
  case 'results':
101
- return result ? <ResultsState result={result} onNewRecording={handleStartNewRecording} /> : null;
115
+ return result ? (
116
+ <ResultsState
117
+ result={result}
118
+ onNewRecording={handleStartNewRecording}
119
+ onSelectEMR={handleSelectEMR}
120
+ />
121
+ ) : null;
102
122
 
103
123
  case 'polling_error':
104
124
  return (
@@ -0,0 +1,50 @@
1
+ interface EMRListStateProps {
2
+ onSelectEMR: (emrId: string) => void;
3
+ onNewRecording: () => void;
4
+ onBack: () => void;
5
+ }
6
+
7
+ const EMR_LIST = [
8
+ { id: 'eka_emr', name: 'Eka EMR', description: 'Eka Care Electronic Medical Records' },
9
+ { id: 'open_emr', name: 'OpenEMR', description: 'Open-source Electronic Health Records' },
10
+ { id: 'open_mrs', name: 'OpenMRS', description: 'Open Medical Record System' },
11
+ ];
12
+
13
+ export function EMRListState({ onSelectEMR, onNewRecording, onBack }: EMRListStateProps) {
14
+ return (
15
+ <div className="emr-list-state">
16
+ <div className="emr-list-header">
17
+ <button className="back-btn" onClick={onBack} title="Back">
18
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
19
+ <path d="M19 12H5M12 19l-7-7 7-7"/>
20
+ </svg>
21
+ </button>
22
+ <span className="emr-list-title">Integrated EMRs</span>
23
+ </div>
24
+
25
+ <div className="emr-list-content">
26
+ {EMR_LIST.map((emr) => (
27
+ <button
28
+ key={emr.id}
29
+ className="emr-item"
30
+ onClick={() => onSelectEMR(emr.id)}
31
+ >
32
+ <div className="emr-item-info">
33
+ <span className="emr-item-name">{emr.name}</span>
34
+ <span className="emr-item-desc">{emr.description}</span>
35
+ </div>
36
+ <svg className="emr-item-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
37
+ <path d="M9 18l6-6-6-6"/>
38
+ </svg>
39
+ </button>
40
+ ))}
41
+ </div>
42
+
43
+ <div className="emr-list-footer">
44
+ <button className="start-new-recording-btn" onClick={onNewRecording}>
45
+ Start New Recording
46
+ </button>
47
+ </div>
48
+ </div>
49
+ );
50
+ }
@@ -11,7 +11,7 @@ export function IdleState({ onStartRecording }: IdleStateProps) {
11
11
  <div className="logo-icon">
12
12
  <LogoIcon />
13
13
  </div>
14
- <span className="logo-text">eka.scribe</span>
14
+ <span className="logo-text">scribe</span>
15
15
  </div>
16
16
  <button className="start-btn" onClick={onStartRecording}>
17
17
  Start Recording
@@ -1,11 +1,19 @@
1
1
  import { GetSessionStatusResponse } from 'med-scribe-alliance-ts-sdk';
2
2
 
3
+ const EMR_LIST = [
4
+ { id: 'eka_emr', name: 'Eka EMR', description: 'Eka Care Electronic Medical Records' },
5
+ { id: 'open_emr', name: 'OpenEMR', description: 'Open-source Electronic Health Records' },
6
+ { id: 'open_mrs', name: 'OpenMRS', description: 'Open Medical Record System' },
7
+ ];
8
+
3
9
  interface ResultsStateProps {
4
10
  result: GetSessionStatusResponse;
5
11
  onNewRecording: () => void;
12
+ onSelectEMR: (emrId: string) => void;
6
13
  }
7
14
 
8
- export function ResultsState({ result, onNewRecording }: ResultsStateProps) {
15
+ export function ResultsState({ result, onNewRecording, onSelectEMR }: ResultsStateProps) {
16
+ console.log('prescription result - WIDGET', result);
9
17
  return (
10
18
  <div className="results-state">
11
19
  <div className="results-header">
@@ -17,8 +25,27 @@ export function ResultsState({ result, onNewRecording }: ResultsStateProps) {
17
25
  <div className="results-content">
18
26
  <div className="transcript-section">
19
27
  <div className="section-title">Transcript</div>
20
- <div className="transcript-text">
21
- {result.transcript || 'No transcript available.'}
28
+ <div className="transcript-text">{result.transcript || 'No transcript available.'}</div>
29
+ </div>
30
+
31
+ <div className="emr-section">
32
+ <div className="section-title">Integrated EMRs</div>
33
+ <div className="emr-list-inline">
34
+ {EMR_LIST.map((emr) => (
35
+ <button
36
+ key={emr.id}
37
+ className="emr-item"
38
+ onClick={() => onSelectEMR(emr.id)}
39
+ >
40
+ <div className="emr-item-info">
41
+ <span className="emr-item-name">{emr.name}</span>
42
+ <span className="emr-item-desc">{emr.description}</span>
43
+ </div>
44
+ <svg className="emr-item-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
45
+ <path d="M9 18l6-6-6-6"/>
46
+ </svg>
47
+ </button>
48
+ ))}
22
49
  </div>
23
50
  </div>
24
51
  </div>
@@ -7,6 +7,7 @@ interface UseScribeSessionReturn {
7
7
  elapsedTime: number;
8
8
  result: GetSessionStatusResponse | null;
9
9
  errorMessage: string;
10
+ initializeSDK: () => Promise<void>;
10
11
  startRecording: () => Promise<void>;
11
12
  pauseRecording: () => void;
12
13
  resumeRecording: () => void;
@@ -35,36 +36,35 @@ export function useScribeSession(config: ScribeWidgetConfig): UseScribeSessionRe
35
36
  [config.debug]
36
37
  );
37
38
 
38
- // Initialize SDK client only when baseUrl is provided
39
- useEffect(() => {
39
+ // Initialize SDK client - can be called explicitly
40
+ const initializeSDK = useCallback(async () => {
40
41
  if (!config.baseUrl) {
41
42
  log('Skipping SDK init - no baseUrl provided');
42
43
  return;
43
44
  }
44
45
 
45
- const initClient = async () => {
46
- try {
47
- // Use getInstance instead of new ScribeClient
48
- clientRef.current = ScribeClient.getInstance({
49
- accessToken: config.accessToken,
50
- baseUrl: config.baseUrl,
51
- debug: config.debug,
52
- });
53
- await clientRef.current.init();
54
- log('SDK initialized');
55
- } catch (error) {
56
- log('Failed to initialize SDK', error);
57
- }
58
- };
59
-
60
- initClient();
46
+ try {
47
+ // Use getInstance instead of new ScribeClient
48
+ clientRef.current = ScribeClient.getInstance({
49
+ accessToken: config.accessToken,
50
+ baseUrl: config.baseUrl,
51
+ debug: config.debug,
52
+ });
53
+ await clientRef.current.init();
54
+ log('SDK initialized');
55
+ } catch (error) {
56
+ log('Failed to initialize SDK', error);
57
+ }
58
+ }, [config.accessToken, config.baseUrl, config.debug, log]);
61
59
 
60
+ // Cleanup timer on unmount
61
+ useEffect(() => {
62
62
  return () => {
63
63
  if (timerRef.current) {
64
64
  clearInterval(timerRef.current);
65
65
  }
66
66
  };
67
- }, [config.accessToken, config.baseUrl, config.debug, log]);
67
+ }, []);
68
68
 
69
69
  const startTimer = useCallback(() => {
70
70
  timerRef.current = window.setInterval(() => {
@@ -253,6 +253,7 @@ export function useScribeSession(config: ScribeWidgetConfig): UseScribeSessionRe
253
253
  elapsedTime,
254
254
  result,
255
255
  errorMessage,
256
+ initializeSDK,
256
257
  startRecording,
257
258
  pauseRecording,
258
259
  resumeRecording,
@@ -18,7 +18,8 @@
18
18
  box-shadow: 0 4px 24px rgba(0, 0, 0, 0.12), 0 0 0 1px rgba(0, 0, 0, 0.05);
19
19
  z-index: 2147483647;
20
20
  overflow: hidden;
21
- min-width: 320px;
21
+ width: 380px;
22
+ max-width: calc(100vw - 40px);
22
23
  }
23
24
 
24
25
  .panel-header {
@@ -545,3 +546,132 @@
545
546
  background: #f3f4f6;
546
547
  color: #374151;
547
548
  }
549
+
550
+ /* EMR Section (inline on results) */
551
+ .emr-section {
552
+ margin-top: 16px;
553
+ padding-top: 16px;
554
+ border-top: 1px solid #e5e7eb;
555
+ }
556
+
557
+ .emr-list-inline {
558
+ display: flex;
559
+ flex-direction: column;
560
+ gap: 8px;
561
+ }
562
+
563
+ /* EMR List State */
564
+ .emr-list-state {
565
+ display: flex;
566
+ flex-direction: column;
567
+ gap: 16px;
568
+ }
569
+
570
+ .emr-list-header {
571
+ display: flex;
572
+ align-items: center;
573
+ gap: 12px;
574
+ }
575
+
576
+ .back-btn {
577
+ background: none;
578
+ border: none;
579
+ cursor: pointer;
580
+ padding: 4px;
581
+ color: #6b7280;
582
+ display: flex;
583
+ align-items: center;
584
+ justify-content: center;
585
+ width: 32px;
586
+ height: 32px;
587
+ border-radius: 6px;
588
+ transition: all 0.2s;
589
+ }
590
+
591
+ .back-btn:hover {
592
+ background: #f3f4f6;
593
+ color: #374151;
594
+ }
595
+
596
+ .back-btn svg {
597
+ width: 20px;
598
+ height: 20px;
599
+ }
600
+
601
+ .emr-list-title {
602
+ font-size: 16px;
603
+ font-weight: 600;
604
+ color: #111827;
605
+ }
606
+
607
+ .emr-list-content {
608
+ display: flex;
609
+ flex-direction: column;
610
+ gap: 8px;
611
+ max-height: 240px;
612
+ overflow-y: auto;
613
+ }
614
+
615
+ .emr-item {
616
+ display: flex;
617
+ align-items: center;
618
+ justify-content: space-between;
619
+ padding: 12px 16px;
620
+ background: #f9fafb;
621
+ border: 1px solid #e5e7eb;
622
+ border-radius: 8px;
623
+ cursor: pointer;
624
+ transition: all 0.2s;
625
+ text-align: left;
626
+ }
627
+
628
+ .emr-item:hover {
629
+ background: #f3f4f6;
630
+ border-color: #d1d5db;
631
+ }
632
+
633
+ .emr-item-info {
634
+ display: flex;
635
+ flex-direction: column;
636
+ gap: 2px;
637
+ }
638
+
639
+ .emr-item-name {
640
+ font-size: 14px;
641
+ font-weight: 500;
642
+ color: #111827;
643
+ }
644
+
645
+ .emr-item-desc {
646
+ font-size: 12px;
647
+ color: #6b7280;
648
+ }
649
+
650
+ .emr-item-arrow {
651
+ width: 20px;
652
+ height: 20px;
653
+ color: #9ca3af;
654
+ flex-shrink: 0;
655
+ }
656
+
657
+ .emr-list-footer {
658
+ padding-top: 12px;
659
+ border-top: 1px solid #e5e7eb;
660
+ }
661
+
662
+ .start-new-recording-btn {
663
+ width: 100%;
664
+ background: #2563eb;
665
+ color: white;
666
+ border: none;
667
+ padding: 12px 20px;
668
+ border-radius: 8px;
669
+ font-size: 14px;
670
+ font-weight: 500;
671
+ cursor: pointer;
672
+ transition: background 0.2s;
673
+ }
674
+
675
+ .start-new-recording-btn:hover {
676
+ background: #1d4ed8;
677
+ }
package/src/types.ts CHANGED
@@ -8,7 +8,8 @@ export type WidgetState =
8
8
  | 'processing'
9
9
  | 'results'
10
10
  | 'error'
11
- | 'polling_error';
11
+ | 'polling_error'
12
+ | 'emr_list';
12
13
 
13
14
  export interface ScribeWidgetConfig {
14
15
  accessToken?: string;