src/App.tsx CHANGED
@@ -1,566 +1,24 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import { Send, MapPin, User, Users, DollarSign, Briefcase, CheckCircle, Bot } from 'lucide-react';
3
- import InsuCompassLogo from './assets/InsuCompass_Logo.png';
4
- import ReactMarkdown from 'react-markdown';
5
 
6
- // Types
7
- interface UserProfile {
8
- zip_code?: string;
9
- city?: string;
10
- state?: string;
11
- county?: string;
12
- age?: number;
13
- gender?: string;
14
- household_size?: number;
15
- income?: number;
16
- employment_status?: string;
17
- citizenship?: string;
18
- medical_history?: string | null;
19
- medications?: string | null;
20
- special_cases?: string | null;
21
- }
22
-
23
- interface ChatMessage {
24
- role: 'user' | 'agent';
25
- content: string;
26
- timestamp: number;
27
- }
28
-
29
- interface Plan {
30
- plan_name: string;
31
- plan_type: string;
32
- key_features: string[];
33
- estimated_premium: string;
34
- reasoning: string;
35
- }
36
-
37
- interface PlanRecommendations {
38
- recommendations: Plan[];
39
- }
40
-
41
- interface ApiResponse {
42
- updated_profile: UserProfile;
43
- updated_history: string[];
44
- is_profile_complete: boolean;
45
- plan_recommendations?: PlanRecommendations | null;
46
- }
47
-
48
- // Constants
49
- const BACKEND_URL = "https://nagur-shareef-shaik-insucompass-api.hf.space/api";
50
-
51
- /* PlanCard helper */
52
- const PlanCard: React.FC<{ plan: Plan }> = ({ plan }) => (
53
- <div className="bg-white border border-gray-200 rounded-2xl shadow-sm p-6 hover:shadow-lg transition">
54
- <h3 className="text-xl font-semibold text-gray-900">{plan.plan_name}</h3>
55
- <p className="text-sm text-gray-600 mb-2">{plan.plan_type}</p>
56
-
57
- <p className="text-sm italic text-gray-700 mb-4">{plan.reasoning}</p>
58
-
59
- <p className="text-sm font-medium text-gray-800 mb-2">
60
- Estimated Premium: {plan.estimated_premium}
61
- </p>
62
-
63
- <ul className="list-disc list-inside space-y-1 text-sm text-gray-700">
64
- {plan.key_features.map((feat, i) => (
65
- <li key={i}>{feat}</li>
66
- ))}
67
- </ul>
68
- </div>
69
- );
70
-
71
- const InsuCompassApp: React.FC = () => {
72
- const [threadId] = useState(() => crypto.randomUUID());
73
- const [userProfile, setUserProfile] = useState<UserProfile>({});
74
- const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
75
- const [isProfileComplete, setIsProfileComplete] = useState(false);
76
  const [phase, setPhase] = useState<'profiling' | 'chat'>('profiling');
77
- const [isLoading, setIsLoading] = useState(false);
78
- const [currentMessage, setCurrentMessage] = useState('');
79
- const [isValidatingZip, setIsValidatingZip] = useState(false);
80
- const [zipError, setZipError] = useState('');
81
- const [planRecs, setPlanRecs] = useState<Plan[] | null>(null); // NEW
82
- const chatEndRef = useRef<HTMLDivElement>(null);
83
-
84
- // Form state
85
- const [formData, setFormData] = useState({
86
- zip_code: '',
87
- age: '',
88
- gender: '',
89
- household_size: '',
90
- income: '',
91
- employment_status: '',
92
- citizenship: ''
93
- });
94
-
95
- // Auto-scroll to bottom of chat
96
- useEffect(() => {
97
- chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
98
- }, [chatHistory]);
99
-
100
- // API Functions
101
- const getGeodata = async (zipCode: string) => {
102
- setIsValidatingZip(true);
103
- setZipError('');
104
- try {
105
- const response = await fetch(`${BACKEND_URL}/geodata/${zipCode}`);
106
- if (!response.ok) throw new Error('Invalid ZIP code');
107
- const data = await response.json();
108
- setUserProfile(prev => ({ ...prev, ...data }));
109
- setFormData(prev => ({ ...prev, zip_code: zipCode }));
110
- return data;
111
- } catch (error) {
112
- setZipError('Invalid ZIP code or could not retrieve location data');
113
- return null;
114
- } finally {
115
- setIsValidatingZip(false);
116
- }
117
- };
118
-
119
-
120
- const sendChatMessage = async (message: string, profileOverride?: UserProfile) => {
121
- setIsLoading(true);
122
- try {
123
- const payload = {
124
- thread_id: threadId,
125
- user_profile: profileOverride ?? userProfile,
126
- message,
127
- is_profile_complete: isProfileComplete,
128
- conversation_history: chatHistory.map(m => `${m.role}: ${m.content}`)
129
- };
130
-
131
- const response = await fetch(`${BACKEND_URL}/chat`, {
132
- method: 'POST',
133
- headers: { 'Content-Type': 'application/json' },
134
- body: JSON.stringify(payload)
135
- });
136
-
137
- if (!response.ok) throw new Error('Failed to send message');
138
-
139
- const data: ApiResponse = await response.json();
140
-
141
- // Update state from backend response
142
- setUserProfile(data.updated_profile);
143
- setIsProfileComplete(data.is_profile_complete);
144
-
145
- if (data.plan_recommendations?.recommendations && !planRecs) {
146
- setPlanRecs(data.plan_recommendations.recommendations);
147
- }
148
-
149
- // Convert backend history format to our chat format
150
- const newHistory: ChatMessage[] = data.updated_history.map(msg => {
151
- const [role, ...contentParts] = msg.split(':');
152
- return {
153
- role: role.toLowerCase() as 'user' | 'agent',
154
- content: contentParts.join(':').trim(),
155
- timestamp: Date.now()
156
- };
157
- });
158
-
159
- setChatHistory(newHistory);
160
- } catch (error) {
161
- console.error('Error sending message:', error);
162
- } finally {
163
- setIsLoading(false);
164
- }
165
- };
166
-
167
- // Handle ZIP code input
168
- const handleZipChange = async (value: string) => {
169
- const numericValue = value.replace(/\D/g, '').slice(0, 5);
170
- setFormData(prev => ({ ...prev, zip_code: numericValue }));
171
-
172
- if (numericValue.length === 5) {
173
- await getGeodata(numericValue);
174
- }
175
- };
176
-
177
- // Handle form submission
178
- const handleSubmit = async () => {
179
-
180
- // Validate all fields
181
- const requiredFields = ['age', 'gender', 'household_size', 'income', 'employment_status', 'citizenship'];
182
- const missingFields = requiredFields.filter(field => !formData[field as keyof typeof formData]);
183
-
184
- if (missingFields.length > 0) {
185
- alert('Please fill out all fields to continue.');
186
- return;
187
- }
188
-
189
- // Update user profile
190
- // const updatedProfile: UserProfile = {
191
- // ...userProfile,
192
- // age: +formData.age,
193
- // gender: formData.gender,
194
- // household_size: +formData.household_size,
195
- // income: +formData.income,
196
- // employment_status: formData.employment_status,
197
- // citizenship: formData.citizenship
198
- // };
199
 
200
- // Update user profile
201
- const updatedProfile: UserProfile = {
202
- ...userProfile,
203
- age: +formData.age,
204
- gender: formData.gender,
205
- household_size: +formData.household_size,
206
- income: +formData.income,
207
- employment_status: formData.employment_status,
208
- citizenship: formData.citizenship,
209
- medical_history: null,
210
- medications: null,
211
- special_cases: null
212
- };
213
-
214
- setUserProfile(updatedProfile);
215
  setPhase('chat');
216
-
217
- // Start profile building conversation
218
- await sendChatMessage('START_PROFILE_BUILDING', updatedProfile);
219
  };
220
 
221
- // Handle chat message send
222
- const handleSendMessage = async () => {
223
- if (!currentMessage.trim()) return;
224
-
225
- // Add user message to chat immediately
226
- const userMessage: ChatMessage = {
227
- role: 'user',
228
- content: currentMessage,
229
- timestamp: Date.now()
230
- };
231
-
232
- setChatHistory(prev => [...prev, userMessage]);
233
- const messageToSend = currentMessage;
234
- setCurrentMessage('');
235
-
236
- await sendChatMessage(messageToSend);
237
- };
238
-
239
- // Render profile form
240
- const renderProfileForm = () => (
241
- <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50">
242
- {/* Header */}
243
- <div className="fixed top-0 left-0 w-full bg-white shadow-sm border-b z-50">
244
- <div className="max-w-10xl mx-auto px-6 py-4">
245
- <div className="flex items-center space-x-3">
246
- <img src={InsuCompassLogo} alt="InsuCompass Logo" className="h-12 w-auto" />
247
- <div>
248
- <h1 className="text-2xl font-bold text-gray-900">InsuCompass</h1>
249
- <p className="text-sm text-gray-600">Your AI guide to U.S. Health Insurance</p>
250
- </div>
251
- </div>
252
- </div>
253
- </div>
254
-
255
- {/* Main Content */}
256
- <div className="flex-1 overflow-y-auto pt-20">
257
- <div className="max-w-2xl mx-auto px-6 py-12">
258
- <div className="text-center mb-8">
259
- <h2 className="text-3xl font-bold text-gray-900 mb-4">Let's Get Started</h2>
260
- <p className="text-lg text-gray-600">Tell us about yourself to receive personalized insurance guidance</p>
261
- </div>
262
-
263
- <div className="space-y-6">
264
- {/* ZIP Code Section */}
265
- <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
266
- <div className="flex items-center space-x-3 mb-4">
267
- <MapPin className="w-5 h-5 text-blue-500" />
268
- <h3 className="text-lg font-semibold text-gray-900">Location</h3>
269
- </div>
270
-
271
- <div className="space-y-4">
272
- <div>
273
- <label className="block text-sm font-medium text-gray-700 mb-2">
274
- ZIP Code
275
- </label>
276
- <input
277
- type="text"
278
- value={formData.zip_code}
279
- onChange={(e) => handleZipChange(e.target.value)}
280
- placeholder="Enter your 5-digit ZIP code"
281
- 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"
282
- maxLength={5}
283
- />
284
- {isValidatingZip && (
285
- <p className="text-sm text-blue-600 mt-1">Validating ZIP code...</p>
286
- )}
287
- {zipError && (
288
- <p className="text-sm text-red-600 mt-1">{zipError}</p>
289
- )}
290
- </div>
291
-
292
- {userProfile.city && (
293
- <div className="bg-green-50 border border-green-200 rounded-lg p-4">
294
- <div className="flex items-center space-x-2">
295
- <CheckCircle className="w-5 h-5 text-green-500" />
296
- <span className="font-medium text-green-800">Location Verified</span>
297
- </div>
298
- <p className="text-sm text-green-700 mt-1">
299
- {userProfile.county} County, {userProfile.city}, {userProfile.state}
300
- </p>
301
- </div>
302
- )}
303
- </div>
304
- </div>
305
-
306
- {/* Personal Information */}
307
- {userProfile.city && (
308
- <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
309
- <div className="flex items-center space-x-3 mb-4">
310
- <User className="w-5 h-5 text-blue-500" />
311
- <h3 className="text-lg font-semibold text-gray-900">Personal Information</h3>
312
- </div>
313
-
314
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
315
- <div>
316
- <label className="block text-sm font-medium text-gray-700 mb-2">Age</label>
317
- <input
318
- type="number"
319
- value={formData.age}
320
- onChange={(e) => setFormData(prev => ({ ...prev, age: e.target.value }))}
321
- placeholder="Enter your age"
322
- min="1"
323
- max="120"
324
- 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"
325
- />
326
- </div>
327
-
328
- <div>
329
- <label className="block text-sm font-medium text-gray-700 mb-2">Gender</label>
330
- <select
331
- value={formData.gender}
332
- onChange={(e) => setFormData(prev => ({ ...prev, gender: e.target.value }))}
333
- 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"
334
- >
335
- <option value="">Select an option</option>
336
- <option value="Male">Male</option>
337
- <option value="Female">Female</option>
338
- <option value="Non-binary">Non-binary</option>
339
- <option value="Prefer not to say">Prefer not to say</option>
340
- </select>
341
- </div>
342
- </div>
343
- </div>
344
- )}
345
-
346
- {/* Household Information */}
347
- {userProfile.city && (
348
- <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
349
- <div className="flex items-center space-x-3 mb-4">
350
- <Users className="w-5 h-5 text-blue-500" />
351
- <h3 className="text-lg font-semibold text-gray-900">Household Information</h3>
352
- </div>
353
-
354
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
355
- <div>
356
- <label className="block text-sm font-medium text-gray-700 mb-2">Household Size</label>
357
- <input
358
- type="number"
359
- value={formData.household_size}
360
- onChange={(e) => setFormData(prev => ({ ...prev, household_size: e.target.value }))}
361
- placeholder="Including yourself"
362
- min="1"
363
- 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"
364
- />
365
- </div>
366
-
367
- <div>
368
- <label className="block text-sm font-medium text-gray-700 mb-2">Annual Income</label>
369
- <div className="relative">
370
- <DollarSign className="absolute left-3 top-3 w-5 h-5 text-gray-400" />
371
- <input
372
- type="number"
373
- value={formData.income}
374
- onChange={(e) => setFormData(prev => ({ ...prev, income: e.target.value }))}
375
- placeholder="55000"
376
- min="0"
377
- 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"
378
- />
379
- </div>
380
- </div>
381
- </div>
382
- </div>
383
- )}
384
-
385
- {/* Employment & Citizenship */}
386
- {userProfile.city && (
387
- <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
388
- <div className="flex items-center space-x-3 mb-4">
389
- <Briefcase className="w-5 h-5 text-blue-500" />
390
- <h3 className="text-lg font-semibold text-gray-900">Employment & Status</h3>
391
- </div>
392
-
393
- <div className="space-y-4">
394
- <div>
395
- <label className="block text-sm font-medium text-gray-700 mb-2">Employment Status</label>
396
- <select
397
- value={formData.employment_status}
398
- onChange={(e) => setFormData(prev => ({ ...prev, employment_status: e.target.value }))}
399
- 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"
400
- >
401
- <option value="">Select an option</option>
402
- <option value="Employed with employer coverage">Employed with employer coverage</option>
403
- <option value="Employed without coverage">Employed without coverage</option>
404
- <option value="Unemployed">Unemployed</option>
405
- <option value="Retired">Retired</option>
406
- <option value="Student">Student</option>
407
- <option value="Self-employed">Self-employed</option>
408
- </select>
409
- </div>
410
-
411
- <div>
412
- <label className="block text-sm font-medium text-gray-700 mb-2">Citizenship Status</label>
413
- <select
414
- value={formData.citizenship}
415
- onChange={(e) => setFormData(prev => ({ ...prev, citizenship: e.target.value }))}
416
- 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"
417
- >
418
- <option value="">Select an option</option>
419
- <option value="US Citizen">US Citizen</option>
420
- <option value="Lawful Permanent Resident">Lawful Permanent Resident</option>
421
- <option value="Other legal resident">Other legal resident</option>
422
- <option value="Non-resident">Non-resident</option>
423
- </select>
424
- </div>
425
- </div>
426
- </div>
427
- )}
428
-
429
- {/* Submit Button */}
430
- {userProfile.city && (
431
- <button
432
- onClick={handleSubmit}
433
- disabled={isLoading}
434
- 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"
435
- >
436
- {isLoading ? 'Starting Session...' : 'Start My Personalized Session'}
437
- </button>
438
- )}
439
- </div>
440
- </div>
441
- </div>
442
- </div>
443
- );
444
-
445
- // Render chat interface
446
- const renderChatInterface = () => (
447
- <div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50 flex flex-col">
448
- {/* Header */}
449
- <div className="fixed top-0 left-0 w-full bg-white shadow-sm border-b z-50">
450
- <div className="max-w-10xl mx-auto px-6 py-4">
451
- <div className="flex items-center space-x-3">
452
- <img src={InsuCompassLogo} alt="InsuCompass Logo" className="h-12 w-auto" />
453
- <div>
454
- <h1 className="text-2xl font-bold text-gray-900">InsuCompass</h1>
455
- <p className="text-sm text-gray-600">Chat with your AI insurance advisor</p>
456
- </div>
457
- </div>
458
- </div>
459
- </div>
460
-
461
- {/* Chat Messages */}
462
- <div className="flex-1 overflow-y-auto pt-20">
463
- <div className="max-w-4xl mx-auto px-6 py-8">
464
- <div className="space-y-6">
465
- {planRecs && (
466
- <>
467
- <h2 className="text-2xl font-bold text-gray-900">Recommended Plans for You</h2>
468
- <div className="grid md:grid-cols-2 gap-6">
469
- {planRecs.map((plan, idx) => (
470
- <PlanCard key={idx} plan={plan} />
471
- ))}
472
- </div>
473
- </>
474
- )}
475
- {chatHistory.map((message, index) => (
476
- <div
477
- key={index}
478
- className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
479
- >
480
- <div className={`flex items-start space-x-3 max-w-3xl ${message.role === 'user' ? 'flex-row-reverse space-x-reverse' : ''}`}>
481
- {/* Avatar */}
482
- <div className={`w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 ${
483
- message.role === 'user'
484
- ? 'bg-blue-500 text-white'
485
- : 'bg-gradient-to-r from-purple-500 to-pink-500 text-white'
486
- }`}>
487
- {message.role === 'user' ? (
488
- <User className="w-5 h-5" />
489
- ) : (
490
- <Bot className="w-5 h-5" />
491
- )}
492
- </div>
493
-
494
- {/* Message Bubble */}
495
- <div className={`px-6 py-4 rounded-2xl shadow-sm ${
496
- message.role === 'user'
497
- ? 'bg-blue-500 text-white rounded-tr-sm'
498
- : 'bg-white text-gray-900 rounded-tl-sm border border-gray-200'
499
- }`}>
500
- <div className="text-sm font-medium mb-1 opacity-75">
501
- {message.role === 'user' ? 'You' : 'InsuCompass AI'}
502
- </div>
503
- {/* <div className="whitespace-pre-wrap">{message.content}</div> */}
504
- <ReactMarkdown className="prose prose-sm max-w-none prose-table:border prose-table:border-gray-300 prose-th:bg-gray-100 prose-th:p-2 prose-td:p-2 prose-td:border prose-td:border-gray-300">
505
- {message.content}
506
- </ReactMarkdown>
507
- </div>
508
- </div>
509
- </div>
510
- ))}
511
-
512
- {/* Loading indicator */}
513
- {isLoading && (
514
- <div className="flex justify-start">
515
- <div className="flex items-start space-x-3">
516
- <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">
517
- <Bot className="w-5 h-5" />
518
- </div>
519
- <div className="bg-white px-6 py-4 rounded-2xl rounded-tl-sm border border-gray-200">
520
- <div className="flex space-x-2">
521
- <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce"></div>
522
- <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.1s' }}></div>
523
- <div className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0.2s' }}></div>
524
- </div>
525
- </div>
526
- </div>
527
- </div>
528
- )}
529
-
530
- <div ref={chatEndRef} />
531
- </div>
532
- </div>
533
- </div>
534
-
535
- {/* Message Input */}
536
- <div className="bg-white border-t border-gray-200 p-4">
537
- <div className="max-w-4xl mx-auto">
538
- <div className="flex items-center space-x-4">
539
- <div className="flex-1 relative">
540
- <input
541
- type="text"
542
- value={currentMessage}
543
- onChange={(e) => setCurrentMessage(e.target.value)}
544
- onKeyPress={(e) => e.key === 'Enter' && handleSendMessage()}
545
- placeholder="Type your message..."
546
- 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"
547
- disabled={isLoading}
548
- />
549
- <button
550
- onClick={handleSendMessage}
551
- disabled={isLoading || !currentMessage.trim()}
552
- 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"
553
- >
554
- <Send className="w-4 h-4" />
555
- </button>
556
- </div>
557
- </div>
558
- </div>
559
- </div>
560
- </div>
561
  );
562
-
563
- return phase === 'profiling' ? renderProfileForm() : renderChatInterface();
564
  };
565
 
566
- export default InsuCompassApp;
 
1
+ import React, { useState } from 'react';
2
+ import ProfilingScreen from './screens/ProfileScreen/ProfilingScreen';
3
+ import ChatInterfaceScreen from './screens/ChatInterfaceScreen/ChatInterfaceScreen';
4
+ import { UserProfile } from './interface';
5
 
6
+ const App: React.FC = () => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  const [phase, setPhase] = useState<'profiling' | 'chat'>('profiling');
8
+ const [userProfile, setUserProfile] = useState<UserProfile>({});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ // Handler to move from profiling to chat, passing userProfile if needed
11
+ const handleStartChat = (profile: UserProfile) => {
12
+ console.log('Starting chat with profile:', profile);
13
+ setUserProfile(profile);
 
 
 
 
 
 
 
 
 
 
 
14
  setPhase('chat');
 
 
 
15
  };
16
 
17
+ return phase === 'profiling' ? (
18
+ <ProfilingScreen onComplete={handleStartChat} />
19
+ ) : (
20
+ <ChatInterfaceScreen userProfile={userProfile} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  );
 
 
22
  };
23
 
24
+ export default App;
src/components/EmploymentSection.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Briefcase } from 'lucide-react';
3
+
4
+ interface Props {
5
+ formData: { employment_status: string; citizenship: string };
6
+ onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
7
+ }
8
+
9
+ const EmploymentSection: React.FC<Props> = ({ formData, onChange }) => (
10
+ <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
11
+ <div className="flex items-center space-x-3 mb-4">
12
+ <Briefcase className="w-5 h-5 text-blue-500" />
13
+ <h3 className="text-lg font-semibold text-gray-900">Employment & Status</h3>
14
+ </div>
15
+ <div className="space-y-4">
16
+ <div>
17
+ <label className="block text-sm font-medium text-gray-700 mb-2">Employment Status</label>
18
+ <select
19
+ name="employment_status"
20
+ value={formData.employment_status}
21
+ onChange={onChange}
22
+ 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"
23
+ >
24
+ <option value="">Select an option</option>
25
+ <option value="Employed with employer coverage">Employed with employer coverage</option>
26
+ <option value="Employed without coverage">Employed without coverage</option>
27
+ <option value="Unemployed">Unemployed</option>
28
+ <option value="Retired">Retired</option>
29
+ <option value="Student">Student</option>
30
+ <option value="Self-employed">Self-employed</option>
31
+ </select>
32
+ </div>
33
+ <div>
34
+ <label className="block text-sm font-medium text-gray-700 mb-2">Citizenship Status</label>
35
+ <select
36
+ name="citizenship"
37
+ value={formData.citizenship}
38
+ onChange={onChange}
39
+ 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"
40
+ >
41
+ <option value="">Select an option</option>
42
+ <option value="US Citizen">US Citizen</option>
43
+ <option value="Lawful Permanent Resident">Lawful Permanent Resident</option>
44
+ <option value="Other legal resident">Other legal resident</option>
45
+ <option value="Non-resident">Non-resident</option>
46
+ </select>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ );
51
+
52
+ export default EmploymentSection;
src/components/HouseholdInfoSection.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { Users, DollarSign } from 'lucide-react';
3
+
4
+ interface Props {
5
+ formData: { household_size: string; income: string };
6
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
7
+ }
8
+
9
+ const HouseholdInfoSection: React.FC<Props> = ({ formData, onChange }) => (
10
+ <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
11
+ <div className="flex items-center space-x-3 mb-4">
12
+ <Users className="w-5 h-5 text-blue-500" />
13
+ <h3 className="text-lg font-semibold text-gray-900">Household Information</h3>
14
+ </div>
15
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
16
+ <div>
17
+ <label className="block text-sm font-medium text-gray-700 mb-2">Household Size</label>
18
+ <input
19
+ type="number"
20
+ name="household_size"
21
+ value={formData.household_size}
22
+ onChange={onChange}
23
+ placeholder="Including yourself"
24
+ min="1"
25
+ 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"
26
+ />
27
+ </div>
28
+ <div>
29
+ <label className="block text-sm font-medium text-gray-700 mb-2">Annual Income</label>
30
+ <div className="relative">
31
+ <DollarSign className="absolute left-3 top-3 w-5 h-5 text-gray-400" />
32
+ <input
33
+ type="number"
34
+ name="income"
35
+ value={formData.income}
36
+ onChange={onChange}
37
+ placeholder="55000"
38
+ min="0"
39
+ 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"
40
+ />
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ );
46
+
47
+ export default HouseholdInfoSection;
src/components/LocationSection.tsx ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { MapPin, CheckCircle } from 'lucide-react';
3
+
4
+ interface Props {
5
+ formData: { zip_code: string };
6
+ onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
7
+ isValidatingZip: boolean;
8
+ zipError: string;
9
+ userProfile: { city?: string; county?: string; state?: string };
10
+ }
11
+
12
+ const LocationSection: React.FC<Props> = ({ formData, onChange, isValidatingZip, zipError, userProfile }) => (
13
+ <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
14
+ <div className="flex items-center space-x-3 mb-4">
15
+ <MapPin className="w-5 h-5 text-blue-500" />
16
+ <h3 className="text-lg font-semibold text-gray-900">Location</h3>
17
+ </div>
18
+ <div className="space-y-4">
19
+ <div>
20
+ <label className="block text-sm font-medium text-gray-700 mb-2">
21
+ ZIP Code
22
+ </label>
23
+ <input
24
+ type="text"
25
+ name="zip_code"
26
+ value={formData.zip_code}
27
+ onChange={onChange}
28
+ placeholder="Enter your 5-digit ZIP code"
29
+ 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"
30
+ maxLength={5}
31
+ />
32
+ {isValidatingZip && (
33
+ <p className="text-sm text-blue-600 mt-1">Validating ZIP code...</p>
34
+ )}
35
+ {zipError && (
36
+ <p className="text-sm text-red-600 mt-1">{zipError}</p>
37
+ )}
38
+ </div>
39
+ {userProfile.city && (
40
+ <div className="bg-green-50 border border-green-200 rounded-lg p-4">
41
+ <div className="flex items-center space-x-2">
42
+ <CheckCircle className="w-5 h-5 text-green-500" />
43
+ <span className="font-medium text-green-800">Location Verified</span>
44
+ </div>
45
+ <p className="text-sm text-green-700 mt-1">
46
+ {userProfile.county} County, {userProfile.city}, {userProfile.state}
47
+ </p>
48
+ </div>
49
+ )}
50
+ </div>
51
+ </div>
52
+ );
53
+
54
+ export default LocationSection;
src/components/PersonalInfoSection.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { User } from 'lucide-react';
3
+
4
+ interface Props {
5
+ formData: { age: string; gender: string };
6
+ onChange: (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => void;
7
+ }
8
+
9
+ const PersonalInfoSection: React.FC<Props> = ({ formData, onChange }) => (
10
+ <div className="bg-white rounded-2xl shadow-lg p-6 border border-gray-100">
11
+ <div className="flex items-center space-x-3 mb-4">
12
+ <User className="w-5 h-5 text-blue-500" />
13
+ <h3 className="text-lg font-semibold text-gray-900">Personal Information</h3>
14
+ </div>
15
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
16
+ <div>
17
+ <label className="block text-sm font-medium text-gray-700 mb-2">Age</label>
18
+ <input
19
+ type="number"
20
+ name="age"
21
+ value={formData.age}
22
+ onChange={onChange}
23
+ placeholder="Enter your age"
24
+ min="1"
25
+ max="120"
26
+ 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"
27
+ />
28
+ </div>
29
+ <div>
30
+ <label className="block text-sm font-medium text-gray-700 mb-2">Gender</label>
31
+ <select
32
+ name="gender"
33
+ value={formData.gender}
34
+ onChange={onChange}
35
+ 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"
36
+ >
37
+ <option value="">Select an option</option>
38
+ <option value="Male">Male</option>
39
+ <option value="Female">Female</option>
40
+ <option value="Non-binary">Non-binary</option>
41
+ <option value="Prefer not to say">Prefer not to say</option>
42
+ </select>
43
+ </div>
44
+ </div>
45
+ </div>
46
+ );
47
+
48
+ export default PersonalInfoSection;
src/endpoint.ts ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ const BACKEND_URL = "https://nagur-shareef-shaik-insucompass-api.hf.space/api";
2
+ export const CHAT_ENDPOINT = `${BACKEND_URL}/chat`;
3
+ export const GEODATA_ENDPOINT = `${BACKEND_URL}/geodata`;
src/interface.ts ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export interface ApiResponse {
2
+ updated_profile: UserProfile;
3
+ updated_history: string[];
4
+ is_profile_complete: boolean;
5
+ plan_recommendations?: PlanRecommendations | null;
6
+ }
7
+
8
+ export interface Plan {
9
+ plan_name: string;
10
+ plan_type: string;
11
+ key_features: string[];
12
+ estimated_premium: string;
13
+ reasoning: string;
14
+ }
15
+
16
+ export interface UserProfile {
17
+ zip_code?: string;
18
+ city?: string;
19
+ state?: string;
20
+ county?: string;
21
+ age?: number;
22
+ gender?: string;
23
+ household_size?: number;
24
+ income?: number;
25
+ employment_status?: string;
26
+ citizenship?: string;
27
+ medical_history?: string | null;
28
+ medications?: string | null;
29
+ special_cases?: string | null;
30
+ }
31
+
32
+ export interface PlanRecommendations {
33
+ recommendations: Plan[];
34
+ }
35
+
36
+ export interface ChatMessage {
37
+ role: 'user' | 'agent';
38
+ content: string;
39
+ timestamp: number;
40
+ plans?: Plan[];
41
+ }