evaldoc 1.0.0
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/.env.staging.example +19 -0
- package/.firebaserc +6 -0
- package/.github/workflows/auto-merge-to-main.yml +28 -0
- package/.github/workflows/ci.yml +27 -0
- package/.github/workflows/npxhub-publish.yml +170 -0
- package/.prettierignore +4 -0
- package/.prettierrc.json +7 -0
- package/README.md +142 -0
- package/SRS.md +448 -0
- package/apphosting.staging.yaml +48 -0
- package/apphosting.yaml +50 -0
- package/bin/evaldoc.cjs +105 -0
- package/cors.json +8 -0
- package/docs/ARCHITECTURE.md +769 -0
- package/docs/ARCHITECTURE_SUMMARY.md +323 -0
- package/docs/DEPLOYMENT.md +86 -0
- package/docs/DEPLOYMENT_STEPS.md +373 -0
- package/docs/DEPLOY_FIRESTORE_RULES.md +239 -0
- package/docs/DEPLOY_STORAGE_RULES.md +82 -0
- package/docs/DOCTOR_TUTORIAL.md +467 -0
- package/docs/FIREBASE_STORAGE_CORS_FIX.md +131 -0
- package/docs/FIXES_APPLIED.md +128 -0
- package/docs/IMPLEMENTATION.md +498 -0
- package/docs/IMPLEMENTATION_COMPLETE.md +317 -0
- package/docs/LLM-SETUP.md +216 -0
- package/docs/MANUAL_TEST_GUIDE.md +327 -0
- package/docs/PATIENT_GUIDE.md +451 -0
- package/docs/PLATFORM_OVERVIEW.md +194 -0
- package/docs/QUICKSTART.md +252 -0
- package/docs/QUICK_REFERENCE.md +222 -0
- package/docs/READY_TO_TEST.md +383 -0
- package/docs/RUN.md +237 -0
- package/docs/START_TESTING.md +130 -0
- package/docs/STRIPE_CONNECT_SETUP.md +454 -0
- package/docs/SYSTEM_READY.md +345 -0
- package/docs/THREADS_IMPLEMENTATION_COMPLETE.md +431 -0
- package/docs/THREAD_IMPLEMENTATION_GUIDE.md +345 -0
- package/docs/firebasefiles.md +21 -0
- package/docs/homepage.md +1208 -0
- package/docs/payments.md +159 -0
- package/eslint.config.js +76 -0
- package/firebase.json +27 -0
- package/firestore.indexes.json +88 -0
- package/firestore.rules +125 -0
- package/functions/package-lock.json +2884 -0
- package/functions/package.json +20 -0
- package/functions/src/analytics.js +101 -0
- package/functions/src/audit.js +28 -0
- package/functions/src/auth.js +19 -0
- package/functions/src/drafts.js +128 -0
- package/functions/src/feedback.js +164 -0
- package/functions/src/firebase-init.js +29 -0
- package/functions/src/i18n.js +134 -0
- package/functions/src/limits.js +27 -0
- package/functions/src/llm.js +271 -0
- package/functions/src/ner.js +162 -0
- package/functions/src/notifications.js +273 -0
- package/functions/src/orgs.js +127 -0
- package/functions/src/patient-api.js +545 -0
- package/functions/src/payment.js +830 -0
- package/functions/src/safety.js +188 -0
- package/functions/src/stripe-config.js +33 -0
- package/functions/src/topics.js +94 -0
- package/functions/src/triage.js +191 -0
- package/functions/src/utils.js +28 -0
- package/functions/src/validate.js +177 -0
- package/functions/test-firebase.js +36 -0
- package/jest.config.js +18 -0
- package/package.json +52 -0
- package/public/404.html +93 -0
- package/public/app/analytics.html +336 -0
- package/public/app/analytics.js +177 -0
- package/public/app/app.css +1400 -0
- package/public/app/app.js +1754 -0
- package/public/app/config.js +14 -0
- package/public/app/inbox-threads.js +843 -0
- package/public/app/inbox.html +598 -0
- package/public/app/inbox.js +397 -0
- package/public/app/index.html +612 -0
- package/public/assets/logo.jpg +0 -0
- package/public/chat.html +137 -0
- package/public/css/chat.css +870 -0
- package/public/css/landing-enhance.css +298 -0
- package/public/css/styles.css +1198 -0
- package/public/favicon.svg +5 -0
- package/public/find-doctor.html +220 -0
- package/public/index.html +399 -0
- package/public/js/chat-threads.js +513 -0
- package/public/js/chat.js +832 -0
- package/public/js/search.js +41 -0
- package/public/privacy.html +101 -0
- package/public/robots.txt +6 -0
- package/public/site.webmanifest +12 -0
- package/public/terms.html +110 -0
- package/scripts/create-test-provider.js +55 -0
- package/scripts/deploy-staging.sh +51 -0
- package/scripts/fix-provider.js +39 -0
- package/scripts/quick-test.sh +37 -0
- package/scripts/test-chat.sh +54 -0
- package/scripts/test-multi-turn.js +137 -0
- package/scripts/test-ner-simple.js +28 -0
- package/scripts/test-ner.js +60 -0
- package/scripts/test-stripe-connect.js +304 -0
- package/scripts/test-webhook-secret.sh +24 -0
- package/server.js +3864 -0
- package/storage.rules +22 -0
- package/test-api.json +1 -0
- package/tests/emulator/README.md +38 -0
- package/tests/emulator/counters.emulator.test.js +81 -0
- package/tests/integration/api-account-selfserve.test.js +312 -0
- package/tests/integration/api-admin-digest.test.js +202 -0
- package/tests/integration/api-chat-satisfaction.test.js +259 -0
- package/tests/integration/api-doctor-drafts.test.js +236 -0
- package/tests/integration/api-doctor-inbox.test.js +401 -0
- package/tests/integration/api-org.test.js +328 -0
- package/tests/integration/api-payment.test.js +664 -0
- package/tests/integration/api-provider-settings.test.js +475 -0
- package/tests/integration/api-qr.test.js +121 -0
- package/tests/integration/api-security.test.js +241 -0
- package/tests/integration/api-stripe-search.test.js +303 -0
- package/tests/integration/api-webhook-deletion.test.js +231 -0
- package/tests/mocks/firebase-admin.js +220 -0
- package/tests/unit/analytics.test.js +151 -0
- package/tests/unit/audit-bugs.test.js +203 -0
- package/tests/unit/chat-ui-logic.test.js +283 -0
- package/tests/unit/drafts.test.js +104 -0
- package/tests/unit/feedback.test.js +148 -0
- package/tests/unit/i18n.test.js +229 -0
- package/tests/unit/llm-retry.test.js +122 -0
- package/tests/unit/notifications.test.js +155 -0
- package/tests/unit/orgs.test.js +328 -0
- package/tests/unit/payment.test.js +889 -0
- package/tests/unit/safety-edge-cases.test.js +509 -0
- package/tests/unit/safety-srs-compliance.test.js +252 -0
- package/tests/unit/safety.test.js +291 -0
- package/tests/unit/server-helpers.test.js +774 -0
- package/tests/unit/topics-limits.test.js +158 -0
- package/tests/unit/triage.test.js +152 -0
- package/tests/unit/validate.test.js +408 -0
package/docs/homepage.md
ADDED
|
@@ -0,0 +1,1208 @@
|
|
|
1
|
+
Excellent idea! This transforms EvalDoc from a back-end API into a **complete patient-facing platform**. Here's my assessment and implementation strategy:
|
|
2
|
+
|
|
3
|
+
## 🎯 **Strategic Value**
|
|
4
|
+
|
|
5
|
+
**BENEFITS:**
|
|
6
|
+
1. **Direct patient access** - No need for providers to share custom URLs
|
|
7
|
+
2. **Professional appearance** - Creates trust and legitimacy
|
|
8
|
+
3. **Provider directory** - Patients can find their doctor
|
|
9
|
+
4. **Scalable onboarding** - Easy for new providers to join
|
|
10
|
+
5. **Brand building** - Creates a recognizable healthcare platform
|
|
11
|
+
|
|
12
|
+
## 🏗️ **Recommended Architecture**
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
┌─────────────────────────────────────┐
|
|
16
|
+
│ EvalDoc Homepage │
|
|
17
|
+
│ ┌─────────────────────────────┐ │
|
|
18
|
+
│ │ 1. Hero Section │ │
|
|
19
|
+
│ │ 2. Provider Search │ │
|
|
20
|
+
│ │ 3. How It Works │ │
|
|
21
|
+
│ │ 4. Login/Signup │ │
|
|
22
|
+
│ └─────────────────────────────┘ │
|
|
23
|
+
└─────────────────────────────────────┘
|
|
24
|
+
│
|
|
25
|
+
▼
|
|
26
|
+
┌─────────────────────────────────────┐
|
|
27
|
+
│ Provider Search Results │
|
|
28
|
+
│ ┌─────────────────────────────┐ │
|
|
29
|
+
│ │ • Search by name/specialty │ │
|
|
30
|
+
│ │ • Filter by location │ │
|
|
31
|
+
│ │ • Verified badges │ │
|
|
32
|
+
│ └─────────────────────────────┘ │
|
|
33
|
+
└─────────────────────────────────────┘
|
|
34
|
+
│
|
|
35
|
+
▼
|
|
36
|
+
┌─────────────────────────────────────┐
|
|
37
|
+
│ Provider Chat Interface │
|
|
38
|
+
│ ┌─────────────────────────────┐ │
|
|
39
|
+
│ │ • Direct messaging │ │
|
|
40
|
+
│ │ • Safety warnings │ │
|
|
41
|
+
│ │ • File upload (optional) │ │
|
|
42
|
+
│ │ • Appointment link │ │
|
|
43
|
+
│ └─────────────────────────────┘ │
|
|
44
|
+
└─────────────────────────────────────┘
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 🚀 **Implementation Plan**
|
|
48
|
+
|
|
49
|
+
### **Phase 1: Homepage & Provider Search (Week 1)**
|
|
50
|
+
|
|
51
|
+
**File Structure:**
|
|
52
|
+
```
|
|
53
|
+
public/
|
|
54
|
+
├── index.html # Homepage
|
|
55
|
+
├── app.html # Chat interface
|
|
56
|
+
├── find-doctor.html # Provider search
|
|
57
|
+
├── provider.html # Provider profile
|
|
58
|
+
├── css/
|
|
59
|
+
│ ├── styles.css # Main styles
|
|
60
|
+
│ └── chat.css # Chat-specific styles
|
|
61
|
+
├── js/
|
|
62
|
+
│ ├── app.js # Main application
|
|
63
|
+
│ ├── search.js # Provider search
|
|
64
|
+
│ └── chat.js # Chat functionality
|
|
65
|
+
└── assets/
|
|
66
|
+
├── logos/
|
|
67
|
+
└── illustrations/
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Homepage (index.html) - Key Sections:**
|
|
71
|
+
```html
|
|
72
|
+
<!DOCTYPE html>
|
|
73
|
+
<html lang="en">
|
|
74
|
+
<head>
|
|
75
|
+
<meta charset="UTF-8">
|
|
76
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
77
|
+
<title>EvalDoc - AI-Powered Patient Support</title>
|
|
78
|
+
<link rel="stylesheet" href="/css/styles.css">
|
|
79
|
+
</head>
|
|
80
|
+
<body>
|
|
81
|
+
<!-- Navigation -->
|
|
82
|
+
<nav class="navbar">
|
|
83
|
+
<div class="nav-container">
|
|
84
|
+
<a href="/" class="logo">
|
|
85
|
+
<img src="/assets/logo.svg" alt="EvalDoc">
|
|
86
|
+
<span>EvalDoc</span>
|
|
87
|
+
</a>
|
|
88
|
+
<div class="nav-links">
|
|
89
|
+
<a href="/find-doctor">Find Your Doctor</a>
|
|
90
|
+
<a href="#how-it-works">How It Works</a>
|
|
91
|
+
<a href="#safety">Safety</a>
|
|
92
|
+
<a href="/login" class="btn-outline">Provider Login</a>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</nav>
|
|
96
|
+
|
|
97
|
+
<!-- Hero Section -->
|
|
98
|
+
<section class="hero">
|
|
99
|
+
<div class="hero-container">
|
|
100
|
+
<div class="hero-text">
|
|
101
|
+
<h1>Connect with your healthcare provider safely</h1>
|
|
102
|
+
<p>AI-powered patient support with medical safety guards. Get answers to non-urgent questions before your appointment.</p>
|
|
103
|
+
|
|
104
|
+
<!-- Provider Search Box -->
|
|
105
|
+
<div class="search-box">
|
|
106
|
+
<h3>Find your provider</h3>
|
|
107
|
+
<div class="search-form">
|
|
108
|
+
<input type="text"
|
|
109
|
+
id="providerSearch"
|
|
110
|
+
placeholder="Enter your doctor's name or specialty...">
|
|
111
|
+
<button onclick="searchProvider()">Find</button>
|
|
112
|
+
</div>
|
|
113
|
+
<p class="search-hint">Start typing to find your healthcare provider</p>
|
|
114
|
+
|
|
115
|
+
<!-- Quick Results -->
|
|
116
|
+
<div id="quickResults" class="quick-results"></div>
|
|
117
|
+
</div>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="hero-image">
|
|
120
|
+
<img src="/assets/hero-illustration.svg" alt="Doctor consultation">
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
</section>
|
|
124
|
+
|
|
125
|
+
<!-- How It Works -->
|
|
126
|
+
<section id="how-it-works" class="section">
|
|
127
|
+
<h2>How EvalDoc Works</h2>
|
|
128
|
+
<div class="steps">
|
|
129
|
+
<div class="step">
|
|
130
|
+
<div class="step-number">1</div>
|
|
131
|
+
<h3>Find Your Provider</h3>
|
|
132
|
+
<p>Search for your doctor by name or specialty</p>
|
|
133
|
+
</div>
|
|
134
|
+
<div class="step">
|
|
135
|
+
<div class="step-number">2</div>
|
|
136
|
+
<h3>Ask Questions Safely</h3>
|
|
137
|
+
<p>Get AI-powered responses with medical safety checks</p>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="step">
|
|
140
|
+
<div class="step-number">3</div>
|
|
141
|
+
<h3>Provider Review</h3>
|
|
142
|
+
<p>Your doctor reviews all conversations for follow-up</p>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</section>
|
|
146
|
+
|
|
147
|
+
<!-- Safety Features -->
|
|
148
|
+
<section id="safety" class="section dark">
|
|
149
|
+
<h2>Built with Medical Safety First</h2>
|
|
150
|
+
<div class="safety-features">
|
|
151
|
+
<div class="feature">
|
|
152
|
+
<div class="feature-icon">⚠️</div>
|
|
153
|
+
<h3>No Diagnoses</h3>
|
|
154
|
+
<p>AI never provides medical diagnoses</p>
|
|
155
|
+
</div>
|
|
156
|
+
<div class="feature">
|
|
157
|
+
<div class="feature-icon">🚨</div>
|
|
158
|
+
<h3>Urgent Symptom Detection</h3>
|
|
159
|
+
<p>Auto-escalates chest pain, breathing issues, etc.</p>
|
|
160
|
+
</div>
|
|
161
|
+
<div class="feature">
|
|
162
|
+
<div class="feature-icon">🔒</div>
|
|
163
|
+
<h3>HIPAA-Compliant</h3>
|
|
164
|
+
<p>PII detection and secure data handling</p>
|
|
165
|
+
</div>
|
|
166
|
+
<div class="feature">
|
|
167
|
+
<div class="feature-icon">👁️</div>
|
|
168
|
+
<h3>Provider Oversight</h3>
|
|
169
|
+
<p>All conversations reviewed by your doctor</p>
|
|
170
|
+
</div>
|
|
171
|
+
</div>
|
|
172
|
+
</section>
|
|
173
|
+
|
|
174
|
+
<!-- Provider Signup CTA -->
|
|
175
|
+
<section class="section cta">
|
|
176
|
+
<div class="cta-container">
|
|
177
|
+
<h2>Are you a healthcare provider?</h2>
|
|
178
|
+
<p>Join EvalDoc to offer AI-powered patient support for your practice.</p>
|
|
179
|
+
<a href="/provider-signup" class="btn-primary">Sign Up as Provider</a>
|
|
180
|
+
</div>
|
|
181
|
+
</section>
|
|
182
|
+
|
|
183
|
+
<script src="/js/search.js"></script>
|
|
184
|
+
</body>
|
|
185
|
+
</html>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### **Phase 2: Provider Search & Profile (Week 1)**
|
|
189
|
+
|
|
190
|
+
**Provider Search Implementation:**
|
|
191
|
+
```javascript
|
|
192
|
+
// public/js/search.js
|
|
193
|
+
|
|
194
|
+
class ProviderSearch {
|
|
195
|
+
constructor() {
|
|
196
|
+
this.providers = [];
|
|
197
|
+
this.init();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async init() {
|
|
201
|
+
// Load providers from API
|
|
202
|
+
await this.loadProviders();
|
|
203
|
+
this.setupSearch();
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async loadProviders() {
|
|
207
|
+
try {
|
|
208
|
+
const response = await fetch('/api/providers');
|
|
209
|
+
this.providers = await response.json();
|
|
210
|
+
console.log(`Loaded ${this.providers.length} providers`);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('Error loading providers:', error);
|
|
213
|
+
this.providers = this.getDemoProviders();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
setupSearch() {
|
|
218
|
+
const searchInput = document.getElementById('providerSearch');
|
|
219
|
+
if (!searchInput) return;
|
|
220
|
+
|
|
221
|
+
searchInput.addEventListener('input', (e) => {
|
|
222
|
+
this.performSearch(e.target.value);
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
searchInput.addEventListener('keypress', (e) => {
|
|
226
|
+
if (e.key === 'Enter') {
|
|
227
|
+
this.performSearch(e.target.value, true);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
performSearch(query, redirect = false) {
|
|
233
|
+
if (!query.trim()) {
|
|
234
|
+
this.clearResults();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const results = this.searchProviders(query);
|
|
239
|
+
this.displayResults(results);
|
|
240
|
+
|
|
241
|
+
if (redirect && results.length === 1) {
|
|
242
|
+
// Auto-redirect if only one match
|
|
243
|
+
this.redirectToProvider(results[0].handle);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
searchProviders(query) {
|
|
248
|
+
const searchTerm = query.toLowerCase();
|
|
249
|
+
|
|
250
|
+
return this.providers.filter(provider => {
|
|
251
|
+
return (
|
|
252
|
+
provider.displayName.toLowerCase().includes(searchTerm) ||
|
|
253
|
+
provider.specialty.toLowerCase().includes(searchTerm) ||
|
|
254
|
+
(provider.keywords || []).some(kw =>
|
|
255
|
+
kw.toLowerCase().includes(searchTerm)
|
|
256
|
+
)
|
|
257
|
+
);
|
|
258
|
+
}).slice(0, 10); // Limit results
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
displayResults(results) {
|
|
262
|
+
const container = document.getElementById('quickResults');
|
|
263
|
+
if (!container) return;
|
|
264
|
+
|
|
265
|
+
if (results.length === 0) {
|
|
266
|
+
container.innerHTML = `
|
|
267
|
+
<div class="no-results">
|
|
268
|
+
<p>No providers found. Try a different search term.</p>
|
|
269
|
+
<p class="small">Providers: Dr. Smith (Cardiology), Dr. Johnson (Pediatrics), etc.</p>
|
|
270
|
+
</div>
|
|
271
|
+
`;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
container.innerHTML = results.map(provider => `
|
|
276
|
+
<div class="provider-result" onclick="window.location='/provider/${provider.handle}'">
|
|
277
|
+
<div class="provider-info">
|
|
278
|
+
<h4>${provider.displayName}</h4>
|
|
279
|
+
<p class="specialty">${provider.specialty}</p>
|
|
280
|
+
<p class="location">${provider.location || 'Online'}</p>
|
|
281
|
+
</div>
|
|
282
|
+
<div class="provider-actions">
|
|
283
|
+
<button class="btn-small" onclick="event.stopPropagation(); window.location='/app?provider=${provider.handle}'">
|
|
284
|
+
Start Chat
|
|
285
|
+
</button>
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
`).join('');
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
clearResults() {
|
|
292
|
+
const container = document.getElementById('quickResults');
|
|
293
|
+
if (container) container.innerHTML = '';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
redirectToProvider(handle) {
|
|
297
|
+
window.location.href = `/provider/${handle}`;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
getDemoProviders() {
|
|
301
|
+
return [
|
|
302
|
+
{
|
|
303
|
+
handle: 'tyrone-norman',
|
|
304
|
+
displayName: 'Dr. Tyrone Norman',
|
|
305
|
+
specialty: 'General Practice',
|
|
306
|
+
location: 'New York, NY',
|
|
307
|
+
verified: true,
|
|
308
|
+
keywords: ['primary care', 'family medicine', 'gp']
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
handle: 'sarah-chen',
|
|
312
|
+
displayName: 'Dr. Sarah Chen',
|
|
313
|
+
specialty: 'Cardiology',
|
|
314
|
+
location: 'San Francisco, CA',
|
|
315
|
+
verified: true,
|
|
316
|
+
keywords: ['heart', 'cardiac', 'echocardiogram']
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
handle: 'michael-rodriguez',
|
|
320
|
+
displayName: 'Dr. Michael Rodriguez',
|
|
321
|
+
specialty: 'Pediatrics',
|
|
322
|
+
location: 'Miami, FL',
|
|
323
|
+
verified: true,
|
|
324
|
+
keywords: ['children', 'pediatric', 'kids']
|
|
325
|
+
}
|
|
326
|
+
];
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Initialize search
|
|
331
|
+
const providerSearch = new ProviderSearch();
|
|
332
|
+
|
|
333
|
+
// Global function for button click
|
|
334
|
+
function searchProvider() {
|
|
335
|
+
const input = document.getElementById('providerSearch');
|
|
336
|
+
if (input && input.value.trim()) {
|
|
337
|
+
window.location.href = `/find-doctor?q=${encodeURIComponent(input.value)}`;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
### **Phase 3: Enhanced Chat Interface (Week 2)**
|
|
343
|
+
|
|
344
|
+
**Simplified Chat Access:**
|
|
345
|
+
```javascript
|
|
346
|
+
// public/js/chat.js
|
|
347
|
+
|
|
348
|
+
class PatientChat {
|
|
349
|
+
constructor() {
|
|
350
|
+
this.providerHandle = this.getProviderFromURL();
|
|
351
|
+
this.sessionId = this.generateSessionId();
|
|
352
|
+
this.conversationId = null;
|
|
353
|
+
this.init();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
init() {
|
|
357
|
+
if (!this.providerHandle) {
|
|
358
|
+
this.showProviderSelector();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
this.loadProviderInfo();
|
|
363
|
+
this.setupChat();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
getProviderFromURL() {
|
|
367
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
368
|
+
return urlParams.get('provider') ||
|
|
369
|
+
window.location.pathname.split('/').pop();
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
showProviderSelector() {
|
|
373
|
+
document.getElementById('chatContainer').innerHTML = `
|
|
374
|
+
<div class="provider-selector">
|
|
375
|
+
<h2>Chat with Your Provider</h2>
|
|
376
|
+
<p>Enter your provider's username or find them in our directory</p>
|
|
377
|
+
|
|
378
|
+
<div class="provider-input">
|
|
379
|
+
<input type="text"
|
|
380
|
+
id="providerHandleInput"
|
|
381
|
+
placeholder="e.g., 'tyrone-norman'">
|
|
382
|
+
<button onclick="connectToProvider()">Connect</button>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<p class="small">Don't know your provider's username?
|
|
386
|
+
<a href="/find-doctor">Find your provider</a>
|
|
387
|
+
</p>
|
|
388
|
+
|
|
389
|
+
<div class="recent-providers">
|
|
390
|
+
<h4>Recent Providers</h4>
|
|
391
|
+
<!-- Dynamically loaded -->
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
async loadProviderInfo() {
|
|
398
|
+
try {
|
|
399
|
+
const response = await fetch(`/api/providers/${this.providerHandle}`);
|
|
400
|
+
const provider = await response.json();
|
|
401
|
+
|
|
402
|
+
document.getElementById('providerName').textContent =
|
|
403
|
+
provider.displayName || this.providerHandle;
|
|
404
|
+
document.getElementById('providerSpecialty').textContent =
|
|
405
|
+
provider.specialty || 'Healthcare Provider';
|
|
406
|
+
|
|
407
|
+
// Update page title
|
|
408
|
+
document.title = `Chat with ${provider.displayName} - EvalDoc`;
|
|
409
|
+
} catch (error) {
|
|
410
|
+
console.error('Error loading provider info:', error);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
setupChat() {
|
|
415
|
+
// Initialize chat UI
|
|
416
|
+
this.renderChatInterface();
|
|
417
|
+
this.setupMessageHandlers();
|
|
418
|
+
this.loadConversationHistory();
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
renderChatInterface() {
|
|
422
|
+
document.getElementById('chatContainer').innerHTML = `
|
|
423
|
+
<div class="chat-wrapper">
|
|
424
|
+
<!-- Chat Header -->
|
|
425
|
+
<div class="chat-header">
|
|
426
|
+
<div class="provider-badge">
|
|
427
|
+
<div class="avatar">${this.getInitials()}</div>
|
|
428
|
+
<div class="provider-info">
|
|
429
|
+
<h3 id="providerName">${this.providerHandle}</h3>
|
|
430
|
+
<p id="providerSpecialty">Loading...</p>
|
|
431
|
+
</div>
|
|
432
|
+
</div>
|
|
433
|
+
<div class="chat-actions">
|
|
434
|
+
<button class="btn-icon" title="Safety Info" onclick="showSafetyInfo()">
|
|
435
|
+
⚕️
|
|
436
|
+
</button>
|
|
437
|
+
<button class="btn-icon" title="End Chat" onclick="endChat()">
|
|
438
|
+
✕
|
|
439
|
+
</button>
|
|
440
|
+
</div>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<!-- Chat Messages -->
|
|
444
|
+
<div class="chat-messages" id="chatMessages">
|
|
445
|
+
<div class="welcome-message">
|
|
446
|
+
<p>You're now chatting with AI-assisted support for
|
|
447
|
+
<strong id="dynamicProviderName">${this.providerHandle}</strong>.
|
|
448
|
+
</p>
|
|
449
|
+
<p class="safety-note">
|
|
450
|
+
⚠️ <strong>Important:</strong> This AI provides educational
|
|
451
|
+
information only. It cannot diagnose, prescribe, or provide
|
|
452
|
+
emergency care.
|
|
453
|
+
</p>
|
|
454
|
+
</div>
|
|
455
|
+
</div>
|
|
456
|
+
|
|
457
|
+
<!-- Message Input -->
|
|
458
|
+
<div class="message-input">
|
|
459
|
+
<textarea
|
|
460
|
+
id="messageInput"
|
|
461
|
+
placeholder="Type your non-urgent health question..."
|
|
462
|
+
rows="1"></textarea>
|
|
463
|
+
<button id="sendButton" onclick="sendMessage()">Send</button>
|
|
464
|
+
</div>
|
|
465
|
+
|
|
466
|
+
<!-- Safety Footer -->
|
|
467
|
+
<div class="safety-footer">
|
|
468
|
+
<p>
|
|
469
|
+
<span class="urgent-warning">🚨 Urgent symptoms?</span>
|
|
470
|
+
Call 911 or go to emergency room immediately.
|
|
471
|
+
</p>
|
|
472
|
+
<p class="disclaimer">
|
|
473
|
+
All conversations are reviewed by your healthcare provider.
|
|
474
|
+
</p>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
`;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async sendMessage() {
|
|
481
|
+
const input = document.getElementById('messageInput');
|
|
482
|
+
const message = input.value.trim();
|
|
483
|
+
|
|
484
|
+
if (!message) return;
|
|
485
|
+
|
|
486
|
+
// Disable input during processing
|
|
487
|
+
input.disabled = true;
|
|
488
|
+
|
|
489
|
+
// Add user message to chat
|
|
490
|
+
this.addMessage('user', message);
|
|
491
|
+
input.value = '';
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
const response = await fetch('/api/chat', {
|
|
495
|
+
method: 'POST',
|
|
496
|
+
headers: { 'Content-Type': 'application/json' },
|
|
497
|
+
body: JSON.stringify({
|
|
498
|
+
handle: this.providerHandle,
|
|
499
|
+
sessionId: this.sessionId,
|
|
500
|
+
message: message,
|
|
501
|
+
conversationId: this.conversationId
|
|
502
|
+
})
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
const data = await response.json();
|
|
506
|
+
|
|
507
|
+
// Store conversation ID for continuity
|
|
508
|
+
if (data.conversationId && !this.conversationId) {
|
|
509
|
+
this.conversationId = data.conversationId;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Add AI response to chat
|
|
513
|
+
this.addMessage('ai', data.response, {
|
|
514
|
+
escalation: data.escalation,
|
|
515
|
+
safetyFlags: data.safetyFlags
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// Handle escalation
|
|
519
|
+
if (data.escalation) {
|
|
520
|
+
this.showEscalationAlert();
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
} catch (error) {
|
|
524
|
+
console.error('Error sending message:', error);
|
|
525
|
+
this.addMessage('system',
|
|
526
|
+
'Sorry, there was an error. Please try again or contact your provider directly.');
|
|
527
|
+
} finally {
|
|
528
|
+
input.disabled = false;
|
|
529
|
+
input.focus();
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
addMessage(sender, text, metadata = {}) {
|
|
534
|
+
const messagesDiv = document.getElementById('chatMessages');
|
|
535
|
+
const messageDiv = document.createElement('div');
|
|
536
|
+
|
|
537
|
+
messageDiv.className = `message ${sender}-message`;
|
|
538
|
+
if (metadata.escalation) {
|
|
539
|
+
messageDiv.classList.add('escalated');
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
messageDiv.innerHTML = `
|
|
543
|
+
<div class="message-content">
|
|
544
|
+
${text}
|
|
545
|
+
</div>
|
|
546
|
+
<div class="message-meta">
|
|
547
|
+
<span class="timestamp">${new Date().toLocaleTimeString([],
|
|
548
|
+
{hour: '2-digit', minute:'2-digit'})}</span>
|
|
549
|
+
${metadata.escalation ? '<span class="escalation-badge">URGENT</span>' : ''}
|
|
550
|
+
</div>
|
|
551
|
+
`;
|
|
552
|
+
|
|
553
|
+
messagesDiv.appendChild(messageDiv);
|
|
554
|
+
messagesDiv.scrollTop = messagesDiv.scrollHeight;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
generateSessionId() {
|
|
558
|
+
return 'patient-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
getInitials() {
|
|
562
|
+
const parts = this.providerHandle.split('-');
|
|
563
|
+
return parts.map(p => p.charAt(0).toUpperCase()).join('');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
showEscalationAlert() {
|
|
567
|
+
const alertDiv = document.createElement('div');
|
|
568
|
+
alertDiv.className = 'escalation-alert';
|
|
569
|
+
alertDiv.innerHTML = `
|
|
570
|
+
<div class="alert-content">
|
|
571
|
+
<h4>⚠️ Urgent Symptoms Detected</h4>
|
|
572
|
+
<p>Your message contained symptoms that may require immediate attention.</p>
|
|
573
|
+
<p><strong>Please:</strong></p>
|
|
574
|
+
<ul>
|
|
575
|
+
<li>Contact your provider directly</li>
|
|
576
|
+
<li>Call 911 for life-threatening symptoms</li>
|
|
577
|
+
<li>Go to the nearest emergency room if needed</li>
|
|
578
|
+
</ul>
|
|
579
|
+
<button onclick="this.parentElement.parentElement.remove()">
|
|
580
|
+
I Understand
|
|
581
|
+
</button>
|
|
582
|
+
</div>
|
|
583
|
+
`;
|
|
584
|
+
|
|
585
|
+
document.body.appendChild(alertDiv);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Initialize chat when page loads
|
|
590
|
+
let chatApp;
|
|
591
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
592
|
+
chatApp = new PatientChat();
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// Global functions for button clicks
|
|
596
|
+
function sendMessage() {
|
|
597
|
+
if (chatApp) chatApp.sendMessage();
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
function connectToProvider() {
|
|
601
|
+
const input = document.getElementById('providerHandleInput');
|
|
602
|
+
if (input && input.value.trim()) {
|
|
603
|
+
window.location.href = `/app?provider=${encodeURIComponent(input.value)}`;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### **Phase 4: Server-Side API Extensions (Week 2)**
|
|
609
|
+
|
|
610
|
+
**Extend your server.js:**
|
|
611
|
+
```javascript
|
|
612
|
+
// Add new API endpoints
|
|
613
|
+
|
|
614
|
+
// Get all providers (for search)
|
|
615
|
+
app.get('/api/providers', async (req, res) => {
|
|
616
|
+
try {
|
|
617
|
+
const providersRef = db.collection('providers')
|
|
618
|
+
.where('status', '==', 'active')
|
|
619
|
+
.limit(100);
|
|
620
|
+
|
|
621
|
+
const snapshot = await providersRef.get();
|
|
622
|
+
const providers = [];
|
|
623
|
+
|
|
624
|
+
snapshot.forEach(doc => {
|
|
625
|
+
providers.push({
|
|
626
|
+
id: doc.id,
|
|
627
|
+
handle: doc.data().handle,
|
|
628
|
+
displayName: doc.data().displayName,
|
|
629
|
+
specialty: doc.data().specialty,
|
|
630
|
+
location: doc.data().location,
|
|
631
|
+
verified: doc.data().verified || false,
|
|
632
|
+
keywords: doc.data().keywords || []
|
|
633
|
+
});
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
res.json(providers);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
console.error('Error fetching providers:', error);
|
|
639
|
+
res.status(500).json({ error: 'Failed to fetch providers' });
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// Get specific provider
|
|
644
|
+
app.get('/api/providers/:handle', async (req, res) => {
|
|
645
|
+
try {
|
|
646
|
+
const snapshot = await db.collection('providers')
|
|
647
|
+
.where('handle', '==', req.params.handle)
|
|
648
|
+
.limit(1)
|
|
649
|
+
.get();
|
|
650
|
+
|
|
651
|
+
if (snapshot.empty) {
|
|
652
|
+
return res.status(404).json({ error: 'Provider not found' });
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const doc = snapshot.docs[0];
|
|
656
|
+
res.json({
|
|
657
|
+
id: doc.id,
|
|
658
|
+
...doc.data()
|
|
659
|
+
});
|
|
660
|
+
} catch (error) {
|
|
661
|
+
console.error('Error fetching provider:', error);
|
|
662
|
+
res.status(500).json({ error: 'Failed to fetch provider' });
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
|
|
666
|
+
// Provider signup endpoint
|
|
667
|
+
app.post('/api/providers/signup', async (req, res) => {
|
|
668
|
+
try {
|
|
669
|
+
const {
|
|
670
|
+
email,
|
|
671
|
+
displayName,
|
|
672
|
+
specialty,
|
|
673
|
+
licenseNumber,
|
|
674
|
+
npiNumber
|
|
675
|
+
} = req.body;
|
|
676
|
+
|
|
677
|
+
// Validate required fields
|
|
678
|
+
if (!email || !displayName || !specialty) {
|
|
679
|
+
return res.status(400).json({ error: 'Missing required fields' });
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Check if provider already exists
|
|
683
|
+
const existing = await db.collection('providers')
|
|
684
|
+
.where('email', '==', email)
|
|
685
|
+
.limit(1)
|
|
686
|
+
.get();
|
|
687
|
+
|
|
688
|
+
if (!existing.empty) {
|
|
689
|
+
return res.status(409).json({ error: 'Provider already exists' });
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Generate unique handle
|
|
693
|
+
const handle = displayName.toLowerCase()
|
|
694
|
+
.replace(/[^a-z0-9]/g, '-')
|
|
695
|
+
.replace(/-+/g, '-')
|
|
696
|
+
.replace(/^-|-$/g, '');
|
|
697
|
+
|
|
698
|
+
// Create provider document
|
|
699
|
+
const providerData = {
|
|
700
|
+
email,
|
|
701
|
+
displayName,
|
|
702
|
+
handle,
|
|
703
|
+
specialty,
|
|
704
|
+
licenseNumber: licenseNumber || '',
|
|
705
|
+
npiNumber: npiNumber || '',
|
|
706
|
+
status: 'pending', // Needs admin approval
|
|
707
|
+
createdAt: admin.firestore.FieldValue.serverTimestamp(),
|
|
708
|
+
updatedAt: admin.firestore.FieldValue.serverTimestamp()
|
|
709
|
+
};
|
|
710
|
+
|
|
711
|
+
const docRef = await db.collection('providers').add(providerData);
|
|
712
|
+
|
|
713
|
+
res.json({
|
|
714
|
+
success: true,
|
|
715
|
+
providerId: docRef.id,
|
|
716
|
+
handle: handle,
|
|
717
|
+
message: 'Provider registration submitted for approval'
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
} catch (error) {
|
|
721
|
+
console.error('Error in provider signup:', error);
|
|
722
|
+
res.status(500).json({ error: 'Registration failed' });
|
|
723
|
+
}
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
// Serve static files (add to your server.js)
|
|
727
|
+
app.use(express.static('public'));
|
|
728
|
+
|
|
729
|
+
// Route for homepage
|
|
730
|
+
app.get('/', (req, res) => {
|
|
731
|
+
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// Route for chat interface
|
|
735
|
+
app.get('/app', (req, res) => {
|
|
736
|
+
res.sendFile(path.join(__dirname, 'public', 'app.html'));
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
// Route for provider search
|
|
740
|
+
app.get('/find-doctor', (req, res) => {
|
|
741
|
+
res.sendFile(path.join(__dirname, 'public', 'find-doctor.html'));
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
// Route for provider profile
|
|
745
|
+
app.get('/provider/:handle', (req, res) => {
|
|
746
|
+
res.sendFile(path.join(__dirname, 'public', 'provider.html'));
|
|
747
|
+
});
|
|
748
|
+
```
|
|
749
|
+
|
|
750
|
+
### **Phase 5: Database Schema Updates**
|
|
751
|
+
|
|
752
|
+
**Firestore Collections:**
|
|
753
|
+
|
|
754
|
+
```javascript
|
|
755
|
+
// providers collection
|
|
756
|
+
{
|
|
757
|
+
"handle": "tyrone-norman", // URL-friendly ID
|
|
758
|
+
"displayName": "Dr. Tyrone Norman",
|
|
759
|
+
"email": "dr.norman@clinic.com",
|
|
760
|
+
"specialty": "General Practice",
|
|
761
|
+
"location": "New York, NY",
|
|
762
|
+
"licenseNumber": "NY123456",
|
|
763
|
+
"npiNumber": "1234567890",
|
|
764
|
+
"verified": true,
|
|
765
|
+
"status": "active", // active, pending, suspended
|
|
766
|
+
"keywords": ["primary care", "family medicine", "gp"],
|
|
767
|
+
"profileImage": "https://...", // Optional
|
|
768
|
+
"clinicHours": "Mon-Fri 9am-5pm", // Optional
|
|
769
|
+
"phone": "+1-555-123-4567", // Optional
|
|
770
|
+
"website": "https://clinic.com", // Optional
|
|
771
|
+
"createdAt": timestamp,
|
|
772
|
+
"updatedAt": timestamp
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// provider_preferences collection (subcollection under providers)
|
|
776
|
+
{
|
|
777
|
+
"safetyLevel": "high",
|
|
778
|
+
"customGreeting": "Hello, I'm Dr. Norman's AI assistant...",
|
|
779
|
+
"escalationContacts": ["nurse@clinic.com", "555-123-4567"],
|
|
780
|
+
"autoReplyHours": "9am-5pm",
|
|
781
|
+
"blockedTerms": ["experimental", "unproven"],
|
|
782
|
+
"preferredTopics": ["appointment prep", "medication info"],
|
|
783
|
+
"updatedAt": timestamp
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
// patient_sessions collection
|
|
787
|
+
{
|
|
788
|
+
"sessionId": "patient-123456789",
|
|
789
|
+
"providerId": "providerDocId",
|
|
790
|
+
"providerHandle": "tyrone-norman",
|
|
791
|
+
"patientId": null, // Anonymous by default
|
|
792
|
+
"patientEmail": null, // Optional if provided
|
|
793
|
+
"conversationId": "conv123",
|
|
794
|
+
"startedAt": timestamp,
|
|
795
|
+
"lastActivity": timestamp,
|
|
796
|
+
"status": "active", // active, completed, abandoned
|
|
797
|
+
"escalationCount": 0,
|
|
798
|
+
"messageCount": 0,
|
|
799
|
+
"safetyEvents": ["medical_entities_detected"],
|
|
800
|
+
"metadata": {
|
|
801
|
+
"userAgent": "Mozilla/5.0...",
|
|
802
|
+
"ip": "192.168.1.1", // Hashed for privacy
|
|
803
|
+
"referrer": "https://evaldoc.com/find-doctor"
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
### **Phase 6: CSS/UI Styling (Week 2)**
|
|
809
|
+
|
|
810
|
+
**public/css/styles.css:**
|
|
811
|
+
```css
|
|
812
|
+
/* Base Styles */
|
|
813
|
+
:root {
|
|
814
|
+
--primary: #0066cc;
|
|
815
|
+
--primary-dark: #0052a3;
|
|
816
|
+
--secondary: #00a896;
|
|
817
|
+
--danger: #e63946;
|
|
818
|
+
--warning: #ff9f1c;
|
|
819
|
+
--success: #2a9d8f;
|
|
820
|
+
--light: #f8f9fa;
|
|
821
|
+
--dark: #212529;
|
|
822
|
+
--gray: #6c757d;
|
|
823
|
+
--border: #dee2e6;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
* {
|
|
827
|
+
margin: 0;
|
|
828
|
+
padding: 0;
|
|
829
|
+
box-sizing: border-box;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
body {
|
|
833
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
834
|
+
line-height: 1.6;
|
|
835
|
+
color: var(--dark);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
/* Navigation */
|
|
839
|
+
.navbar {
|
|
840
|
+
background: white;
|
|
841
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
842
|
+
position: sticky;
|
|
843
|
+
top: 0;
|
|
844
|
+
z-index: 1000;
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
.nav-container {
|
|
848
|
+
max-width: 1200px;
|
|
849
|
+
margin: 0 auto;
|
|
850
|
+
padding: 1rem 2rem;
|
|
851
|
+
display: flex;
|
|
852
|
+
justify-content: space-between;
|
|
853
|
+
align-items: center;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.logo {
|
|
857
|
+
display: flex;
|
|
858
|
+
align-items: center;
|
|
859
|
+
gap: 0.5rem;
|
|
860
|
+
text-decoration: none;
|
|
861
|
+
font-size: 1.5rem;
|
|
862
|
+
font-weight: bold;
|
|
863
|
+
color: var(--primary);
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.nav-links {
|
|
867
|
+
display: flex;
|
|
868
|
+
gap: 2rem;
|
|
869
|
+
align-items: center;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
.nav-links a {
|
|
873
|
+
text-decoration: none;
|
|
874
|
+
color: var(--dark);
|
|
875
|
+
transition: color 0.2s;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
.nav-links a:hover {
|
|
879
|
+
color: var(--primary);
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
.btn-outline {
|
|
883
|
+
border: 2px solid var(--primary);
|
|
884
|
+
color: var(--primary);
|
|
885
|
+
padding: 0.5rem 1.5rem;
|
|
886
|
+
border-radius: 50px;
|
|
887
|
+
text-decoration: none;
|
|
888
|
+
font-weight: 600;
|
|
889
|
+
transition: all 0.2s;
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
.btn-outline:hover {
|
|
893
|
+
background: var(--primary);
|
|
894
|
+
color: white;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/* Hero Section */
|
|
898
|
+
.hero {
|
|
899
|
+
padding: 4rem 2rem;
|
|
900
|
+
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
.hero-container {
|
|
904
|
+
max-width: 1200px;
|
|
905
|
+
margin: 0 auto;
|
|
906
|
+
display: grid;
|
|
907
|
+
grid-template-columns: 1fr 1fr;
|
|
908
|
+
gap: 4rem;
|
|
909
|
+
align-items: center;
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
.hero-text h1 {
|
|
913
|
+
font-size: 3rem;
|
|
914
|
+
margin-bottom: 1.5rem;
|
|
915
|
+
color: var(--dark);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
.hero-text p {
|
|
919
|
+
font-size: 1.2rem;
|
|
920
|
+
color: var(--gray);
|
|
921
|
+
margin-bottom: 2rem;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/* Search Box */
|
|
925
|
+
.search-box {
|
|
926
|
+
background: white;
|
|
927
|
+
padding: 2rem;
|
|
928
|
+
border-radius: 16px;
|
|
929
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
|
930
|
+
margin-top: 2rem;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
.search-form {
|
|
934
|
+
display: flex;
|
|
935
|
+
gap: 1rem;
|
|
936
|
+
margin: 1rem 0;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.search-form input {
|
|
940
|
+
flex: 1;
|
|
941
|
+
padding: 1rem;
|
|
942
|
+
border: 2px solid var(--border);
|
|
943
|
+
border-radius: 8px;
|
|
944
|
+
font-size: 1rem;
|
|
945
|
+
transition: border-color 0.2s;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
.search-form input:focus {
|
|
949
|
+
outline: none;
|
|
950
|
+
border-color: var(--primary);
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
.search-form button {
|
|
954
|
+
background: var(--primary);
|
|
955
|
+
color: white;
|
|
956
|
+
border: none;
|
|
957
|
+
padding: 0 2rem;
|
|
958
|
+
border-radius: 8px;
|
|
959
|
+
font-weight: 600;
|
|
960
|
+
cursor: pointer;
|
|
961
|
+
transition: background 0.2s;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
.search-form button:hover {
|
|
965
|
+
background: var(--primary-dark);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
/* Quick Results */
|
|
969
|
+
.quick-results {
|
|
970
|
+
margin-top: 1rem;
|
|
971
|
+
max-height: 300px;
|
|
972
|
+
overflow-y: auto;
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
.provider-result {
|
|
976
|
+
display: flex;
|
|
977
|
+
justify-content: space-between;
|
|
978
|
+
align-items: center;
|
|
979
|
+
padding: 1rem;
|
|
980
|
+
border: 1px solid var(--border);
|
|
981
|
+
border-radius: 8px;
|
|
982
|
+
margin-bottom: 0.5rem;
|
|
983
|
+
cursor: pointer;
|
|
984
|
+
transition: all 0.2s;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
.provider-result:hover {
|
|
988
|
+
border-color: var(--primary);
|
|
989
|
+
background: #f8f9fa;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
.provider-info h4 {
|
|
993
|
+
margin-bottom: 0.25rem;
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
.specialty {
|
|
997
|
+
color: var(--gray);
|
|
998
|
+
font-size: 0.9rem;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
.btn-small {
|
|
1002
|
+
padding: 0.5rem 1rem;
|
|
1003
|
+
background: var(--primary);
|
|
1004
|
+
color: white;
|
|
1005
|
+
border: none;
|
|
1006
|
+
border-radius: 6px;
|
|
1007
|
+
cursor: pointer;
|
|
1008
|
+
font-size: 0.9rem;
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/* Chat Interface */
|
|
1012
|
+
.chat-wrapper {
|
|
1013
|
+
max-width: 800px;
|
|
1014
|
+
margin: 0 auto;
|
|
1015
|
+
height: 100vh;
|
|
1016
|
+
display: flex;
|
|
1017
|
+
flex-direction: column;
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
.chat-header {
|
|
1021
|
+
background: white;
|
|
1022
|
+
padding: 1rem;
|
|
1023
|
+
border-bottom: 1px solid var(--border);
|
|
1024
|
+
display: flex;
|
|
1025
|
+
justify-content: space-between;
|
|
1026
|
+
align-items: center;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.provider-badge {
|
|
1030
|
+
display: flex;
|
|
1031
|
+
align-items: center;
|
|
1032
|
+
gap: 1rem;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
.avatar {
|
|
1036
|
+
width: 48px;
|
|
1037
|
+
height: 48px;
|
|
1038
|
+
background: var(--primary);
|
|
1039
|
+
color: white;
|
|
1040
|
+
border-radius: 50%;
|
|
1041
|
+
display: flex;
|
|
1042
|
+
align-items: center;
|
|
1043
|
+
justify-content: center;
|
|
1044
|
+
font-weight: bold;
|
|
1045
|
+
font-size: 1.2rem;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
.chat-messages {
|
|
1049
|
+
flex: 1;
|
|
1050
|
+
overflow-y: auto;
|
|
1051
|
+
padding: 2rem;
|
|
1052
|
+
background: #f8f9fa;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
.message {
|
|
1056
|
+
margin-bottom: 1.5rem;
|
|
1057
|
+
max-width: 80%;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
.user-message {
|
|
1061
|
+
margin-left: auto;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
.user-message .message-content {
|
|
1065
|
+
background: var(--primary);
|
|
1066
|
+
color: white;
|
|
1067
|
+
border-radius: 18px 18px 4px 18px;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
.ai-message .message-content {
|
|
1071
|
+
background: white;
|
|
1072
|
+
color: var(--dark);
|
|
1073
|
+
border-radius: 18px 18px 18px 4px;
|
|
1074
|
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.message-content {
|
|
1078
|
+
padding: 1rem;
|
|
1079
|
+
line-height: 1.5;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
.message.escalated .message-content {
|
|
1083
|
+
border-left: 4px solid var(--warning);
|
|
1084
|
+
background: #fff9e6;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
.escalation-badge {
|
|
1088
|
+
background: var(--warning);
|
|
1089
|
+
color: white;
|
|
1090
|
+
padding: 0.25rem 0.5rem;
|
|
1091
|
+
border-radius: 4px;
|
|
1092
|
+
font-size: 0.8rem;
|
|
1093
|
+
font-weight: bold;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
.message-input {
|
|
1097
|
+
padding: 1rem;
|
|
1098
|
+
background: white;
|
|
1099
|
+
border-top: 1px solid var(--border);
|
|
1100
|
+
display: flex;
|
|
1101
|
+
gap: 1rem;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
.message-input textarea {
|
|
1105
|
+
flex: 1;
|
|
1106
|
+
padding: 1rem;
|
|
1107
|
+
border: 2px solid var(--border);
|
|
1108
|
+
border-radius: 8px;
|
|
1109
|
+
resize: none;
|
|
1110
|
+
font-family: inherit;
|
|
1111
|
+
font-size: 1rem;
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
.message-input textarea:focus {
|
|
1115
|
+
outline: none;
|
|
1116
|
+
border-color: var(--primary);
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.message-input button {
|
|
1120
|
+
background: var(--primary);
|
|
1121
|
+
color: white;
|
|
1122
|
+
border: none;
|
|
1123
|
+
padding: 0 2rem;
|
|
1124
|
+
border-radius: 8px;
|
|
1125
|
+
font-weight: 600;
|
|
1126
|
+
cursor: pointer;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
.safety-footer {
|
|
1130
|
+
background: #fff9e6;
|
|
1131
|
+
padding: 1rem;
|
|
1132
|
+
text-align: center;
|
|
1133
|
+
font-size: 0.9rem;
|
|
1134
|
+
border-top: 1px solid #ffd166;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
.urgent-warning {
|
|
1138
|
+
color: var(--danger);
|
|
1139
|
+
font-weight: bold;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
/* Responsive Design */
|
|
1143
|
+
@media (max-width: 768px) {
|
|
1144
|
+
.hero-container {
|
|
1145
|
+
grid-template-columns: 1fr;
|
|
1146
|
+
gap: 2rem;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
.nav-links {
|
|
1150
|
+
display: none;
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
.message {
|
|
1154
|
+
max-width: 90%;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
```
|
|
1158
|
+
|
|
1159
|
+
## 🚀 **Deployment Checklist**
|
|
1160
|
+
|
|
1161
|
+
### **Week 1: MVP Launch**
|
|
1162
|
+
- [ ] Create homepage with provider search
|
|
1163
|
+
- [ ] Implement basic chat interface
|
|
1164
|
+
- [ ] Add provider directory API
|
|
1165
|
+
- [ ] Deploy to staging environment
|
|
1166
|
+
- [ ] Test end-to-end flow
|
|
1167
|
+
|
|
1168
|
+
### **Week 2: Enhancements**
|
|
1169
|
+
- [ ] Add provider profiles
|
|
1170
|
+
- [ ] Implement session management
|
|
1171
|
+
- [ ] Add safety info modals
|
|
1172
|
+
- [ ] Create provider signup form
|
|
1173
|
+
- [ ] Add analytics tracking
|
|
1174
|
+
|
|
1175
|
+
### **Week 3: Polish & Launch**
|
|
1176
|
+
- [ ] Mobile responsiveness testing
|
|
1177
|
+
- [ ] Performance optimization
|
|
1178
|
+
- [ ] SEO meta tags
|
|
1179
|
+
- [ ] Privacy policy page
|
|
1180
|
+
- [ ] Launch to production
|
|
1181
|
+
|
|
1182
|
+
## 📈 **Growth Strategy**
|
|
1183
|
+
|
|
1184
|
+
### **Phase 1: Patient Acquisition**
|
|
1185
|
+
1. **Provider referrals** - Existing providers share their EvalDoc link
|
|
1186
|
+
2. **Clinic partnerships** - Embed widget on clinic websites
|
|
1187
|
+
3. **Health directories** - List in healthcare directories
|
|
1188
|
+
|
|
1189
|
+
### **Phase 2: Network Effects**
|
|
1190
|
+
1. **Provider reviews** - Patients can rate their experience
|
|
1191
|
+
2. **Specialty filtering** - Find specialists by condition
|
|
1192
|
+
3. **Appointment booking** - Direct scheduling integration
|
|
1193
|
+
|
|
1194
|
+
### **Phase 3: Advanced Features**
|
|
1195
|
+
1. **Patient accounts** - Save conversation history
|
|
1196
|
+
2. **File upload** - Share documents with provider
|
|
1197
|
+
3. **Telehealth integration** - Video consult upsell
|
|
1198
|
+
4. **Insurance verification** - Check coverage
|
|
1199
|
+
|
|
1200
|
+
## 💡 **Key Advantages of This Approach**
|
|
1201
|
+
|
|
1202
|
+
1. **Lower Barrier to Entry** - Patients don't need accounts
|
|
1203
|
+
2. **Provider Control** - Doctors manage their own presence
|
|
1204
|
+
3. **Scalable** - New providers can join easily
|
|
1205
|
+
4. **Trust Building** - Professional interface builds confidence
|
|
1206
|
+
5. **Data Ownership** - Providers own patient relationships
|
|
1207
|
+
|
|
1208
|
+
This transforms EvalDoc from a backend service into a **complete patient engagement platform**
|