Improve email templates and update README
- Replace bare-bones email HTML with branded templates: gradient header, action button, plain-text URL fallback, security footer notice - Welcome/verify email: friendly greeting with user's name - Password reset email: clear instructions, 1-hour expiry notice, explicit 'if you didn't request this, ignore it' message - README: updated features list, vendor model 4.6 models, plain-text output note
This commit is contained in:
parent
a752de60ab
commit
797b22e58b
2 changed files with 76 additions and 20 deletions
28
README.md
28
README.md
|
|
@ -1,19 +1,23 @@
|
|||
# 🏥 Pediatric AI Scribe v3
|
||||
# 🩺 Pediatric AI Scribe v3
|
||||
|
||||
AI-powered clinical documentation platform for pediatric medicine. Generates HPIs, hospital courses, chart reviews, SOAP notes, and developmental milestone assessments from voice recordings or dictation.
|
||||
AI-powered clinical documentation platform for pediatric medicine. Generates HPIs, hospital courses, chart reviews, SOAP notes, and developmental milestone assessments from voice recordings or dictation — in seconds, in plain copy-ready text.
|
||||
|
||||
## Features
|
||||
|
||||
- **Live Encounter → HPI** — record doctor-patient conversation, AI generates structured HPI
|
||||
- **Voice Dictation → HPI / SOAP** — dictate your narrative, AI restructures it
|
||||
- **Hospital Course Generator** — paste progress notes by date, AI generates organized discharge summary (prose, day-by-day, organ-system, or psych format)
|
||||
- **Chart Review / Precharting** — summarize outpatient, subspecialty, and ED notes
|
||||
- **Live Encounter → HPI** — record a live doctor-patient conversation, AI generates a structured OLDCARTS HPI
|
||||
- **Voice Dictation → HPI / SOAP** — dictate your narrative, AI cleans and restructures it
|
||||
- **Hospital Course Generator** — paste progress notes, AI generates prose, day-by-day, organ-system (ICU), or psych format summaries
|
||||
- **Chart Review / Precharting** — summarize outpatient, subspecialty, and ED notes into a precharting brief
|
||||
- **SOAP Note Generator** — full SOAP or subjective-only from dictation
|
||||
- **Developmental Milestones** — AAP/Nelson milestone tracker with narrative output
|
||||
- **Admin Panel** — user management, registration control, usage stats
|
||||
- **Per-tab model selector** — choose fast vs. smart models per task
|
||||
- **Nextcloud integration** — export documents to your Nextcloud instance
|
||||
- **Developmental Milestones** — AAP/Nelson milestone tracker with narrative, structured list, or 3-sentence summary output
|
||||
- **Plain text output** — all documents generated without markdown, ready to paste into any EHR
|
||||
- **Read Aloud** — browser TTS reads generated documents; ElevenLabs (Adam voice) supported
|
||||
- **Copy & Export** — one-click copy or export to Nextcloud
|
||||
- **Refine & Shorten** — edit any document with plain-language AI instructions
|
||||
- **Per-tab model selector** — choose fast vs. smart vs. reasoning models per task
|
||||
- **Admin Panel** — user management, registration control, audit logs
|
||||
- **2FA** — TOTP-based two-factor authentication
|
||||
- **Multi-provider AI** — OpenRouter, AWS Bedrock, or Azure OpenAI
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -126,9 +130,11 @@ AWS_SECRET_ACCESS_KEY=...
|
|||
Or use an IAM role (no keys needed when running on EC2/ECS — just set the region).
|
||||
|
||||
Available Bedrock models (auto-selected when `AI_PROVIDER=bedrock`):
|
||||
- vendor model Opus 4.6 — best language nuance (`anthropic.agent-config-opus-4-6-20251001-v1:0`)
|
||||
- vendor model Sonnet 4.6 — recommended (`anthropic.agent-config-sonnet-4-6-20251001-v1:0`)
|
||||
- vendor model Sonnet 4 (`anthropic.agent-config-sonnet-4-20250514-v1:0`)
|
||||
- vendor model 3.5 Sonnet (`anthropic.agent-config-3-5-sonnet-20241022-v2:0`)
|
||||
- vendor model 3 Haiku — default, cheapest (`anthropic.agent-config-3-haiku-20240307-v1:0`)
|
||||
- vendor model 3 Haiku — cheapest (`anthropic.agent-config-3-haiku-20240307-v1:0`)
|
||||
- Llama 3.1 70B / 8B
|
||||
- Mistral Large
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,33 @@ const crypto = require('crypto');
|
|||
const db = require('../db/database');
|
||||
const { JWT_SECRET, authMiddleware } = require('../middleware/auth');
|
||||
|
||||
// ============================================================
|
||||
// EMAIL TEMPLATES
|
||||
// ============================================================
|
||||
function emailWrapper(body) {
|
||||
return `<!DOCTYPE html><html><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head>
|
||||
<body style="margin:0;padding:0;background:#f3f4f6;font-family:'Segoe UI',Arial,sans-serif;">
|
||||
<table width="100%" cellpadding="0" cellspacing="0" style="background:#f3f4f6;padding:40px 20px;">
|
||||
<tr><td align="center">
|
||||
<table width="520" cellpadding="0" cellspacing="0" style="background:white;border-radius:12px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,0.08);">
|
||||
<tr><td style="background:linear-gradient(135deg,#2563eb 0%,#7c3aed 100%);padding:28px 36px;">
|
||||
<p style="margin:0;color:white;font-size:22px;font-weight:700;">🩺 Pediatric AI Scribe</p>
|
||||
<p style="margin:6px 0 0;color:rgba(255,255,255,0.8);font-size:13px;">AI-Powered Clinical Documentation</p>
|
||||
</td></tr>
|
||||
<tr><td style="padding:36px;">${body}</td></tr>
|
||||
<tr><td style="padding:16px 36px 28px;border-top:1px solid #e5e7eb;">
|
||||
<p style="margin:0;color:#9ca3af;font-size:12px;">This email was sent from Pediatric AI Scribe. If you did not request this, you can safely ignore it — no action is needed and your account remains secure.</p>
|
||||
</td></tr>
|
||||
</table>
|
||||
</td></tr>
|
||||
</table>
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
function btnStyle() {
|
||||
return 'display:inline-block;background:linear-gradient(135deg,#2563eb,#7c3aed);color:white;padding:13px 32px;border-radius:8px;text-decoration:none;font-weight:600;font-size:15px;';
|
||||
}
|
||||
|
||||
// Email helper
|
||||
async function sendEmail(to, subject, html) {
|
||||
if (!process.env.SMTP_HOST) {
|
||||
|
|
@ -64,13 +91,16 @@ router.post('/register', async (req, res) => {
|
|||
var userId = result.lastInsertRowid;
|
||||
|
||||
var verifyUrl = (process.env.APP_URL || 'http://localhost:3000') + '/api/auth/verify-email?token=' + verifyToken;
|
||||
await sendEmail(email, 'Verify your email - Pediatric AI Scribe',
|
||||
'<div style="font-family:sans-serif;max-width:500px;margin:0 auto;padding:20px;">' +
|
||||
'<h2 style="color:#2563eb;">Welcome to Pediatric AI Scribe!</h2>' +
|
||||
'<p>Hi ' + name + ',</p><p>Verify your email:</p>' +
|
||||
'<p style="text-align:center;margin:30px 0;"><a href="' + verifyUrl + '" style="background:#2563eb;color:white;padding:12px 30px;border-radius:8px;text-decoration:none;font-weight:600;">Verify Email</a></p>' +
|
||||
'<p style="color:#6b7280;font-size:13px;">Expires in 24 hours.</p></div>'
|
||||
);
|
||||
await sendEmail(email, 'Welcome — please verify your email', emailWrapper(
|
||||
`<p style="margin:0 0 8px;font-size:22px;">👋 Welcome aboard, ${name}!</p>
|
||||
<p style="color:#4b5563;margin:12px 0 24px;line-height:1.6;">Great to have you on Pediatric AI Scribe. Before you start generating clinical notes, please confirm your email address by clicking the button below.</p>
|
||||
<p style="text-align:center;margin:28px 0;">
|
||||
<a href="${verifyUrl}" style="${btnStyle()}">Verify My Email</a>
|
||||
</p>
|
||||
<p style="color:#6b7280;font-size:13px;margin:20px 0 4px;">Button not working? Copy and paste this link into your browser:</p>
|
||||
<p style="background:#f3f4f6;border-radius:6px;padding:10px 14px;font-size:12px;word-break:break-all;color:#374151;margin:0;">${verifyUrl}</p>
|
||||
<p style="color:#9ca3af;font-size:12px;margin:20px 0 0;">This link expires in 24 hours.</p>`
|
||||
));
|
||||
|
||||
await db.run('INSERT INTO audit_log (user_id, action, ip_address, details) VALUES (?, ?, ?, ?)',
|
||||
[userId, 'register', req.ip, role === 'admin' ? 'First user — auto admin' : 'standard user']);
|
||||
|
|
@ -120,7 +150,16 @@ router.post('/resend-verification', async (req, res) => {
|
|||
var verifyToken = crypto.randomBytes(32).toString('hex');
|
||||
await db.run('UPDATE users SET verify_token = ?, verify_expires = ? WHERE id = ?', [verifyToken, Date.now() + 86400000, user.id]);
|
||||
var verifyUrl = (process.env.APP_URL || 'http://localhost:3000') + '/api/auth/verify-email?token=' + verifyToken;
|
||||
await sendEmail(req.body.email, 'Verify your email', '<p>Click to verify: <a href="' + verifyUrl + '">' + verifyUrl + '</a></p>');
|
||||
await sendEmail(req.body.email, 'Verify your email — Pediatric AI Scribe', emailWrapper(
|
||||
`<p style="margin:0 0 8px;font-size:20px;">📧 Verify your email</p>
|
||||
<p style="color:#4b5563;margin:12px 0 24px;line-height:1.6;">Here's a fresh verification link for your account. Click below to confirm your email and get started.</p>
|
||||
<p style="text-align:center;margin:28px 0;">
|
||||
<a href="${verifyUrl}" style="${btnStyle()}">Verify My Email</a>
|
||||
</p>
|
||||
<p style="color:#6b7280;font-size:13px;margin:20px 0 4px;">Button not working? Copy and paste this link into your browser:</p>
|
||||
<p style="background:#f3f4f6;border-radius:6px;padding:10px 14px;font-size:12px;word-break:break-all;color:#374151;margin:0;">${verifyUrl}</p>
|
||||
<p style="color:#9ca3af;font-size:12px;margin:20px 0 0;">This link expires in 24 hours.</p>`
|
||||
));
|
||||
res.json({ success: true, message: 'Verification email sent' });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
|
@ -205,7 +244,18 @@ router.post('/forgot-password', async (req, res) => {
|
|||
if (!user) return res.json({ success: true, message: 'If account exists, reset email sent' });
|
||||
var token = crypto.randomBytes(32).toString('hex');
|
||||
await db.run('UPDATE users SET reset_token = ?, reset_expires = ? WHERE id = ?', [token, Date.now() + 3600000, user.id]);
|
||||
await sendEmail(req.body.email, 'Password Reset', '<p>Reset: <a href="' + (process.env.APP_URL || 'http://localhost:3000') + '/reset-password?token=' + token + '">Click here</a></p>');
|
||||
var resetUrl = (process.env.APP_URL || 'http://localhost:3000') + '/reset-password?token=' + token;
|
||||
await sendEmail(req.body.email, 'Reset your password — Pediatric AI Scribe', emailWrapper(
|
||||
`<p style="margin:0 0 8px;font-size:20px;">🔐 Password reset request</p>
|
||||
<p style="color:#4b5563;margin:12px 0 6px;line-height:1.6;">Hi there,</p>
|
||||
<p style="color:#4b5563;margin:0 0 24px;line-height:1.6;">Someone requested a password reset for your Pediatric AI Scribe account. If that was you, click the button below to choose a new password.</p>
|
||||
<p style="text-align:center;margin:28px 0;">
|
||||
<a href="${resetUrl}" style="${btnStyle()}">Reset My Password</a>
|
||||
</p>
|
||||
<p style="color:#6b7280;font-size:13px;margin:20px 0 4px;">Button not working? Copy and paste this link into your browser:</p>
|
||||
<p style="background:#f3f4f6;border-radius:6px;padding:10px 14px;font-size:12px;word-break:break-all;color:#374151;margin:0;">${resetUrl}</p>
|
||||
<p style="color:#9ca3af;font-size:12px;margin:20px 0 0;">This link expires in 1 hour. If you did not request a password reset, no action is needed — your password has not been changed.</p>`
|
||||
));
|
||||
res.json({ success: true, message: 'If account exists, reset email sent' });
|
||||
} catch (err) { res.status(500).json({ error: err.message }); }
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue