Skip to content

Commit 4eda10f

Browse files
committed
Open registration
1 parent bbeafeb commit 4eda10f

11 files changed

Lines changed: 358 additions & 19 deletions

File tree

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
VITE_MEDPLUM_BASE_URL=https://api.medplum.com
22
VITE_MEDPLUM_PROJECT_ID=<your-medplum-project-id-here>
3+
VITE_MEDPLUM_RECAPTCHA_SITE_KEY=<your-recaptcha-site-key-here>
34
VITE_COPILOT_RUNTIME_URL=http://localhost:4000/copilotkit
45
OPENAI_API_KEY=your-openai-api-key-here
56
OPENAI_MODEL=<choose-an-openai-llm-model-here>

.github/workflows/deploy.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
# Runs on pushes targeting the default branch
5+
push:
6+
branches: ['main']
7+
8+
# Allows you to run this workflow manually from the Actions tab
9+
workflow_dispatch:
10+
11+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
12+
permissions:
13+
contents: read
14+
pages: write
15+
id-token: write
16+
17+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
18+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
19+
concurrency:
20+
group: 'pages'
21+
cancel-in-progress: false
22+
23+
jobs:
24+
# Build job
25+
build:
26+
runs-on: ubuntu-latest
27+
steps:
28+
- name: Checkout
29+
uses: actions/checkout@v4
30+
31+
- name: Setup Node.js
32+
uses: actions/setup-node@v4
33+
with:
34+
node-version: '22'
35+
cache: 'npm'
36+
37+
- name: Install dependencies
38+
run: npm ci
39+
40+
- name: Create .env file
41+
run: |
42+
cat > .env << EOF
43+
VITE_MEDPLUM_BASE_URL=${{ secrets.MEDPLUM_BASE_URL }}
44+
VITE_MEDPLUM_PROJECT_ID=${{ secrets.MEDPLUM_PROJECT_ID }}
45+
VITE_MEDPLUM_RECAPTCHA_SITE_KEY=${{ secrets.MEDPLUM_RECAPTCHA_SITE_KEY }}
46+
VITE_COPILOT_RUNTIME_URL=${{ secrets.COPILOT_RUNTIME_URL }}
47+
OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}
48+
OPENAI_MODEL=${{ secrets.OPENAI_MODEL }}
49+
EOF
50+
51+
- name: Build app
52+
run: npm run build
53+
54+
- name: Setup Pages
55+
uses: actions/configure-pages@v4
56+
57+
- name: Upload artifact
58+
uses: actions/upload-pages-artifact@v3
59+
with:
60+
path: './dist'
61+
62+
# Deployment job
63+
deploy:
64+
environment:
65+
name: github-pages
66+
url: ${{ steps.deployment.outputs.page_url }}
67+
runs-on: ubuntu-latest
68+
needs: build
69+
steps:
70+
- name: Deploy to GitHub Pages
71+
id: deployment
72+
uses: actions/deploy-pages@v4
73+

README.md

Lines changed: 90 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ Demo of a healthcare dashboard with AI-powered chart and report generation using
44

55
The patient can chat with an AI agent and analyze their health records. The AI agent can generate charts and reports to display the patient's health data dynamically and provide insights. Current version focuses on weight-loss and diabetes care.
66

7+
This application demonstrates the usage of Generative UI techniques for healthcare dashboards, specifically with the [Agent–User Interaction (AG-UI) Protocol with CopilotKit](https://docs.ag-ui.com/introduction).
8+
79
## Demo video
810

911
Demo video coming soon...
@@ -22,7 +24,8 @@ Demo video coming soon...
2224

2325
- Node.js 22.18 or higher
2426
- npm 11.6 or higher
25-
- Configured Medplum project (see section below)
27+
- Configured Medplum project (see steps below)
28+
- reCAPTCHA site key (for patient registration)
2629
- OpenAI API key
2730

2831
### Medplum Project setup
@@ -31,6 +34,28 @@ Create a new Medplum project at https://app.medplum.com/register.
3134

3235
Get your Medplum project ID from the app.medplum.com [Project page](https://app.medplum.com/admin/project). Keep the project ID at hand to set it as an environment variable.
3336

37+
### Create a patient default access policy
38+
39+
1. Go to [app.medplum.com New AccessPolicy page, JSON tab](https://app.medplum.com/AccessPolicy/new/json)
40+
2. Paste the contents from [patient-access-policy.json](patient-access-policy.json)
41+
3. Click "OK"
42+
43+
### Set up open patient registration
44+
45+
To allow patients to self-register, set the patient access policy as the default.
46+
47+
1. Navigate to [app.medplum.com Project page](https://app.medplum.com/Project) and select your project.
48+
2. In the "Edit" tab, set the "Default Patient Access Policy" field to your patient access policy and click "Update".
49+
50+
For more details, see the [Open Patient Registration documentation](https://www.medplum.com/docs/user-management/open-patient-registration).
51+
52+
### Set up reCAPTCHA
53+
54+
A reCAPTCHA configuration is required for the registration form to work.
55+
56+
1. Create a new reCAPTCHA configuration to get the site key and secret key at [google.com/recaptcha/admin/create](https://www.google.com/recaptcha/admin/create).
57+
2. Go to [app.medplum.com](https://app.medplum.com), go to Project, then Sites. Create a Site with domains `localhost` and `127.0.0.1` and set the reCAPTCHA site key and secret key.
58+
3459
### OpenAI API key setup
3560

3661
Get your API key from [OpenAI Platform](https://platform.openai.com/api-keys).
@@ -48,11 +73,18 @@ Create a `.env` file in the project root:
4873

4974
```env
5075
VITE_MEDPLUM_PROJECT_ID=<your-medplum-project-id-here>
76+
VITE_MEDPLUM_RECAPTCHA_SITE_KEY=<your-recaptcha-site-key-here>
5177
OPENAI_API_KEY=<your-openai-api-key-here>
52-
OPENAI_MODEL=<choose-an-openai-llm-model-here>
5378
```
5479

55-
4. **Start the application**
80+
Optional variables (defaults to the values shown):
81+
```env
82+
VITE_MEDPLUM_BASE_URL=https://api.medplum.com
83+
VITE_COPILOT_RUNTIME_URL=http://localhost:4000/copilotkit
84+
OPENAI_MODEL=gpt-5-nano
85+
```
86+
87+
3. **Start the application**
5688

5789
**Option A: Start both services together (recommended)**
5890
```bash
@@ -68,10 +100,16 @@ npm run server
68100
npm run dev
69101
```
70102

71-
5. **Open the app**: [http://localhost:3000](http://localhost:3000)
103+
4. **Open the app**: [http://localhost:3000](http://localhost:3000)
72104

73105
## Usage
74106

107+
### Patient Sign-up and Sign-in
108+
109+
1. **New users**: Click "Register here" to create a new patient account
110+
2. **Existing users**: Sign in as a Patient user
111+
3. After signing in, you'll access your AI Concierge dashboard
112+
75113
### AI Assistant Examples
76114

77115
The AI assistant generates widgets dynamically based on natural language queries. Try these:
@@ -112,11 +150,11 @@ The AI analyzes all visible widget data and presents the interpretation in the c
112150
| `VITE_COPILOT_RUNTIME_URL` | No | `http://localhost:4000/copilotkit` | CopilotKit runtime endpoint |
113151
| `VITE_MEDPLUM_BASE_URL` | No | `https://api.medplum.com/` | Medplum API endpoint |
114152
| `VITE_MEDPLUM_PROJECT_ID` | Yes | - | Your Medplum project ID |
153+
| `VITE_MEDPLUM_RECAPTCHA_SITE_KEY` | Yes | - | Your reCAPTCHA site key |
115154
| `OPENAI_API_KEY` | Yes | - | Your OpenAI API key |
116155
| `OPENAI_MODEL` | No | `gpt-5-nano` | OpenAI model to use for the AI assistant |
117156
| `SERVER_PORT` | No | `4000` | Backend server port |
118157

119-
120158
## Development
121159

122160
### Development Guidelines
@@ -157,6 +195,47 @@ npm test -- src/components/widgets/VitalsWidget.test.tsx
157195
npm test -- --testNamePattern="VitalsWidget"
158196
```
159197

198+
## Deployment to GitHub Pages
199+
200+
This project includes a GitHub Actions workflow to automatically deploy the application to GitHub Pages.
201+
202+
### Setup
203+
204+
1. **Enable GitHub Pages for your repository:**
205+
- Go to your repository Settings → Pages
206+
- Under "Build and deployment", select "GitHub Actions" as the source
207+
208+
2. **Configure GitHub Secrets:**
209+
210+
Add the following secrets to your repository (Settings → Secrets and variables → Actions → New repository secret):
211+
212+
- `MEDPLUM_BASE_URL`: e.g., `https://api.medplum.com`
213+
- `MEDPLUM_CLIENT_ID`
214+
- `MEDPLUM_PROJECT_ID`
215+
- `MEDPLUM_RECAPTCHA_SITE_KEY`
216+
- `COPILOT_RUNTIME_URL`
217+
- `OPENAI_API_KEY`
218+
- `OPENAI_MODEL`
219+
220+
3. **Configure base path (if needed):**
221+
222+
If your repository is hosted at `https://username.github.io/repository-name/` (not at a custom domain or root), you'll need to set the base path:
223+
224+
```bash
225+
export VITE_BASE_PATH=/repository-name/
226+
npm run build
227+
```
228+
229+
Or add to GitHub Actions secrets and set in the workflow.
230+
231+
### Deployment Triggering
232+
233+
The workflow automatically deploys when you push to the `main` branch, or you can manually trigger it from the Actions tab in your GitHub repository.
234+
235+
After deployment, your app will be available at:
236+
- `https://username.github.io/` (for user pages)
237+
- `https://username.github.io/repository-name/` (for project pages)
238+
160239
## License
161240

162241
Apache 2.0 - See [LICENSE.txt](LICENSE.txt) for details
@@ -170,3 +249,9 @@ Apache 2.0 - See [LICENSE.txt](LICENSE.txt) for details
170249
- **CopilotKit Self-Hosting Guide**: [docs.copilotkit.ai/direct-to-llm/guides/self-hosting](https://docs.copilotkit.ai/direct-to-llm/guides/self-hosting)
171250

172251
**Need Help?** Please feel free to create an issue!
252+
253+
## Commercial Support
254+
255+
[![alt text](https://avatars2.githubusercontent.com/u/5529080?s=80&v=4 "Vinta Logo")](https://www.vinta.com.br/)
256+
257+
This project is maintained by [Vinta Software](https://www.vinta.com.br/). We offer design and development services for healthcare companies. If you need any commercial support, feel free to get in touch: contact@vinta.com.br

patient-access-policy.json

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
{
2+
"resourceType": "AccessPolicy",
3+
"name": "Patient Access Policy Template",
4+
"id": "patient-access-policy-template",
5+
"compartment": {
6+
"reference": "%patient"
7+
},
8+
"resource": [
9+
{
10+
"resourceType": "Patient",
11+
"criteria": "Patient?_compartment=%patient"
12+
},
13+
{
14+
"resourceType": "Observation",
15+
"criteria": "Observation?_compartment=%patient"
16+
},
17+
{
18+
"resourceType": "DiagnosticReport",
19+
"criteria": "DiagnosticReport?_compartment=%patient"
20+
},
21+
{
22+
"resourceType": "MedicationRequest",
23+
"criteria": "MedicationRequest?_compartment=%patient"
24+
},
25+
{
26+
"resourceType": "Coverage",
27+
"criteria": "Coverage?_compartment=%patient"
28+
},
29+
{
30+
"resourceType": "PaymentNotice",
31+
"criteria": "PaymentNotice?_compartment=%patient"
32+
},
33+
{
34+
"resourceType": "CarePlan",
35+
"criteria": "CarePlan?_compartment=%patient"
36+
},
37+
{
38+
"resourceType": "Immunization",
39+
"criteria": "Immunization?_compartment=%patient"
40+
},
41+
{
42+
"resourceType": "Communication",
43+
"criteria": "Communication?_compartment=%patient"
44+
},
45+
{
46+
"resourceType": "Organization",
47+
"readonly": true
48+
},
49+
{
50+
"resourceType": "Practitioner",
51+
"readonly": true
52+
},
53+
{
54+
"resourceType": "Schedule",
55+
"readonly": true
56+
},
57+
{
58+
"resourceType": "Slot",
59+
"readonly": true
60+
},
61+
{
62+
"resourceType": "Binary"
63+
},
64+
{
65+
"resourceType": "Bot",
66+
"readonly": true
67+
},
68+
{
69+
"resourceType": "OperationDefinition",
70+
"readonly": true
71+
}
72+
]
73+
}

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import '@copilotkit/react-ui/styles.css';
2+
13
import { AppShell } from '@mantine/core';
24
import { ErrorBoundary, Loading, useMedplum } from '@medplum/react';
35
import type { JSX } from 'react';
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { render, screen } from '../test-utils/render';
2+
import { RegisterForm } from './RegisterForm';
3+
4+
describe('RegisterForm', () => {
5+
test('renders registration form', () => {
6+
render(<RegisterForm />);
7+
expect(screen.getByText(/Create Account/)).toBeInTheDocument();
8+
expect(screen.getByText(/Register as a new patient/)).toBeInTheDocument();
9+
});
10+
11+
test('shows back button when onCancel is provided', () => {
12+
const onCancel = jest.fn();
13+
render(<RegisterForm onCancel={onCancel} />);
14+
const backButton = screen.getByRole('button', { name: /Back to sign in/ });
15+
expect(backButton).toBeInTheDocument();
16+
backButton.click();
17+
expect(onCancel).toHaveBeenCalled();
18+
});
19+
20+
test('does not show back button when onCancel is not provided', () => {
21+
render(<RegisterForm />);
22+
const backButton = screen.queryByRole('button', { name: /Back to sign in/ });
23+
expect(backButton).not.toBeInTheDocument();
24+
});
25+
});

src/components/RegisterForm.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Box, Button, Group, Text, Title } from '@mantine/core';
2+
import { RegisterForm as MedplumRegisterForm } from '@medplum/react';
3+
import type { JSX } from 'react';
4+
import { MEDPLUM_PROJECT_ID, MEDPLUM_RECAPTCHA_SITE_KEY } from '../config';
5+
import { IconArrowLeft } from '@tabler/icons-react';
6+
7+
interface RegisterFormProps {
8+
onSuccess?: () => void;
9+
onCancel?: () => void;
10+
}
11+
12+
export function RegisterForm({ onSuccess, onCancel }: RegisterFormProps): JSX.Element {
13+
return (
14+
<Box p="md">
15+
<div>
16+
<Title order={3} ta="center" my="md">
17+
Create Account
18+
</Title>
19+
<Text c="dimmed" size="sm" ta="center">
20+
Register as a new patient to access your health information
21+
</Text>
22+
</div>
23+
24+
<MedplumRegisterForm
25+
type="patient"
26+
projectId={MEDPLUM_PROJECT_ID}
27+
recaptchaSiteKey={MEDPLUM_RECAPTCHA_SITE_KEY}
28+
onSuccess={() => {
29+
onSuccess?.();
30+
}}
31+
/>
32+
33+
{onCancel && (
34+
<Group justify="center" mt="md">
35+
<Button variant="subtle" onClick={onCancel} leftSection={<IconArrowLeft size={16} />}>
36+
Back to sign in
37+
</Button>
38+
</Group>
39+
)}
40+
</Box>
41+
);
42+
}

src/config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Medplum Configuration
22
export const MEDPLUM_BASE_URL = import.meta.env.VITE_MEDPLUM_BASE_URL || 'https://api.medplum.com/';
33
export const MEDPLUM_PROJECT_ID = import.meta.env.VITE_MEDPLUM_PROJECT_ID;
4+
export const MEDPLUM_CLIENT_ID = import.meta.env.VITE_MEDPLUM_CLIENT_ID;
5+
export const MEDPLUM_RECAPTCHA_SITE_KEY = import.meta.env.VITE_MEDPLUM_RECAPTCHA_SITE_KEY;
46

57
// CopilotKit Configuration - Self-hosted Runtime
68
export const COPILOT_RUNTIME_URL = import.meta.env.VITE_COPILOT_RUNTIME_URL || 'http://localhost:4000/copilotkit';

0 commit comments

Comments
 (0)