nagur-shareef-shaik commited on
Commit
f884d0d
·
1 Parent(s): b3fe2bc

Add Application code

Browse files
Files changed (12) hide show
  1. .gitignore +54 -0
  2. Dockerfile +27 -0
  3. index.html +13 -0
  4. package.json +28 -0
  5. postcss.config.js +6 -0
  6. src/App.tsx +500 -0
  7. src/index.css +17 -0
  8. src/main.tsx +10 -0
  9. tailwind.config.js +11 -0
  10. tsconfig.json +21 -0
  11. tsconfig.node.json +10 -0
  12. vite.config.ts +10 -0
.gitignore ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Node.js
2
+ node_modules/
3
+ npm-debug.log
4
+ yarn-error.log
5
+ package-lock.json
6
+
7
+ # Vite
8
+ /dist
9
+ /.vite
10
+ vite.config.ts.timestamp*
11
+
12
+ # TypeScript
13
+ *.tsbuildinfo
14
+
15
+ # Environment variables
16
+ .env
17
+ .env.local
18
+ .env.*.local
19
+
20
+ # Build output
21
+ /build
22
+ /dist
23
+ /out
24
+
25
+ # IDE and editor files
26
+ .vscode/
27
+ .idea/
28
+ *.suo
29
+ *.ntvs*
30
+ *.njsproj
31
+ *.sln
32
+ *.swp
33
+
34
+ # OS generated files
35
+ .DS_Store
36
+ Thumbs.db
37
+
38
+ # Logs
39
+ logs/
40
+ *.log
41
+ *.log.*
42
+
43
+ # Cache
44
+ .eslintcache
45
+ .stylelintcache
46
+ .cache
47
+
48
+ # Testing
49
+ /coverage
50
+
51
+ # Miscellaneous
52
+ *.bak
53
+ *.tmp
54
+ *.temp
Dockerfile ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use official Node.js LTS as base image
2
+ FROM node:18-alpine
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Copy package.json and package-lock.json (if exists)
8
+ COPY package*.json ./
9
+
10
+ # Install dependencies
11
+ RUN npm install
12
+
13
+ # Copy all project files
14
+ COPY . .
15
+
16
+ # Build the application
17
+ RUN npm run build
18
+
19
+ # Expose port 3000 (Hugging Face Spaces default)
20
+ EXPOSE 3000
21
+
22
+ # Set environment variables
23
+ ENV NODE_ENV=production
24
+ ENV PORT=3000
25
+
26
+ # Start the application with Vite preview
27
+ CMD ["npm", "run", "preview"]
index.html ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>InsuCompass AI</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
package.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "insucompass",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "dev": "vite",
7
+ "build": "tsc && vite build",
8
+ "preview": "vite preview"
9
+ },
10
+ "keywords": [],
11
+ "author": "",
12
+ "license": "ISC",
13
+ "description": "",
14
+ "dependencies": {
15
+ "@types/react": "^18.3.23",
16
+ "@types/react-dom": "^18.3.7",
17
+ "lucide-react": "^0.263.1",
18
+ "react": "^18.3.1",
19
+ "react-dom": "^18.3.1"
20
+ },
21
+ "devDependencies": {
22
+ "@vitejs/plugin-react": "^4.6.0",
23
+ "autoprefixer": "^10.4.21",
24
+ "postcss": "^8.5.6",
25
+ "tailwindcss": "^3.4.17",
26
+ "vite": "^4.5.14"
27
+ }
28
+ }
postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ module.exports = {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
src/App.tsx ADDED
@@ -0,0 +1,500 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { Send, MapPin, User, Users, DollarSign, Briefcase, CheckCircle, MessageCircle, Bot } from 'lucide-react';
3
+
4
+ // Types
5
+ interface UserProfile {
6
+ zip_code?: string;
7
+ city?: string;
8
+ state?: string;
9
+ county?: string;
10
+ age?: number;
11
+ gender?: string;
12
+ household_size?: number;
13
+ income?: number;
14
+ employment_status?: string;
15
+ citizenship?: string;
16
+ medical_history?: string;
17
+ medications?: string;
18
+ special_cases?: string;
19
+ }
20
+
21
+ interface ChatMessage {
22
+ role: 'user' | 'agent';
23
+ content: string;
24
+ timestamp: number;
25
+ }
26
+
27
+ interface ApiResponse {
28
+ updated_profile: UserProfile;
29
+ updated_history: string[];
30
+ is_profile_complete: boolean;
31
+ }
32
+
33
+ // Constants
34
+ const BACKEND_URL = "https://nagur-shareef-shaik-insucompass-api.hf.space/api";
35
+
36
+ const InsuCompassApp: React.FC = () => {
37
+ const [threadId] = useState(() => crypto.randomUUID());
38
+ const [userProfile, setUserProfile] = useState<UserProfile>({});
39
+ const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
40
+ const [isProfileComplete, setIsProfileComplete] = useState(false);
41
+ const [phase, setPhase] = useState<'profiling' | 'chat'>('profiling');
42
+ const [isLoading, setIsLoading] = useState(false);
43
+ const [currentMessage, setCurrentMessage] = useState('');
44
+ const [isValidatingZip, setIsValidatingZip] = useState(false);
45
+ const [zipError, setZipError] = useState('');
46
+ const chatEndRef = useRef<HTMLDivElement>(null);
47
+
48
+ // Form state
49
+ const [formData, setFormData] = useState({
50
+ zip_code: '',
51
+ age: '',
52
+ gender: '',
53
+ household_size: '',
54
+ income: '',
55
+ employment_status: '',
56
+ citizenship: ''
57
+ });
58
+
59
+ // Auto-scroll to bottom of chat
60
+ useEffect(() => {
61
+ chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
62
+ }, [chatHistory]);
63
+
64
+ // API Functions
65
+ const getGeodata = async (zipCode: string) => {
66
+ setIsValidatingZip(true);
67
+ setZipError('');
68
+ try {
69
+ const response = await fetch(`${BACKEND_URL}/geodata/${zipCode}`);
70
+ if (!response.ok) throw new Error('Invalid ZIP code');
71
+ const data = await response.json();
72
+ setUserProfile(prev => ({ ...prev, ...data }));
73
+ setFormData(prev => ({ ...prev, zip_code: zipCode }));
74
+ return data;
75
+ } catch (error) {
76
+ setZipError('Invalid ZIP code or could not retrieve location data');
77
+ return null;
78
+ } finally {
79
+ setIsValidatingZip(false);
80
+ }
81
+ };
82
+
83
+ const sendChatMessage = async (message: string) => {
84
+ setIsLoading(true);
85
+ try {
86
+ const payload = {
87
+ thread_id: threadId,
88
+ user_profile: userProfile,
89
+ message,
90
+ is_profile_complete: isProfileComplete,
91
+ conversation_history: chatHistory.map(msg => `${msg.role}: ${msg.content}`)
92
+ };
93
+
94
+ const response = await fetch(`${BACKEND_URL}/chat`, {
95
+ method: 'POST',
96
+ headers: { 'Content-Type': 'application/json' },
97
+ body: JSON.stringify(payload)
98
+ });
99
+
100
+ if (!response.ok) throw new Error('Failed to send message');
101
+
102
+ const data: ApiResponse = await response.json();
103
+
104
+ // Update state from backend response
105
+ setUserProfile(data.updated_profile);
106
+ setIsProfileComplete(data.is_profile_complete);
107
+
108
+ // Convert backend history format to our chat format
109
+ const newHistory: ChatMessage[] = data.updated_history.map(msg => {
110
+ const [role, ...contentParts] = msg.split(':');
111
+ return {
112
+ role: role.toLowerCase() as 'user' | 'agent',
113
+ content: contentParts.join(':').trim(),
114
+ timestamp: Date.now()
115
+ };
116
+ });
117
+
118
+ setChatHistory(newHistory);
119
+ } catch (error) {
120
+ console.error('Error sending message:', error);
121
+ } finally {
122
+ setIsLoading(false);
123
+ }
124
+ };
125
+
126
+ // Handle ZIP code input
127
+ const handleZipChange = async (value: string) => {
128
+ const numericValue = value.replace(/\D/g, '').slice(0, 5);
129
+ setFormData(prev => ({ ...prev, zip_code: numericValue }));
130
+
131
+ if (numericValue.length === 5) {
132
+ await getGeodata(numericValue);
133
+ }
134
+ };
135
+
136
+ // Handle form submission
137
+ const handleSubmit = async () => {
138
+
139
+ // Validate all fields
140
+ const requiredFields = ['age', 'gender', 'household_size', 'income', 'employment_status', 'citizenship'];
141
+ const missingFields = requiredFields.filter(field => !formData[field as keyof typeof formData]);
142
+
143
+ if (missingFields.length > 0) {
144
+ alert('Please fill out all fields to continue.');
145
+ return;
146
+ }
147
+
148
+ // Update user profile
149
+ const updatedProfile = {
150
+ ...userProfile,
151
+ age: parseInt(formData.age),
152
+ gender: formData.gender,
153
+ household_size: parseInt(formData.household_size),
154
+ income: parseInt(formData.income),
155
+ employment_status: formData.employment_status,
156
+ citizenship: formData.citizenship
157
+ };
158
+
159
+ setUserProfile(updatedProfile);
160
+ setPhase('chat');
161
+
162
+ // Start profile building conversation
163
+ await sendChatMessage('START_PROFILE_BUILDING');
164
+ };
165
+
166
+ // Handle chat message send
167
+ const handleSendMessage = async () => {
168
+ if (!currentMessage.trim()) return;
169
+
170
+ // Add user message to chat immediately
171
+ const userMessage: ChatMessage = {
172
+ role: 'user',
173
+ content: currentMessage,
174
+ timestamp: Date.now()
175
+ };
176
+
177
+ setChatHistory(prev => [...prev, userMessage]);
178
+ const messageToSend = currentMessage;
179
+ setCurrentMessage('');
180
+
181
+ await sendChatMessage(messageToSend);
182
+ };
183
+
184
+ // Render profile form
185
+ const renderProfileForm = () => (
186
+ <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50">
187
+ {/* Header */}
188
+ <div className="bg-white shadow-sm border-b">
189
+ <div className="max-w-4xl mx-auto px-6 py-4">
190
+ <div className="flex items-center space-x-3">
191
+ <div className="w-12 h-12 bg-gradient-to-r from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center">
192
+ <span className="text-white font-bold text-xl">🧭</span>
193
+ </div>
194
+ <div>
195
+ <h1 className="text-2xl font-bold text-gray-900">InsuCompass</h1>
196
+ <p className="text-sm text-gray-600">Your AI guide to U.S. Health Insurance</p>
197
+ </div>
198
+ </div>
199
+ </div>
200
+ </div>
201
+
202
+ {/* Main Content */}
203
+ <div className="max-w-2xl mx-auto px-6 py-12">
204
+ <div className="text-center mb-8">
205
+ <h2 className="text-3xl font-bold text-gray-900 mb-4">Let's Get Started</h2>
206
+ <p className="text-lg text-gray-600">Tell us about yourself to receive personalized insurance guidance</p>
207
+ </div>
208
+
209
+ <div className="space-y-6">
210
+ {/* ZIP Code Section */}
211
+ <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
212
+ <div className="flex items-center space-x-3 mb-4">
213
+ <MapPin className="w-5 h-5 text-blue-500" />
214
+ <h3 className="text-lg font-semibold text-gray-900">Location</h3>
215
+ </div>
216
+
217
+ <div className="space-y-4">
218
+ <div>
219
+ <label className="block text-sm font-medium text-gray-700 mb-2">
220
+ ZIP Code
221
+ </label>
222
+ <input
223
+ type="text"
224
+ value={formData.zip_code}
225
+ onChange={(e) => handleZipChange(e.target.value)}
226
+ placeholder="Enter your 5-digit ZIP code"
227
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
228
+ maxLength={5}
229
+ />
230
+ {isValidatingZip && (
231
+ <p className="text-sm text-blue-600 mt-1">Validating ZIP code...</p>
232
+ )}
233
+ {zipError && (
234
+ <p className="text-sm text-red-600 mt-1">{zipError}</p>
235
+ )}
236
+ </div>
237
+
238
+ {userProfile.city && (
239
+ <div className="bg-green-50 border border-green-200 rounded-lg p-4">
240
+ <div className="flex items-center space-x-2">
241
+ <CheckCircle className="w-5 h-5 text-green-500" />
242
+ <span className="font-medium text-green-800">Location Verified</span>
243
+ </div>
244
+ <p className="text-sm text-green-700 mt-1">
245
+ {userProfile.county} County, {userProfile.city}, {userProfile.state}
246
+ </p>
247
+ </div>
248
+ )}
249
+ </div>
250
+ </div>
251
+
252
+ {/* Personal Information */}
253
+ {userProfile.city && (
254
+ <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
255
+ <div className="flex items-center space-x-3 mb-4">
256
+ <User className="w-5 h-5 text-blue-500" />
257
+ <h3 className="text-lg font-semibold text-gray-900">Personal Information</h3>
258
+ </div>
259
+
260
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
261
+ <div>
262
+ <label className="block text-sm font-medium text-gray-700 mb-2">Age</label>
263
+ <input
264
+ type="number"
265
+ value={formData.age}
266
+ onChange={(e) => setFormData(prev => ({ ...prev, age: e.target.value }))}
267
+ placeholder="Enter your age"
268
+ min="1"
269
+ max="120"
270
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
271
+ />
272
+ </div>
273
+
274
+ <div>
275
+ <label className="block text-sm font-medium text-gray-700 mb-2">Gender</label>
276
+ <select
277
+ value={formData.gender}
278
+ onChange={(e) => setFormData(prev => ({ ...prev, gender: e.target.value }))}
279
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
280
+ >
281
+ <option value="">Select an option</option>
282
+ <option value="Male">Male</option>
283
+ <option value="Female">Female</option>
284
+ <option value="Non-binary">Non-binary</option>
285
+ <option value="Prefer not to say">Prefer not to say</option>
286
+ </select>
287
+ </div>
288
+ </div>
289
+ </div>
290
+ )}
291
+
292
+ {/* Household Information */}
293
+ {userProfile.city && (
294
+ <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
295
+ <div className="flex items-center space-x-3 mb-4">
296
+ <Users className="w-5 h-5 text-blue-500" />
297
+ <h3 className="text-lg font-semibold text-gray-900">Household Information</h3>
298
+ </div>
299
+
300
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
301
+ <div>
302
+ <label className="block text-sm font-medium text-gray-700 mb-2">Household Size</label>
303
+ <input
304
+ type="number"
305
+ value={formData.household_size}
306
+ onChange={(e) => setFormData(prev => ({ ...prev, household_size: e.target.value }))}
307
+ placeholder="Including yourself"
308
+ min="1"
309
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
310
+ />
311
+ </div>
312
+
313
+ <div>
314
+ <label className="block text-sm font-medium text-gray-700 mb-2">Annual Income</label>
315
+ <div className="relative">
316
+ <DollarSign className="absolute left-3 top-3 w-5 h-5 text-gray-400" />
317
+ <input
318
+ type="number"
319
+ value={formData.income}
320
+ onChange={(e) => setFormData(prev => ({ ...prev, income: e.target.value }))}
321
+ placeholder="55000"
322
+ min="0"
323
+ className="w-full pl-10 pr-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
324
+ />
325
+ </div>
326
+ </div>
327
+ </div>
328
+ </div>
329
+ )}
330
+
331
+ {/* Employment & Citizenship */}
332
+ {userProfile.city && (
333
+ <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
334
+ <div className="flex items-center space-x-3 mb-4">
335
+ <Briefcase className="w-5 h-5 text-blue-500" />
336
+ <h3 className="text-lg font-semibold text-gray-900">Employment & Status</h3>
337
+ </div>
338
+
339
+ <div className="space-y-4">
340
+ <div>
341
+ <label className="block text-sm font-medium text-gray-700 mb-2">Employment Status</label>
342
+ <select
343
+ value={formData.employment_status}
344
+ onChange={(e) => setFormData(prev => ({ ...prev, employment_status: e.target.value }))}
345
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
346
+ >
347
+ <option value="">Select an option</option>
348
+ <option value="Employed with employer coverage">Employed with employer coverage</option>
349
+ <option value="Employed without coverage">Employed without coverage</option>
350
+ <option value="Unemployed">Unemployed</option>
351
+ <option value="Retired">Retired</option>
352
+ <option value="Student">Student</option>
353
+ <option value="Self-employed">Self-employed</option>
354
+ </select>
355
+ </div>
356
+
357
+ <div>
358
+ <label className="block text-sm font-medium text-gray-700 mb-2">Citizenship Status</label>
359
+ <select
360
+ value={formData.citizenship}
361
+ onChange={(e) => setFormData(prev => ({ ...prev, citizenship: e.target.value }))}
362
+ className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
363
+ >
364
+ <option value="">Select an option</option>
365
+ <option value="US Citizen">US Citizen</option>
366
+ <option value="Lawful Permanent Resident">Lawful Permanent Resident</option>
367
+ <option value="Other legal resident">Other legal resident</option>
368
+ <option value="Non-resident">Non-resident</option>
369
+ </select>
370
+ </div>
371
+ </div>
372
+ </div>
373
+ )}
374
+
375
+ {/* Submit Button */}
376
+ {userProfile.city && (
377
+ <button
378
+ onClick={handleSubmit}
379
+ disabled={isLoading}
380
+ className="w-full bg-gradient-to-r from-blue-500 to-indigo-600 text-white py-4 px-8 rounded-xl font-semibold text-lg shadow-lg hover:shadow-xl transform hover:scale-[1.02] transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
381
+ >
382
+ {isLoading ? 'Starting Session...' : 'Start My Personalized Session'}
383
+ </button>
384
+ )}
385
+ </div>
386
+ </div>
387
+ </div>
388
+ );
389
+
390
+ // Render chat interface
391
+ const renderChatInterface = () => (
392
+ <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50 flex flex-col">
393
+ {/* Header */}
394
+ <div className="bg-white shadow-sm border-b">
395
+ <div className="max-w-4xl mx-auto px-6 py-4">
396
+ <div className="flex items-center space-x-3">
397
+ <div className="w-12 h-12 bg-gradient-to-r from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center">
398
+ <span className="text-white font-bold text-xl">🧭</span>
399
+ </div>
400
+ <div>
401
+ <h1 className="text-2xl font-bold text-gray-900">InsuCompass</h1>
402
+ <p className="text-sm text-gray-600">Chat with your AI insurance advisor</p>
403
+ </div>
404
+ </div>
405
+ </div>
406
+ </div>
407
+
408
+ {/* Chat Messages */}
409
+ <div className="flex-1 overflow-y-auto">
410
+ <div className="max-w-4xl mx-auto px-6 py-8">
411
+ <div className="space-y-6">
412
+ {chatHistory.map((message, index) => (
413
+ <div
414
+ key={index}
415
+ className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
416
+ >
417
+ <div className={`flex items-start space-x-3 max-w-3xl ${message.role === 'user' ? 'flex-row-reverse space-x-reverse' : ''}`}>
418
+ {/* Avatar */}
419
+ <div className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${
420
+ message.role === 'user'
421
+ ? 'bg-blue-500 text-white'
422
+ : 'bg-gradient-to-r from-purple-500 to-pink-500 text-white'
423
+ }`}>
424
+ {message.role === 'user' ? (
425
+ <User className="w-5 h-5" />
426
+ ) : (
427
+ <Bot className="w-5 h-5" />
428
+ )}
429
+ </div>
430
+
431
+ {/* Message Bubble */}
432
+ <div className={`px-6 py-4 rounded-2xl shadow-sm ${
433
+ message.role === 'user'
434
+ ? 'bg-blue-500 text-white rounded-tr-sm'
435
+ : 'bg-white text-gray-900 rounded-tl-sm border border-gray-200'
436
+ }`}>
437
+ <div className="text-sm font-medium mb-1 opacity-75">
438
+ {message.role === 'user' ? 'You' : 'InsuCompass AI'}
439
+ </div>
440
+ <div className="whitespace-pre-wrap">{message.content}</div>
441
+ </div>
442
+ </div>
443
+ </div>
444
+ ))}
445
+
446
+ {/* Loading indicator */}
447
+ {isLoading && (
448
+ <div className="flex justify-start">
449
+ <div className="flex items-start space-x-3">
450
+ <div className="w-10 h-10 rounded-full bg-gradient-to-r from-purple-500 to-pink-500 text-white flex items-center justify-center">
451
+ <Bot className="w-5 h-5" />
452
+ </div>
453
+ <div className="bg-white px-6 py-4 rounded-2xl rounded-tl-sm border border-gray-200">
454
+ <div className="flex space-x-2">
455
+ <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
456
+ <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
457
+ <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
458
+ </div>
459
+ </div>
460
+ </div>
461
+ </div>
462
+ )}
463
+
464
+ <div ref={chatEndRef} />
465
+ </div>
466
+ </div>
467
+ </div>
468
+
469
+ {/* Message Input */}
470
+ <div className="bg-white border-t border-gray-200 p-4">
471
+ <div className="max-w-4xl mx-auto">
472
+ <div className="flex items-center space-x-4">
473
+ <div className="flex-1 relative">
474
+ <input
475
+ type="text"
476
+ value={currentMessage}
477
+ onChange={(e) => setCurrentMessage(e.target.value)}
478
+ onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
479
+ placeholder="Type your message..."
480
+ className="w-full px-4 py-3 pr-12 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-colors"
481
+ disabled={isLoading}
482
+ />
483
+ <button
484
+ onClick={handleSendMessage}
485
+ disabled={isLoading || !currentMessage.trim()}
486
+ className="absolute right-2 top-1/2 transform -translate-y-1/2 p-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
487
+ >
488
+ <Send className="w-4 h-4" />
489
+ </button>
490
+ </div>
491
+ </div>
492
+ </div>
493
+ </div>
494
+ </div>
495
+ );
496
+
497
+ return phase === 'profiling' ? renderProfileForm() : renderChatInterface();
498
+ };
499
+
500
+ export default InsuCompassApp;
src/index.css ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ * {
6
+ margin: 0;
7
+ padding: 0;
8
+ box-sizing: border-box;
9
+ }
10
+
11
+ body {
12
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
13
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
14
+ sans-serif;
15
+ -webkit-font-smoothing: antialiased;
16
+ -moz-osx-font-smoothing: grayscale;
17
+ }
src/main.tsx ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react'
2
+ import ReactDOM from 'react-dom/client'
3
+ import App from './App'
4
+ import './index.css'
5
+
6
+ ReactDOM.createRoot(document.getElementById('root')!).render(
7
+ <React.StrictMode>
8
+ <App />
9
+ </React.StrictMode>,
10
+ )
tailwind.config.js ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('tailwindcss').Config} */
2
+ module.exports = {
3
+ content: [
4
+ "./index.html",
5
+ "./src/**/*.{js,ts,jsx,tsx}",
6
+ ],
7
+ theme: {
8
+ extend: {},
9
+ },
10
+ plugins: [],
11
+ }
tsconfig.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true
18
+ },
19
+ "include": ["src"],
20
+ "references": [{ "path": "./tsconfig.node.json" }]
21
+ }
tsconfig.node.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true
8
+ },
9
+ "include": ["vite.config.ts"]
10
+ }
vite.config.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { defineConfig } from 'vite'
2
+ import react from '@vitejs/plugin-react'
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ server: {
7
+ port: 3000,
8
+ open: true
9
+ }
10
+ })