arshenoy commited on
Commit
744989e
·
verified ·
1 Parent(s): fe2eef4

create somAI

Browse files
Files changed (8) hide show
  1. .env.local +1 -0
  2. .gitignore +24 -0
  3. App.tsx +389 -0
  4. README.md +20 -12
  5. index.html +119 -0
  6. index.tsx +15 -0
  7. metadata.json +7 -0
  8. package.json +24 -0
.env.local ADDED
@@ -0,0 +1 @@
 
 
1
+ GEMINI_API_KEY=PLACEHOLDER_API_KEY
.gitignore ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+
10
+ node_modules
11
+ dist
12
+ dist-ssr
13
+ *.local
14
+
15
+ # Editor directories and files
16
+ .vscode/*
17
+ !.vscode/extensions.json
18
+ .idea
19
+ .DS_Store
20
+ *.suo
21
+ *.ntvs*
22
+ *.njsproj
23
+ *.sln
24
+ *.sw?
App.tsx ADDED
@@ -0,0 +1,389 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import React, { useState, useEffect } from 'react';
3
+ import { createPortal } from 'react-dom';
4
+ import { Settings, X, Trash2, MessageSquare, FileText } from 'lucide-react';
5
+ import {
6
+ PatientProfile,
7
+ ClinicalVitals,
8
+ INITIAL_PROFILE,
9
+ INITIAL_VITALS,
10
+ AppMode,
11
+ RiskAnalysisResult,
12
+ ChatMessage,
13
+ Medication
14
+ } from './types';
15
+ import { analyzeRisk, generateChatResponse, summarizeConversation } from './services/geminiService';
16
+
17
+ // Extracted Components
18
+ import Sidebar from './components/Sidebar';
19
+ import Dashboard from './components/Dashboard';
20
+ import Chat from './components/Chat';
21
+ import Profile from './components/Profile';
22
+ import PrintReport from './components/PrintReport';
23
+ import MedicationTracker from './components/MedicationTracker';
24
+
25
+ const App: React.FC = () => {
26
+ // --- STATE ---
27
+ const [activeTab, setActiveTab] = useState<'dashboard' | 'chat' | 'profile' | 'medication'>('dashboard');
28
+ const [showSettings, setShowSettings] = useState(false);
29
+
30
+ const [profile, setProfile] = useState<PatientProfile>(() => {
31
+ try {
32
+ const saved = localStorage.getItem('somai_profile');
33
+ return saved ? JSON.parse(saved) : INITIAL_PROFILE;
34
+ } catch {
35
+ return INITIAL_PROFILE;
36
+ }
37
+ });
38
+
39
+ const [medications, setMedications] = useState<Medication[]>(() => {
40
+ try {
41
+ const saved = localStorage.getItem('somai_medications');
42
+ return saved ? JSON.parse(saved) : [];
43
+ } catch {
44
+ return [];
45
+ }
46
+ });
47
+
48
+ const [vitals, setVitals] = useState<ClinicalVitals>(INITIAL_VITALS);
49
+ const [riskResult, setRiskResult] = useState<RiskAnalysisResult | null>(null);
50
+ const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
51
+ const [currentInput, setCurrentInput] = useState('');
52
+ const [chatSummary, setChatSummary] = useState('');
53
+ const [mode, setMode] = useState<AppMode>(AppMode.GENERAL);
54
+
55
+ // Loading States
56
+ const [isProcessing, setIsProcessing] = useState(false);
57
+ const [isAnalyzing, setIsAnalyzing] = useState(false);
58
+ const [isSummarizing, setIsSummarizing] = useState(false);
59
+
60
+ // --- STREAK LOGIC ON LOAD ---
61
+ useEffect(() => {
62
+ const checkStreak = () => {
63
+ const lastUpdateDate = new Date(profile.lastStreakUpdate);
64
+ const todayDate = new Date();
65
+
66
+ // Reset time portion to compare dates accurately
67
+ lastUpdateDate.setHours(0,0,0,0);
68
+ todayDate.setHours(0,0,0,0);
69
+
70
+ const diffTime = todayDate.getTime() - lastUpdateDate.getTime();
71
+ const diffDays = Math.round(diffTime / (1000 * 60 * 60 * 24));
72
+
73
+ // If more than 1 day passed (skipped yesterday), reset streak
74
+ // diffDays = 0 (Today), diffDays = 1 (Yesterday - ok), diffDays > 1 (Broken)
75
+ if (diffDays > 1 && profile.streak > 0) {
76
+ setProfile(prev => ({ ...prev, streak: 0 }));
77
+ }
78
+ };
79
+ checkStreak();
80
+ }, []);
81
+
82
+ // --- STORAGE EFFECTS ---
83
+ useEffect(() => {
84
+ try {
85
+ localStorage.setItem('somai_profile', JSON.stringify(profile));
86
+ } catch (e) {
87
+ console.error("Storage failed", e);
88
+ }
89
+ }, [profile]);
90
+
91
+ useEffect(() => {
92
+ try {
93
+ localStorage.setItem('somai_medications', JSON.stringify(medications));
94
+ } catch (e) {
95
+ console.error("Storage failed", e);
96
+ }
97
+ }, [medications]);
98
+
99
+ const handleProfileUpdate = () => {
100
+ setRiskResult(null);
101
+ };
102
+
103
+ // --- ADVANCED RISK ENGINE ---
104
+ const calculateRiskScore = (): number => {
105
+ let score = 10; // Base risk
106
+
107
+ // 1. Blood Pressure Risk (Non-linear)
108
+ if (vitals.systolicBp >= 180) {
109
+ score += 60; // Hypertensive Crisis - Critical
110
+ } else if (vitals.systolicBp >= 140) {
111
+ score += 30; // Stage 2 Hypertension
112
+ } else if (vitals.systolicBp >= 130) {
113
+ score += 15; // Stage 1
114
+ }
115
+
116
+ // 2. Glucose Risk (Non-linear)
117
+ if (vitals.glucose >= 250) {
118
+ score += 50; // Critical Hyperglycemia
119
+ } else if (vitals.glucose >= 180) {
120
+ score += 25; // Elevated
121
+ } else if (vitals.glucose < 70) {
122
+ score += 40; // Hypoglycemia Risk
123
+ }
124
+
125
+ // 3. Comorbidity Multiplier (BP + Glucose)
126
+ if (vitals.systolicBp > 140 && vitals.glucose > 180) {
127
+ score = score * 1.2; // 20% penalty for combined issues
128
+ }
129
+
130
+ // 4. Lifestyle Factors
131
+ if (vitals.sleepQuality < 4) score += 15;
132
+ if (vitals.sleepQuality > 8) score -= 5; // Good sleep bonus
133
+
134
+ // 5. Adherence Penalty (Critical)
135
+ if (vitals.missedDoses > 0) {
136
+ score += (vitals.missedDoses * 5); // 5 points per missed dose
137
+ }
138
+
139
+ // Cap at 100, Min at 1
140
+ return Math.min(Math.max(Math.round(score), 1), 100);
141
+ };
142
+
143
+ const handleRunAnalysis = async () => {
144
+ setIsAnalyzing(true);
145
+ try {
146
+ const score = calculateRiskScore();
147
+ const result = await analyzeRisk(profile, vitals, score);
148
+ setRiskResult(result);
149
+ } catch (e) {
150
+ console.error(e);
151
+ alert("Failed to analyze. Please check connection.");
152
+ } finally {
153
+ setIsAnalyzing(false);
154
+ }
155
+ };
156
+
157
+ const handleSendMessage = async (image?: string) => {
158
+ if (!currentInput.trim() && !image) return;
159
+
160
+ const newUserMsg: ChatMessage = {
161
+ id: Date.now().toString(),
162
+ role: 'user',
163
+ text: currentInput,
164
+ timestamp: Date.now(),
165
+ image: image
166
+ };
167
+
168
+ setChatHistory(prev => [...prev, newUserMsg]);
169
+ setCurrentInput('');
170
+ setIsProcessing(true);
171
+
172
+ try {
173
+ const aiResponseText = await generateChatResponse(
174
+ chatHistory,
175
+ newUserMsg.text,
176
+ image,
177
+ profile,
178
+ mode
179
+ );
180
+
181
+ const newAiMsg: ChatMessage = {
182
+ id: (Date.now() + 1).toString(),
183
+ role: 'model',
184
+ text: aiResponseText,
185
+ timestamp: Date.now()
186
+ };
187
+
188
+ setChatHistory(prev => [...prev, newAiMsg]);
189
+ } catch (e) {
190
+ console.error(e);
191
+ } finally {
192
+ setIsProcessing(false);
193
+ }
194
+ };
195
+
196
+ const handleSummarizeChat = async () => {
197
+ if (chatHistory.length === 0) return;
198
+ setIsSummarizing(true);
199
+ try {
200
+ const summary = await summarizeConversation(chatHistory);
201
+ setChatSummary(summary);
202
+ } catch (e) {
203
+ console.error(e);
204
+ } finally {
205
+ setIsSummarizing(false);
206
+ }
207
+ };
208
+
209
+ const handlePrintReport = () => {
210
+ window.print();
211
+ };
212
+
213
+ const handleResetData = () => {
214
+ if(confirm('Are you sure you want to reset all application data?')) {
215
+ try {
216
+ localStorage.clear();
217
+ window.location.reload();
218
+ } catch (e) {
219
+ console.error("Clear failed", e);
220
+ }
221
+ }
222
+ };
223
+
224
+ const handleClearChat = () => {
225
+ setChatHistory([]);
226
+ setChatSummary('');
227
+ setShowSettings(false);
228
+ };
229
+
230
+ // --- RENDER HELPERS ---
231
+ const printableRoot = document.getElementById('printable-root');
232
+
233
+ return (
234
+ <div className="min-h-screen bg-black text-white selection:bg-neon-green selection:text-black font-sans overflow-x-hidden">
235
+
236
+ <Sidebar activeTab={activeTab} setActiveTab={setActiveTab} />
237
+
238
+ <div className="md:hidden p-4 border-b border-white/10 flex justify-between items-center bg-black/80 backdrop-blur-md sticky top-0 z-50">
239
+ <span className="font-mono font-bold text-white tracking-wider">SomAI</span>
240
+ <button onClick={() => setShowSettings(true)}><Settings size={20} className="text-gray-400" /></button>
241
+ </div>
242
+
243
+ <button
244
+ onClick={() => setShowSettings(true)}
245
+ className="hidden md:block fixed top-6 right-6 z-30 p-2 text-gray-500 hover:text-white transition-colors bg-black/20 rounded-full hover:bg-white/10"
246
+ >
247
+ <Settings size={20} />
248
+ </button>
249
+
250
+ <main className="md:ml-64 p-4 md:p-8 pt-6 pb-24 max-w-7xl mx-auto min-h-screen">
251
+
252
+ <div className="md:hidden flex gap-2 mb-6 overflow-x-auto pb-2 scrollbar-hide">
253
+ {[
254
+ { id: 'dashboard', label: 'Dashboard' },
255
+ { id: 'chat', label: 'Chat' },
256
+ { id: 'medication', label: 'Meds' },
257
+ { id: 'profile', label: 'Profile' }
258
+ ].map(t => (
259
+ <button
260
+ key={t.id}
261
+ onClick={() => setActiveTab(t.id as 'dashboard' | 'chat' | 'profile' | 'medication')}
262
+ className={`px-5 py-2 rounded-full text-xs font-bold uppercase tracking-wide whitespace-nowrap transition-colors ${activeTab === t.id ? 'bg-neon-green text-black' : 'bg-gray-900 text-gray-400'}`}
263
+ >
264
+ {t.label}
265
+ </button>
266
+ ))}
267
+ </div>
268
+
269
+ {activeTab === 'dashboard' && (
270
+ <Dashboard
271
+ vitals={vitals}
272
+ setVitals={setVitals}
273
+ riskResult={riskResult}
274
+ chatSummary={chatSummary}
275
+ handleRunAnalysis={handleRunAnalysis}
276
+ isAnalyzing={isAnalyzing}
277
+ onPrint={handlePrintReport}
278
+ profile={profile}
279
+ medications={medications}
280
+ chatHistory={chatHistory}
281
+ />
282
+ )}
283
+
284
+ {activeTab === 'chat' && (
285
+ <Chat
286
+ chatHistory={chatHistory}
287
+ currentInput={currentInput}
288
+ setCurrentInput={setCurrentInput}
289
+ onSendMessage={handleSendMessage}
290
+ isProcessing={isProcessing}
291
+ mode={mode}
292
+ setMode={setMode}
293
+ onSummarize={handleSummarizeChat}
294
+ isSummarizing={isSummarizing}
295
+ chatSummary={chatSummary}
296
+ />
297
+ )}
298
+
299
+ {activeTab === 'medication' && (
300
+ <MedicationTracker
301
+ medications={medications}
302
+ setMedications={setMedications}
303
+ profile={profile}
304
+ setProfile={setProfile}
305
+ />
306
+ )}
307
+
308
+ {activeTab === 'profile' && (
309
+ <Profile
310
+ profile={profile}
311
+ setProfile={setProfile}
312
+ onProfileUpdate={handleProfileUpdate}
313
+ riskResult={riskResult}
314
+ chatSummary={chatSummary}
315
+ onPrint={handlePrintReport}
316
+ vitals={vitals}
317
+ medications={medications}
318
+ chatHistory={chatHistory}
319
+ />
320
+ )}
321
+
322
+ </main>
323
+
324
+ {showSettings && (
325
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4">
326
+ <div className="glass-panel w-full max-w-md p-6 rounded-2xl border border-white/10 relative animate-in zoom-in-95 duration-200">
327
+ <button onClick={() => setShowSettings(false)} className="absolute top-4 right-4 text-gray-500 hover:text-white">
328
+ <X size={20} />
329
+ </button>
330
+ <h2 className="text-xl font-bold text-white mb-6 flex items-center gap-2">
331
+ <Settings className="text-neon-green" size={20} /> Settings
332
+ </h2>
333
+
334
+ <div className="space-y-4">
335
+ <button
336
+ type="button"
337
+ onClick={handlePrintReport}
338
+ className="w-full p-4 rounded-xl bg-white/5 hover:bg-white/10 border border-white/5 flex items-center gap-3 text-left transition-all group"
339
+ >
340
+ <div className="p-2 bg-green-500/10 rounded-lg text-green-400"><FileText size={18} /></div>
341
+ <div>
342
+ <h3 className="font-bold text-sm text-gray-200">Download PDF Report</h3>
343
+ <p className="text-xs text-gray-500">Save full analysis & chat history</p>
344
+ </div>
345
+ </button>
346
+ <button
347
+ type="button"
348
+ onClick={handleClearChat}
349
+ className="w-full p-4 rounded-xl bg-white/5 hover:bg-white/10 border border-white/5 flex items-center gap-3 text-left transition-all group"
350
+ >
351
+ <div className="p-2 bg-blue-500/10 rounded-lg text-blue-400"><MessageSquare size={18} /></div>
352
+ <div>
353
+ <h3 className="font-bold text-sm text-gray-200">Clear Conversation</h3>
354
+ <p className="text-xs text-gray-500">Remove current chat history</p>
355
+ </div>
356
+ </button>
357
+
358
+ <button
359
+ type="button"
360
+ onClick={handleResetData}
361
+ className="w-full p-4 rounded-xl bg-red-500/10 hover:bg-red-500/20 border border-red-500/10 flex items-center gap-3 text-left transition-all group"
362
+ >
363
+ <div className="p-2 bg-red-500/10 rounded-lg text-red-400"><Trash2 size={18} /></div>
364
+ <div>
365
+ <h3 className="font-bold text-sm text-red-200">Reset Application</h3>
366
+ <p className="text-xs text-red-400/60">Delete all local data & profile</p>
367
+ </div>
368
+ </button>
369
+ </div>
370
+ </div>
371
+ </div>
372
+ )}
373
+
374
+ {printableRoot && createPortal(
375
+ <PrintReport
376
+ profile={profile}
377
+ vitals={vitals}
378
+ riskResult={riskResult}
379
+ chatHistory={chatHistory}
380
+ chatSummary={chatSummary}
381
+ medications={medications}
382
+ />,
383
+ printableRoot
384
+ )}
385
+ </div>
386
+ );
387
+ };
388
+
389
+ export default App;
README.md CHANGED
@@ -1,12 +1,20 @@
1
- ---
2
- title: SomAI
3
- emoji: 🚀
4
- colorFrom: gray
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- short_description: Medical AI companion
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
1
+ <div align="center">
2
+ <img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
3
+ </div>
4
+
5
+ # Run and deploy your AI Studio app
6
+
7
+ This contains everything you need to run your app locally.
8
+
9
+ View your app in AI Studio: https://ai.studio/apps/drive/1qXCn5eEO0oaAya6brHvZQtMeHUi-HmTA
10
+
11
+ ## Run Locally
12
+
13
+ **Prerequisites:** Node.js
14
+
15
+
16
+ 1. Install dependencies:
17
+ `npm install`
18
+ 2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
19
+ 3. Run the app:
20
+ `npm run dev`
index.html ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>SomAI - Patient Education Companion</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
9
+ <script>
10
+ tailwind.config = {
11
+ theme: {
12
+ extend: {
13
+ fontFamily: {
14
+ sans: ['Inter', 'sans-serif'],
15
+ mono: ['JetBrains Mono', 'monospace'],
16
+ },
17
+ colors: {
18
+ neon: {
19
+ green: '#00ff80',
20
+ yellow: '#ffc300',
21
+ red: '#ff3300',
22
+ blue: '#00ccff',
23
+ bg: '#050505',
24
+ panel: 'rgba(255, 255, 255, 0.03)',
25
+ border: 'rgba(255, 255, 255, 0.1)',
26
+ }
27
+ },
28
+ animation: {
29
+ 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
30
+ }
31
+ }
32
+ }
33
+ }
34
+ </script>
35
+ <style>
36
+ body {
37
+ background-color: #050505;
38
+ background-image: radial-gradient(circle at 50% 50%, #1a1a1a 0%, #000000 100%);
39
+ color: white;
40
+ font-family: 'Inter', sans-serif;
41
+ }
42
+ .glass-panel {
43
+ background: rgba(10, 10, 10, 0.6);
44
+ backdrop-filter: blur(16px);
45
+ -webkit-backdrop-filter: blur(16px);
46
+ border: 1px solid rgba(255, 255, 255, 0.08);
47
+ }
48
+ .glass-card {
49
+ background: rgba(255, 255, 255, 0.03);
50
+ border: 1px solid rgba(255, 255, 255, 0.05);
51
+ transition: all 0.2s ease;
52
+ }
53
+ .glass-card:hover {
54
+ background: rgba(255, 255, 255, 0.05);
55
+ border-color: rgba(255, 255, 255, 0.1);
56
+ }
57
+
58
+ /* Print Logic Configuration */
59
+ #printable-root {
60
+ position: absolute;
61
+ top: 0;
62
+ left: 0;
63
+ width: 0;
64
+ height: 0;
65
+ overflow: hidden;
66
+ visibility: hidden;
67
+ }
68
+
69
+ @media print {
70
+ @page { size: auto; margin: 10mm; }
71
+
72
+ body {
73
+ background: white !important;
74
+ color: black !important;
75
+ overflow: visible !important;
76
+ }
77
+
78
+ #root {
79
+ display: none !important;
80
+ }
81
+
82
+ #printable-root {
83
+ position: relative !important;
84
+ width: 100% !important;
85
+ height: auto !important;
86
+ overflow: visible !important;
87
+ visibility: visible !important;
88
+ display: block !important;
89
+ z-index: 9999;
90
+ }
91
+
92
+ /* Ensure background colors print */
93
+ * {
94
+ -webkit-print-color-adjust: exact !important;
95
+ print-color-adjust: exact !important;
96
+ }
97
+ }
98
+ </style>
99
+ <script type="importmap">
100
+ {
101
+ "imports": {
102
+ "react": "https://aistudiocdn.com/react@^19.2.0",
103
+ "react-dom/": "https://aistudiocdn.com/react-dom@^19.2.0/",
104
+ "react/": "https://aistudiocdn.com/react@^19.2.0/",
105
+ "@google/genai": "https://aistudiocdn.com/@google/genai@^1.30.0",
106
+ "recharts": "https://aistudiocdn.com/recharts@^3.5.1",
107
+ "lucide-react": "https://aistudiocdn.com/lucide-react@^0.555.0",
108
+ "react-dom": "https://aistudiocdn.com/react-dom@^19.2.0"
109
+ }
110
+ }
111
+ </script>
112
+ <link rel="stylesheet" href="/index.css">
113
+ </head>
114
+ <body>
115
+ <div id="root"></div>
116
+ <div id="printable-root"></div>
117
+ <script type="module" src="/index.tsx"></script>
118
+ </body>
119
+ </html>
index.tsx ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import ReactDOM from 'react-dom/client';
3
+ import App from './App';
4
+
5
+ const rootElement = document.getElementById('root');
6
+ if (!rootElement) {
7
+ throw new Error("Could not find root element to mount to");
8
+ }
9
+
10
+ const root = ReactDOM.createRoot(rootElement);
11
+ root.render(
12
+ <React.StrictMode>
13
+ <App />
14
+ </React.StrictMode>
15
+ );
metadata.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "SomAI - Patient Education Companion",
3
+ "description": "A non-clinical AI guide educating patients about their condition, risks, triggers, and mental empowerment using Gemini API.",
4
+ "requestFramePermissions": [
5
+ "microphone"
6
+ ]
7
+ }
package.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "somai---patient-education-companion",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^19.2.0",
13
+ "react-dom": "^19.2.0",
14
+ "@google/genai": "^1.30.0",
15
+ "recharts": "^3.5.1",
16
+ "lucide-react": "^0.555.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^22.14.0",
20
+ "@vitejs/plugin-react": "^5.0.0",
21
+ "typescript": "~5.8.2",
22
+ "vite": "^6.2.0"
23
+ }
24
+ }