Skip to content

Commit 3ff4ed9

Browse files
authored
Sam 69104 mic badge (#1)
1 parent 58d0182 commit 3ff4ed9

File tree

10 files changed

+101
-67
lines changed

10 files changed

+101
-67
lines changed

vim--ai-scribe--react/package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

vim--ai-scribe--react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"react-hook-form": "^7.54.2",
2525
"react-router-dom": "^7.2.0",
2626
"tailwind-merge": "^2.5.2",
27-
"vim-os-js-browser": "^2.0.1"
27+
"vim-os-js-browser": "^2.0.6"
2828
},
2929
"devDependencies": {
3030
"@cloudflare/workers-types": "^4.20240821.1",

vim--ai-scribe--react/src/components/organisms/RecordingTab/RecordingTab.tsx

Lines changed: 8 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,20 @@
1-
import { type Dispatch, type SetStateAction } from "react";
2-
import { User, Mic } from "lucide-react";
1+
import { Mic, User } from "lucide-react";
32
import { Button } from "../../atoms/Button";
4-
import { Input } from "../../atoms/Input";
5-
import { useVimOsContext } from "@/providers/VimOSContext";
6-
import { buildName } from "../ai-scribe-demo/buildName";
3+
import { useProviderName } from "@/vimOs/useProviderName";
74

85
export const RecordingTab = ({
96
patientName,
10-
setPatientName,
117
simulateRecording,
128
}: {
139
patientName: string;
14-
setPatientName: Dispatch<SetStateAction<string>>;
1510
simulateRecording: () => void;
1611
}) => {
17-
const vimOS = useVimOsContext();
18-
const username = buildName(vimOS) || ""
12+
const providerName = useProviderName();
1913

2014
return (
2115
<div className="space-y-6">
22-
<div className="flex flex-col items-center justify-between">
23-
<div>{username}</div>
16+
<div className="flex flex-col items-center justify-between space-y-2">
17+
<div>Hi there, {providerName}</div>
2418
<div className="flex items-center space-x-2">
2519
<User className="h-5 w-5 text-gray-500" />
2620
<h2 className="text-lg font-semibold text-gray-900">
@@ -29,12 +23,8 @@ export const RecordingTab = ({
2923
</div>
3024
</div>
3125

32-
<div className="max-w-xl mx-auto">
33-
<Input
34-
value={patientName}
35-
onChange={setPatientName}
36-
placeholder="Enter patient name"
37-
/>
26+
<div className="flex flex-col items-center">
27+
<div>{patientName}</div>
3828

3929
<Button
4030
onClick={simulateRecording}
@@ -43,7 +33,7 @@ export const RecordingTab = ({
4333
className="mt-4 py-4"
4434
>
4535
<Mic className="h-5 w-5 mr-2" />
46-
Start Recording
36+
{patientName ? "Start Recording" : "Waiting for patient"}
4737
</Button>
4838
</div>
4939
</div>

vim--ai-scribe--react/src/components/organisms/ai-scribe-demo/AiScribeDemo.tsx

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,22 @@
1-
import { useState } from "react";
2-
import { useUpdateEncounter } from "@/vimOs/useUpdateEncounter";
31
import { NavigationBar } from "@/components/molecules/NavigationBar";
42
import { useNoteFormContext } from "@/providers/NoteFormContext";
3+
import { useVimOsContext } from "@/providers/VimOSContext";
4+
import { useUpdateEncounter } from "@/vimOs/useUpdateEncounter";
5+
import { useState } from "react";
6+
import { NotesTab } from "../notes-tab/NotesTab";
57
import { RecordingPanel } from "../recording-panel/RecordingPanel";
6-
import { MEDICAL_KEYWORDS } from "./keywords.mock";
7-
import { useRecorder } from "./useRecorder";
88
import { RecordingTab } from "../RecordingTab/RecordingTab";
9-
import { ProcessingTab } from "./ProcessingTab";
9+
import { AppHeader } from "./AppHeader";
1010
import {
1111
DiagnosisCodesModal,
1212
type DiagnosisCodesList,
1313
} from "./DiagnosisCodesModal";
14+
import { MEDICAL_KEYWORDS } from "./keywords.mock";
1415
import type { Note } from "./Note.interface";
16+
import { ProcessingTab } from "./ProcessingTab";
17+
import { usePatientName } from "../../../vimOs/usePatientName";
18+
import { useRecorder } from "./useRecorder";
1519
import { UserTab } from "./UserTab";
16-
import { AppHeader } from "./AppHeader";
17-
import { useVimOsContext } from "@/providers/VimOSContext";
18-
import { NotesTab } from "../notes-tab/NotesTab";
19-
import { buildName } from "./buildName";
2020

2121
const RECORDING_RESULT = {
2222
subjective:
@@ -69,9 +69,8 @@ export const AiScribeDemo = () => {
6969
const vimOS = useVimOsContext();
7070
const [activeTab, setActiveTab] = useState<TabType>("record");
7171
const [notes, setNotes] = useState<Note[]>([]);
72-
const [patientName, setPatientName] = useState<string>(
73-
() => buildName(vimOS) || ""
74-
);
72+
const patientName = usePatientName();
73+
const [visitedPatient, setVisitedPatient] = useState<string | null>(null);
7574
const [isProcessing, setIsProcessing] = useState(false);
7675
const [selectedKeyword, setSelectedKeyword] = useState<string | null>(null);
7776
const { watch, reset } = useNoteFormContext();
@@ -108,6 +107,7 @@ export const AiScribeDemo = () => {
108107

109108
const handleEndVisit = () => {
110109
stopRecording();
110+
setVisitedPatient(patientName);
111111
setIsProcessing(true);
112112

113113
setTimeout(() => {
@@ -191,7 +191,6 @@ export const AiScribeDemo = () => {
191191
{activeTab === "record" && !isRecording && !isProcessing && (
192192
<RecordingTab
193193
patientName={patientName}
194-
setPatientName={setPatientName}
195194
simulateRecording={simulateRecording}
196195
/>
197196
)}
@@ -209,17 +208,13 @@ export const AiScribeDemo = () => {
209208

210209
{activeTab === "notes" && (
211210
<NotesTab
212-
patientName={patientName}
211+
patientName={visitedPatient}
213212
handleFullEhrUpdate={handleFullEhrUpdate}
214213
renderHighlightedText={renderHighlightedText}
215214
/>
216215
)}
217216

218-
{activeTab === "user" && (
219-
<UserTab
220-
notes={notes}
221-
/>
222-
)}
217+
{activeTab === "user" && <UserTab notes={notes} />}
223218
</div>
224219

225220
{selectedKeyword && (

vim--ai-scribe--react/src/components/organisms/ai-scribe-demo/buildName.ts

Lines changed: 0 additions & 13 deletions
This file was deleted.

vim--ai-scribe--react/src/components/organisms/ai-scribe-demo/useRecorder.ts

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
import { useCallback } from "react";
12
import { useState, useEffect } from "react";
3+
import { useVimOsContext } from "@/providers/VimOSContext";
24

35
export const useRecorder = () => {
6+
const [stream, setStream] = useState<MediaStream | null>(null);
47
const [isPaused, setIsPaused] = useState(false);
58
const [isRecording, setIsRecording] = useState(false);
69
const [recordingTime, setRecordingTime] = useState(0);
10+
const vimOS = useVimOsContext();
711

812
useEffect(() => {
913
let interval: NodeJS.Timeout;
@@ -15,17 +19,32 @@ export const useRecorder = () => {
1519
return () => clearInterval(interval);
1620
}, [isRecording, isPaused]);
1721

18-
const simulateRecording = () => {
19-
setIsRecording(true);
20-
setIsPaused(false);
21-
setRecordingTime(0);
22-
};
22+
const simulateRecording = useCallback(async () => {
23+
try {
24+
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
25+
setStream(stream);
26+
// Show the VimOS microphone badge to indicate active recording
27+
vimOS?.hub?.microphoneBadge?.show?.();
28+
setIsRecording(true);
29+
setIsPaused(false);
30+
setRecordingTime(0);
31+
} catch {
32+
// Hide the badge if access is denied or an error occurs
33+
vimOS?.hub?.microphoneBadge?.hide?.();
34+
console.error("Microphone access denied or error occurred.");
35+
}
36+
}, [vimOS]);
2337

24-
const stopRecording = () => {
38+
const stopRecording = useCallback(() => {
39+
if (stream) {
40+
stream.getTracks().forEach((track) => track.stop());
41+
}
2542
setIsRecording(false);
2643
setIsPaused(false);
2744
setRecordingTime(0);
28-
};
45+
// Hide the VimOS microphone badge when recording stops
46+
vimOS?.hub?.microphoneBadge?.hide?.();
47+
}, [vimOS, stream]);
2948

3049
return {
3150
isPaused,

vim--ai-scribe--react/src/components/organisms/notes-tab/NotesTab.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const NotesTab = ({
1111
handleFullEhrUpdate,
1212
renderHighlightedText,
1313
}: {
14-
patientName: string;
14+
patientName: string | null;
1515
handleFullEhrUpdate: () => Promise<void>;
1616
renderHighlightedText: (text: string) => JSX.Element;
1717
}) => {
@@ -29,8 +29,9 @@ export const NotesTab = ({
2929
return (
3030
<>
3131
<div className="flex flex-col justify-between items-center gap-2">
32-
<h2 className="text-3xl font-bold text-gray-800">
33-
{patientName || "Patient Name"}
32+
<h2 className="text-2xl text-gray-800">
33+
Notes for{" "}
34+
<span className="font-bold">{patientName || "Patient Name"}</span>
3435
</h2>
3536
<div className="flex items-center space-x-4">
3637
<Button
@@ -40,7 +41,7 @@ export const NotesTab = ({
4041
>
4142
Transcription
4243
</Button>
43-
<Button onClick={() => handleFullEhrUpdate()}>Push all to EHR</Button>
44+
<Button onClick={handleFullEhrUpdate}>Push all to EHR</Button>
4445
</div>
4546
<div className="text-sm text-gray-500">Note saved automatically</div>
4647
</div>

vim--ai-scribe--react/src/components/organisms/recording-panel/RecordingPanel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const RecordingPanel = ({
4646
</Button>
4747

4848
<a
49-
href="#"
49+
href="https://docs.getvim.com/"
5050
className="text-green-600 hover:text-green-800 underline mt-4"
5151
>
5252
How do I tell my patient about AI Scribe?
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useVimOsContext } from "@/providers/VimOSContext";
2+
import { EHR } from "vim-os-js-browser/types";
3+
import { useEffect, useState } from "react";
4+
5+
/**
6+
* Securely fetches the current user's first and last name using the VimOS SDK session context.
7+
* Does not expose sensitive error details. Only returns first and last name.
8+
*
9+
* @returns { providerName, loading }
10+
*/
11+
export function usePatientName() {
12+
const vimOS = useVimOsContext();
13+
const [patientName, setPatientName] = useState("");
14+
useEffect(() => {
15+
const onPatientChange = (patient: EHR.Patient | undefined) => {
16+
setPatientName(
17+
[patient?.demographics?.firstName, patient?.demographics?.lastName]
18+
.filter(Boolean)
19+
.join(" ")
20+
);
21+
};
22+
vimOS.ehr.subscribe("patient", onPatientChange);
23+
24+
return () => vimOS.ehr.unsubscribe("patient", onPatientChange);
25+
}, [vimOS.ehr]);
26+
27+
return patientName;
28+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { useVimOsContext } from "@/providers/VimOSContext";
2+
3+
/**
4+
* Securely fetches the current user's first and last name using the VimOS SDK session context.
5+
* Does not expose sensitive error details. Only returns first and last name.
6+
*
7+
* @returns { providerName, loading }
8+
*/
9+
export function useProviderName() {
10+
const vimOS = useVimOsContext();
11+
12+
const { firstName, lastName } = vimOS.sessionContext.user.demographics || {};
13+
return [firstName, lastName].filter(Boolean).join(" ");
14+
}

0 commit comments

Comments
 (0)