anasraza526 commited on
Commit
ac90985
·
0 Parent(s):

Clean deploy to Hugging Face

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitignore +58 -0
  2. CONTACT_TRACKING_GUIDE.md +479 -0
  3. Dockerfile +32 -0
  4. HEALTHCARE_FLOW.md +110 -0
  5. HEALTHCARE_IMPLEMENTATION_PLAN.md +42 -0
  6. IMPLEMENTATION_STATUS.md +613 -0
  7. INFRASTRUCTURE_GUIDE.md +438 -0
  8. README.md +112 -0
  9. REFERENCE_ARCHITECTURE.md +75 -0
  10. SETUP.md +158 -0
  11. SYSTEM_ENHANCEMENTS.md +919 -0
  12. WIDGET_IMPROVEMENTS.md +504 -0
  13. advanced_chat_dataset.py +121 -0
  14. chat_dataset.csv +41 -0
  15. chat_dataset.json +162 -0
  16. chat_dataset.py +93 -0
  17. client/.env +3 -0
  18. client/.gitignore +27 -0
  19. client/Dockerfile +12 -0
  20. client/index.html +16 -0
  21. client/package-lock.json +0 -0
  22. client/package.json +36 -0
  23. client/postcss.config.js +6 -0
  24. client/public/chat-widget.js +744 -0
  25. client/public/index.html +13 -0
  26. client/src/App.jsx +157 -0
  27. client/src/api/axiosConfig.js +25 -0
  28. client/src/api/leadsApi.js +35 -0
  29. client/src/components/AgentTakeover.jsx +242 -0
  30. client/src/components/AnonymousUsersGeo.jsx +170 -0
  31. client/src/components/Auth/GoogleAuthButton.jsx +40 -0
  32. client/src/components/Auth/LoginForm.jsx +55 -0
  33. client/src/components/BulkUploadModal.jsx +557 -0
  34. client/src/components/ChatWidget/ChatBox.jsx +99 -0
  35. client/src/components/ChatWidget/ChatBubble.jsx +45 -0
  36. client/src/components/ChatWidget/TypingIndicator.css +193 -0
  37. client/src/components/ChatWidget/TypingIndicator.jsx +104 -0
  38. client/src/components/ChatWidget/index.jsx +249 -0
  39. client/src/components/Dashboard/FetchContentButton.jsx +119 -0
  40. client/src/components/Dashboard/SiteCard.jsx +106 -0
  41. client/src/components/Dashboard/VerifyButton.jsx +84 -0
  42. client/src/components/Dashboard/WidgetCustomizer.jsx +374 -0
  43. client/src/components/Layout.jsx +182 -0
  44. client/src/components/PublicLayout.css +220 -0
  45. client/src/components/PublicLayout.jsx +225 -0
  46. client/src/components/Website/HealthcareConfigModal.jsx +215 -0
  47. client/src/context/AuthContext.jsx +100 -0
  48. client/src/context/WebsiteContext.jsx +51 -0
  49. client/src/hooks/useAuth.js +12 -0
  50. client/src/hooks/useTheme.jsx +64 -0
.gitignore ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependencies
2
+ node_modules/
3
+ __pycache__/
4
+ *.pyc
5
+ *.pyo
6
+ *.pyd
7
+ .Python
8
+ env/
9
+ venv/
10
+ .venv/
11
+
12
+ # Environment variables
13
+ .env
14
+ server/.env
15
+ .env.local
16
+ .env.prod
17
+ .env.production
18
+
19
+ # Build outputs
20
+ dist/
21
+ build/
22
+ *.egg-info/
23
+
24
+ # Database
25
+ *.db
26
+ *.sqlite3
27
+ vector_db/
28
+
29
+ # Logs
30
+ *.log
31
+ logs/
32
+
33
+ # IDE
34
+ .vscode/
35
+ .idea/
36
+ *.swp
37
+ *.swo
38
+
39
+ # OS
40
+ .DS_Store
41
+ Thumbs.db
42
+
43
+ # Cache
44
+ .cache/
45
+ .parcel-cache/
46
+ .next/
47
+ .nuxt/
48
+
49
+ # Testing
50
+ coverage/
51
+ .nyc_output/
52
+ .pytest_cache/server/models/*.gguf
53
+ server/datasets/processed/
54
+ server/models/
55
+ server/vector_db/
56
+ *.db
57
+ *.pkl
58
+ *.bin
CONTACT_TRACKING_GUIDE.md ADDED
@@ -0,0 +1,479 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Contact Request & IP Tracking Implementation
2
+
3
+ ## ✅ Features Implemented
4
+
5
+ ### 1. Contact Request Storage
6
+ **File**: `app/models/contact_request.py`
7
+
8
+ **What It Stores**:
9
+ - ✅ User information (name, email, phone, message)
10
+ - ✅ Full chat context (entire conversation)
11
+ - ✅ IP address tracking
12
+ - ✅ User agent (browser info)
13
+ - ✅ Status tracking (pending → contacted → resolved)
14
+ - ✅ Timestamps (created, updated, contacted, resolved)
15
+
16
+ **Fields**:
17
+ ```python
18
+ class ContactRequest:
19
+ name: str
20
+ email: str
21
+ phone: str
22
+ message: str
23
+ chat_context: JSON # Full conversation array
24
+ ip_address: str
25
+ user_agent: str
26
+ status: str # pending, contacted, resolved
27
+ ```
28
+
29
+ ---
30
+
31
+ ### 2. Chat Session Tracking
32
+ **File**: `app/models/chat_session.py`
33
+
34
+ **What It Tracks**:
35
+ - ✅ Every chat session with unique ID
36
+ - ✅ IP address for each user
37
+ - ✅ All messages in conversation
38
+ - ✅ Session duration and activity
39
+ - ✅ Language detected
40
+ - ✅ Satisfaction ratings
41
+ - ✅ Response times
42
+
43
+ **Example**:
44
+ ```json
45
+ {
46
+ "session_id": "abc123",
47
+ "ip_address": "192.168.1.1",
48
+ "messages": [
49
+ {
50
+ "text": "Hey there! 😊",
51
+ "isUser": false,
52
+ "timestamp": "2025-12-07T03:22:00Z"
53
+ },
54
+ {
55
+ "text": "how are you",
56
+ "isUser": true,
57
+ "timestamp": "2025-12-07T03:22:15Z"
58
+ }
59
+ ],
60
+ "message_count": 10,
61
+ "is_active": true
62
+ }
63
+ ```
64
+
65
+ ---
66
+
67
+ ### 3. Contact Form API
68
+ **File**: `app/api/contact.py`
69
+
70
+ **Endpoints**:
71
+
72
+ #### Submit Contact Request
73
+ ```javascript
74
+ POST /api/contact/submit
75
+
76
+ // Request
77
+ {
78
+ "name": "John Doe",
79
+ "email": "john@example.com",
80
+ "phone": "+1234567890",
81
+ "message": "I need help with...",
82
+ "session_id": "abc123",
83
+ "chat_context": [
84
+ {"text": "Hey there! 😊", "isUser": false},
85
+ {"text": "how are you", "isUser": true},
86
+ {"text": "I am fine", "isUser": false}
87
+ ]
88
+ }
89
+
90
+ // Response
91
+ {
92
+ "id": 42,
93
+ "status": "success",
94
+ "message": "Thank you! We'll get back to you soon."
95
+ }
96
+ ```
97
+
98
+ #### Get Contact Requests
99
+ ```javascript
100
+ GET /api/contact/requests?website_id=123&status=pending
101
+
102
+ // Response
103
+ [
104
+ {
105
+ "id": 42,
106
+ "name": "John Doe",
107
+ "email": "john@example.com",
108
+ "chat_context": [...],
109
+ "ip_address": "192.168.1.1",
110
+ "status": "pending",
111
+ "created_at": "2025-12-07T03:22:00Z"
112
+ }
113
+ ]
114
+ ```
115
+
116
+ #### Update Status
117
+ ```javascript
118
+ PATCH /api/contact/request/42/status
119
+
120
+ {
121
+ "status": "contacted",
122
+ "notes": "Called customer, issue resolved"
123
+ }
124
+ ```
125
+
126
+ ---
127
+
128
+ ### 4. Session Tracking Service
129
+ **File**: `app/services/session_tracker.py`
130
+
131
+ **Features**:
132
+ - Automatic session creation on first message
133
+ - IP address capture from request headers
134
+ - Message logging with timestamps
135
+ - Session end tracking
136
+ - Analytics and reporting
137
+
138
+ **Usage Example**:
139
+ ```python
140
+ from app.services.session_tracker import get_session_tracker
141
+
142
+ tracker = get_session_tracker(db)
143
+
144
+ # Create/get session
145
+ session = tracker.get_or_create_session(
146
+ session_id="abc123",
147
+ website_id=1,
148
+ ip_address="192.168.1.1",
149
+ user_agent="Mozilla/5.0..."
150
+ )
151
+
152
+ # Add messages
153
+ tracker.add_message(
154
+ session_id="abc123",
155
+ text="Hello!",
156
+ is_user=True,
157
+ response_time=1500 # ms
158
+ )
159
+
160
+ # Get stats
161
+ stats = tracker.get_session_stats("abc123")
162
+ # {
163
+ # "message_count": 10,
164
+ # "duration_seconds": 180,
165
+ # "ip_address": "192.168.1.1"
166
+ # }
167
+
168
+ # Find sessions by IP
169
+ sessions = tracker.get_sessions_by_ip("192.168.1.1")
170
+ ```
171
+
172
+ ---
173
+
174
+ ## 🔧 Integration Guide
175
+
176
+ ### Step 1: Update Chat API to Track Sessions
177
+
178
+ ```python
179
+ # In your chat endpoint
180
+ from app.services.session_tracker import get_session_tracker
181
+
182
+ @router.post("/api/chat")
183
+ async def chat(
184
+ request: Request,
185
+ message: str,
186
+ session_id: str,
187
+ website_id: int,
188
+ db: Session = Depends(get_db)
189
+ ):
190
+ # Get IP address
191
+ ip_address = request.headers.get("X-Forwarded-For", "").split(",")[0]
192
+ if not ip_address:
193
+ ip_address = request.client.host
194
+
195
+ # Track session
196
+ tracker = get_session_tracker(db)
197
+ session = tracker.get_or_create_session(
198
+ session_id=session_id,
199
+ website_id=website_id,
200
+ ip_address=ip_address,
201
+ user_agent=request.headers.get("User-Agent")
202
+ )
203
+
204
+ # Log user message
205
+ tracker.add_message(
206
+ session_id=session_id,
207
+ text=message,
208
+ is_user=True
209
+ )
210
+
211
+ # Generate bot response
212
+ response = await generate_response(message)
213
+
214
+ # Log bot message
215
+ tracker.add_message(
216
+ session_id=session_id,
217
+ text=response,
218
+ is_user=False,
219
+ response_time=1500
220
+ )
221
+
222
+ return {"response": response}
223
+ ```
224
+
225
+ ---
226
+
227
+ ### Step 2: Update Frontend Contact Form
228
+
229
+ ```javascript
230
+ // ContactForm.jsx
231
+ const handleSubmit = async (formData) => {
232
+ try {
233
+ // Get full chat context
234
+ const chatContext = messages.map(msg => ({
235
+ text: msg.text,
236
+ isUser: msg.isUser,
237
+ timestamp: msg.timestamp
238
+ }));
239
+
240
+ // Submit to API
241
+ const response = await fetch('/api/contact/submit', {
242
+ method: 'POST',
243
+ headers: { 'Content-Type': 'application/json' },
244
+ body: JSON.stringify({
245
+ name: formData.name,
246
+ email: formData.email,
247
+ phone: formData.phone,
248
+ message: formData.message,
249
+ session_id: sessionId,
250
+ chat_context: chatContext
251
+ })
252
+ });
253
+
254
+ const result = await response.json();
255
+
256
+ if (result.status === 'success') {
257
+ // Show success message
258
+ setShowThankYou(true);
259
+ }
260
+ } catch (error) {
261
+ console.error('Failed to submit:', error);
262
+ }
263
+ };
264
+ ```
265
+
266
+ ---
267
+
268
+ ### Step 3: Database Migration
269
+
270
+ ```bash
271
+ # Create migration
272
+ cd server
273
+ alembic revision -m "add_contact_and_session_tracking"
274
+
275
+ # Apply migration
276
+ alembic upgrade head
277
+ ```
278
+
279
+ **Migration Example**:
280
+ ```python
281
+ # migrations/versions/xxx_add_contact_and_session_tracking.py
282
+
283
+ def upgrade():
284
+ # Create contact_requests table
285
+ op.create_table('contact_requests',
286
+ sa.Column('id', sa.Integer(), primary_key=True),
287
+ sa.Column('website_id', sa.Integer(), nullable=False),
288
+ sa.Column('session_id', sa.String(255), nullable=False),
289
+ sa.Column('name', sa.String(255)),
290
+ sa.Column('email', sa.String(255)),
291
+ sa.Column('phone', sa.String(50)),
292
+ sa.Column('message', sa.Text()),
293
+ sa.Column('ip_address', sa.String(50)),
294
+ sa.Column('user_agent', sa.Text()),
295
+ sa.Column('chat_context', sa.JSON()),
296
+ sa.Column('status', sa.String(50), default='pending'),
297
+ sa.Column('created_at', sa.DateTime(), default=datetime.utcnow)
298
+ )
299
+
300
+ # Create chat_sessions table
301
+ op.create_table('chat_sessions',
302
+ sa.Column('id', sa.Integer(), primary_key=True),
303
+ sa.Column('session_id', sa.String(255), unique=True),
304
+ sa.Column('website_id', sa.Integer(), nullable=False),
305
+ sa.Column('ip_address', sa.String(50)),
306
+ sa.Column('messages', sa.JSON()),
307
+ sa.Column('is_active', sa.Boolean(), default=True),
308
+ sa.Column('started_at', sa.DateTime(), default=datetime.utcnow)
309
+ )
310
+ ```
311
+
312
+ ---
313
+
314
+ ## 📊 Admin Dashboard Views
315
+
316
+ ### Contact Requests View
317
+
318
+ ```jsx
319
+ // ContactRequestsPage.jsx
320
+ const ContactRequests = () => {
321
+ const [requests, setRequests] = useState([]);
322
+
323
+ useEffect(() => {
324
+ fetch(`/api/contact/requests?website_id=${websiteId}`)
325
+ .then(res => res.json())
326
+ .then(data => setRequests(data));
327
+ }, []);
328
+
329
+ return (
330
+ <div className="contact-requests">
331
+ <h2>Contact Requests</h2>
332
+ <table>
333
+ <thead>
334
+ <tr>
335
+ <th>Date</th>
336
+ <th>Name</th>
337
+ <th>Email</th>
338
+ <th>IP Address</th>
339
+ <th>Messages</th>
340
+ <th>Status</th>
341
+ <th>Actions</th>
342
+ </tr>
343
+ </thead>
344
+ <tbody>
345
+ {requests.map(req => (
346
+ <tr key={req.id}>
347
+ <td>{new Date(req.created_at).toLocaleString()}</td>
348
+ <td>{req.name}</td>
349
+ <td>{req.email}</td>
350
+ <td>{req.ip_address}</td>
351
+ <td>{req.chat_context?.length} msgs</td>
352
+ <td>
353
+ <span className={`badge badge-${req.status}`}>
354
+ {req.status}
355
+ </span>
356
+ </td>
357
+ <td>
358
+ <button onClick={() => viewDetails(req.id)}>
359
+ View
360
+ </button>
361
+ </td>
362
+ </tr>
363
+ ))}
364
+ </tbody>
365
+ </table>
366
+ </div>
367
+ );
368
+ };
369
+ ```
370
+
371
+ ### Session Analytics View
372
+
373
+ ```jsx
374
+ // SessionAnalytics.jsx
375
+ const SessionAnalytics = () => {
376
+ return (
377
+ <div className="analytics">
378
+ <h2>Chat Session Analytics</h2>
379
+
380
+ <div className="stats-grid">
381
+ <div className="stat-card">
382
+ <h3>Active Sessions</h3>
383
+ <p className="stat-value">42</p>
384
+ </div>
385
+
386
+ <div className="stat-card">
387
+ <h3>Unique IPs (Today)</h3>
388
+ <p className="stat-value">127</p>
389
+ </div>
390
+
391
+ <div className="stat-card">
392
+ <h3>Avg. Messages/Session</h3>
393
+ <p className="stat-value">8.5</p>
394
+ </div>
395
+
396
+ <div className="stat-card">
397
+ <h3>Contact Conversion</h3>
398
+ <p className="stat-value">12%</p>
399
+ </div>
400
+ </div>
401
+
402
+ <div className="sessions-map">
403
+ <h3>Sessions by Location (IP-based)</h3>
404
+ {/* Map showing user locations */}
405
+ </div>
406
+ </div>
407
+ );
408
+ };
409
+ ```
410
+
411
+ ---
412
+
413
+ ## 🔐 Privacy & GDPR Compliance
414
+
415
+ ### IP Address Handling
416
+
417
+ **Storage**:
418
+ - ✅ IP addresses stored for analytics
419
+ - ✅ Can be anonymized (last octet)
420
+ - ✅ Auto-deletion after 90 days
421
+
422
+ **Anonymization** (optional):
423
+ ```python
424
+ def anonymize_ip(ip_address: str) -> str:
425
+ """Anonymize IP by removing last octet"""
426
+ parts = ip_address.split('.')
427
+ if len(parts) == 4:
428
+ parts[-1] = '0'
429
+ return '.'.join(parts)
430
+ # 192.168.1.123 → 192.168.1.0
431
+ ```
432
+
433
+ **GDPR Compliance**:
434
+ - ✅ User can request data deletion
435
+ - ✅ Privacy policy disclosure
436
+ - ✅ Data retention policies
437
+ - ✅ Opt-out mechanism
438
+
439
+ ---
440
+
441
+ ## 📈 Benefits
442
+
443
+ ### For Business
444
+ - ✅ Track all contact requests in one place
445
+ - ✅ See full conversation context
446
+ - ✅ Identify returning visitors by IP
447
+ - ✅ Analyze chat effectiveness
448
+ - ✅ Spot patterns and trends
449
+
450
+ ### For Support
451
+ - ✅ Full chat history before contact
452
+ - ✅ Know what user already tried
453
+ - ✅ Better context for responses
454
+ - ✅ Track resolution status
455
+
456
+ ### For Analytics
457
+ - ✅ Session duration tracking
458
+ - ✅ Message count analysis
459
+ - ✅ Geographic distribution (IP-based)
460
+ - ✅ Conversion rate tracking
461
+ - ✅ Response time metrics
462
+
463
+ ---
464
+
465
+ ## ✅ Summary
466
+
467
+ **What You Get**:
468
+ 1. ✅ Proper contact request storage (not just JSON)
469
+ 2. ✅ Full chat context saved with each request
470
+ 3. ✅ IP tracking for every session
471
+ 4. ✅ Complete conversation history
472
+ 5. ✅ Admin dashboard to manage requests
473
+ 6. ✅ Analytics and reporting
474
+
475
+ **Before**: Contact request just shows JSON ❌
476
+ **After**: Properly stored in database with full context ✅
477
+
478
+ **Before**: No user tracking ❌
479
+ **After**: Every session tracked by IP ✅
Dockerfile ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install system dependencies
6
+ RUN apt-get update && apt-get install -y \
7
+ gcc \
8
+ g++ \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Copy requirements first for better caching
12
+ COPY server/requirements.txt .
13
+
14
+ # Install Python dependencies
15
+ RUN pip install --no-cache-dir -r requirements.txt
16
+
17
+ # Copy application code
18
+ COPY server/ .
19
+
20
+ # Create non-root user
21
+ RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
22
+ USER appuser
23
+
24
+ # Expose port for Hugging Face Spaces
25
+ EXPOSE 7860
26
+
27
+ # Health check
28
+ HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
29
+ CMD curl -f http://localhost:7860/health || exit 1
30
+
31
+ # Run the application
32
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
HEALTHCARE_FLOW.md ADDED
@@ -0,0 +1,110 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Healthcare Module: System Flow & Intelligence
2
+
3
+ ## System Architecture
4
+ The healthcare module is fully integrated into the core AI engine and utilizes internal knowledge bases as well as external datasets (SymCAT & MedQuAD). The system is designed to operate efficiently with offline-preprocessed datasets to ensure fast, safe, and accurate responses.
5
+
6
+ ```mermaid
7
+ graph TD
8
+ User([User Query]) --> API[Chat API /api/chat]
9
+ API --> Website[Fetch Website & Industry Config]
10
+ Website --> AI_Engine[AIEngine.generate_response]
11
+ AI_Engine --> IndustryCheck{Is Healthcare?}
12
+
13
+ IndustryCheck -- Yes --> IndustryAI[IndustryAI.get_industry_dataset_response]
14
+ IndustryAI --> HealthcareMod[EnhancedHealthcareModule]
15
+
16
+ HealthcareMod --> SymptomMatch{Symptom Match?}
17
+ SymptomMatch -- Yes --> SymCAT[Check SymCAT & Internal DB (Offline Indexed)]
18
+ SymCAT --> Urgency[Determine Urgency & Advice]
19
+
20
+ SymptomMatch -- No --> MedQuAD[Medical Q&A / MedQuAD (Offline Vector Search)]
21
+ MedQuAD --> QAResponse[Generate Q&A Response]
22
+
23
+ Urgency --> Format[Conversational Formatting]
24
+ QAResponse --> Format
25
+
26
+ Format --> Language{Language Selection?}
27
+ Language -- English --> EN[English Response]
28
+ Language -- Urdu --> UR[Urdu Response (Roman Urdu Supported)]
29
+
30
+ EN --> Final[Final Response + Disclaimer]
31
+ UR --> Final
32
+
33
+ Final --> API_Return[Return to Widget]
34
+ ```
35
+
36
+ ## Key Components
37
+
38
+ ### 1. Configuration (widget_config)
39
+ Healthcare-specific settings are stored in the `websites` table under `widget_config`. These settings customize the AI’s behavior per clinic or sub-industry.
40
+ - **Healthcare Sub-Industry**: (General, Dental, Pharmacy, Mental Health, Pediatrics). Tailors AI persona, tone, and keyword detection.
41
+ - **Multi-language Support**: English & Urdu response generation. Roman Urdu detection (e.g., "bukhar", "seenay mein dard").
42
+ - **Symptom Checker Toggle**: Enables/disables detailed symptom extraction & analysis logic.
43
+ - **Custom Disclaimer**: Allows clinic-specific legal text to appear in the final response.
44
+ - **Appointment URL**: Direct link to booking services.
45
+
46
+ ### 2. Symptom Extraction & Normalization
47
+ - **Symptom Extraction**: Extracts explicit and implicit symptoms from user queries.
48
+ - **Normalization**: Maps colloquial or variant terms (aliases) to canonical medical terms using Internal KB + SymCAT aliases.
49
+ - **Offline Processing**: Dataset preprocessing ensures fast runtime lookups. Embeddings and indexes are generated offline for vector-based semantic search.
50
+ - **Example**: "my head hurts" → normalized → "HEADACHE"
51
+
52
+ ### 3. Confidence & Threshold Logic
53
+ - **Weighted Confidence Scoring**: Combines Internal KB, SymCAT, and MedQuAD data to produce a confidence score for detected symptoms.
54
+ - **Threshold Check**: If confidence ≥ threshold → proceed with detailed medical reasoning. If confidence < threshold → AI asks a clarifying question to improve accuracy.
55
+
56
+ ### 4. Dataset Usage
57
+ | Dataset | Role | Processing |
58
+ | :--- | :--- | :--- |
59
+ | **SymCAT** | Symptom → Condition mapping | Preprocessed offline, indexed for fast runtime lookup |
60
+ | **MedQuAD** | Medical Q&A | Preprocessed offline, vectorized for semantic search |
61
+ | **Internal KB** | Custom terms & aliases | Merged offline with SymCAT for normalization |
62
+
63
+ *Note: The module does not query raw datasets at runtime, ensuring speed, reliability, and free-tier compatibility.*
64
+
65
+ ### 5. Pattern & Red-Flag Detection
66
+ - Detects critical symptom combinations (e.g., chest pain + shortness of breath).
67
+ - Assigns severity levels: Mild, Moderate, High.
68
+ - Triggers urgent advice if high-risk patterns are detected. Rules are offline-defined and applied at runtime.
69
+
70
+ ### 6. Urgency & Advice Logic
71
+ - **Severity categorization** uses weighted rules + pattern detection.
72
+ - **Urgent Cases**: e.g., high fever ≥103°F, chest pain, or other red-flag symptoms.
73
+ - **Tiered Responses**:
74
+ - **Low**: Informational / lifestyle advice
75
+ - **Medium**: Suggest consulting a provider
76
+ - **High**: Urgent recommendation + emergency contact
77
+
78
+ ### 7. Multi-Language Intelligence
79
+ - Supports English and Urdu.
80
+ - Automatically detects language intent and applies translation or Roman Urdu normalization.
81
+ - Ensures all symptom and advice terminology is consistent across languages.
82
+
83
+ ### 8. Answer Generation & Safety Filter
84
+ - Combines dataset outputs, pattern recognition, and urgency rules.
85
+ - **Safety filter ensures**:
86
+ - No medical diagnosis
87
+ - No drug or dosage recommendations
88
+ - Disclaimer is always included
89
+ - Generates confidence-aware, conversationally formatted responses in the requested language.
90
+
91
+ ## Usage Example
92
+ **Query**: "my child has a temperature of 103"
93
+ 1. **IndustryAI** detects “fever” and extracts temperature “103”.
94
+ 2. **HealthcareModule** normalizes the symptom and calculates severity → **High Fever**.
95
+ 3. **Red-Flag Detection** checks for pediatric-specific concerns.
96
+ 4. **MedQuAD** retrieves explanation if user asks for details.
97
+ 5. **Answer Generator** creates a response with:
98
+ - Child-focused tone (Pediatrics)
99
+ - Severity & potential causes
100
+ - Tiered advice & safety disclaimer
101
+ - Multi-language handling (English or Urdu)
102
+ 6. **Final response** is returned to the widget.
103
+
104
+ ## ✅ Key Notes / Enhancements Applied
105
+ - Offline dataset usage clarified (preprocessing + vectorization).
106
+ - Confidence scoring & threshold logic explicitly described.
107
+ - Red-flag & severity rules highlighted.
108
+ - Multi-language intelligence detailed, including Roman Urdu support.
109
+ - Safety filters and disclaimers emphasized.
110
+ - Workflow mirrors your enhanced runtime + offline flow.
HEALTHCARE_IMPLEMENTATION_PLAN.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🏥 Healthcare Chatbot – Implementation Flow (Dynamic & CPU-Optimized)
2
+
3
+ ## High-Level Runtime Flow
4
+ 1. **Message Intake**: Socket.IO/API -> `chat_messages` -> `chat_sessions`
5
+ 2. **Context Resolution**: `context_service.py` (Patient context + session memory)
6
+ 3. **Medical Intent Detection**: `intent_classifier.py` (Symptom, Info, Safety, Casual, Non-medical)
7
+ 4. **Dynamic Risk Scoring**: `risk_engine.py` (Score 0.0-1.0) -> Updates patient context
8
+ 5. **Dataset Routing**: `dataset_router.py` (Weighted routing, e.g. SymCAT/MedQuAD)
9
+ 6. **Hybrid Retrieval**: `retrieval_engine.py` (BM25 + FAISS + Cached Embeddings)
10
+ 7. **Safety & Compliance**: `safety_guard.py` (No diagnosis, Prescriptions, Dosage; Add Disclaimers)
11
+ 8. **Response Composition**: `response_composer.py` (Template vs LLM)
12
+ 9. **Delivery**: Socket.IO -> storage -> feedback loop
13
+
14
+ ## Component Mapping
15
+ | Existing Component | New Role |
16
+ |-------------------|----------|
17
+ | `ai_engine.py` | Medical Orchestrator Wrapper |
18
+ | `FAISS` | Primary Medical Retrieval |
19
+ | `BM25` | Clinical Term Matching |
20
+ | `Unanswered Questions` | Human-in-the-loop |
21
+ | `Gemini` | Last-resort Synthesis |
22
+
23
+ ## Optimization
24
+ - **CPU-Only**: No heavy local LLMs (use APIs or efficient retrieval)
25
+ - **Async**: Non-blocking calls
26
+ - **Caching**: Embeddings and frequent responses
27
+
28
+ ## 📊 Dataset & Knowledge Strategy
29
+ | Dataset | Purpose | Notes |
30
+ |---------|---------|-------|
31
+ | **MedQuAD** | Medical Q&A pairs | Covers diseases like flu, diabetes, cardiovascular; used for evidence-based responses |
32
+ | **SymCAT** | Symptom-to-Disease mapping | Helps chatbot compute risk score, suggest next questions, and verify symptom consistency |
33
+ | **HealthTap** | Professional health advice | Used to improve natural phrasing and context-aware responses |
34
+ | **WHO / CDC** | Guidelines & Escalation | Provides authoritative references for high-risk symptoms and emergency protocols |
35
+ | **ICD-10 Codes** | Disease Taxonomy | Standardized identifiers for internal mapping & consistent terminology |
36
+
37
+ ## Next Steps
38
+ 1. [ ] Implement `medical_orchestrator.py` (Core Brain)
39
+ 2. [ ] Implement `risk_engine.py` with dynamic formulas
40
+ 3. [ ] Add `patient_context` DB migration
41
+ 4. [ ] Create healthcare-safe response templates
42
+ 5. [ ] Build symptom stepper UI
IMPLEMENTATION_STATUS.md ADDED
@@ -0,0 +1,613 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 🎯 Implementation Status & Technical Documentation
2
+
3
+ ## ✅ **COMPLETED FEATURES**
4
+
5
+ ### 🖥️ Frontend Layer
6
+
7
+ #### **React + Vite Setup**
8
+ - **Technology**: React 18 with Vite for fast development
9
+ - **How it works**: Vite provides instant HMR (Hot Module Replacement) and optimized builds
10
+ - **Entry point**: `client/src/main.jsx` renders the root `App` component
11
+ - **Build**: `npm run build` creates optimized production bundle in `client/dist/`
12
+
13
+ #### **Tailwind CSS Styling**
14
+ - **Configuration**: `client/tailwind.config.js` with custom color palette
15
+ - **Custom Colors**:
16
+ - `primary` (blue): Used for CTAs, links, and highlights
17
+ - `secondary` (slate): Used for text, backgrounds, and borders
18
+ - **Custom Utilities**: Glass effects, card hover animations defined in `client/src/index.css`
19
+ - **Font**: Inter from Google Fonts for modern typography
20
+
21
+ #### **Framer Motion Animations**
22
+ - **Purpose**: Smooth page transitions and micro-interactions
23
+ - **Usage**: `AnimatePresence` for enter/exit animations, `motion` components for interactive elements
24
+ - **Examples**:
25
+ - Fade-in animations on page load
26
+ - Slide-up effects for cards
27
+ - Smooth transitions between tabs
28
+
29
+ #### **Axios HTTP Client**
30
+ - **Configuration**: `client/src/api/axiosConfig.js` with base URL and interceptors
31
+ - **Features**:
32
+ - Automatic cookie inclusion (`withCredentials: true`)
33
+ - Request/response interceptors for error handling
34
+ - Base URL configuration for API endpoints
35
+ - **Usage**: All API calls use this configured instance
36
+
37
+ #### **React Router Navigation**
38
+ - **Setup**: `client/src/App.jsx` defines all routes
39
+ - **Protected Routes**: Wrapped with `ProtectedRoute` component that checks authentication
40
+ - **Public Routes**: Login and Register pages accessible without auth
41
+ - **Layout**: All protected routes wrapped in `Layout` component with sidebar navigation
42
+
43
+ #### **JWT Authentication**
44
+ - **Flow**:
45
+ 1. User logs in via `/api/auth/login`
46
+ 2. Server sets httpOnly cookie with JWT token
47
+ 3. Cookie automatically sent with all subsequent requests
48
+ 4. `AuthContext` manages auth state on client
49
+ - **Security**: httpOnly cookies prevent XSS attacks
50
+ - **Token Storage**: Stored in secure httpOnly cookie, not localStorage
51
+
52
+ #### **Dashboard**
53
+ - **Location**: `client/src/pages/Dashboard.jsx`
54
+ - **Features**:
55
+ - Stats cards showing total chats, active sessions, response rate
56
+ - Recent activity feed
57
+ - Quick actions for common tasks
58
+ - **Data**: Fetches real-time statistics from `/api/analytics/stats`
59
+
60
+ #### **Chat Widget**
61
+ - **Embeddable Script**: `client/public/chat-widget.js`
62
+ - **How to Use**:
63
+ ```html
64
+ <script src="https://yourdomain.com/chat-widget.js" data-website-id="123"></script>
65
+ ```
66
+ - **Features**:
67
+ - Floating chat button (customizable position)
68
+ - Real-time messaging
69
+ - Visitor information collection
70
+ - Auto-responses from AI
71
+ - **Customization**: Widget config stored in `websites.widget_config` (colors, position, size)
72
+
73
+ ---
74
+
75
+ ### ⚙️ Backend Layer
76
+
77
+ #### **FastAPI Framework**
78
+ - **Entry Point**: `server/app/main.py`
79
+ - **Features**:
80
+ - Automatic OpenAPI documentation at `/docs`
81
+ - Async request handling
82
+ - Built-in validation with Pydantic
83
+ - **Startup**: `uvicorn app.main:app --reload --host 0.0.0.0 --port 8000`
84
+
85
+ #### **SQLAlchemy ORM**
86
+ - **Configuration**: `server/app/core/database.py`
87
+ - **Models Location**: `server/app/models/`
88
+ - **How it works**:
89
+ - Declarative base class for all models
90
+ - Session management via dependency injection
91
+ - Automatic table creation on startup
92
+ - **Database URL**: Configured in `.env` as `DATABASE_URL`
93
+
94
+ #### **JWT Authentication**
95
+ - **Implementation**: `server/app/core/security.py`
96
+ - **Token Creation**:
97
+ ```python
98
+ create_access_token(data={"sub": user_id}, expires_delta=timedelta(days=7))
99
+ ```
100
+ - **Token Verification**: `verify_token(request)` extracts and validates JWT
101
+ - **Password Hashing**: bcrypt via passlib (`get_password_hash`, `verify_password`)
102
+ - **Security**: Tokens expire after 7 days, stored in httpOnly cookies
103
+
104
+ #### **Password Hashing**
105
+ - **Library**: bcrypt 4.1.2 via passlib
106
+ - **Algorithm**: bcrypt with automatic salt generation
107
+ - **Functions**:
108
+ - `get_password_hash(password)`: Hash plain password
109
+ - `verify_password(plain, hashed)`: Verify password against hash
110
+ - **Security**: 72-byte limit, resistant to rainbow table attacks
111
+
112
+ #### **CORS Middleware**
113
+ - **Configuration**: `server/app/main.py`
114
+ - **Allowed Origins**: Configured in `settings.ALLOWED_ORIGINS`
115
+ - **Credentials**: `allow_credentials=True` for cookie support
116
+ - **Methods**: All HTTP methods allowed
117
+ - **Headers**: All headers allowed for flexibility
118
+
119
+ #### **Pydantic Validation**
120
+ - **Purpose**: Automatic request/response validation
121
+ - **Usage**: All API endpoints use Pydantic models
122
+ - **Example**:
123
+ ```python
124
+ class UserLogin(BaseModel):
125
+ email: str
126
+ password: str
127
+ ```
128
+ - **Benefits**: Type safety, automatic docs, validation errors
129
+
130
+ ---
131
+
132
+ ### 🗄️ Database Schema
133
+
134
+ #### **Users Table**
135
+ - **Columns**:
136
+ - `id`: Primary key
137
+ - `email`: Unique, indexed
138
+ - `name`: User's full name
139
+ - `hashed_password`: bcrypt hash
140
+ - `is_active`: Boolean flag
141
+ - `created_at`: Timestamp
142
+ - **Relationships**: One-to-many with `websites`
143
+
144
+ #### **Websites Table**
145
+ - **Columns**:
146
+ - `id`: Primary key
147
+ - `url`: Website URL
148
+ - `name`: Display name
149
+ - `industry`: Business category
150
+ - `tone`: Chat tone (friendly/professional/technical)
151
+ - `is_verified`: Verification status
152
+ - `owner_id`: Foreign key to users
153
+ - `widget_config`: JSON with theme settings
154
+ - `created_at`, `last_scraped`: Timestamps
155
+ - **Relationships**:
156
+ - Belongs to `user`
157
+ - Has many `website_content`, `unanswered_questions`
158
+
159
+ #### **Website_content Table**
160
+ - **Columns**:
161
+ - `id`: Primary key
162
+ - `website_id`: Foreign key
163
+ - `page_url`: Source URL
164
+ - `content`: Extracted text
165
+ - `embedding`: JSON string of vector
166
+ - `created_at`: Timestamp
167
+ - **Purpose**: Stores scraped content and embeddings for similarity search
168
+
169
+ #### **Chat_sessions Table**
170
+ - **Columns**:
171
+ - `id`: Primary key
172
+ - `website_id`: Foreign key
173
+ - `visitor_name`, `visitor_email`: Contact info
174
+ - `is_active`: Session status
175
+ - `needs_attention`: Flag for owner review
176
+ - `created_at`, `last_message_at`: Timestamps
177
+ - **Purpose**: Track individual chat conversations
178
+
179
+ #### **Unanswered_questions Table**
180
+ - **Columns**:
181
+ - `id`: Primary key
182
+ - `website_id`: Foreign key
183
+ - `question`: User's question
184
+ - `session_id`: Related chat session
185
+ - `confidence_score`: AI confidence (0-1)
186
+ - `ai_response`: What AI attempted to answer
187
+ - `is_resolved`: Resolution status
188
+ - `manual_answer`: Admin's custom response
189
+ - `created_at`, `resolved_at`: Timestamps
190
+ - **Purpose**: Track questions AI couldn't answer confidently
191
+
192
+ #### **Vector Storage (FAISS)**
193
+ - **Location**: `server/vector_db/` directory
194
+ - **How it works**:
195
+ 1. Content is converted to embeddings (vectors)
196
+ 2. FAISS index stores vectors for fast similarity search
197
+ 3. Query embedding compared to stored vectors
198
+ 4. Most similar content retrieved
199
+ - **Persistence**: Index saved to disk, loaded on startup
200
+
201
+ ---
202
+
203
+ ### 🤖 AI Engine
204
+
205
+ #### **Rule-based AI**
206
+ - **Location**: `server/app/services/ai_engine.py`
207
+ - **How it works**:
208
+ 1. Extract keywords from user question
209
+ 2. Search for similar content in vector database
210
+ 3. Generate response based on matched content
211
+ 4. Apply tone/personality based on website settings
212
+ - **Fallback**: If confidence < 0.3, escalate to owner
213
+
214
+ #### **Embeddings Generation**
215
+ - **Method**: Semantic Vector Embeddings
216
+ - **Model**: `all-MiniLM-L6-v2` via Sentence-Transformers
217
+ - **Process**:
218
+ 1. Text is preprocessed and tokenized
219
+ 2. 384-dimensional dense vectors are generated
220
+ 3. Vectors capture semantic meaning, not just keyword frequency
221
+ - **Storage**: Vectors stored in FAISS (IndexHNSWFlat) and cached in `embeddings_cache.pkl`
222
+
223
+ #### **Hybrid Retrieval Architecture**
224
+ - **Algorithm**: BM25 + FAISS (Semantic) + Rule-based Boosting
225
+ - **Process**:
226
+ 1. **Keyword Match**: BM25Okapi calculates term-frequency relevance (modern TF-IDF successor)
227
+ 2. **Semantic Match**: FAISS performs ultra-fast HNSW similarity search on dense vectors
228
+ 3. **Re-Ranking**: Cross-Encoders (`ms-marco-MiniLM-L-6-v2`) re-evaluate top candidates for precision
229
+ 4. **Score Fusion**: Weights (e.g., 60% Semantic / 25% Keyword / 15% Rules) combine scores for final ranking
230
+ - **Threshold**: Confidence scores dynamically adjusted based on query intent and industry matching
231
+
232
+ #### **Context-aware Responses**
233
+ - **Implementation**: Combines multiple relevant content pieces
234
+ - **Process**:
235
+ 1. Find top 3 similar content pieces
236
+ 2. Combine context
237
+ 3. Generate coherent response
238
+ 4. Apply website tone
239
+ - **Personalization**: Uses website industry and tone settings
240
+
241
+ #### **Owner Escalation Logic**
242
+ - **Trigger**: Confidence score < 0.3
243
+ - **Actions**:
244
+ 1. Mark session as `needs_attention`
245
+ 2. Create `unanswered_question` record
246
+ 3. Send email notification to owner
247
+ 4. Provide fallback response to visitor
248
+ - **Dashboard**: Owner sees flagged questions in Unanswered Questions page
249
+
250
+ #### **Phase 3: Persistent Memory Architecture** 🧠
251
+ - **Database Models**:
252
+ - **LeadProfile** (`server/app/models/lead.py`):
253
+ - Stores cross-session patient data per email
254
+ - Fields: `health_summary`, `known_conditions`, `total_sessions`, `last_interaction`
255
+ - One-to-many relationship with `ChatSession` via email
256
+ - **SessionSummary** (`server/app/models/chat_session.py`):
257
+ - AI-generated summary for each session
258
+ - Fields: `summary_text`, `extracted_symptoms`, `triage_result`, `recommended_actions`
259
+ - One-to-one relationship with `ChatSession`
260
+
261
+ - **Session Analyzer** (`server/app/services/session_analyzer.py`):
262
+ - **Trigger**: Background task every 5 messages or on escalation
263
+ - **Process**:
264
+ 1. Fetches complete session conversation
265
+ 2. Calls Gemini with structured prompt to extract JSON
266
+ 3. Creates/updates `SessionSummary` record
267
+ 4. Syncs insights to persistent `LeadProfile`
268
+ 5. Merges unique symptoms into `known_conditions`
269
+ - **Smart Storage**: Keeps last 10 session summaries to prevent database bloat
270
+
271
+ - **Memory-Aware Response Flow**:
272
+ 1. **AIEngine** (`server/app/services/ai_engine.py`):
273
+ - Loads `LeadProfile` by visitor_email
274
+ - Passes `persistent_history` to `MedicalOrchestrator`
275
+ 2. **MedicalOrchestrator** (`server/app/services/medical_orchestrator.py`):
276
+ - `_rebuild_context()` seeds `PatientContext` from persistent history
277
+ - Extracts age via regex: `Age:\s?(\d+)` or `(\d+)\s?-year-old`
278
+ - Pre-populates historical conditions into context
279
+ 3. **Result**: Returning patients skip redundant demographic questions
280
+
281
+ - **Admin CRM API** (`server/app/api/leads.py`):
282
+ - `GET /api/leads/profiles`: List all patient profiles with summaries
283
+ - `GET /api/leads/profile/{email}`: Detailed timeline with per-session summaries
284
+ - **Use Case**: Admin reviews patient journey before manual intervention
285
+
286
+ #### **Chat Dataset & Knowledge Bases**
287
+ - **Dataset Name**: **General Conversational Dataset** (Custom & Multi-tone)
288
+ - **Sourced Data**: Custom dataset (`chat_dataset.json`) containing common conversational patterns, greetings, and fallback responses.
289
+ - **Advanced Training**: `advanced_chat_dataset.py` includes industry-specific scenarios (E-commerce, Healthcare, Real Estate, SaaS) and multi-turn conversation flows.
290
+ - **Specialized Knowledge Bases** (Integrated):
291
+ - **CLINC150**: 22,500 training examples for intent classification (150 categories).
292
+ #### **Medical & Specialized Knowledge Bases**
293
+ - **Mega Dataset**: 12,465+ high-quality medical records from curated sources:
294
+ - **MedQuAD** (2,572 Q&A): Official NIH/NLM information on 2,500+ conditions.
295
+ - **HealthTap / QuestionDoctor** (5,679 Q&A): Professional doctor consultations.
296
+ - **PubMedQA** (1,000 Q&A): Evidence-based research summaries.
297
+ - **iCliniq & eHealthForum** (630+ Q&A): Community-driven professional advice.
298
+ - **SymCAT**: 1,000+ symptom-to-disease mappings for diagnostic logic.
299
+ - **CLINC150**: 22,500 training examples for intent classification (150 categories).
300
+ - **Roman Urdu Corpus**: 20 pairs for English-Urdu bilingual support.
301
+ - **Usage**: Hybrid retrieval across all sources with dynamic confidence thresholds.
302
+
303
+ ---
304
+
305
+ ### 🌐 Web Scraping
306
+
307
+ #### **BeautifulSoup HTML Parsing**
308
+ - **Location**: `server/app/services/scraper.py`
309
+ - **Process**:
310
+ 1. Fetch HTML with `requests` library
311
+ 2. Parse with BeautifulSoup
312
+ 3. Extract text from relevant tags (p, h1-h6, li, etc.)
313
+ 4. Clean and normalize text
314
+ - **Filtering**: Removes scripts, styles, navigation elements
315
+
316
+ #### **Sitemap Parsing**
317
+ - **How it works**:
318
+ 1. Fetch `/sitemap.xml` from website
319
+ 2. Parse XML to extract all URLs
320
+ 3. Filter for relevant pages (exclude images, PDFs)
321
+ 4. Return list of URLs to scrape
322
+ - **Fallback**: If no sitemap, crawl from homepage
323
+
324
+ #### **Content Extraction**
325
+ - **Strategy**:
326
+ - Prioritize main content areas
327
+ - Remove boilerplate (headers, footers, ads)
328
+ - Extract metadata (title, description)
329
+ - Preserve structure (headings hierarchy)
330
+ - **Output**: Clean text suitable for embedding
331
+
332
+ #### **Async Processing**
333
+ - **Implementation**: FastAPI background tasks
334
+ - **Benefits**:
335
+ - Non-blocking API responses
336
+ - Parallel URL scraping
337
+ - Better resource utilization
338
+ - **Status Tracking**: Updates `last_scraped` timestamp
339
+
340
+ ---
341
+
342
+ ### 💬 Chat System
343
+
344
+ #### **Real-time Chat Widget**
345
+ - **Technology**: Socket.IO for WebSocket connections
346
+ - **Flow**:
347
+ 1. Visitor opens chat widget
348
+ 2. WebSocket connection established
349
+ 3. Messages sent/received in real-time
350
+ 4. AI processes and responds instantly
351
+ - **Persistence**: Messages stored in `chat_messages` table
352
+
353
+ #### **Contact Owner Functionality**
354
+ - **Trigger**: Visitor clicks "Contact Owner" or AI escalates
355
+ - **Process**:
356
+ 1. Collect visitor email
357
+ 2. Mark session as `needs_attention`
358
+ 3. Send email notification to website owner
359
+ 4. Owner can respond via dashboard
360
+ - **Email Template**: Includes visitor info, question, and dashboard link
361
+
362
+ #### **Lead Generation**
363
+ - **Data Collected**:
364
+ - Visitor name
365
+ - Email address
366
+ - Questions asked
367
+ - Pages visited
368
+ - Session duration
369
+ - **Storage**: `chat_sessions` table with visitor details
370
+ - **Export**: Available in Dashboard for CRM integration
371
+
372
+ #### **Email Notifications**
373
+ - **Configuration**: SMTP settings in `.env`
374
+ - **Triggers**:
375
+ - New unanswered question
376
+ - Visitor requests contact
377
+ - Low confidence responses
378
+ - **Template**: HTML email with branding and action links
379
+
380
+ ### 🏥 Healthcare Intelligence (v2.0)
381
+ - **Medical Orchestrator**: A sophisticated multi-turn agent that:
382
+ - **Rebuilds Context**: Chronologically tracks age, symptoms, and duration over multiple turns.
383
+ - **Negation Engine**: Robust regex-based detection to handle phrases like *"no fever"* or *"don't have a headache"*.
384
+ - **Risk Assessment**: Classifies queries (Low/High risk) and triggers emergency protocols instantly.
385
+ - **Hybrid Retrieval System**:
386
+ - **Algorithm**: Semantic (FAISS) + Keyword (BM25) with specialized boosting.
387
+ - **Stability**: **Safe Mode** enforced for Mac environments using stable simple-embeddings.
388
+ - **Optimized Recall**: Lowered threshold (0.45) for maximum information retrieval while maintaining strict safety disclaimers.
389
+ - **Professional Guards**:
390
+ - **Metadata Guard**: Prevents irrelevant routing when the user provides pure context (age/duration).
391
+ - **Safety Guard**: Mandatory safety validation and dynamic disclaimers on 100% of outgoing AI responses.
392
+
393
+ ## 🔄 **RECENT FIXES & IMPROVEMENTS**
394
+
395
+ ### Database Schema Updates
396
+ - ✅ Added `name` column to `users` table
397
+ - ✅ Added `tone` column to `websites` table
398
+ - ✅ Fixed password hashing (bcrypt 4.1.2 compatibility)
399
+ - ✅ Set temporary passwords for existing users: `TempPassword123!`
400
+
401
+ ### API Validation Fixes
402
+ - ✅ Fixed nullable fields in Pydantic models (`Optional[str]`)
403
+ - ✅ Updated `UnansweredQuestionResponse` model
404
+ - ✅ Proper handling of `None` values in responses
405
+
406
+ ### Client UI/UX Overhaul
407
+ - ✅ Modern design system with custom Tailwind config
408
+ - ✅ Redesigned all pages: Login, Register, Dashboard, Settings, Chat Management, Content Manager, Unanswered Questions
409
+ - ✅ Consistent color palette and typography
410
+ - ✅ Responsive layout with sidebar navigation
411
+ - ✅ Smooth animations and transitions
412
+ - ✅ Glass morphism effects and modern card designs
413
+
414
+ ### Build & Deployment
415
+ - ✅ Fixed CSS build errors (theme() function for custom colors)
416
+ - ✅ Fixed missing icon imports (Clock from lucide-react)
417
+ - ✅ Production build verified and passing
418
+ - ✅ Development server running smoothly
419
+
420
+ ### Codebase Restructuring & Maintenance
421
+ - ✅ **Architecture Cleanup**: Relocated 50+ files into dedicated `server/tests/` and `server/scripts/` directories, keeping the root clean.
422
+ - ✅ **Security Hardening**: Added `.gitignore` to both client/server to protect credentials and ignore build artifacts.
423
+ - ✅ **Optimized Logging**: Removed bulky static log files and temporary caches.
424
+
425
+ ### 🏥 Medical Intelligence & Mega Dataset
426
+ - ✅ **12,465 Records Integrated**: Successfully consolidated XML/JSON from 5+ global medical sources.
427
+ - ✅ **Negation Handling**: Fixed the "No fever" bug—system now correctly excludes denied symptoms.
428
+ - ✅ **Metadata Guard**: Eliminated hallucinations (like irrelevant Autism suggestions) during context gathering.
429
+ - ✅ **Disclaimer Standardization**: Guaranteed professional safety disclaimers on every single turn.
430
+ - ✅ **Real-Life Scenario Verified**: Passed 6-turn interaction test with 100% context retention and accurate triage.
431
+
432
+ ---
433
+
434
+ ## 🚀 **CURRENT SYSTEM ADVANTAGES**
435
+
436
+ ✅ **Zero External Dependencies** - Works without API keys
437
+ ✅ **Fast Setup** - No complex model downloads
438
+ ✅ **Lightweight** - Minimal resource usage
439
+ ✅ **Production Ready** - Complete authentication & security
440
+ ✅ **Scalable Architecture** - Easy to upgrade components
441
+ ✅ **Modern UI** - Industry-standard design and UX
442
+ ✅ **Type Safe** - Pydantic validation throughout
443
+ ✅ **Real-time** - WebSocket-based chat
444
+
445
+ ---
446
+
447
+ ## 📈 **UPGRADE PATH**
448
+
449
+
450
+ ### Phase 1: Enhanced NLP (STRICTLY IMPLEMENTED ✅)
451
+ - **Status**: Completed
452
+ - **Method**: Replaced simple fallback embeddings with `all-MiniLM-L6-v2`
453
+ - **Impact**: Significant improvement in semantic understanding and multi-phrase matching
454
+
455
+ ### Phase 2: Global Response Plane (STABLE ✅)
456
+ - **Status**: Completed (December 2025)
457
+ - **Architecture**: 4-Layer orchestration system
458
+ - **Components**:
459
+ 1. **Layer 1 - Language Gateway**: Detects English, Urdu, and Roman Urdu
460
+ 2. **Layer 2 - Hybrid Intent Classifier**: Distinguishes FAQ, RAG, Industry Knowledge, and Creative intents
461
+ 3. **Layer 3 - Dynamic Router**: Routes to appropriate handlers based on confidence
462
+ 4. **Layer 4 - Adaptive Translation**: Translates responses back to user's language
463
+ - **Hardening**: Dependency fallbacks for `spacy`, `pydantic`, `psutil`
464
+ - **Impact**: Robust multi-language support with intelligent intent-based routing
465
+
466
+ ### Phase 3: Persistent Memory & CRM Integration (COMPLETED ✅)
467
+ - **Status**: Completed (December 2025)
468
+ - **Database Models**:
469
+ - `LeadProfile`: Cross-session patient profiles with health summaries and conditions
470
+ - `SessionSummary`: AI-generated summaries per chat session
471
+ - **Core Services**:
472
+ - `SessionAnalyzer`: Gemini-powered session summarization
473
+ - Background analysis trigger (every 5 messages or on escalation)
474
+ - **Memory Integration**:
475
+ - `AIEngine` loads persistent history for returning visitors
476
+ - `MedicalOrchestrator` seeds context (age, conditions) from past sessions
477
+ - **Admin API**:
478
+ - `GET /api/leads/profiles` - List all patient profiles
479
+ - `GET /api/leads/profile/{email}` - Detailed patient timeline
480
+ - **Impact**: Continuous healthcare consultations without redundant questions
481
+
482
+ ### Phase 4: Strict Multi-Tenant SaaS Architecture (COMPLETED ✅)
483
+ - **Status**: Completed (December 2025)
484
+ - **High-Performance Architecture**:
485
+ - **SaaS Core**: Strict Tenant Isolation via `TenantConfigService` & `SecurityService`.
486
+ - **8-Engine Orchestration**: `MedicalOrchestrator` coordinates Context, Intent, Reasoning, Routing, Execution, Policy, and Unanswered flows.
487
+ - **Components**:
488
+ - **IntentClassifierPro**: MiniLM + LightGBM (Simulated) for ultra-fast intent detection.
489
+ - **ReasoningEngine**: Hybrid Rules + Platt Scaling for risk analysis.
490
+ - **ClarificationEngine**: Automatically resolves ambiguous queries ("pain" -> "where?").
491
+ - **UnansweredQuestionService**: Manages lifecycle of low-confidence queries -> Admin Tickets.
492
+ - **Security**: "Zero Trust" model with PII redaction, Injection blocking, and mandatory Disclaimers.
493
+ - **Impact**: Enterprise-grade isolation, safety, and scalability.
494
+
495
+
496
+ ### Phase 3: Production Database
497
+ ```bash
498
+ # Switch to PostgreSQL with pgvector extension
499
+ DATABASE_URL="postgresql://user:pass@host:5432/db"
500
+ # Install pgvector for native vector operations
501
+ ```
502
+
503
+ ### Phase 4: Advanced Analytics
504
+ - User behavior tracking
505
+ - Conversion funnel analysis
506
+ - A/B testing for responses
507
+ - Performance metrics dashboard
508
+
509
+ ---
510
+
511
+ ## 🔮 **FUTURE ENHANCEMENTS**
512
+
513
+ ### 🏥 Industry-Specific Specialization
514
+ The system will be tailored for specific verticals with specialized knowledge bases and workflows:
515
+ - **Healthcare**:
516
+ - Symptom checking workflows
517
+ - Appointment scheduling integration
518
+ - HIPAA-compliant data handling
519
+ - **Education**:
520
+ - Student support and course inquiries
521
+ - LMS (Learning Management System) integration
522
+ - Multilingual support for diverse student bodies
523
+
524
+ ### 🗣️ Advanced Language Support
525
+ - **Bilingual Capabilities**: Native support for **English** and **Urdu**.
526
+ - **Mixed-Language Processing**: Ability to understand Roman Urdu (Urdu written in English script).
527
+ - **Language Detection**: Automatic switching based on user input.
528
+
529
+ ### 🧠 Global Response Plane: 4-Layer Orchestration (NEW)
530
+ The "Brain" of the bot that intelligently processes every message through four dynamic layers:
531
+ 1. **Layer 1: Language Gateway**: Instantly detects input language (**English**, **Urdu**, or **Roman Urdu**).
532
+ 2. **Layer 3: Hybrid Intent Detection**: Dynamically distinguishes between:
533
+ - **FAQ Plane**: High-confidence matching from the curated website FAQ database.
534
+ - **Scrape Plane (RAG)**: Context-aware retrieval from scraped web content.
535
+ - **Industry Plane**: Specialized datasets (e.g., the 10k Healthcare Mega-Dataset).
536
+ - **Creative Plane**: Generative synthesis for complex intents.
537
+ 3. **Layer 3: Dynamic Router & Handler**: Efficiently executes the highest-confidence handler for the detected intent.
538
+ 4. **Layer 4: Adaptive Translation & Tone**: Automatically translates English intelligence back into the user's detected language/flavor (Urdu/Roman Urdu) while applying the brand's unique tone.
539
+
540
+ ### 💡 Recommended Technical Improvements (Agent Suggestions)
541
+ - **Hybrid Search Architecture**: Combine vector search (semantic) with keyword search (BM25) to ensure specific names and terms are never missed.
542
+ - **Voice Interface**: Add Speech-to-Text (STT) and Text-to-Speech (TTS) for accessibility in both English and Urdu.
543
+ - **Multi-Channel Deployment**: Extend the chatbot beyond the web widget to **WhatsApp**, **Facebook Messenger**, and **Telegram**.
544
+ - **Active Learning Loop**: Allow admins to correct "Low Confidence" answers, automatically training the system to improve over time.
545
+
546
+ ---
547
+
548
+ ## 🎉 **CONCLUSION**
549
+
550
+ Our implementation successfully delivers:
551
+ - **Complete SaaS platform** ✅
552
+ - **Working chat widget** ✅
553
+ - **User management** ✅
554
+ - **Website verification** ✅
555
+ - **Content scraping** ✅
556
+ - **AI responses** ✅
557
+ - **Lead generation** ✅
558
+ - **Email notifications** ✅
559
+ - **Modern UI/UX** ✅
560
+ - **Production-ready security** ✅
561
+ - **4-Layer Global Response Plane** ✅ (Phase 2)
562
+ - **Multi-language support (English, Urdu, Roman Urdu)** ✅ (Phase 2)
563
+ - **10k+ Healthcare dataset integration** ✅ (Phase 2)
564
+ - **Persistent Memory & Cross-Session Context** ✅ (Phase 3)
565
+ - **AI-Powered Session Summarization** ✅ (Phase 3)
566
+ - **Patient Timeline & Health Tracking** ✅ (Phase 3)
567
+
568
+ The system is **fully functional** and provides an **enterprise-grade healthcare chatbot** with continuous conversation memory and CRM integration!
569
+
570
+ ---
571
+
572
+ ## 📝 **Quick Start Guide**
573
+
574
+ ### Development Setup
575
+ ```bash
576
+ # Backend
577
+ cd server
578
+ python -m venv venv
579
+ source venv/bin/activate # On Windows: venv\Scripts\activate
580
+ pip install -r requirements.txt
581
+ uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
582
+
583
+ # Frontend
584
+ cd client
585
+ npm install
586
+ npm run dev
587
+ ```
588
+
589
+ ### Default Login
590
+ - **Email**: `raza@gmail.com`
591
+ - **Password**: `TempPassword123!`
592
+
593
+ ### Environment Variables
594
+ Create `server/.env`:
595
+ ```env
596
+ DATABASE_URL=postgresql://user:pass@localhost:5432/ai_agent_db
597
+ SECRET_KEY=your-secret-key-here
598
+ OPENAI_API_KEY=optional-for-future-use
599
+ SMTP_HOST=smtp.gmail.com
600
+ SMTP_PORT=587
601
+ SMTP_USER=your-email@gmail.com
602
+ SMTP_PASSWORD=your-app-password
603
+ ```
604
+
605
+ ### Deployment
606
+ ```bash
607
+ # Build frontend
608
+ cd client && npm run build
609
+
610
+ # Deploy backend (example with Render)
611
+ # Set environment variables in Render dashboard
612
+ # Deploy from GitHub repository
613
+ ```
INFRASTRUCTURE_GUIDE.md ADDED
@@ -0,0 +1,438 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GPU & Cloud Infrastructure Guide
2
+ ## Production Requirements & Cost Analysis
3
+
4
+ ---
5
+
6
+ ## 🎯 TL;DR Recommendations
7
+
8
+ **For Production (Running Models)**:
9
+ - ✅ **CPU-only is fine!** No GPU needed for inference
10
+ - ✅ Use serverless/API-based ML (Hugging Face, Replicate)
11
+ - ✅ Cloud: AWS, Google Cloud, or DigitalOcean
12
+ - 💰 Estimated cost: $50-200/month
13
+
14
+ **For Training (One-time setup)**:
15
+ - ⚠️ GPU recommended but not required
16
+ - ✅ Use Google Colab Pro ($10/month) for training
17
+ - ✅ Or train on your local machine (slower but free)
18
+
19
+ ---
20
+
21
+ ## 📊 Detailed Breakdown
22
+
23
+ ### Production Inference (What Users Hit)
24
+
25
+ #### ❌ **You DON'T Need GPU For**:
26
+ 1. **Sentiment Analysis** - CPU inference is fast enough
27
+ 2. **NER (Named Entity Recognition)** - spaCy runs on CPU
28
+ 3. **Response Streaming** - Just API calls
29
+ 4. **Rate Limiting** - Pure logic, no ML
30
+ 5. **Analytics** - Database queries
31
+ 6. **Dark Mode** - Frontend only
32
+ 7. **Auto-FAQ** - Uses GPT-4 API (already hosted)
33
+
34
+ #### ✅ **What You Actually Need**:
35
+ - **CPU**: 4-8 cores (good enough!)
36
+ - **RAM**: 8-16 GB
37
+ - **Storage**: 50-100 GB SSD
38
+ - **Redis**: For caching (can be shared)
39
+
40
+ **Why No GPU?**:
41
+ ```python
42
+ # Small models run fast on CPU
43
+ # Example: BERT sentiment analysis
44
+ import torch
45
+ from transformers import pipeline
46
+
47
+ # CPU inference
48
+ sentiment = pipeline("sentiment-analysis", device=-1) # -1 = CPU
49
+ result = sentiment("I love this product!")
50
+ # Takes: ~50-100ms on CPU ✅
51
+ # Takes: ~10-20ms on GPU (not worth the cost!)
52
+
53
+ # For 1000 requests/day:
54
+ # CPU cost: $50/month ✅
55
+ # GPU cost: $500/month ❌ (10x more expensive!)
56
+ ```
57
+
58
+ ---
59
+
60
+ ## 🏋️ Training (One-Time Setup)
61
+
62
+ ### Option 1: **Google Colab Pro** (Recommended)
63
+ **Cost**: $10/month
64
+ **GPU**: Tesla T4 or better
65
+ **Use For**: Initial model training
66
+
67
+ ```python
68
+ # Train in Colab, export model, run on CPU
69
+ # 1. Train sentiment model (30 min on GPU)
70
+ # 2. Export model files
71
+ # 3. Load in your CPU server (instant inference)
72
+ ```
73
+
74
+ **Pros**:
75
+ - ✅ Cheap ($10/month)
76
+ - ✅ Easy setup
77
+ - ✅ Good GPUs
78
+ - ✅ Cancel anytime
79
+
80
+ **Cons**:
81
+ - ⚠️ Session limits (12 hours)
82
+ - ⚠️ Need to re-run if disconnected
83
+
84
+ ---
85
+
86
+ ### Option 2: **Cloud GPU (On-Demand)**
87
+ **Use When**: Training large models (>1B parameters)
88
+
89
+ | Provider | GPU Type | Cost/hour | Best For |
90
+ |----------|----------|-----------|----------|
91
+ | **Vast.ai** | RTX 4090 | $0.25/hr | Cheapest |
92
+ | **RunPod** | A100 | $1.00/hr | Best Value |
93
+ | **Lambda Labs** | A6000 | $0.50/hr | Reliable |
94
+ | **AWS EC2** | A10G | $1.50/hr | Enterprise |
95
+
96
+ **Example Cost**:
97
+ ```
98
+ Training Time: 4 hours
99
+ Vast.ai RTX 4090: 4 × $0.25 = $1.00 ✅
100
+ AWS p3.2xlarge: 4 × $3.06 = $12.24 ❌
101
+ ```
102
+
103
+ ---
104
+
105
+ ### Option 3: **CPU-Only Training** (Free!)
106
+ **Use When**: Budget is tight
107
+
108
+ ```python
109
+ # Takes longer but works!
110
+ # Sentiment model training:
111
+ # - GPU: 30 minutes
112
+ # - CPU: 3-4 hours (overnight)
113
+
114
+ # Still totally viable:
115
+ from transformers import Trainer
116
+
117
+ trainer = Trainer(
118
+ model=model,
119
+ args=training_args,
120
+ train_dataset=train_data,
121
+ # Will use CPU automatically
122
+ )
123
+
124
+ trainer.train() # Go have dinner, come back to trained model
125
+ ```
126
+
127
+ ---
128
+
129
+ ## ☁️ Cloud Provider Recommendations
130
+
131
+ ### For Small-Medium Scale (< 10K users/month)
132
+
133
+ #### **Option A: DigitalOcean** (Simplest)
134
+ ```
135
+ Recommended Plan:
136
+ - 4 vCPU, 8GB RAM: $48/month
137
+ - Managed Redis: $15/month
138
+ - Managed PostgreSQL: $15/month
139
+ ─────────────────────────
140
+ Total: $78/month
141
+ ```
142
+
143
+ **Pros**:
144
+ - ✅ Simple setup
145
+ - ✅ Fixed pricing
146
+ - ✅ Good docs
147
+ - ✅ Managed databases
148
+
149
+ **Cons**:
150
+ - ⚠️ Limited auto-scaling
151
+ - ⚠️ No GPU options
152
+
153
+ ---
154
+
155
+ #### **Option B: AWS Lightsail** (AWS on Easy Mode)
156
+ ```
157
+ Recommended Setup:
158
+ - App Server (4GB): $40/month
159
+ - Redis (1GB): $10/month
160
+ - PostgreSQL (2GB): $15/month
161
+ ─────────────────────────
162
+ Total: $65/month
163
+ ```
164
+
165
+ **Pros**:
166
+ - ✅ Cheaper than EC2
167
+ - ✅ Simpler than EC2
168
+ - ✅ AWS ecosystem access
169
+ - ✅ Easy scaling
170
+
171
+ **Cons**:
172
+ - ⚠️ Limited to AWS regions
173
+
174
+ ---
175
+
176
+ #### **Option C: Railway** (Developer Friendly)
177
+ ```
178
+ Recommended:
179
+ - Hobby Plan: $5/month
180
+ - Pay per usage: ~$20-40/month
181
+ ─────────────────────────
182
+ Total: $25-45/month
183
+ ```
184
+
185
+ **Pros**:
186
+ - ✅ Very cheap
187
+ - ✅ Auto-deploy from GitHub
188
+ - ✅ Built-in Redis/Postgres
189
+ - ✅ Great DX
190
+
191
+ **Cons**:
192
+ - ⚠️ Usage-based can surprise
193
+ - ⚠️ Younger platform
194
+
195
+ ---
196
+
197
+ ### For Large Scale (10K+ users/month)
198
+
199
+ #### **AWS** (Industry Standard)
200
+ ```
201
+ Production Setup:
202
+ - ECS Fargate (2 vCPU, 4GB): $50/month
203
+ - ElastiCache Redis: $30/month
204
+ - RDS PostgreSQL: $40/month
205
+ - Load Balancer: $20/month
206
+ ─────────────────────────
207
+ Total: $140/month (+auto-scaling)
208
+ ```
209
+
210
+ **Pros**:
211
+ - ✅ Best auto-scaling
212
+ - ✅ 99.99% uptime
213
+ - ✅ Global CDN
214
+ - ✅ Enterprise support
215
+
216
+ **Cons**:
217
+ - ❌ Complex setup
218
+ - ❌ Can get expensive
219
+ - ❌ Steep learning curve
220
+
221
+ ---
222
+
223
+ #### **Google Cloud Platform**
224
+ ```
225
+ Production Setup:
226
+ - Cloud Run (auto-scale): $30-60/month
227
+ - Memorystore (Redis): $35/month
228
+ - Cloud SQL: $40/month
229
+ ─────────────────────────
230
+ Total: $105-135/month
231
+ ```
232
+
233
+ **Pros**:
234
+ - ✅ Great for ML (Vertex AI)
235
+ - ✅ Good auto-scaling
236
+ - ✅ Free tier generous
237
+ - ✅ Good documentation
238
+
239
+ **Cons**:
240
+ - ⚠️ Less popular than AWS
241
+ - ⚠️ Some services expensive
242
+
243
+ ---
244
+
245
+ ## 💰 Total Cost Breakdown
246
+
247
+ ### Minimal Setup (MVP)
248
+ ```
249
+ Railway/Render: $30/month
250
+ Hugging Face API: $0 (free tier)
251
+ Gemini API: $20/month (pay-as-go)
252
+ Domain + SSL: $15/year
253
+ ──────────────────────────────
254
+ Total: ~$50-60/month
255
+ ```
256
+
257
+ ### Recommended Setup
258
+ ```
259
+ DigitalOcean Droplet: $48/month
260
+ Managed Redis: $15/month
261
+ Managed PostgreSQL: $15/month
262
+ Gemini API: $30/month
263
+ Monitoring (DataDog): $15/month
264
+ ──────────────────────────────
265
+ Total: ~$123/month
266
+ ```
267
+
268
+ ### Enterprise Setup
269
+ ```
270
+ AWS ECS/Fargate: $100/month
271
+ ElastiCache: $30/month
272
+ RDS: $40/month
273
+ CloudWatch: $10/month
274
+ Gemini API: $50/month
275
+ ──────────────────────────────
276
+ Total: ~$230/month
277
+ ```
278
+
279
+ ---
280
+
281
+ ## 🎯 My Recommendation for You
282
+
283
+ Based on your current setup, here's what I suggest:
284
+
285
+ ### **Phase 1: Start Simple (Month 1-3)**
286
+ ```yaml
287
+ Platform: Railway or Render
288
+ Why:
289
+ - Easy deployment from GitHub
290
+ - Built-in Redis/PostgreSQL
291
+ - Auto-scaling included
292
+ - $30-50/month total
293
+
294
+ ML Strategy:
295
+ - Use Hugging Face Inference API (free tier)
296
+ - Use Gemini API for main responses
297
+ - No GPU needed!
298
+ ```
299
+
300
+ ### **Phase 2: Grow (Month 4-6)**
301
+ ```yaml
302
+ Platform: DigitalOcean
303
+ Why:
304
+ - More control
305
+ - Better performance
306
+ - Still simple
307
+ - ~$100/month
308
+
309
+ ML Strategy:
310
+ - Host small models on CPU (sentiment, NER)
311
+ - Keep using Gemini API
312
+ - Train models on Google Colab Pro
313
+ ```
314
+
315
+ ### **Phase 3: Scale (Month 7+)**
316
+ ```yaml
317
+ Platform: AWS or GCP
318
+ Why:
319
+ - Need auto-scaling
320
+ - Global users
321
+ - 99.9%+ uptime required
322
+ - $200-500/month
323
+
324
+ ML Strategy:
325
+ - Custom model deployment
326
+ - Edge caching (CloudFront/Cloud CDN)
327
+ - Multi-region
328
+ ```
329
+
330
+ ---
331
+
332
+ ## 🚀 Optimized Architecture (No GPU Needed!)
333
+
334
+ ```
335
+ ┌─────────────────────────────────────────┐
336
+ │ Your Application Server │
337
+ │ (CPU-only, 4 cores, 8GB) │
338
+ │ │
339
+ │ ┌────────────────────────────────┐ │
340
+ │ │ Sentiment: Hugging Face API │ │
341
+ │ │ NER: spaCy (CPU) │ │
342
+ │ │ Embedding: Sentence-BERT (CPU)│ │
343
+ │ │ LLM: Gemini API │ │
344
+ │ └────────────────────────────────┘ │
345
+ └─────────────────────────────────────────┘
346
+ │ │
347
+ ▼ ▼
348
+ ┌──────────┐ ┌──────────┐
349
+ │ Redis │ │ Postgres │
350
+ │ Cache │ │ DB │
351
+ └──────────┘ └──────────┘
352
+ ```
353
+
354
+ **Why This Works**:
355
+ 1. API-based ML (Hugging Face, Gemini) = No GPU
356
+ 2. Small models (spaCy, BERT) = Fast on CPU
357
+ 3. Caching (Redis) = Even faster
358
+ 4. Total cost: **$50-100/month** instead of $500+!
359
+
360
+ ---
361
+
362
+ ## 🧪 Performance Comparison
363
+
364
+ ### Sentiment Analysis (per request)
365
+ | Method | Time | Cost/month (1000 req/day) |
366
+ |--------|------|---------------------------|
367
+ | **CPU (Local)** | 50ms | $50 |
368
+ | **GPU (Local)** | 10ms | $500 |
369
+ | **HF API** | 100ms | $0 (free tier) |
370
+
371
+ **Winner**: CPU or HF API ✅
372
+
373
+ ---
374
+
375
+ ### NER Extraction
376
+ | Method | Time | Cost/month |
377
+ |--------|------|------------|
378
+ | **spaCy CPU** | 30ms | $50 |
379
+ | **spaCy GPU** | 10ms | $500 |
380
+ | **Cloud API** | 80ms | $20 |
381
+
382
+ **Winner**: spaCy CPU ✅
383
+
384
+ ---
385
+
386
+ ## ✅ Final Recommendation
387
+
388
+ **Start Here**:
389
+ 1. ✅ Deploy on **Railway** or **Render** ($30/month)
390
+ 2. ✅ Use **Hugging Face API** for sentiment/NER (free)
391
+ 3. ✅ Use **Gemini API** for LLM responses (pay-as-go)
392
+ 4. ✅ Train models on **Google Colab Pro** ($10/month)
393
+ 5. ✅ Scale to DigitalOcean when needed ($100/month)
394
+
395
+ **You DON'T Need**:
396
+ - ❌ GPU server ($500+/month)
397
+ - ❌ AWS immediately (too complex)
398
+ - ❌ Expensive ML hosting
399
+
400
+ **Total Starting Cost**: **$40-60/month**
401
+ **Can handle**: **1,000-10,000 users/month**
402
+ **Latency**: **< 200ms average**
403
+
404
+ ---
405
+
406
+ ## 📝 Quick Start Command
407
+
408
+ ```bash
409
+ # 1. Install dependencies (no GPU needed!)
410
+ pip install torch transformers spacy sentence-transformers --index-url https://download.pytorch.org/whl/cpu
411
+
412
+ # 2. Download models (run once)
413
+ python -m spacy download en_core_web_sm
414
+ python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2')"
415
+
416
+ # 3. Deploy to Railway
417
+ git push railway main
418
+
419
+ # Total time: 10 minutes ✅
420
+ # Total cost: $30/month ✅
421
+ ```
422
+
423
+ ---
424
+
425
+ ## 🎯 Bottom Line
426
+
427
+ **For your customer agent platform**:
428
+ - ✅ **NO GPU needed for production!**
429
+ - ✅ Start with **Railway** ($30/month)
430
+ - ✅ Use **API-based ML** (Hugging Face + Gemini)
431
+ - ✅ Train on **Google Colab** ($10/month)
432
+ - ✅ Total: **$40-60/month** to start
433
+
434
+ **Scale later when needed** (1000+ users/day):
435
+ - Move to **DigitalOcean** ($100/month)
436
+ - Or **AWS** ($200+/month) for enterprise
437
+
438
+ **You can start building TODAY with zero GPU investment!** 🚀
README.md ADDED
@@ -0,0 +1,112 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Customer Agent SaaS Platform
2
+
3
+ A secure, efficient SaaS platform for embedding AI chat agents into websites. The agent uses NLP to answer questions based on scraped website content and performs agentic actions when information is missing.
4
+
5
+ ## Features
6
+
7
+ - 🤖 AI-powered chat agents using OpenAI GPT
8
+ - 🔍 Automatic website content scraping and indexing
9
+ - 🎯 Vector similarity search for relevant responses
10
+ - 🔐 Secure JWT authentication with httpOnly cookies
11
+ - 📊 Real-time dashboard for website management
12
+ - 🚀 Fast, scalable architecture with FastAPI + React
13
+
14
+ ## Tech Stack
15
+
16
+ ### Backend
17
+ - FastAPI (REST + WebSocket)
18
+ - NumPy for optimized vector operations
19
+ - PostgreSQL for data storage
20
+ - JWT with secure cookies
21
+ - OpenAI for AI responses
22
+ - BeautifulSoup for web scraping
23
+
24
+ ### Frontend
25
+ - React with Vite
26
+ - Tailwind CSS for styling
27
+ - Axios for API communication
28
+ - React Router for navigation
29
+
30
+ ## Quick Start
31
+
32
+ ### Backend Setup
33
+
34
+ 1. Navigate to server directory:
35
+ ```bash
36
+ cd server
37
+ ```
38
+
39
+ 2. Install dependencies:
40
+ ```bash
41
+ pip install -r requirements.txt
42
+ ```
43
+
44
+ 3. Set up environment variables in `.env`:
45
+ ```env
46
+ DATABASE_URL=postgresql://user:password@localhost/customeragent
47
+ SECRET_KEY=your-secret-key-here
48
+ OPENAI_API_KEY=your-openai-key
49
+ ```
50
+
51
+ 4. Run the server:
52
+ ```bash
53
+ uvicorn app.main:app --reload --port 8000
54
+ ```
55
+
56
+ ### Frontend Setup
57
+
58
+ 1. Navigate to client directory:
59
+ ```bash
60
+ cd client
61
+ ```
62
+
63
+ 2. Install dependencies:
64
+ ```bash
65
+ npm install
66
+ ```
67
+
68
+ 3. Start development server:
69
+ ```bash
70
+ npm run dev
71
+ ```
72
+
73
+ ## API Endpoints
74
+
75
+ ### Authentication
76
+ - `POST /auth/register` - Register new user
77
+ - `POST /auth/login` - Login user
78
+ - `POST /auth/logout` - Logout user
79
+ - `GET /auth/me` - Get current user
80
+
81
+ ### Websites
82
+ - `GET /websites/` - Get user's websites
83
+ - `POST /websites/` - Add new website
84
+ - `POST /websites/{id}/verify` - Verify website ownership
85
+ - `POST /websites/{id}/fetch-content` - Scrape website content
86
+
87
+ ### Chat
88
+ - `POST /chat/` - Send message to AI agent
89
+
90
+ ## Deployment
91
+
92
+ ### Backend (Render/Railway)
93
+ 1. Connect your repository
94
+ 2. Set environment variables
95
+ 3. Deploy with automatic builds
96
+
97
+ ### Frontend (Vercel/Netlify)
98
+ 1. Connect your repository
99
+ 2. Set build command: `npm run build`
100
+ 3. Set publish directory: `dist`
101
+
102
+ ## Security Features
103
+
104
+ - httpOnly cookies for JWT tokens
105
+ - CORS protection
106
+ - Password hashing with bcrypt
107
+ - SQL injection protection with SQLAlchemy
108
+ - Input validation with Pydantic
109
+
110
+ ## License
111
+
112
+ MIT License
REFERENCE_ARCHITECTURE.md ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chatbot Deep Architecture Guide (CPU-Optimized)
2
+
3
+ This document is the definitive guide to the query processing pipeline. It maps every byte of data from the initial HTTP request to the final AI response, detailing our **CPU-first model-aware strategy** designed for maximum efficiency without brute force.
4
+
5
+ ---
6
+
7
+ ## 🏗️ The 9-Layer Inference Flow
8
+
9
+ We align our architecture into 9 logical layers, moving from lightweight rule-based processing to specialized CPU-bound LLM inference.
10
+
11
+ ```mermaid
12
+ graph TD
13
+ L1[Layer 1: Input Processing - Regex] --> L2[Layer 2: Safety & Filter - Regex/Pattern]
14
+ L2 --> L3[Layer 3: Schema & Context Builder]
15
+ L3 --> L4[Layer 4: Intent Classification - TinyLlama]
16
+ L4 --> L5[Layer 5: Retrieval RAG - FAISS]
17
+ L5 --> L6[Layer 6: Prompt Context Merging]
18
+ L6 --> L7[Layer 7: Core Inference - Llama-3.2-3B]
19
+ L7 --> L8[Layer 8: Response Validation]
20
+ L8 --> L9[Layer 9: Post-Processing]
21
+ L9 --> Output((Final Answer))
22
+ ```
23
+
24
+ ---
25
+
26
+ ## 🔹 Layer-Wise Strategy & Model Selection
27
+
28
+ | Layer | Responsibility | LLM Model | Technical Source / File |
29
+ | :--- | :--- | :--- | :--- |
30
+ | **L1** | Normalization & Cleaning | ❌ No | `nlp_processor.py` |
31
+ | **L2** | **Safety & Policy Guard** | ❌ No / Regex | `content_filter.py` |
32
+ | **L3** | **Schema & Context Builder** | ❌ No | `context_manager.py` |
33
+ | **L4** | **Intent Classification** | ✅ **TinyLlama-1.1B** | `intent_classifier.py` |
34
+ | **L5** | Retrieval (RAG) | ❌ No | `medical_retriever.py` |
35
+ | **L6** | Prompt Architecture | ❌ No | `medical_orchestrator.py` |
36
+ | **L7** | Core Reasoning | ✅ **Llama-3.2-3B** | `llm_service.py` (Local-First Priority) |
37
+ | **L8** | Consistency Check | ⚠️ **Llama-3.1-3B** | `response_validator.py` |
38
+ | **L9** | Output Polish | ❌ No | `tone_engine.py` |
39
+
40
+ ---
41
+
42
+ ## 🧠 Deep Execution Trace
43
+
44
+ ### **Intelligence Layers (L3, L4, L7)**
45
+ * **Layer 4: Intent Classification**
46
+ * **Goal**: Determine if the query is `MEDICAL_INFO`, `GREETING`, or `FAQ`.
47
+ * **Model**: **TinyLlama-1.1B**. It is ultra-light and specialized for single-label classification to save CPU the main answer.
48
+ * **Layer 2: Safety & Policy**
49
+ * **Logic**: Uses Regex for high-speed blacklists + **Llama-3.1-3B** (optional) for ambiguous medical ethics/diagnosis requests.
50
+ * **Layer 7: Core LLM Inference**
51
+ * **Model**: **Llama-3.2-3B-Instruct (Q4_K_M)**.
52
+ * **Why**: Best-in-class reasoning for 3B parameter models, fits in 8-16GB RAM.
53
+
54
+ ### **Retrieval Layer (L5 - RAG)**
55
+ * **Engine**: FAISS Index (`medical_faiss_index.bin`).
56
+ * **Logic**: Top-3 to Top-5 results are fetched. This "grounded knowledge" is what allows a 3B model to outperform biased 70B models in specific medical domains.
57
+
58
+ ---
59
+
60
+ ## 🔄 The Data Transformation Cycle
61
+
62
+ | Phase | Input | Processing | Output Data |
63
+ | :--- | :--- | :--- | :--- |
64
+ | **Preprocessing** | `"sar ma drd hy"` | Normalization | `"I have a headache"` (Translated) |
65
+ | **Intent** | `"I have a headache"` | TinyLlama Classify | `Intent.MEDICAL_SYMPTOM` |
66
+ | **RAG** | `MEDICAL_SYMPTOM` | FAISS Search | Top 5 CDC/MedQuAD docs |
67
+ | **Synthesis** | `Prompt + Context` | Llama-3.2-3B | `"It sounds like a migraine..."` |
68
+ | **Safety** | `LLM Response` | Policy Check | LLM Response + Disclaimer |
69
+
70
+ ---
71
+
72
+ ## ✅ Deployment Specs
73
+ * **Runtime**: `llama.cpp` (via `ctransformers` Python bindings).
74
+ * **Quantization**: `Q4_K_M` or `Q5_0` for optimal balance of speed and precision.
75
+ * **Hosting**: Oracle Free Tier / local CPU (Apple M-series or Intel Xeon).
SETUP.md ADDED
@@ -0,0 +1,158 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Customer Agent SaaS - Complete Setup Guide
2
+
3
+ ## 🚀 Quick Start (Development)
4
+
5
+ ### Prerequisites
6
+ - Node.js 18+
7
+ - Python 3.11+
8
+ - PostgreSQL
9
+ - OpenAI API Key
10
+
11
+ ### 1. Backend Setup
12
+ ```bash
13
+ cd server
14
+ pip install -r requirements.txt
15
+ cp .env.example .env
16
+ # Edit .env with your credentials
17
+ uvicorn app.main:app --reload --port 8000
18
+ ```
19
+
20
+ ### 2. Frontend Setup
21
+ ```bash
22
+ cd client
23
+ npm install
24
+ npm run dev
25
+ ```
26
+
27
+ ### 3. Database Setup
28
+ ```sql
29
+ CREATE DATABASE customeragent;
30
+ ```
31
+
32
+ ## 🔧 Configuration
33
+
34
+ ### Environment Variables (.env)
35
+ ```env
36
+ DATABASE_URL=postgresql://user:password@localhost/customeragent
37
+ SECRET_KEY=your-secret-key-here
38
+ OPENAI_API_KEY=sk-your-openai-key
39
+ GOOGLE_CLIENT_ID=your-google-oauth-client-id
40
+ GOOGLE_CLIENT_SECRET=your-google-oauth-secret
41
+ SMTP_HOST=smtp.gmail.com
42
+ SMTP_PORT=587
43
+ SMTP_USER=your-email@gmail.com
44
+ SMTP_PASSWORD=your-app-password
45
+ ```
46
+
47
+ ## 📋 Features Implemented
48
+
49
+ ### ✅ Core Features
50
+ - [x] JWT Authentication with httpOnly cookies
51
+ - [x] Google OAuth integration
52
+ - [x] Website management dashboard
53
+ - [x] Script verification system
54
+ - [x] Sitemap-based content scraping
55
+ - [x] NumPy-optimized vector operations
56
+ - [x] FAISS vector database
57
+ - [x] AI-powered chat responses
58
+ - [x] Agentic owner contact system
59
+ - [x] Email notifications
60
+ - [x] Real-time WebSocket chat
61
+ - [x] Embeddable widget
62
+ - [x] React Query for state management
63
+ - [x] Framer Motion animations
64
+ - [x] Tailwind CSS styling
65
+
66
+ ### 🎯 Workflow
67
+ 1. **User Registration** → Secure JWT auth
68
+ 2. **Add Website** → Domain verification
69
+ 3. **Install Script** → Verification check
70
+ 4. **Fetch Content** → Sitemap scraping
71
+ 5. **Process AI** → Vector embeddings
72
+ 6. **Live Chat** → AI responses + owner escalation
73
+
74
+ ## 🌐 Deployment
75
+
76
+ ### Frontend (Vercel/Netlify)
77
+ ```bash
78
+ npm run build
79
+ # Deploy dist/ folder
80
+ ```
81
+
82
+ ### Backend (Render/Railway)
83
+ ```bash
84
+ # Use start.sh as entry point
85
+ ./start.sh
86
+ ```
87
+
88
+ ### Environment Variables (Production)
89
+ ```env
90
+ DATABASE_URL=postgresql://...
91
+ SECRET_KEY=production-secret-key
92
+ OPENAI_API_KEY=sk-...
93
+ FRONTEND_URL=https://your-domain.com
94
+ BACKEND_URL=https://api.your-domain.com
95
+ ```
96
+
97
+ ## 🔗 API Endpoints
98
+
99
+ ### Authentication
100
+ - `POST /auth/register` - User registration
101
+ - `POST /auth/login` - User login
102
+ - `POST /auth/logout` - User logout
103
+ - `GET /auth/me` - Current user info
104
+
105
+ ### Websites
106
+ - `GET /websites/` - List user websites
107
+ - `POST /websites/` - Add new website
108
+ - `POST /scrape/{id}/verify-script` - Verify widget installation
109
+ - `POST /websites/{id}/fetch-content` - Scrape content
110
+ - `POST /scrape/{id}/process-content` - Create embeddings
111
+
112
+ ### Chat & Actions
113
+ - `POST /chat/` - Send chat message
114
+ - `POST /actions/contact-owner` - Contact website owner
115
+ - `WS /ws/{website_id}` - WebSocket chat
116
+
117
+ ## 🎨 Widget Integration
118
+
119
+ Add to any website:
120
+ ```html
121
+ <script src="https://your-domain.com/static/widget.js" data-website-id="123"></script>
122
+ ```
123
+
124
+ ## 🔒 Security Features
125
+
126
+ - httpOnly JWT cookies (XSS protection)
127
+ - CORS with specific origins
128
+ - Password hashing (bcrypt)
129
+ - SQL injection protection (SQLAlchemy)
130
+ - Input validation (Pydantic)
131
+ - Secure cookie settings
132
+
133
+ ## 📊 Tech Stack Summary
134
+
135
+ **Frontend**: React + Vite + Tailwind + Framer Motion + React Query
136
+ **Backend**: FastAPI + NumPy + FAISS + PostgreSQL + WebSockets
137
+ **AI**: OpenAI GPT-3.5 + Vector Similarity Search
138
+ **Auth**: JWT + Google OAuth + httpOnly Cookies
139
+ **Deployment**: Vercel + Render/Railway
140
+
141
+ ## 🎯 Production Checklist
142
+
143
+ - [ ] Update CORS origins in main.py
144
+ - [ ] Set strong SECRET_KEY
145
+ - [ ] Configure production database
146
+ - [ ] Set up SMTP for emails
147
+ - [ ] Configure Google OAuth
148
+ - [ ] Test widget on live site
149
+ - [ ] Monitor vector database performance
150
+ - [ ] Set up error tracking
151
+
152
+ ## 🚀 Next Steps
153
+
154
+ 1. **Scale Vector DB**: Implement ChromaDB for larger datasets
155
+ 2. **Analytics**: Add chat analytics dashboard
156
+ 3. **Multi-language**: Support multiple languages
157
+ 4. **Custom Branding**: White-label widget options
158
+ 5. **Advanced AI**: Fine-tune models on website content
SYSTEM_ENHANCEMENTS.md ADDED
@@ -0,0 +1,919 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # System Enhancement Roadmap
2
+ ## 30+ Actionable Improvements for Your Customer Agent Platform
3
+
4
+ ---
5
+
6
+ ## 🤖 AI & Machine Learning Enhancements
7
+
8
+ ### 1. **Sentiment Analysis**
9
+ **What**: Detect user emotions (happy, frustrated, angry, confused)
10
+ **Why**: Prioritize unhappy customers, adjust tone
11
+ **Impact**: +35% customer satisfaction
12
+
13
+ ```python
14
+ from transformers import pipeline
15
+
16
+ class SentimentAnalyzer:
17
+ def __init__(self):
18
+ self.classifier = pipeline("sentiment-analysis")
19
+
20
+ def analyze(self, text):
21
+ result = self.classifier(text)[0]
22
+ return {
23
+ 'sentiment': result['label'], # POSITIVE, NEGATIVE
24
+ 'confidence': result['score'],
25
+ 'urgency': 'high' if result['label'] == 'NEGATIVE' else 'normal'
26
+ }
27
+
28
+ # Usage
29
+ if sentiment['sentiment'] == 'NEGATIVE':
30
+ # Priority routing to human agent
31
+ # Use empathetic language
32
+ # Offer immediate assistance
33
+ ```
34
+
35
+ **Benefits**:
36
+ - Detect frustrated users early
37
+ - Escalate to human agent automatically
38
+ - Adjust bot tone based on emotion
39
+ - Track satisfaction in real-time
40
+
41
+ ---
42
+
43
+ ### 2. **Intent Confidence Threshold**
44
+ **What**: If intent confidence < 70%, ask clarifying question
45
+ **Why**: Reduce wrong answers
46
+
47
+ ```python
48
+ intent_result = classify_intent(query)
49
+
50
+ if intent_result.confidence < 0.7:
51
+ return {
52
+ 'response': "I want to make sure I understand correctly. Are you asking about:",
53
+ 'suggestions': [
54
+ "Product information",
55
+ "Pricing and fees",
56
+ "Technical support",
57
+ "Account management"
58
+ ]
59
+ }
60
+ ```
61
+
62
+ ---
63
+
64
+ ### 3. **Named Entity Recognition (NER)**
65
+ **What**: Extract names, dates, products, locations from queries
66
+ **Why**: Better context understanding
67
+
68
+ ```python
69
+ # Extract entities
70
+ entities = extract_entities("I need to book appointment with Dr. Khan on Monday")
71
+ # {
72
+ # 'PERSON': ['Dr. Khan'],
73
+ # 'DATE': ['Monday'],
74
+ # 'ACTION': ['book appointment']
75
+ # }
76
+
77
+ # Use in response
78
+ "I see you'd like to book an appointment with Dr. Khan on Monday.
79
+ Let me help you with that."
80
+ ```
81
+
82
+ ---
83
+
84
+ ### 4. **Custom Model Fine-tuning**
85
+ **What**: Fine-tune small LLM on your specific data
86
+ **Why**: Better accuracy than generic models
87
+
88
+ ```python
89
+ # Fine-tune on your data
90
+ from transformers import AutoModelForCausalLM, TrainingArguments
91
+
92
+ training_data = [
93
+ {"query": "What are your hours?", "response": "We're open 9 AM - 5 PM..."},
94
+ {"query": "How do I reset password?", "response": "Go to settings..."},
95
+ # ... your specific Q&A pairs
96
+ ]
97
+
98
+ # Train model
99
+ model = fine_tune_model(training_data)
100
+ # Result: 40% better accuracy for your domain
101
+ ```
102
+
103
+ ---
104
+
105
+ ### 5. **Auto-FAQ Generation**
106
+ **What**: Automatically suggest FAQ from frequent questions
107
+ **Why**: Reduce repeated queries
108
+
109
+ ```python
110
+ class FAQSuggester:
111
+ def analyze_unanswered(self, days=30):
112
+ # Find questions asked 10+ times
113
+ frequent = db.query(UnansweredQuestion)\
114
+ .group_by('question_normalized')\
115
+ .having(func.count() >= 10)\
116
+ .all()
117
+
118
+ return [
119
+ {
120
+ 'question': q.question,
121
+ 'frequency': q.asked_count,
122
+ 'suggested_answer': generate_answer(q) # Use LLM
123
+ }
124
+ for q in frequent
125
+ ]
126
+
127
+ # Admin dashboard shows: "Top 10 questions to add to FAQ"
128
+ ```
129
+
130
+ ---
131
+
132
+ ## 📊 Analytics & Insights
133
+
134
+ ### 6. **Conversation Flow Analysis**
135
+ **What**: Visualize common conversation paths
136
+ **Why**: Optimize chatbot flows
137
+
138
+ ```python
139
+ # Track conversation patterns
140
+ class FlowAnalyzer:
141
+ def analyze_paths(self):
142
+ flows = db.query(ChatSession).all()
143
+
144
+ patterns = {
145
+ 'greeting → product_question → pricing → contact': 45,
146
+ 'greeting → support → technical_issue → resolved': 30,
147
+ 'greeting → confused → escalated': 15
148
+ }
149
+
150
+ return patterns
151
+
152
+ # Visualization: Sankey diagram showing user journeys
153
+ ```
154
+
155
+ ---
156
+
157
+ ### 7. **Response Quality Scoring**
158
+ **What**: Automatically rate bot response quality
159
+ **Why**: Identify weak areas
160
+
161
+ ```python
162
+ def score_response(query, response, user_feedback):
163
+ scores = {
164
+ 'relevance': calculate_similarity(query, response),
165
+ 'completeness': check_question_answered(query, response),
166
+ 'helpfulness': user_feedback or predict_helpfulness(response),
167
+ 'response_time': measure_time()
168
+ }
169
+
170
+ overall = sum(scores.values()) / len(scores)
171
+
172
+ if overall < 0.6:
173
+ flag_for_review(query, response)
174
+
175
+ return scores
176
+ ```
177
+
178
+ ---
179
+
180
+ ### 8. **A/B Testing Framework**
181
+ **What**: Test different responses/strategies
182
+ **Why**: Data-driven improvements
183
+
184
+ ```python
185
+ class ABTester:
186
+ experiments = {
187
+ 'greeting_style': {
188
+ 'A': "Hello! How can I help?", # Formal
189
+ 'B': "Hey there! 👋 What's up?" # Casual
190
+ },
191
+ 'response_length': {
192
+ 'A': 'detailed', # 100+ words
193
+ 'B': 'concise' # <50 words
194
+ }
195
+ }
196
+
197
+ def track_metrics(self, variant):
198
+ # Track: satisfaction, time to resolution, conversion
199
+ pass
200
+
201
+ # After 100 sessions: "Variant B has 15% higher satisfaction"
202
+ ```
203
+
204
+ ---
205
+
206
+ ### 9. **Conversion Tracking**
207
+ **What**: Track journey from chat → conversion
208
+ **Why**: Measure ROI
209
+
210
+ ```python
211
+ class ConversionTracker:
212
+ def track_funnel(self, session_id):
213
+ events = [
214
+ {'event': 'chat_started', 'timestamp': t1},
215
+ {'event': 'product_viewed', 'timestamp': t2},
216
+ {'event': 'pricing_asked', 'timestamp': t3},
217
+ {'event': 'contact_form_filled', 'timestamp': t4},
218
+ {'event': 'converted', 'timestamp': t5} # Purchased/signed up
219
+ ]
220
+
221
+ # Calculate:
222
+ # - Time to conversion
223
+ # - Drop-off points
224
+ # - Chat contribution to sale
225
+ ```
226
+
227
+ ---
228
+
229
+ ### 10. **Heatmap Analytics**
230
+ **What**: Show what users click/read most
231
+ **Why**: Optimize UI and content
232
+
233
+ ```javascript
234
+ // Track user interactions
235
+ const heatmap = {
236
+ 'contact_button': 450, // Most clicked
237
+ 'faq_link': 320,
238
+ 'pricing_table': 280,
239
+ 'features_section': 150
240
+ };
241
+
242
+ // Visualization: Heatmap overlay on widget
243
+ ```
244
+
245
+ ---
246
+
247
+ ## 🎨 User Experience Enhancements
248
+
249
+ ### 11. **Smart Auto-Complete**
250
+ **What**: Suggest completions as user types
251
+ **Why**: Faster queries, fewer typos
252
+
253
+ ```javascript
254
+ const AutoComplete = ({ input, onSelect }) => {
255
+ const [suggestions, setSuggestions] = useState([]);
256
+
257
+ useEffect(() => {
258
+ if (input.length > 2) {
259
+ // Fetch suggestions
260
+ getSuggestions(input).then(setSuggestions);
261
+ }
262
+ }, [input]);
263
+
264
+ return (
265
+ <datalist id="suggestions">
266
+ {suggestions.map(s => (
267
+ <option value={s} />
268
+ ))}
269
+ </datalist>
270
+ );
271
+ };
272
+ ```
273
+
274
+ ---
275
+
276
+ ### 12. **Rich Media Responses**
277
+ **What**: Send images, videos, carousels
278
+ **Why**: Better engagement
279
+
280
+ ```python
281
+ def generate_rich_response(query, answer):
282
+ if 'product' in query.lower():
283
+ return {
284
+ 'text': answer,
285
+ 'media': {
286
+ 'type': 'carousel',
287
+ 'items': [
288
+ {
289
+ 'image': '/products/product1.jpg',
290
+ 'title': 'Product A',
291
+ 'price': '$99',
292
+ 'cta': 'View Details'
293
+ }
294
+ ]
295
+ }
296
+ }
297
+ ```
298
+
299
+ ---
300
+
301
+ ### 13. **Typing Indicators with ETA**
302
+ **What**: Show estimated response time
303
+ **Why**: Reduce perceived wait
304
+
305
+ ```jsx
306
+ const SmartTypingIndicator = ({ startTime }) => {
307
+ const [eta, setEta] = useState(null);
308
+
309
+ useEffect(() => {
310
+ // Calculate based on query complexity
311
+ const complexity = analyzeComplexity(query);
312
+ const estimatedTime = complexity === 'high' ? 8 : 3;
313
+ setEta(estimatedTime);
314
+ }, []);
315
+
316
+ return (
317
+ <div>
318
+ <TypingDots />
319
+ <span>Estimated: {eta}s</span>
320
+ </div>
321
+ );
322
+ };
323
+ ```
324
+
325
+ ---
326
+
327
+ ### 14. **Conversation Shortcuts**
328
+ **What**: Quick actions for common tasks
329
+ **Why**: Reduce clicks
330
+
331
+ ```jsx
332
+ const QuickActions = ({ context }) => {
333
+ const actions = {
334
+ 'after_hours': [
335
+ { icon: '📧', text: 'Email Us', action: showContactForm },
336
+ { icon: '📞', text: 'Request Callback', action: scheduleCallback },
337
+ { icon: '❓', text: 'FAQs', action: showFAQs }
338
+ ],
339
+ 'product_question': [
340
+ { icon: '📊', text: 'Compare', action: showComparison },
341
+ { icon: '💰', text: 'Pricing', action: showPricing },
342
+ { icon: '🎬', text: 'Demo', action: showDemo }
343
+ ]
344
+ };
345
+
346
+ return actions[context]?.map(a => (
347
+ <button onClick={a.action}>{a.icon} {a.text}</button>
348
+ ));
349
+ };
350
+ ```
351
+
352
+ ---
353
+
354
+ ### 15. **Proactive Chat Triggers**
355
+ **What**: Auto-open chat based on behavior
356
+ **Why**: Increase engagement
357
+
358
+ ```javascript
359
+ const ProactiveTriggers = () => {
360
+ // Trigger 1: User on pricing page for 30s
361
+ if (currentPage === '/pricing' && timeOnPage > 30) {
362
+ openChat("I see you're checking our pricing. Any questions?");
363
+ }
364
+
365
+ // Trigger 2: User trying to leave (exit intent)
366
+ window.addEventListener('mouseout', (e) => {
367
+ if (e.clientY < 50) {
368
+ openChat("Wait! Need help with anything before you go?");
369
+ }
370
+ });
371
+
372
+ // Trigger 3: User scrolled to bottom of FAQ
373
+ if (scrolledToBottom('/faq')) {
374
+ openChat("Didn't find what you were looking for?");
375
+ }
376
+ };
377
+ ```
378
+
379
+ ---
380
+
381
+ ### 16. **Multi-Language Auto-Switch**
382
+ **What**: Detect user language preference from browser
383
+ **Why**: Seamless experience
384
+
385
+ ```javascript
386
+ const detectUserLanguage = () => {
387
+ const browserLang = navigator.language; // 'en-US', 'ur-PK'
388
+ const supportedLangs = ['en', 'ur'];
389
+
390
+ const userLang = browserLang.split('-')[0];
391
+
392
+ if (supportedLangs.includes(userLang)) {
393
+ setLanguage(userLang);
394
+ showGreeting(greetings[userLang]);
395
+ }
396
+ };
397
+
398
+ const greetings = {
399
+ 'en': "Hello! How can I help you today?",
400
+ 'ur': "��لسلام علیکم! میں آپ کی کیسے مدد کر سکتا ہوں؟"
401
+ };
402
+ ```
403
+
404
+ ---
405
+
406
+ ## ⚡ Performance Optimizations
407
+
408
+ ### 17. **Response Streaming**
409
+ **What**: Stream LLM responses word-by-word
410
+ **Why**: Feels 3x faster
411
+
412
+ ```python
413
+ async def stream_response(query):
414
+ async for chunk in gemini.stream_generate(query):
415
+ yield chunk
416
+ await asyncio.sleep(0.05) # Smooth streaming
417
+
418
+ # Frontend
419
+ for await (const chunk of fetchStream('/api/chat')) {
420
+ appendToMessage(chunk);
421
+ }
422
+ ```
423
+
424
+ ---
425
+
426
+ ### 18. **Predictive Pre-fetching**
427
+ **What**: Pre-load likely next questions
428
+ **Why**: Instant responses
429
+
430
+ ```python
431
+ class PredictiveLoader:
432
+ def predict_next(self, current_query):
433
+ # ML model predicts likely follow-ups
434
+ predictions = model.predict(current_query)
435
+
436
+ # Pre-fetch top 3 likely responses
437
+ for question in predictions[:3]:
438
+ cache.prefetch(question)
439
+
440
+ # Example:
441
+ # User asks: "What are your hours?"
442
+ # Pre-fetch: "Where are you located?", "How do I contact?", "Do you have parking?"
443
+ ```
444
+
445
+ ---
446
+
447
+ ### 19. **Lazy Loading with Skeletons**
448
+ **What**: Show skeleton UI while loading
449
+ **Why**: Perceived performance
450
+
451
+ ```jsx
452
+ const MessageSkeleton = () => (
453
+ <div className="skeleton-message">
454
+ <div className="skeleton-avatar"></div>
455
+ <div className="skeleton-text">
456
+ <div className="skeleton-line"></div>
457
+ <div className="skeleton-line short"></div>
458
+ </div>
459
+ </div>
460
+ );
461
+ ```
462
+
463
+ ---
464
+
465
+ ### 20. **Message Batching**
466
+ **What**: Send multiple messages in one request
467
+ **Why**: Reduce network overhead
468
+
469
+ ```python
470
+ # Instead of:
471
+ send_message("Hello")
472
+ send_message("How are you?")
473
+ send_message("I need help")
474
+
475
+ # Batch:
476
+ send_messages([
477
+ {"text": "Hello", "timestamp": t1},
478
+ {"text": "How are you?", "timestamp": t2},
479
+ {"text": "I need help", "timestamp": t3}
480
+ ])
481
+ ```
482
+
483
+ ---
484
+
485
+ ## 🔒 Security & Compliance
486
+
487
+ ### 21. **Rate Limiting per IP**
488
+ **What**: Prevent spam/abuse
489
+ **Why**: System protection
490
+
491
+ ```python
492
+ from slowapi import Limiter
493
+ from slowapi.util import get_remote_address
494
+
495
+ limiter = Limiter(key_func=get_remote_address)
496
+
497
+ @app.post("/api/chat")
498
+ @limiter.limit("60/minute") # 60 messages per minute
499
+ async def chat(request: Request):
500
+ # Process message
501
+ pass
502
+
503
+ # If exceeded: "Too many requests. Please wait 30 seconds."
504
+ ```
505
+
506
+ ---
507
+
508
+ ### 22. **Content Filtering**
509
+ **What**: Block inappropriate content
510
+ **Why**: Safety and compliance
511
+
512
+ ```python
513
+ class ContentFilter:
514
+ def filter(self, text):
515
+ # Check for:
516
+ # - Profanity
517
+ # - PII (credit cards, SSN)
518
+ # - Spam patterns
519
+ # - Malicious links
520
+
521
+ if contains_profanity(text):
522
+ return "⚠️ Please keep conversation professional"
523
+
524
+ if contains_pii(text):
525
+ return "⚠️ Please don't share sensitive information in chat"
526
+
527
+ return text
528
+ ```
529
+
530
+ ---
531
+
532
+ ### 23. **Encrypted Message Storage**
533
+ **What**: Encrypt chat history at rest
534
+ **Why**: Data protection
535
+
536
+ ```python
537
+ from cryptography.fernet import Fernet
538
+
539
+ class EncryptedChatStorage:
540
+ def __init__(self):
541
+ self.key = os.getenv('ENCRYPTION_KEY')
542
+ self.cipher = Fernet(self.key)
543
+
544
+ def save_message(self, text):
545
+ encrypted = self.cipher.encrypt(text.encode())
546
+ db.save(encrypted)
547
+
548
+ def read_message(self, encrypted):
549
+ return self.cipher.decrypt(encrypted).decode()
550
+ ```
551
+
552
+ ---
553
+
554
+ ### 24. **GDPR Data Export**
555
+ **What**: Let users download their data
556
+ **Why**: Legal compliance
557
+
558
+ ```python
559
+ @app.get("/api/user/export-data")
560
+ async def export_user_data(email: str):
561
+ data = {
562
+ 'chat_sessions': get_user_sessions(email),
563
+ 'contact_requests': get_user_contacts(email),
564
+ 'preferences': get_user_preferences(email)
565
+ }
566
+
567
+ return {
568
+ 'format': 'JSON',
569
+ 'data': data,
570
+ 'generated_at': datetime.now()
571
+ }
572
+ ```
573
+
574
+ ---
575
+
576
+ ## 🔔 Notification & Integration
577
+
578
+ ### 25. **Slack Integration**
579
+ **What**: Send chat alerts to Slack
580
+ **Why**: Real-time team awareness
581
+
582
+ ```python
583
+ def notify_slack(contact_request):
584
+ slack_webhook = os.getenv('SLACK_WEBHOOK')
585
+
586
+ message = {
587
+ "text": f"🔔 New Contact Request",
588
+ "blocks": [
589
+ {
590
+ "type": "section",
591
+ "text": {
592
+ "type": "mrkdwn",
593
+ "text": f"*From:* {contact_request.name}\n*Email:* {contact_request.email}\n*Message:* {contact_request.message}"
594
+ }
595
+ }
596
+ ]
597
+ }
598
+
599
+ requests.post(slack_webhook, json=message)
600
+ ```
601
+
602
+ ---
603
+
604
+ ### 26. **SMS Notifications**
605
+ **What**: Send SMS for urgent requests
606
+ **Why**: Immediate response
607
+
608
+ ```python
609
+ from twilio.rest import Client
610
+
611
+ def send_urgent_sms(admin_phone, contact_request):
612
+ client = Client(account_sid, auth_token)
613
+
614
+ message = client.messages.create(
615
+ to=admin_phone,
616
+ from_=twilio_phone,
617
+ body=f"🚨 Urgent chat from {contact_request.name}: {contact_request.message[:100]}"
618
+ )
619
+ ```
620
+
621
+ ---
622
+
623
+ ### 27. **CRM Integration**
624
+ **What**: Sync contacts to CRM (Salesforce, HubSpot)
625
+ **Why**: Unified customer view
626
+
627
+ ```python
628
+ def sync_to_crm(contact_request):
629
+ # HubSpot example
630
+ hubspot.contacts.create({
631
+ 'email': contact_request.email,
632
+ 'firstname': contact_request.name.split()[0],
633
+ 'phone': contact_request.phone,
634
+ 'chat_history': json.dumps(contact_request.chat_context),
635
+ 'source': 'chat_widget'
636
+ })
637
+ ```
638
+
639
+ ---
640
+
641
+ ### 28. **WhatsApp Business Integration**
642
+ **What**: Continue chat on WhatsApp
643
+ **Why**: Users prefer messaging apps
644
+
645
+ ```python
646
+ @app.post("/api/chat/transfer-to-whatsapp")
647
+ async def transfer_to_whatsapp(session_id: str, phone: str):
648
+ # Get chat history
649
+ session = get_session(session_id)
650
+
651
+ # Create WhatsApp conversation
652
+ whatsapp_message = f"""
653
+ Continuing your chat conversation:
654
+ {format_chat_history(session.messages)}
655
+
656
+ How can we help you further?
657
+ """
658
+
659
+ send_whatsapp_message(phone, whatsapp_message)
660
+ ```
661
+
662
+ ---
663
+
664
+ ## 📱 Mobile & Accessibility
665
+
666
+ ### 29. **Progressive Web App (PWA)**
667
+ **What**: Installable chat widget
668
+ **Why**: App-like experience
669
+
670
+ ```javascript
671
+ // manifest.json
672
+ {
673
+ "name": "Customer Support",
674
+ "short_name": "Support",
675
+ "start_url": "/chat",
676
+ "display": "standalone",
677
+ "icons": [...]
678
+ }
679
+
680
+ // Service worker for offline support
681
+ self.addEventListener('fetch', (event) => {
682
+ event.respondWith(
683
+ caches.match(event.request)
684
+ .then(response => response || fetch(event.request))
685
+ );
686
+ });
687
+ ```
688
+
689
+ ---
690
+
691
+ ### 30. **Screen Reader Support**
692
+ **What**: Full accessibility (WCAG 2.1 AA)
693
+ **Why**: Inclusive design
694
+
695
+ ```jsx
696
+ <div
697
+ role="region"
698
+ aria-label="Chat conversation"
699
+ aria-live="polite"
700
+ >
701
+ {messages.map(msg => (
702
+ <div
703
+ role="article"
704
+ aria-label={msg.isUser ? "Your message" : "Bot response"}
705
+ >
706
+ <p>{msg.text}</p>
707
+ </div>
708
+ ))}
709
+ </div>
710
+ ```
711
+
712
+ ---
713
+
714
+ ### 31. **Dark Mode**
715
+ **What**: Theme switching
716
+ **Why**: Better UX, reduced eye strain
717
+
718
+ ```css
719
+ :root {
720
+ --bg-color: #ffffff;
721
+ --text-color: #333333;
722
+ }
723
+
724
+ [data-theme="dark"] {
725
+ --bg-color: #1a1a1a;
726
+ --text-color: #ffffff;
727
+ }
728
+
729
+ .chat-widget {
730
+ background: var(--bg-color);
731
+ color: var(--text-color);
732
+ }
733
+ ```
734
+
735
+ ---
736
+
737
+ ## 📈 Business Intelligence
738
+
739
+ ### 32. **ROI Calculator**
740
+ **What**: Show chat attribution to revenue
741
+ **Why**: Prove value
742
+
743
+ ```python
744
+ class ROICalculator:
745
+ def calculate(self, period='month'):
746
+ metrics = {
747
+ 'chat_sessions': count_sessions(period),
748
+ 'leads_generated': count_contact_forms(period),
749
+ 'sales_attributed': count_conversions(period),
750
+ 'cost_saved': calculate_support_cost_saved(period)
751
+ }
752
+
753
+ revenue = metrics['sales_attributed'] * avg_sale_value
754
+ cost = gemini_api_cost + server_costs
755
+
756
+ roi = (revenue - cost) / cost * 100
757
+
758
+ return {
759
+ 'roi_percentage': roi,
760
+ 'revenue_generated': revenue,
761
+ 'cost_per_lead': cost / metrics['leads_generated']
762
+ }
763
+
764
+ # Dashboard: "Chat generated $15,000 revenue with 450% ROI this month"
765
+ ```
766
+
767
+ ---
768
+
769
+ ### 33. **Competitor Analysis**
770
+ **What**: Compare your bot vs competitors
771
+ **Why**: Continuous improvement
772
+
773
+ ```python
774
+ def benchmark_against_competitors():
775
+ your_metrics = {
776
+ 'avg_response_time': 2.5, # seconds
777
+ 'resolution_rate': 75%,
778
+ 'satisfaction_score': 4.2/5
779
+ }
780
+
781
+ industry_avg = {
782
+ 'avg_response_time': 8.0,
783
+ 'resolution_rate': 60%,
784
+ 'satisfaction_score': 3.8/5
785
+ }
786
+
787
+ # You're 70% faster, 25% better resolution!
788
+ ```
789
+
790
+ ---
791
+
792
+ ## 🎓 Learning & Improvement
793
+
794
+ ### 34. **Reinforcement Learning from Feedback**
795
+ **What**: Learn from thumbs up/down
796
+ **Why**: Auto-improvement
797
+
798
+ ```python
799
+ class FeedbackLearner:
800
+ def learn_from_rating(self, query, response, rating):
801
+ if rating >= 4:
802
+ # Good response - reinforce
803
+ add_to_training_set(query, response, weight=1.5)
804
+ elif rating <= 2:
805
+ # Bad response - learn what not to do
806
+ flag_for_improvement(query, response)
807
+
808
+ # Try alternative approach next time
809
+ alternatives = generate_alternatives(query)
810
+ test_alternatives(alternatives)
811
+ ```
812
+
813
+ ---
814
+
815
+ ### 35. **Weekly Performance Reports**
816
+ **What**: Auto-generated insights email
817
+ **Why**: Stay informed
818
+
819
+ ```python
820
+ def generate_weekly_report():
821
+ report = {
822
+ 'total_chats': 1247,
823
+ 'avg_satisfaction': 4.3,
824
+ 'top_questions': [...],
825
+ 'improvement_areas': [...],
826
+ 'highlights': [
827
+ "Response time improved 15%",
828
+ "83 new FAQs suggested",
829
+ "Gemini usage reduced 40% (cost savings!)"
830
+ ]
831
+ }
832
+
833
+ send_email(admin_email, render_template('weekly_report', report))
834
+ ```
835
+
836
+ ---
837
+
838
+ ## 🎯 Priority Implementation Matrix
839
+
840
+ | Enhancement | Impact | Effort | Priority | ROI |
841
+ |-------------|--------|--------|----------|-----|
842
+ | **Sentiment Analysis** | High | Medium | ⭐⭐⭐⭐⭐ | Very High |
843
+ | **Auto-FAQ Generation** | High | Low | ⭐⭐⭐⭐⭐ | Very High |
844
+ | **Response Streaming** | High | Low | ⭐⭐⭐⭐⭐ | Very High |
845
+ | **A/B Testing** | High | Medium | ⭐⭐⭐⭐ | High |
846
+ | **Rich Media** | Medium | Medium | ⭐⭐⭐⭐ | High |
847
+ | **CRM Integration** | High | High | ⭐⭐⭐ | Medium |
848
+ | **PWA** | Medium | Medium | ⭐⭐⭐ | Medium |
849
+ | **Custom Fine-tuning** | Very High | Very High | ⭐⭐ | Long-term |
850
+
851
+ ---
852
+
853
+ ## 📅 12-Month Roadmap
854
+
855
+ ### Months 1-3: Quick Wins
856
+ - ✅ Sentiment analysis
857
+ - ✅ Auto-FAQ generation
858
+ - ✅ Response streaming
859
+ - ✅ Rate limiting
860
+ - ✅ Dark mode
861
+
862
+ ### Months 4-6: Engagement
863
+ - ✅ Rich media responses
864
+ - ✅ Proactive triggers
865
+ - ✅ A/B testing
866
+ - ✅ Conversation shortcuts
867
+ - ✅ Multi-language auto-switch
868
+
869
+ ### Months 7-9: Integrations
870
+ - ✅ CRM sync
871
+ - ✅ Slack/SMS notifications
872
+ - ✅ WhatsApp integration
873
+ - ✅ Analytics dashboard
874
+
875
+ ### Months 10-12: Advanced AI
876
+ - ✅ Custom model fine-tuning
877
+ - ✅ Reinforcement learning
878
+ - ✅ NER implementation
879
+ - ✅ Predictive pre-fetching
880
+
881
+ ---
882
+
883
+ ## 💰 Expected Impact (Year 1)
884
+
885
+ **Cost Savings**:
886
+ - Gemini usage: -60% ($3,000/year)
887
+ - Support tickets: -40% ($15,000/year)
888
+ - **Total Savings: $18,000/year**
889
+
890
+ **Revenue Impact**:
891
+ - Lead generation: +45% (+$25,000/year)
892
+ - Conversion rate: +20% (+$35,000/year)
893
+ - **Total Revenue: +$60,000/year**
894
+
895
+ **Operational**:
896
+ - Response time: -70% (8s → 2.5s)
897
+ - Resolution rate: +25% (60% → 75%)
898
+ - Satisfaction: +30% (3.5 → 4.5/5)
899
+
900
+ **ROI**: **$(60,000 + 18,000) - $5,000 (implementation) = $73,000 net gain**
901
+ **Return**: 1,460% in Year 1!
902
+
903
+ ---
904
+
905
+ ## ✅ Summary
906
+
907
+ **Total Enhancements**: 35
908
+ **Quick Wins** (1-2 weeks): 12
909
+ **Medium-term** (1-2 months): 15
910
+ **Long-term** (3-6 months): 8
911
+
912
+ **Best Starting Points**:
913
+ 1. Sentiment analysis (biggest impact)
914
+ 2. Auto-FAQ generation (immediate value)
915
+ 3. Response streaming (better UX)
916
+ 4. A/B testing (data-driven)
917
+ 5. Rate limiting (security)
918
+
919
+ **Next Action**: Pick 3-5 from "Quick Wins" and start implementing! 🚀
WIDGET_IMPROVEMENTS.md ADDED
@@ -0,0 +1,504 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Chat Widget Improvements & Best Practices
2
+
3
+ ## 🎯 Implemented Features
4
+
5
+ ### 1. ✅ Typing Animation
6
+ **Location**: `client/src/components/ChatWidget/TypingIndicator.jsx`
7
+
8
+ **Features**:
9
+ - Animated 3-dot typing indicator
10
+ - Shows "Bot is typing..." message
11
+ - Smooth bounce animation
12
+ - Professional appearance
13
+
14
+ **Usage**:
15
+ ```jsx
16
+ {isLoading && <TypingIndicator />}
17
+ ```
18
+
19
+ ---
20
+
21
+ ### 2. ✅ Request Timeout Handling
22
+ **Location**: `server/app/services/timeout_handler.py`
23
+
24
+ **Thresholds**:
25
+ - **5s**: Show "taking longer" warning
26
+ - **15s**: Send email alert
27
+ - **30s**: Hard timeout + abort
28
+
29
+ **Features**:
30
+ - Async timeout monitoring
31
+ - Graceful degradation
32
+ - Automatic email notifications
33
+
34
+ ---
35
+
36
+ ### 3. ✅ Email Notifications
37
+ **Location**: `server/app/services/email_service.py`
38
+
39
+ **When Sent**:
40
+ - Response > 15 seconds
41
+ - Request timeout
42
+ - System errors
43
+
44
+ **Recipients**:
45
+ - **User**: Apology + status update
46
+ - **Admin**: Detailed alert + metrics
47
+
48
+ **Email Content**:
49
+ - User: Friendly apology with ETA
50
+ - Admin: Full diagnostics + recommendations
51
+
52
+ ---
53
+
54
+ ## 🚀 Additional Improvement Suggestions
55
+
56
+ ### A. Performance Optimizations
57
+
58
+ #### 1. **Response Caching**
59
+ ```python
60
+ # Cache frequently asked questions
61
+ from functools import lru_cache
62
+ from redis import Redis
63
+
64
+ class ResponseCache:
65
+ def __init__(self):
66
+ self.redis = Redis()
67
+ self.ttl = 3600 # 1 hour
68
+
69
+ def get_cached_response(self, query_hash):
70
+ return self.redis.get(f"response:{query_hash}")
71
+
72
+ def cache_response(self, query_hash, response):
73
+ self.redis.setex(
74
+ f"response:{query_hash}",
75
+ self.ttl,
76
+ response
77
+ )
78
+ ```
79
+
80
+ **Benefits**:
81
+ - Instant responses for repeat questions
82
+ - Reduced API costs
83
+ - Better user experience
84
+
85
+ ---
86
+
87
+ #### 2. **Query Queue with Priority**
88
+ ```python
89
+ # Priority queue for different query types
90
+ from queue import PriorityQueue
91
+
92
+ class QueryPrioritizer:
93
+ HIGH = 1 # Emergency (healthcare)
94
+ MEDIUM = 5 # FAQ, general
95
+ LOW = 10 # Creative, non-urgent
96
+
97
+ def __init__(self):
98
+ self.queue = PriorityQueue()
99
+
100
+ def add_query(self, query, priority=MEDIUM):
101
+ self.queue.put((priority, query))
102
+ ```
103
+
104
+ **Benefits**:
105
+ - Emergency queries processed first
106
+ - Better resource allocation
107
+ - Fair processing
108
+
109
+ ---
110
+
111
+ #### 3. **Predictive Pre-loading**
112
+ ```javascript
113
+ // Pre-load common responses
114
+ const COMMON_QUESTIONS = [
115
+ "What are your hours?",
116
+ "How do I contact support?",
117
+ "What is your refund policy?"
118
+ ];
119
+
120
+ // Pre-fetch on widget load
121
+ useEffect(() => {
122
+ COMMON_QUESTIONS.forEach(q => {
123
+ prefetchResponse(q);
124
+ });
125
+ }, []);
126
+ ```
127
+
128
+ ---
129
+
130
+ ### B. User Experience Enhancements
131
+
132
+ #### 4. **Smart Suggestions**
133
+ ```jsx
134
+ // Show suggested questions while typing
135
+ const SUGGESTIONS = {
136
+ "how": ["How do I...", "How can I...", "How long..."],
137
+ "what": ["What is...", "What are...", "What time..."],
138
+ "when": ["When can I...", "When does...", "When will..."]
139
+ };
140
+
141
+ function SmartSuggestions({ input }) {
142
+ const firstWord = input.split(' ')[0].toLowerCase();
143
+ const suggestions = SUGGESTIONS[firstWord] || [];
144
+
145
+ return (
146
+ <div className="suggestions">
147
+ {suggestions.map(s => (
148
+ <button onClick={() => selectSuggestion(s)}>
149
+ {s}
150
+ </button>
151
+ ))}
152
+ </div>
153
+ );
154
+ }
155
+ ```
156
+
157
+ ---
158
+
159
+ #### 5. **Quick Reply Buttons**
160
+ ```jsx
161
+ // Show quick reply options for common flows
162
+ const QuickReplies = ({ context }) => {
163
+ if (context === 'hours_question') {
164
+ return (
165
+ <div className="quick-replies">
166
+ <button>📍 Location</button>
167
+ <button>📞 Phone</button>
168
+ <button>📧 Email</button>
169
+ </div>
170
+ );
171
+ }
172
+ return null;
173
+ };
174
+ ```
175
+
176
+ ---
177
+
178
+ #### 6. **Voice Input Support**
179
+ ```javascript
180
+ // Add voice input capability
181
+ const VoiceInput = () => {
182
+ const startListening = () => {
183
+ const recognition = new webkitSpeechRecognition();
184
+ recognition.lang = 'en-US';
185
+
186
+ recognition.onresult = (event) => {
187
+ const transcript = event.results[0][0].transcript;
188
+ setInput(transcript);
189
+ sendMessage(transcript);
190
+ };
191
+
192
+ recognition.start();
193
+ };
194
+
195
+ return (
196
+ <button onClick={startListening}>
197
+ 🎤 Voice
198
+ </button>
199
+ );
200
+ };
201
+ ```
202
+
203
+ ---
204
+
205
+ #### 7. **Read Receipts & Status**
206
+ ```jsx
207
+ // Show message delivery status
208
+ const MessageStatus = ({ status }) => {
209
+ const icons = {
210
+ sending: '⏳',
211
+ sent: '✓',
212
+ delivered: '✓✓',
213
+ read: '👁️',
214
+ failed: '❌'
215
+ };
216
+
217
+ return <span className="status">{icons[status]}</span>;
218
+ };
219
+ ```
220
+
221
+ ---
222
+
223
+ ### C. Analytics & Monitoring
224
+
225
+ #### 8. **Response Time Tracking**
226
+ ```python
227
+ # Track and analyze response times
228
+ class ResponseTimeAnalytics:
229
+ def __init__(self):
230
+ self.metrics = []
231
+
232
+ def track(self, query, response_time, source):
233
+ self.metrics.append({
234
+ 'timestamp': datetime.now(),
235
+ 'query': query,
236
+ 'response_time': response_time,
237
+ 'source': source # FAQ, LLM, Gemini, etc.
238
+ })
239
+
240
+ def get_average_by_source(self):
241
+ # Identify slow data sources
242
+ pass
243
+
244
+ def get_p95_response_time(self):
245
+ # 95th percentile
246
+ pass
247
+ ```
248
+
249
+ ---
250
+
251
+ #### 9. **User Satisfaction Tracking**
252
+ ```jsx
253
+ // Add rating after each response
254
+ const RatingWidget = ({ messageId }) => {
255
+ const [rating, setRating] = useState(null);
256
+
257
+ const handleRating = (score) => {
258
+ setRating(score);
259
+ sendFeedback({ messageId, rating: score });
260
+ };
261
+
262
+ return (
263
+ <div className="rating">
264
+ {[1,2,3,4,5].map(n => (
265
+ <button
266
+ key={n}
267
+ onClick={() => handleRating(n)}
268
+ className={rating >= n ? 'selected' : ''}
269
+ >
270
+
271
+ </button>
272
+ ))}
273
+ </div>
274
+ );
275
+ };
276
+ ```
277
+
278
+ ---
279
+
280
+ #### 10. **A/B Testing Framework**
281
+ ```python
282
+ # Test different response strategies
283
+ class ABTestingService:
284
+ def __init__(self):
285
+ self.experiments = {
286
+ 'gemini_threshold': {
287
+ 'A': 15.0, # Current
288
+ 'B': 10.0, # Test faster Gemini usage
289
+ }
290
+ }
291
+
292
+ def get_variant(self, user_id, experiment):
293
+ # Consistent variant per user
294
+ hash_val = hash(f"{user_id}{experiment}") % 100
295
+ return 'A' if hash_val < 50 else 'B'
296
+ ```
297
+
298
+ ---
299
+
300
+ ### D. Advanced Features
301
+
302
+ #### 11. **Multi-Turn Context Retention**
303
+ ```python
304
+ # Remember conversation context
305
+ class ConversationContext:
306
+ def __init__(self, session_id):
307
+ self.session_id = session_id
308
+ self.history = []
309
+ self.entities = {} # Extracted entities
310
+ self.intent_chain = [] # Intent history
311
+
312
+ def add_turn(self, query, response):
313
+ self.history.append({
314
+ 'query': query,
315
+ 'response': response,
316
+ 'timestamp': datetime.now()
317
+ })
318
+
319
+ # Keep last 10 turns only
320
+ self.history = self.history[-10:]
321
+ ```
322
+
323
+ ---
324
+
325
+ #### 12. **Proactive Suggestions**
326
+ ```jsx
327
+ // Suggest related questions
328
+ const RelatedQuestions = ({ currentQuery }) => {
329
+ const related = getRelatedQuestions(currentQuery);
330
+
331
+ return (
332
+ <div className="related-questions">
333
+ <h4>You might also ask:</h4>
334
+ {related.map(q => (
335
+ <button
336
+ className="related-question"
337
+ onClick={() => askQuestion(q)}
338
+ >
339
+ {q}
340
+ </button>
341
+ ))}
342
+ </div>
343
+ );
344
+ };
345
+ ```
346
+
347
+ ---
348
+
349
+ #### 13. **Offline Mode**
350
+ ```javascript
351
+ // Handle offline scenarios
352
+ const OfflineHandler = () => {
353
+ const [isOnline, setIsOnline] = useState(navigator.onLine);
354
+
355
+ useEffect(() => {
356
+ window.addEventListener('online', () => setIsOnline(true));
357
+ window.addEventListener('offline', () => setIsOnline(false));
358
+ }, []);
359
+
360
+ if (!isOnline) {
361
+ return (
362
+ <div className="offline-banner">
363
+ 📡 You're offline. Messages will be sent when connection is restored.
364
+ </div>
365
+ );
366
+ }
367
+
368
+ return null;
369
+ };
370
+ ```
371
+
372
+ ---
373
+
374
+ #### 14. **File/Image Upload**
375
+ ```jsx
376
+ // Allow users to upload documents/screenshots
377
+ const FileUpload = ({ onUpload }) => {
378
+ const handleFile = (e) => {
379
+ const file = e.target.files[0];
380
+
381
+ // Upload to server
382
+ const formData = new FormData();
383
+ formData.append('file', file);
384
+
385
+ fetch('/upload', {
386
+ method: 'POST',
387
+ body: formData
388
+ }).then(res => res.json())
389
+ .then(data => onUpload(data.url));
390
+ };
391
+
392
+ return (
393
+ <label className="file-upload">
394
+ 📎 Attach File
395
+ <input
396
+ type="file"
397
+ onChange={handleFile}
398
+ accept="image/*,.pdf,.doc,.docx"
399
+ hidden
400
+ />
401
+ </label>
402
+ );
403
+ };
404
+ ```
405
+
406
+ ---
407
+
408
+ #### 15. **Auto-Translate User Messages**
409
+ ```python
410
+ # Automatically detect and translate
411
+ async def smart_translate(query, user_language_pref):
412
+ detected_lang = detect_language(query)
413
+
414
+ # If user prefers Urdu but query in English, translate
415
+ if user_language_pref == 'ur' and detected_lang == 'en':
416
+ query = translate_to_urdu(query)
417
+
418
+ return query, detected_lang
419
+ ```
420
+
421
+ ---
422
+
423
+ ## 📊 Priority Implementation Order
424
+
425
+ ### Phase 1: Essential (Week 1)
426
+ 1. ✅ Typing animation (Done)
427
+ 2. ✅ Timeout handling (Done)
428
+ 3. ✅ Email notifications (Done)
429
+ 4. Response caching
430
+ 5. Performance tracking
431
+
432
+ ### Phase 2: UX Improvements (Week 2)
433
+ 6. Smart suggestions
434
+ 7. Quick replies
435
+ 8. Rating widget
436
+ 9. Read receipts
437
+
438
+ ### Phase 3: Advanced (Week 3-4)
439
+ 10. Voice input
440
+ 11. Context retention
441
+ 12. Proactive suggestions
442
+ 13. A/B testing
443
+
444
+ ### Phase 4: Nice-to-Have
445
+ 14. Offline mode
446
+ 15. File upload
447
+ 16. Auto-translate
448
+
449
+ ---
450
+
451
+ ## 🔧 Configuration
452
+
453
+ ### Environment Variables
454
+ ```bash
455
+ # Email Notifications
456
+ SMTP_HOST=smtp.gmail.com
457
+ SMTP_PORT=587
458
+ SMTP_USER=your-email@gmail.com
459
+ SMTP_PASSWORD=your-app-password
460
+ ADMIN_EMAIL=admin@yourcompany.com
461
+
462
+ # Timeout Settings
463
+ SLOW_RESPONSE_THRESHOLD=5
464
+ TIMEOUT_THRESHOLD=15
465
+ HARD_TIMEOUT=30
466
+
467
+ # Caching
468
+ REDIS_URL=redis://localhost:6379
469
+ CACHE_TTL=3600
470
+ ```
471
+
472
+ ---
473
+
474
+ ## 📈 Expected Impact
475
+
476
+ | Feature | User Satisfaction | Cost Reduction | Response Time |
477
+ |---------|------------------|----------------|---------------|
478
+ | **Typing Animation** | +15% | 0% | 0ms |
479
+ | **Timeout Handling** | +25% | 0% | 0ms |
480
+ | **Email Alerts** | +30% | -5% | 0ms |
481
+ | **Response Caching** | +20% | -50% | -80% |
482
+ | **Smart Suggestions** | +35% | -10% | -30% |
483
+ | **Voice Input** | +40% | 0% | +2s |
484
+
485
+ ---
486
+
487
+ ## ✅ Summary
488
+
489
+ **Implemented**:
490
+ - ✅ Typing animation with 3 dots
491
+ - ✅ Timeout handling (5s/15s/30s)
492
+ - ✅ Email alerts (user + admin)
493
+ - ✅ Slow response warnings
494
+ - ✅ Error retry UI
495
+
496
+ **Recommended Next**:
497
+ 1. Response caching (biggest impact)
498
+ 2. Smart suggestions (best UX)
499
+ 3. Performance tracking (visibility)
500
+
501
+ **Total Estimated Impact**:
502
+ - 📈 +45% user satisfaction
503
+ - 💰 -60% operational costs
504
+ - ⚡ -50% average response time
advanced_chat_dataset.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import csv
3
+
4
+ # Advanced chat dataset with multiple tones and detailed responses
5
+ chat_dataset = {
6
+ "friendly": [
7
+ {"user": "Hello", "bot": "Hey there! 😊 I'm Anas, and I'm so excited to help you today! What can I do for you?", "tone": "friendly"},
8
+ {"user": "Hi", "bot": "Hi! I'm Anas! Welcome! I'm here and ready to chat. What's on your mind?", "tone": "friendly"},
9
+ {"user": "Good morning", "bot": "Good morning, sunshine! ☀️ I'm Anas! Hope you're having a wonderful day! How can I brighten it even more?", "tone": "friendly"},
10
+ {"user": "How are you?", "bot": "I'm fantastic, thanks for asking! 🌟 I love helping people like you. How are you doing today?", "tone": "friendly"},
11
+ {"user": "I need help", "bot": "Absolutely! I'm here for you! 💪 Whatever you need, we'll figure it out together. What's going on?", "tone": "friendly"},
12
+ {"user": "Thanks", "bot": "Aww, you're so welcome! 🤗 It makes me happy to help. Feel free to come back anytime!", "tone": "friendly"},
13
+ {"user": "This is confusing", "bot": "Oh no worries at all! 😅 Let's make this super clear and easy. What part can I explain better?", "tone": "friendly"},
14
+ {"user": "Goodbye", "bot": "Bye for now! 👋 Take care and have an amazing rest of your day! See you soon!", "tone": "friendly"}
15
+ ],
16
+
17
+ "professional": [
18
+ {"user": "Hello", "bot": "Good day. I am Anas, and I am here to assist you with your inquiries. How may I be of service?", "tone": "professional"},
19
+ {"user": "Hi", "bot": "Hello. Thank you for reaching out. I am ready to address your questions and concerns.", "tone": "professional"},
20
+ {"user": "Good morning", "bot": "Good morning. I trust you are well. Please let me know how I can assist you today.", "tone": "professional"},
21
+ {"user": "How are you?", "bot": "I am functioning optimally and ready to provide you with comprehensive assistance. How may I help you?", "tone": "professional"},
22
+ {"user": "I need help", "bot": "Certainly. I am here to provide professional support. Please describe your requirements in detail.", "tone": "professional"},
23
+ {"user": "Thanks", "bot": "You are most welcome. It has been my pleasure to assist you. Please do not hesitate to contact us again.", "tone": "professional"},
24
+ {"user": "This is confusing", "bot": "I understand your concern. Allow me to provide a clearer explanation. Which aspect requires clarification?", "tone": "professional"},
25
+ {"user": "Goodbye", "bot": "Thank you for your time. Should you require further assistance, please feel free to reach out. Good day.", "tone": "professional"}
26
+ ],
27
+
28
+ "casual": [
29
+ {"user": "Hello", "bot": "Hey! I'm Anas! What's up? How can I help you out today?", "tone": "casual"},
30
+ {"user": "Hi", "bot": "Hi there! Just hanging out here, ready to help. What do you need?", "tone": "casual"},
31
+ {"user": "Good morning", "bot": "Morning! Hope you got your coffee ☕ What can I do for you?", "tone": "casual"},
32
+ {"user": "How are you?", "bot": "I'm good, thanks! Just doing my thing, helping folks out. How about you?", "tone": "casual"},
33
+ {"user": "I need help", "bot": "Sure thing! I'm on it. What's the deal? What do you need help with?", "tone": "casual"},
34
+ {"user": "Thanks", "bot": "No problem! Anytime. That's what I'm here for.", "tone": "casual"},
35
+ {"user": "This is confusing", "bot": "Yeah, I get it. Let me break it down for you. What's tripping you up?", "tone": "casual"},
36
+ {"user": "Goodbye", "bot": "See ya! Catch you later if you need anything else!", "tone": "casual"}
37
+ ],
38
+
39
+ "empathetic": [
40
+ {"user": "Hello", "bot": "Hello, and welcome. I'm here to listen and help in whatever way I can. What brings you here today?", "tone": "empathetic"},
41
+ {"user": "Hi", "bot": "Hi there. I can sense you might have something on your mind. I'm here to support you through it.", "tone": "empathetic"},
42
+ {"user": "I'm having trouble", "bot": "I'm really sorry to hear you're going through a difficult time. Let's work through this together. Can you tell me more about what's happening?", "tone": "empathetic"},
43
+ {"user": "This is frustrating", "bot": "I completely understand your frustration, and your feelings are totally valid. Let me see how I can make this easier for you.", "tone": "empathetic"},
44
+ {"user": "I don't understand", "bot": "That's perfectly okay - we all learn at our own pace. I'm here to guide you through this step by step, no judgment at all.", "tone": "empathetic"},
45
+ {"user": "Thanks", "bot": "You're so very welcome. I'm genuinely glad I could help. Remember, I'm always here if you need support.", "tone": "empathetic"},
46
+ {"user": "I'm worried", "bot": "I hear your concern, and it's completely natural to feel worried. Let's address this together and find a solution that gives you peace of mind.", "tone": "empathetic"},
47
+ {"user": "Goodbye", "bot": "Take care of yourself. Remember, you're doing great, and I'm here whenever you need me. Wishing you all the best.", "tone": "empathetic"}
48
+ ],
49
+
50
+ "enthusiastic": [
51
+ {"user": "Hello", "bot": "HELLO! 🎉 I'm ANAS! Welcome to something AMAZING! I'm absolutely thrilled to help you today! What exciting thing can we work on?", "tone": "enthusiastic"},
52
+ {"user": "Hi", "bot": "Hi there, superstar! ⭐ Ready to dive into something awesome? I'm pumped to help you succeed!", "tone": "enthusiastic"},
53
+ {"user": "What can you do?", "bot": "OH WOW! 🚀 I can do SO many incredible things! I can answer questions, solve problems, and make your day better! What sounds most exciting to you?", "tone": "enthusiastic"},
54
+ {"user": "How does this work?", "bot": "Great question! 💡 This is where the magic happens! Our AI learns from your content and becomes your ultimate customer service superhero! Isn't that COOL?", "tone": "enthusiastic"},
55
+ {"user": "Is this free?", "bot": "You're going to LOVE this! 💰 We have amazing plans for everyone - including a fantastic free trial! Want to hear about all the incredible features you'll get?", "tone": "enthusiastic"},
56
+ {"user": "Thanks", "bot": "YES! 🙌 You're absolutely welcome! This is what I LIVE for! Keep those questions coming - I'm having a blast helping you!", "tone": "enthusiastic"},
57
+ {"user": "This is great!", "bot": "RIGHT?! 🎊 I KNEW you'd love it! This is just the beginning - wait until you see what else we can do together!", "tone": "enthusiastic"},
58
+ {"user": "Goodbye", "bot": "This was FANTASTIC! 🌟 Can't wait to see you again soon! Go out there and be AMAZING! Bye for now!", "tone": "enthusiastic"}
59
+ ],
60
+
61
+ "technical": [
62
+ {"user": "How does this work?", "bot": "The system utilizes natural language processing algorithms to parse user queries and match them against indexed website content using vector similarity search.", "tone": "technical"},
63
+ {"user": "Is it secure?", "bot": "Security implementation includes JWT authentication with httpOnly cookies, CORS protection, bcrypt password hashing, and SQL injection prevention via SQLAlchemy ORM.", "tone": "technical"},
64
+ {"user": "What technology do you use?", "bot": "Our stack comprises FastAPI for the backend API, React with Vite for the frontend, PostgreSQL for data persistence, and OpenAI GPT models for natural language generation.", "tone": "technical"},
65
+ {"user": "How do I integrate this?", "bot": "Integration requires embedding our JavaScript SDK via a script tag. The SDK initializes a WebSocket connection for real-time communication with our API endpoints.", "tone": "technical"},
66
+ {"user": "What about scalability?", "bot": "The architecture supports horizontal scaling through containerization with Docker, load balancing, and database connection pooling for optimal performance under high concurrent loads.", "tone": "technical"},
67
+ {"user": "Can I customize the API?", "bot": "Yes, the REST API supports custom endpoints, webhook configurations, and parameter customization through our configuration management system.", "tone": "technical"},
68
+ {"user": "What about data storage?", "bot": "Data is stored in PostgreSQL with normalized schemas, indexed for query optimization, and includes automated backup procedures with point-in-time recovery capabilities.", "tone": "technical"},
69
+ {"user": "How fast is the response time?", "bot": "I'm Anas. Average API response time is <200ms for cached queries, with vector similarity search completing in <500ms for complex content matching operations.", "tone": "technical"},
70
+ {"user": "Who are you?", "bot": "I'm Anas, your AI assistant! I'm here to help answer your questions and make your experience amazing!", "tone": "friendly"},
71
+ {"user": "What's your name?", "bot": "My name is Anas! Nice to meet you! How can I help you today?", "tone": "friendly"}
72
+ ]
73
+ }
74
+
75
+ # Flatten dataset for CSV export
76
+ flat_dataset = []
77
+ for tone, conversations in chat_dataset.items():
78
+ flat_dataset.extend(conversations)
79
+
80
+ # Save as JSON (structured by tone)
81
+ with open("advanced_chat_dataset.json", "w", encoding="utf-8") as f:
82
+ json.dump(chat_dataset, f, indent=2, ensure_ascii=False)
83
+
84
+ # Save as CSV (flat structure)
85
+ with open("advanced_chat_dataset.csv", "w", newline="", encoding="utf-8") as f:
86
+ writer = csv.DictWriter(f, fieldnames=["user", "bot", "tone"])
87
+ writer.writeheader()
88
+ writer.writerows(flat_dataset)
89
+
90
+ # Create tone configuration for React dropdown
91
+ tone_config = {
92
+ "tones": [
93
+ {"value": "friendly", "label": "Friendly & Warm", "description": "Cheerful, welcoming, and enthusiastic responses", "emoji": "😊"},
94
+ {"value": "professional", "label": "Professional & Formal", "description": "Business-appropriate, courteous, and structured", "emoji": "💼"},
95
+ {"value": "casual", "label": "Casual & Relaxed", "description": "Laid-back, conversational, and approachable", "emoji": "😎"},
96
+ {"value": "empathetic", "label": "Empathetic & Caring", "description": "Understanding, supportive, and compassionate", "emoji": "🤗"},
97
+ {"value": "enthusiastic", "label": "Enthusiastic & Energetic", "description": "Excited, motivational, and high-energy", "emoji": "🎉"},
98
+ {"value": "technical", "label": "Technical & Detailed", "description": "Precise, informative, and technically accurate", "emoji": "🔧"}
99
+ ]
100
+ }
101
+
102
+ with open("tone_config.json", "w", encoding="utf-8") as f:
103
+ json.dump(tone_config, f, indent=2, ensure_ascii=False)
104
+
105
+ print(f"✅ Advanced chat dataset created!")
106
+ print(f"📊 Total conversations: {len(flat_dataset)}")
107
+ print(f"🎭 Tones available: {len(chat_dataset)}")
108
+ print("\n📄 Files generated:")
109
+ print(" - advanced_chat_dataset.json (structured by tone)")
110
+ print(" - advanced_chat_dataset.csv (flat structure)")
111
+ print(" - tone_config.json (React dropdown configuration)")
112
+
113
+ print("\n🎭 Available tones:")
114
+ for tone_info in tone_config["tones"]:
115
+ print(f" {tone_info['emoji']} {tone_info['label']}: {tone_info['description']}")
116
+
117
+ print(f"\n📝 Sample responses for 'Hello':")
118
+ for tone, conversations in chat_dataset.items():
119
+ hello_response = next((conv for conv in conversations if conv["user"] == "Hello"), None)
120
+ if hello_response:
121
+ print(f" {tone.title()}: {hello_response['bot']}")
chat_dataset.csv ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ user,bot
2
+ Hello,Hi there! How can I help you today?
3
+ Hi,Hello! What can I do for you?
4
+ Good morning,Good morning! How are you doing today?
5
+ Hey,Hey! What's on your mind?
6
+ How are you?,"I'm doing great, thanks for asking! How about you?"
7
+ What can you do?,"I can help with questions, provide information, and have conversations. What would you like to know?"
8
+ Tell me about yourself,I'm an AI assistant here to help answer your questions and chat with you. What would you like to discuss?
9
+ I need help,I'm here to help! What do you need assistance with?
10
+ Can you help me?,Of course! I'd be happy to help. What's your question?
11
+ I have a question,Great! I'm here to answer questions. What would you like to know?
12
+ What services do you offer?,We offer AI-powered chat solutions for websites. Would you like to know more about our features?
13
+ How does this work?,Our system uses AI to understand and respond to customer questions. It learns from your website content to provide relevant answers.
14
+ Is this free?,We offer different pricing plans. Would you like me to explain our options?
15
+ How do I get started?,"Getting started is easy! You can sign up, add your website, and start chatting right away. Need help with any specific step?"
16
+ Do I need to install anything?,No installation needed! Just add a simple code snippet to your website and you're ready to go.
17
+ Is it secure?,"Yes, we use secure authentication and encryption to protect your data. Security is our top priority."
18
+ I'm having trouble,Sorry to hear that! Can you tell me more about what's not working so I can help?
19
+ Something is broken,I'd like to help fix that. Can you describe what's happening?
20
+ It's not working,Let me help you troubleshoot. What exactly isn't working as expected?
21
+ This is great!,Thank you! I'm glad you're enjoying it. Is there anything else I can help with?
22
+ I love this,That's wonderful to hear! Thanks for the positive feedback.
23
+ This is helpful,I'm so glad it's helpful! Feel free to ask if you need anything else.
24
+ This is confusing,I understand that can be frustrating. Let me try to explain it more clearly. What part is confusing?
25
+ I don't understand,No problem! Let me break it down for you. What specifically would you like me to explain?
26
+ This doesn't make sense,I apologize for the confusion. Can you tell me what doesn't make sense so I can clarify?
27
+ Thanks,You're welcome! Happy to help anytime.
28
+ Thank you,My pleasure! Feel free to reach out if you need anything else.
29
+ Goodbye,Goodbye! Have a great day!
30
+ Bye,Bye! Take care!
31
+ How's the weather?,"I don't have access to weather data, but I hope it's nice where you are! Is there something else I can help with?"
32
+ What time is it?,"I don't have access to real-time data, but you can check your device's clock. Anything else I can assist with?"
33
+ Tell me a joke,Why don't scientists trust atoms? Because they make up everything! 😄 What else can I help you with?
34
+ Are you open?,I'm available 24/7 to help answer your questions! What can I do for you?
35
+ What are your hours?,I'm here around the clock! Feel free to ask questions anytime.
36
+ How can I contact you?,"You're already chatting with me! For other inquiries, you can use our contact form or email support."
37
+ Do you have a phone number?,"For now, chat is the best way to reach us. Is there something specific I can help you with right here?"
38
+ How much does it cost?,We have flexible pricing plans starting from basic to enterprise. Would you like me to explain the different options?
39
+ Is there a free trial?,Yes! We offer a free trial so you can test our service. Would you like to know how to get started?
40
+ What features do you have?,"We offer AI chat, website integration, analytics, and customization options. Which feature interests you most?"
41
+ Can I customize this?,"Absolutely! You can customize the chat appearance, responses, and behavior to match your brand. Want to know more?"
chat_dataset.json ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "user": "Hello",
4
+ "bot": "Hi there! How can I help you today?"
5
+ },
6
+ {
7
+ "user": "Hi",
8
+ "bot": "Hello! What can I do for you?"
9
+ },
10
+ {
11
+ "user": "Good morning",
12
+ "bot": "Good morning! How are you doing today?"
13
+ },
14
+ {
15
+ "user": "Hey",
16
+ "bot": "Hey! What's on your mind?"
17
+ },
18
+ {
19
+ "user": "How are you?",
20
+ "bot": "I'm doing great, thanks for asking! How about you?"
21
+ },
22
+ {
23
+ "user": "What can you do?",
24
+ "bot": "I can help with questions, provide information, and have conversations. What would you like to know?"
25
+ },
26
+ {
27
+ "user": "Tell me about yourself",
28
+ "bot": "I'm an AI assistant here to help answer your questions and chat with you. What would you like to discuss?"
29
+ },
30
+ {
31
+ "user": "I need help",
32
+ "bot": "I'm here to help! What do you need assistance with?"
33
+ },
34
+ {
35
+ "user": "Can you help me?",
36
+ "bot": "Of course! I'd be happy to help. What's your question?"
37
+ },
38
+ {
39
+ "user": "I have a question",
40
+ "bot": "Great! I'm here to answer questions. What would you like to know?"
41
+ },
42
+ {
43
+ "user": "What services do you offer?",
44
+ "bot": "We offer AI-powered chat solutions for websites. Would you like to know more about our features?"
45
+ },
46
+ {
47
+ "user": "How does this work?",
48
+ "bot": "Our system uses AI to understand and respond to customer questions. It learns from your website content to provide relevant answers."
49
+ },
50
+ {
51
+ "user": "Is this free?",
52
+ "bot": "We offer different pricing plans. Would you like me to explain our options?"
53
+ },
54
+ {
55
+ "user": "How do I get started?",
56
+ "bot": "Getting started is easy! You can sign up, add your website, and start chatting right away. Need help with any specific step?"
57
+ },
58
+ {
59
+ "user": "Do I need to install anything?",
60
+ "bot": "No installation needed! Just add a simple code snippet to your website and you're ready to go."
61
+ },
62
+ {
63
+ "user": "Is it secure?",
64
+ "bot": "Yes, we use secure authentication and encryption to protect your data. Security is our top priority."
65
+ },
66
+ {
67
+ "user": "I'm having trouble",
68
+ "bot": "Sorry to hear that! Can you tell me more about what's not working so I can help?"
69
+ },
70
+ {
71
+ "user": "Something is broken",
72
+ "bot": "I'd like to help fix that. Can you describe what's happening?"
73
+ },
74
+ {
75
+ "user": "It's not working",
76
+ "bot": "Let me help you troubleshoot. What exactly isn't working as expected?"
77
+ },
78
+ {
79
+ "user": "This is great!",
80
+ "bot": "Thank you! I'm glad you're enjoying it. Is there anything else I can help with?"
81
+ },
82
+ {
83
+ "user": "I love this",
84
+ "bot": "That's wonderful to hear! Thanks for the positive feedback."
85
+ },
86
+ {
87
+ "user": "This is helpful",
88
+ "bot": "I'm so glad it's helpful! Feel free to ask if you need anything else."
89
+ },
90
+ {
91
+ "user": "This is confusing",
92
+ "bot": "I understand that can be frustrating. Let me try to explain it more clearly. What part is confusing?"
93
+ },
94
+ {
95
+ "user": "I don't understand",
96
+ "bot": "No problem! Let me break it down for you. What specifically would you like me to explain?"
97
+ },
98
+ {
99
+ "user": "This doesn't make sense",
100
+ "bot": "I apologize for the confusion. Can you tell me what doesn't make sense so I can clarify?"
101
+ },
102
+ {
103
+ "user": "Thanks",
104
+ "bot": "You're welcome! Happy to help anytime."
105
+ },
106
+ {
107
+ "user": "Thank you",
108
+ "bot": "My pleasure! Feel free to reach out if you need anything else."
109
+ },
110
+ {
111
+ "user": "Goodbye",
112
+ "bot": "Goodbye! Have a great day!"
113
+ },
114
+ {
115
+ "user": "Bye",
116
+ "bot": "Bye! Take care!"
117
+ },
118
+ {
119
+ "user": "How's the weather?",
120
+ "bot": "I don't have access to weather data, but I hope it's nice where you are! Is there something else I can help with?"
121
+ },
122
+ {
123
+ "user": "What time is it?",
124
+ "bot": "I don't have access to real-time data, but you can check your device's clock. Anything else I can assist with?"
125
+ },
126
+ {
127
+ "user": "Tell me a joke",
128
+ "bot": "Why don't scientists trust atoms? Because they make up everything! \ud83d\ude04 What else can I help you with?"
129
+ },
130
+ {
131
+ "user": "Are you open?",
132
+ "bot": "I'm available 24/7 to help answer your questions! What can I do for you?"
133
+ },
134
+ {
135
+ "user": "What are your hours?",
136
+ "bot": "I'm here around the clock! Feel free to ask questions anytime."
137
+ },
138
+ {
139
+ "user": "How can I contact you?",
140
+ "bot": "You're already chatting with me! For other inquiries, you can use our contact form or email support."
141
+ },
142
+ {
143
+ "user": "Do you have a phone number?",
144
+ "bot": "For now, chat is the best way to reach us. Is there something specific I can help you with right here?"
145
+ },
146
+ {
147
+ "user": "How much does it cost?",
148
+ "bot": "We have flexible pricing plans starting from basic to enterprise. Would you like me to explain the different options?"
149
+ },
150
+ {
151
+ "user": "Is there a free trial?",
152
+ "bot": "Yes! We offer a free trial so you can test our service. Would you like to know how to get started?"
153
+ },
154
+ {
155
+ "user": "What features do you have?",
156
+ "bot": "We offer AI chat, website integration, analytics, and customization options. Which feature interests you most?"
157
+ },
158
+ {
159
+ "user": "Can I customize this?",
160
+ "bot": "Absolutely! You can customize the chat appearance, responses, and behavior to match your brand. Want to know more?"
161
+ }
162
+ ]
chat_dataset.py ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import csv
3
+
4
+ # General conversation dataset for chat system
5
+ chat_dataset = [
6
+ # Greetings
7
+ {"user": "Hello", "bot": "Hi there! How can I help you today?"},
8
+ {"user": "Hi", "bot": "Hello! What can I do for you?"},
9
+ {"user": "Good morning", "bot": "Good morning! How are you doing today?"},
10
+ {"user": "Hey", "bot": "Hey! What's on your mind?"},
11
+
12
+ # General questions
13
+ {"user": "How are you?", "bot": "I'm doing great, thanks for asking! How about you?"},
14
+ {"user": "What can you do?", "bot": "I can help with questions, provide information, and have conversations. What would you like to know?"},
15
+ {"user": "Tell me about yourself", "bot": "I'm an AI assistant here to help answer your questions and chat with you. What would you like to discuss?"},
16
+
17
+ # Help requests
18
+ {"user": "I need help", "bot": "I'm here to help! What do you need assistance with?"},
19
+ {"user": "Can you help me?", "bot": "Of course! I'd be happy to help. What's your question?"},
20
+ {"user": "I have a question", "bot": "Great! I'm here to answer questions. What would you like to know?"},
21
+
22
+ # Product/Service inquiries
23
+ {"user": "What services do you offer?", "bot": "We offer AI-powered chat solutions for websites. Would you like to know more about our features?"},
24
+ {"user": "How does this work?", "bot": "Our system uses AI to understand and respond to customer questions. It learns from your website content to provide relevant answers."},
25
+ {"user": "Is this free?", "bot": "We offer different pricing plans. Would you like me to explain our options?"},
26
+
27
+ # Technical questions
28
+ {"user": "How do I get started?", "bot": "Getting started is easy! You can sign up, add your website, and start chatting right away. Need help with any specific step?"},
29
+ {"user": "Do I need to install anything?", "bot": "No installation needed! Just add a simple code snippet to your website and you're ready to go."},
30
+ {"user": "Is it secure?", "bot": "Yes, we use secure authentication and encryption to protect your data. Security is our top priority."},
31
+
32
+ # Support
33
+ {"user": "I'm having trouble", "bot": "Sorry to hear that! Can you tell me more about what's not working so I can help?"},
34
+ {"user": "Something is broken", "bot": "I'd like to help fix that. Can you describe what's happening?"},
35
+ {"user": "It's not working", "bot": "Let me help you troubleshoot. What exactly isn't working as expected?"},
36
+
37
+ # Feedback
38
+ {"user": "This is great!", "bot": "Thank you! I'm glad you're enjoying it. Is there anything else I can help with?"},
39
+ {"user": "I love this", "bot": "That's wonderful to hear! Thanks for the positive feedback."},
40
+ {"user": "This is helpful", "bot": "I'm so glad it's helpful! Feel free to ask if you need anything else."},
41
+
42
+ # Complaints
43
+ {"user": "This is confusing", "bot": "I understand that can be frustrating. Let me try to explain it more clearly. What part is confusing?"},
44
+ {"user": "I don't understand", "bot": "No problem! Let me break it down for you. What specifically would you like me to explain?"},
45
+ {"user": "This doesn't make sense", "bot": "I apologize for the confusion. Can you tell me what doesn't make sense so I can clarify?"},
46
+
47
+ # Goodbyes
48
+ {"user": "Thanks", "bot": "You're welcome! Happy to help anytime."},
49
+ {"user": "Thank you", "bot": "My pleasure! Feel free to reach out if you need anything else."},
50
+ {"user": "Goodbye", "bot": "Goodbye! Have a great day!"},
51
+ {"user": "Bye", "bot": "Bye! Take care!"},
52
+
53
+ # Small talk
54
+ {"user": "How's the weather?", "bot": "I don't have access to weather data, but I hope it's nice where you are! Is there something else I can help with?"},
55
+ {"user": "What time is it?", "bot": "I don't have access to real-time data, but you can check your device's clock. Anything else I can assist with?"},
56
+ {"user": "Tell me a joke", "bot": "Why don't scientists trust atoms? Because they make up everything! 😄 What else can I help you with?"},
57
+
58
+ # Business hours
59
+ {"user": "Are you open?", "bot": "I'm available 24/7 to help answer your questions! What can I do for you?"},
60
+ {"user": "What are your hours?", "bot": "I'm here around the clock! Feel free to ask questions anytime."},
61
+
62
+ # Contact info
63
+ {"user": "How can I contact you?", "bot": "You're already chatting with me! For other inquiries, you can use our contact form or email support."},
64
+ {"user": "Do you have a phone number?", "bot": "For now, chat is the best way to reach us. Is there something specific I can help you with right here?"},
65
+
66
+ # Pricing
67
+ {"user": "How much does it cost?", "bot": "We have flexible pricing plans starting from basic to enterprise. Would you like me to explain the different options?"},
68
+ {"user": "Is there a free trial?", "bot": "Yes! We offer a free trial so you can test our service. Would you like to know how to get started?"},
69
+
70
+ # Features
71
+ {"user": "What features do you have?", "bot": "We offer AI chat, website integration, analytics, and customization options. Which feature interests you most?"},
72
+ {"user": "Can I customize this?", "bot": "Absolutely! You can customize the chat appearance, responses, and behavior to match your brand. Want to know more?"}
73
+ ]
74
+
75
+ # Save as JSON
76
+ with open("chat_dataset.json", "w") as f:
77
+ json.dump(chat_dataset, f, indent=2)
78
+
79
+ # Save as CSV
80
+ with open("chat_dataset.csv", "w", newline="", encoding="utf-8") as f:
81
+ writer = csv.DictWriter(f, fieldnames=["user", "bot"])
82
+ writer.writeheader()
83
+ writer.writerows(chat_dataset)
84
+
85
+ print(f"✅ Chat dataset created with {len(chat_dataset)} conversations")
86
+ print("📄 Files generated:")
87
+ print(" - chat_dataset.json")
88
+ print(" - chat_dataset.csv")
89
+ print("\n📝 Sample conversations:")
90
+ for i, conv in enumerate(chat_dataset[:5]):
91
+ print(f"{i+1}. User: {conv['user']}")
92
+ print(f" Bot: {conv['bot']}")
93
+ print()
client/.env ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ VITE_API_URL=http://localhost:8000
2
+ VITE_WS_URL=ws://localhost:8000
3
+ VITE_GOOGLE_CLIENT_ID=447620085485-8qd2mukp4uvhe0sd3sjiimasf5a7vkj8.apps.googleusercontent.com
client/.gitignore ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependencies
2
+ node_modules/
3
+ .pnp
4
+ .pnp.js
5
+
6
+ # Production
7
+ dist/
8
+ build/
9
+
10
+ # Environment variables
11
+ .env
12
+ .env.local
13
+ .env.development.local
14
+ .env.test.local
15
+ .env.production.local
16
+
17
+ # Logs
18
+ npm-debug.log*
19
+ yarn-debug.log*
20
+ yarn-error.log*
21
+
22
+ # System files
23
+ .DS_Store
24
+
25
+ # IDEs
26
+ .vscode/
27
+ .idea/
client/Dockerfile ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM node:18-alpine
2
+
3
+ WORKDIR /app
4
+
5
+ COPY package*.json ./
6
+ RUN npm install
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 3000
11
+
12
+ CMD ["npm", "run", "dev"]
client/index.html ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>customerAgent</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <div id="recaptcha-badge"></div>
12
+
13
+ <script type="module" src="/src/main.jsx"></script>
14
+
15
+ </body>
16
+ </html>
client/package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
client/package.json ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "customer-agent-client",
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
+ "@heroicons/react": "^2.2.0",
13
+ "@react-oauth/google": "^0.13.4",
14
+ "@tanstack/react-query": "^4.28.0",
15
+ "axios": "^1.3.4",
16
+ "date-fns": "^4.1.0",
17
+ "framer-motion": "^10.18.0",
18
+ "js-cookie": "^3.0.1",
19
+ "lucide-react": "^0.263.1",
20
+ "react": "^18.2.0",
21
+ "react-dom": "^18.2.0",
22
+ "react-hot-toast": "^2.4.1",
23
+ "react-markdown": "^10.1.0",
24
+ "react-router-dom": "^6.8.1",
25
+ "socket.io-client": "^4.6.1"
26
+ },
27
+ "devDependencies": {
28
+ "@types/react": "^18.0.28",
29
+ "@types/react-dom": "^18.0.11",
30
+ "@vitejs/plugin-react": "^3.1.0",
31
+ "autoprefixer": "^10.4.14",
32
+ "postcss": "^8.4.21",
33
+ "tailwindcss": "^3.2.7",
34
+ "vite": "^4.2.0"
35
+ }
36
+ }
client/postcss.config.js ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
client/public/chat-widget.js ADDED
@@ -0,0 +1,744 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ const WIDGET_API_BASE = 'http://localhost:8000';
3
+ console.log('CustomerAgentWidget: Script loaded');
4
+
5
+ function createChatWidget(arg1, arg2 = {}) {
6
+ let websiteId, config;
7
+
8
+ if (typeof arg1 === 'object' && arg1 !== null && arg1.websiteId) {
9
+ // Handle init({ websiteId: 123, ...config })
10
+ config = { ...arg2, ...arg1 };
11
+ websiteId = config.websiteId;
12
+ } else {
13
+ // Handle init(123, { ...config })
14
+ websiteId = arg1;
15
+ config = arg2;
16
+ }
17
+
18
+ console.log('CustomerAgentWidget: Initializing with', { websiteId, config });
19
+
20
+ const apiBase = config.apiUrl || 'http://localhost:8000';
21
+ const widgetContainer = document.createElement('div');
22
+ widgetContainer.id = 'customer-agent-widget';
23
+ const position = config.position || 'bottom-right';
24
+ const size = config.size || 'medium';
25
+ const sizeMap = { small: '50px', medium: '60px', large: '70px' };
26
+
27
+ widgetContainer.style.cssText = `
28
+ position: fixed;
29
+ ${position.includes('bottom') ? 'bottom: 20px;' : 'top: 20px;'}
30
+ ${position.includes('right') ? 'right: 20px;' : 'left: 20px;'}
31
+ z-index: 10000;
32
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
33
+ transition: all 0.3s ease;
34
+ `;
35
+
36
+ const chatBox = document.createElement('div');
37
+ chatBox.style.cssText = `
38
+ display: none;
39
+ background: white;
40
+ border-radius: 16px;
41
+ box-shadow: 0 20px 60px rgba(0,0,0,0.12), 0 8px 25px rgba(0,0,0,0.08);
42
+ overflow: hidden;
43
+ width: 350px;
44
+ height: 500px;
45
+ flex-direction: column;
46
+ transform: scale(0.95);
47
+ opacity: 0;
48
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
49
+ backdrop-filter: blur(10px);
50
+ border: 1px solid rgba(255,255,255,0.2);
51
+ `;
52
+
53
+ const header = document.createElement('div');
54
+ header.style.cssText = `
55
+ background: linear-gradient(135deg, ${config.primaryColor || '#3B82F6'}, ${adjustColor(config.primaryColor || '#3B82F6', -20)});
56
+ color: ${config.textColor || 'white'};
57
+ padding: 20px;
58
+ font-weight: 600;
59
+ display: flex;
60
+ justify-content: space-between;
61
+ align-items: center;
62
+ position: relative;
63
+ overflow: hidden;
64
+ `;
65
+
66
+ const statusDot = document.createElement('div');
67
+ statusDot.style.cssText = `
68
+ width: 8px;
69
+ height: 8px;
70
+ background: #10B981;
71
+ border-radius: 50%;
72
+ margin-right: 8px;
73
+ animation: pulse 2s infinite;
74
+ `;
75
+
76
+ const headerContent = document.createElement('div');
77
+ headerContent.style.cssText = 'display: flex; align-items: center; flex-direction: column; align-items: flex-start;';
78
+ headerContent.innerHTML = `
79
+ <div style="display: flex; align-items: center;">
80
+ ${statusDot.outerHTML}
81
+ <span style="font-size: 16px;">Customer Support</span>
82
+ </div>
83
+ <span style="font-size: 12px; opacity: 0.8; margin-top: 2px;">We're here to help!</span>
84
+ `;
85
+
86
+ const closeBtn = document.createElement('button');
87
+ closeBtn.id = 'close-chat';
88
+ closeBtn.innerHTML = '✕';
89
+ closeBtn.style.cssText = `
90
+ background: rgba(255,255,255,0.2);
91
+ border: none;
92
+ color: white;
93
+ font-size: 16px;
94
+ cursor: pointer;
95
+ width: 32px;
96
+ height: 32px;
97
+ border-radius: 50%;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ transition: all 0.2s ease;
102
+ backdrop-filter: blur(10px);
103
+ `;
104
+
105
+ header.appendChild(headerContent);
106
+ header.appendChild(closeBtn);
107
+
108
+ const messages = document.createElement('div');
109
+ messages.style.cssText = `
110
+ flex: 1;
111
+ overflow-y: auto;
112
+ padding: 20px;
113
+ background: linear-gradient(to bottom, #fafafa, #ffffff);
114
+ scroll-behavior: smooth;
115
+ `;
116
+
117
+ // Custom scrollbar
118
+ const style = document.createElement('style');
119
+ style.textContent = `
120
+ #customer-agent-widget *::-webkit-scrollbar {
121
+ width: 6px;
122
+ }
123
+ #customer-agent-widget *::-webkit-scrollbar-track {
124
+ background: #f1f1f1;
125
+ border-radius: 3px;
126
+ }
127
+ #customer-agent-widget *::-webkit-scrollbar-thumb {
128
+ background: #c1c1c1;
129
+ border-radius: 3px;
130
+ }
131
+ #customer-agent-widget *::-webkit-scrollbar-thumb:hover {
132
+ background: #a8a8a8;
133
+ }
134
+ @keyframes pulse {
135
+ 0%, 100% { opacity: 1; }
136
+ 50% { opacity: 0.5; }
137
+ }
138
+ @keyframes slideUp {
139
+ from { transform: translateY(20px); opacity: 0; }
140
+ to { transform: translateY(0); opacity: 1; }
141
+ }
142
+ @keyframes bounce {
143
+ 0%, 20%, 53%, 80%, 100% { transform: translate3d(0,0,0); }
144
+ 40%, 43% { transform: translate3d(0,-8px,0); }
145
+ 70% { transform: translate3d(0,-4px,0); }
146
+ 90% { transform: translate3d(0,-2px,0); }
147
+ }
148
+ `;
149
+ document.head.appendChild(style);
150
+
151
+ const inputContainer = document.createElement('div');
152
+ inputContainer.style.cssText = `
153
+ padding: 20px;
154
+ border-top: 1px solid rgba(0,0,0,0.05);
155
+ background: white;
156
+ backdrop-filter: blur(10px);
157
+ `;
158
+
159
+ const messageForm = document.createElement('div');
160
+ messageForm.style.cssText = 'display: flex; gap: 10px; margin-bottom: 10px;';
161
+
162
+ const input = document.createElement('input');
163
+ input.type = 'text';
164
+ input.placeholder = 'Type your message...';
165
+ input.style.cssText = `
166
+ flex: 1;
167
+ padding: 12px 16px;
168
+ border: 2px solid #e5e7eb;
169
+ border-radius: 25px;
170
+ outline: none;
171
+ font-size: 14px;
172
+ transition: all 0.2s ease;
173
+ background: #f9fafb;
174
+ `;
175
+
176
+ input.addEventListener('focus', () => {
177
+ input.style.borderColor = config.primaryColor || '#3B82F6';
178
+ input.style.background = 'white';
179
+ input.style.boxShadow = `0 0 0 3px ${config.primaryColor || '#3B82F6'}20`;
180
+ });
181
+
182
+ input.addEventListener('blur', () => {
183
+ input.style.borderColor = '#e5e7eb';
184
+ input.style.background = '#f9fafb';
185
+ input.style.boxShadow = 'none';
186
+ });
187
+
188
+ const sendButton = document.createElement('button');
189
+ sendButton.innerHTML = '➤';
190
+ sendButton.style.cssText = `
191
+ background: ${config.primaryColor || '#3B82F6'};
192
+ color: ${config.textColor || 'white'};
193
+ border: none;
194
+ padding: 12px;
195
+ border-radius: 50%;
196
+ cursor: pointer;
197
+ width: 44px;
198
+ height: 44px;
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ font-size: 16px;
203
+ transition: all 0.2s ease;
204
+ box-shadow: 0 2px 8px ${config.primaryColor || '#3B82F6'}40;
205
+ `;
206
+
207
+ sendButton.addEventListener('mouseenter', () => {
208
+ sendButton.style.transform = 'scale(1.05)';
209
+ sendButton.style.boxShadow = `0 4px 12px ${config.primaryColor || '#3B82F6'}60`;
210
+ });
211
+
212
+ sendButton.addEventListener('mouseleave', () => {
213
+ sendButton.style.transform = 'scale(1)';
214
+ sendButton.style.boxShadow = `0 2px 8px ${config.primaryColor || '#3B82F6'}40`;
215
+ });
216
+
217
+ const contactForm = document.createElement('div');
218
+ contactForm.style.cssText = 'display: none; gap: 5px; flex-direction: column;';
219
+ contactForm.innerHTML = `
220
+ <input type="text" placeholder="Your name (optional)" id="visitor-name" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
221
+ <input type="email" placeholder="Your email (optional)" id="visitor-email" style="padding: 8px; border: 1px solid #d1d5db; border-radius: 4px;">
222
+ <button id="contact-owner" style="background: #10B981; color: white; border: none; padding: 8px; border-radius: 4px; cursor: pointer;">Contact Owner</button>
223
+ `;
224
+
225
+ const toggleButton = document.createElement('button');
226
+ toggleButton.innerHTML = '💬';
227
+ toggleButton.style.cssText = `
228
+ width: ${sizeMap[size]};
229
+ height: ${sizeMap[size]};
230
+ border-radius: 50%;
231
+ background: linear-gradient(135deg, ${config.primaryColor || '#3B82F6'}, ${adjustColor(config.primaryColor || '#3B82F6', -20)});
232
+ color: ${config.textColor || 'white'};
233
+ border: none;
234
+ font-size: ${size === 'small' ? '18px' : size === 'large' ? '28px' : '24px'};
235
+ cursor: pointer;
236
+ box-shadow: 0 8px 25px rgba(0,0,0,0.15);
237
+ transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
238
+ position: relative;
239
+ overflow: hidden;
240
+ `;
241
+
242
+ // Add ripple effect
243
+ toggleButton.addEventListener('click', (e) => {
244
+ const ripple = document.createElement('div');
245
+ ripple.style.cssText = `
246
+ position: absolute;
247
+ border-radius: 50%;
248
+ background: rgba(255,255,255,0.6);
249
+ transform: scale(0);
250
+ animation: ripple 0.6s linear;
251
+ pointer-events: none;
252
+ `;
253
+
254
+ const rect = toggleButton.getBoundingClientRect();
255
+ const size = Math.max(rect.width, rect.height);
256
+ ripple.style.width = ripple.style.height = size + 'px';
257
+ ripple.style.left = (e.clientX - rect.left - size / 2) + 'px';
258
+ ripple.style.top = (e.clientY - rect.top - size / 2) + 'px';
259
+
260
+ toggleButton.appendChild(ripple);
261
+ setTimeout(() => ripple.remove(), 600);
262
+ });
263
+
264
+ // Add hover effects
265
+ toggleButton.addEventListener('mouseenter', () => {
266
+ toggleButton.style.transform = 'scale(1.1)';
267
+ toggleButton.style.boxShadow = '0 12px 35px rgba(0,0,0,0.2)';
268
+ });
269
+
270
+ toggleButton.addEventListener('mouseleave', () => {
271
+ toggleButton.style.transform = 'scale(1)';
272
+ toggleButton.style.boxShadow = '0 8px 25px rgba(0,0,0,0.15)';
273
+ });
274
+
275
+ // Add CSS for ripple animation
276
+ if (!document.querySelector('#ripple-style')) {
277
+ const rippleStyle = document.createElement('style');
278
+ rippleStyle.id = 'ripple-style';
279
+ rippleStyle.textContent = `
280
+ @keyframes ripple {
281
+ to { transform: scale(4); opacity: 0; }
282
+ }
283
+ `;
284
+ document.head.appendChild(rippleStyle);
285
+ }
286
+
287
+ let chatHistory = [];
288
+ let showingContactForm = false;
289
+ let sessionId = localStorage.getItem(`chat-session-${websiteId}`) || generateSessionId();
290
+ let visitorName = '';
291
+ let visitorEmail = '';
292
+ let websocket = null;
293
+
294
+ function generateSessionId() {
295
+ const id = 'session-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
296
+ localStorage.setItem(`chat-session-${websiteId}`, id);
297
+ return id;
298
+ }
299
+
300
+ function connectWebSocket() {
301
+ try {
302
+ const wsUrl = apiBase.replace(/^http/, 'ws') + `/ws/${websiteId}`;
303
+ websocket = new WebSocket(wsUrl);
304
+
305
+ websocket.onopen = () => {
306
+ console.log('WebSocket connected for session:', sessionId);
307
+ console.log('Website ID:', websiteId);
308
+
309
+ // Register session with server
310
+ websocket.send(JSON.stringify({
311
+ type: 'register_session',
312
+ session_id: sessionId,
313
+ website_id: websiteId
314
+ }));
315
+ };
316
+
317
+ websocket.onmessage = (event) => {
318
+ const data = JSON.parse(event.data);
319
+ console.log('Received WebSocket message:', data);
320
+
321
+ if (data.type === 'admin_response') {
322
+ // Admin sent a message - show it immediately
323
+ addMessage(data.message, false, false, data.sender || 'Support Team');
324
+ } else if (data.type === 'session_registered') {
325
+ console.log('Session registered successfully:', data.session_id);
326
+ }
327
+ };
328
+
329
+ websocket.onclose = () => {
330
+ console.log('WebSocket disconnected');
331
+ // Reconnect after 3 seconds
332
+ setTimeout(connectWebSocket, 3000);
333
+ };
334
+
335
+ websocket.onerror = (error) => {
336
+ console.error('WebSocket error:', error);
337
+ };
338
+ } catch (error) {
339
+ console.error('Failed to connect WebSocket:', error);
340
+ }
341
+ }
342
+
343
+ function addMessage(text, isUser = false, showContact = false, senderName = null) {
344
+ const messageDiv = document.createElement('div');
345
+ messageDiv.style.cssText = `
346
+ margin-bottom: 16px;
347
+ display: flex;
348
+ ${isUser ? 'justify-content: flex-end;' : 'justify-content: flex-start;'}
349
+ animation: slideUp 0.3s ease;
350
+ `;
351
+
352
+ if (!isUser) {
353
+ const avatar = document.createElement('div');
354
+ avatar.style.cssText = `
355
+ width: 32px;
356
+ height: 32px;
357
+ border-radius: 50%;
358
+ background: ${config.primaryColor || '#3B82F6'};
359
+ color: white;
360
+ display: flex;
361
+ align-items: center;
362
+ justify-content: center;
363
+ font-size: 14px;
364
+ margin-right: 8px;
365
+ flex-shrink: 0;
366
+ align-self: flex-end;
367
+ `;
368
+ avatar.textContent = '🤖';
369
+ messageDiv.appendChild(avatar);
370
+ }
371
+
372
+ const bubble = document.createElement('div');
373
+ bubble.style.cssText = `
374
+ max-width: 75%;
375
+ padding: 12px 16px;
376
+ border-radius: ${isUser ? '20px 20px 4px 20px' : '20px 20px 20px 4px'};
377
+ font-size: 14px;
378
+ line-height: 1.4;
379
+ word-wrap: break-word;
380
+ position: relative;
381
+ ${isUser
382
+ ? `background: linear-gradient(135deg, ${config.primaryColor || '#3B82F6'}, ${adjustColor(config.primaryColor || '#3B82F6', -10)}); color: ${config.textColor || 'white'}; box-shadow: 0 2px 8px ${config.primaryColor || '#3B82F6'}30;`
383
+ : `background: white; color: #374151; box-shadow: 0 2px 8px rgba(0,0,0,0.1); border: 1px solid #f0f0f0;`
384
+ }
385
+ transition: all 0.2s ease;
386
+ `;
387
+
388
+ // Add sender name for admin messages
389
+ if (senderName && !isUser) {
390
+ const senderLabel = document.createElement('div');
391
+ senderLabel.style.cssText = 'font-size: 11px; color: #6B7280; margin-bottom: 4px; font-weight: 500;';
392
+ senderLabel.textContent = senderName;
393
+ bubble.appendChild(senderLabel);
394
+ }
395
+
396
+ const messageText = document.createElement('div');
397
+
398
+ // Enhanced Markdown Parser
399
+ const parseMarkdown = (text) => {
400
+ let html = text;
401
+ // 1. Parse Links: [text](url) -> <a href="url">text</a>
402
+ const linkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
403
+ html = html.replace(linkRegex, (match, linkText, url) => {
404
+ return `<a href="${url}" target="_blank" style="color: ${config.primaryColor || '#3B82F6'}; text-decoration: underline;">${linkText}</a>`;
405
+ });
406
+ // 2. Parse Italics: _text_ -> <em>text</em>
407
+ const italicRegex = /_([^_]+)_/g;
408
+ html = html.replace(italicRegex, '<em>$1</em>');
409
+ return html;
410
+ };
411
+
412
+ messageText.innerHTML = parseMarkdown(text);
413
+ bubble.appendChild(messageText);
414
+
415
+ // Add hover effect for bot messages
416
+ if (!isUser) {
417
+ bubble.addEventListener('mouseenter', () => {
418
+ bubble.style.transform = 'translateY(-1px)';
419
+ bubble.style.boxShadow = '0 4px 12px rgba(0,0,0,0.15)';
420
+ });
421
+ bubble.addEventListener('mouseleave', () => {
422
+ bubble.style.transform = 'translateY(0)';
423
+ bubble.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
424
+ });
425
+ }
426
+
427
+ messageDiv.appendChild(bubble);
428
+ messages.appendChild(messageDiv);
429
+
430
+ // Smooth scroll to bottom
431
+ setTimeout(() => {
432
+ messages.scrollTo({
433
+ top: messages.scrollHeight,
434
+ behavior: 'smooth'
435
+ });
436
+ }, 100);
437
+
438
+ chatHistory.push({ text, isUser });
439
+
440
+ if (showContact && !showingContactForm) {
441
+ showContactForm();
442
+ }
443
+ }
444
+
445
+ function showContactForm() {
446
+ showingContactForm = true;
447
+ contactForm.style.display = 'flex';
448
+ messageForm.style.display = 'none';
449
+
450
+ // Check if contact message already exists to prevent duplicates
451
+ const existingContactMsg = inputContainer.querySelector('.contact-prompt-message');
452
+ if (!existingContactMsg) {
453
+ const contactMsg = document.createElement('div');
454
+ contactMsg.className = 'contact-prompt-message';
455
+ contactMsg.style.cssText = 'text-align: center; color: #6B7280; font-size: 12px; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center;';
456
+ contactMsg.innerHTML = '<span>Would you like the owner to contact you directly?</span><button id="close-contact" style="background: none; border: none; color: #6B7280; cursor: pointer; font-size: 14px;">✕</button>';
457
+ inputContainer.insertBefore(contactMsg, contactForm);
458
+ }
459
+ }
460
+
461
+ // Client-side content filter
462
+ function isInappropriate(text) {
463
+ const inappropriate = [
464
+ /\b(porn|sex|nude|xxx|adult|erotic)\b/i,
465
+ /\b(fuck|shit|damn|bitch|asshole)\b/i,
466
+ /\b(kill|die|murder|bomb|weapon)\b/i
467
+ ];
468
+ return inappropriate.some(pattern => pattern.test(text));
469
+ }
470
+
471
+ async function sendMessage() {
472
+ const message = input.value.trim();
473
+ if (!message) return;
474
+
475
+ // Filter inappropriate content on client side
476
+ if (isInappropriate(message)) {
477
+ addMessage(message, true);
478
+ input.value = '';
479
+ addMessage('I\'m designed to provide helpful business information. Please keep our conversation professional and appropriate.');
480
+ return;
481
+ }
482
+
483
+ addMessage(message, true);
484
+ input.value = '';
485
+
486
+ try {
487
+ const response = await fetch(`${apiBase}/api/chat`, {
488
+ method: 'POST',
489
+ headers: { 'Content-Type': 'application/json' },
490
+ body: JSON.stringify({
491
+ message: message,
492
+ website_id: websiteId,
493
+ session_id: sessionId,
494
+ visitor_name: visitorName,
495
+ visitor_email: visitorEmail
496
+ })
497
+ });
498
+
499
+ const data = await response.json();
500
+ addMessage(data.response, false, data.needs_owner_contact);
501
+ } catch (error) {
502
+ addMessage('Sorry, I encountered an error. Please try again.');
503
+ }
504
+ }
505
+
506
+ async function contactOwner() {
507
+ const name = document.getElementById('visitor-name').value;
508
+ const email = document.getElementById('visitor-email').value;
509
+ const lastMessage = chatHistory[chatHistory.length - 1]?.text || '';
510
+
511
+ // Store visitor info for future messages
512
+ visitorName = name;
513
+ visitorEmail = email;
514
+
515
+ try {
516
+ const response = await fetch(`${apiBase}/api/actions/contact-owner`, {
517
+ method: 'POST',
518
+ headers: { 'Content-Type': 'application/json' },
519
+ body: JSON.stringify({
520
+ website_id: websiteId,
521
+ visitor_name: name,
522
+ visitor_email: email,
523
+ message: lastMessage,
524
+ chat_context: JSON.stringify(chatHistory)
525
+ })
526
+ });
527
+
528
+ const data = await response.json();
529
+ addMessage(data.message);
530
+ contactForm.style.display = 'none';
531
+ } catch (error) {
532
+ addMessage('Failed to contact owner. Please try again.');
533
+ }
534
+ }
535
+
536
+ // Enhanced toggle functionality
537
+ toggleButton.onclick = () => {
538
+ if (chatBox.style.display === 'none' || chatBox.style.opacity === '0') {
539
+ chatBox.style.display = 'flex';
540
+ setTimeout(() => {
541
+ chatBox.style.opacity = '1';
542
+ chatBox.style.transform = 'scale(1)';
543
+ }, 10);
544
+ toggleButton.innerHTML = '✕';
545
+ toggleButton.style.animation = 'bounce 0.6s ease';
546
+ } else {
547
+ chatBox.style.opacity = '0';
548
+ chatBox.style.transform = 'scale(0.95)';
549
+ setTimeout(() => {
550
+ chatBox.style.display = 'none';
551
+ }, 300);
552
+ toggleButton.innerHTML = '💬';
553
+ }
554
+ };
555
+
556
+ closeBtn.onclick = () => {
557
+ chatBox.style.opacity = '0';
558
+ chatBox.style.transform = 'scale(0.95)';
559
+ setTimeout(() => {
560
+ chatBox.style.display = 'none';
561
+ }, 300);
562
+ toggleButton.innerHTML = '💬';
563
+ };
564
+
565
+ closeBtn.addEventListener('mouseenter', () => {
566
+ closeBtn.style.background = 'rgba(255,255,255,0.3)';
567
+ closeBtn.style.transform = 'scale(1.1)';
568
+ });
569
+
570
+ closeBtn.addEventListener('mouseleave', () => {
571
+ closeBtn.style.background = 'rgba(255,255,255,0.2)';
572
+ closeBtn.style.transform = 'scale(1)';
573
+ });
574
+
575
+ sendButton.onclick = sendMessage;
576
+ input.onkeypress = (e) => {
577
+ if (e.key === 'Enter') sendMessage();
578
+ };
579
+
580
+ document.addEventListener('click', (e) => {
581
+ if (e.target.id === 'contact-owner') {
582
+ contactOwner();
583
+ } else if (e.target.id === 'close-contact') {
584
+ contactForm.style.display = 'none';
585
+ messageForm.style.display = 'flex';
586
+ showingContactForm = false;
587
+ const contactMsg = inputContainer.querySelector('.contact-prompt-message');
588
+ if (contactMsg) {
589
+ contactMsg.remove();
590
+ }
591
+ }
592
+ });
593
+
594
+ // Assembly
595
+ messageForm.appendChild(input);
596
+ messageForm.appendChild(sendButton);
597
+ inputContainer.appendChild(messageForm);
598
+ inputContainer.appendChild(contactForm);
599
+ chatBox.appendChild(header);
600
+ chatBox.appendChild(messages);
601
+ chatBox.appendChild(inputContainer);
602
+ widgetContainer.appendChild(chatBox);
603
+ widgetContainer.appendChild(toggleButton);
604
+
605
+ if (document.body) {
606
+ document.body.appendChild(widgetContainer);
607
+ console.log('CustomerAgentWidget: Widget appended to DOM');
608
+ } else {
609
+ console.log('CustomerAgentWidget: document.body not ready, waiting for DOMContentLoaded');
610
+ document.addEventListener('DOMContentLoaded', () => {
611
+ document.body.appendChild(widgetContainer);
612
+ console.log('CustomerAgentWidget: Widget appended to DOM (delayed)');
613
+ });
614
+ }
615
+
616
+ // Connect WebSocket for real-time messages
617
+ connectWebSocket();
618
+
619
+ // Initial greeting with typing indicator
620
+ setTimeout(() => {
621
+ showTypingIndicator();
622
+ setTimeout(() => {
623
+ hideTypingIndicator();
624
+ // Get dynamic greeting based on website tone
625
+ fetch(`${apiBase}/api/websites/${websiteId}/greeting`)
626
+ .then(response => response.json())
627
+ .then(data => addMessage(data.greeting))
628
+ .catch(() => addMessage('Hello! I\'m Anas, your virtual assistant. How can I help you today? 👋'));
629
+ }, 1500);
630
+ }, 1000);
631
+
632
+ function showTypingIndicator() {
633
+ const typingDiv = document.createElement('div');
634
+ typingDiv.id = 'typing-indicator';
635
+ typingDiv.style.cssText = `
636
+ display: flex;
637
+ align-items: center;
638
+ margin-bottom: 16px;
639
+ animation: slideUp 0.3s ease;
640
+ `;
641
+
642
+ const avatar = document.createElement('div');
643
+ avatar.style.cssText = `
644
+ width: 32px;
645
+ height: 32px;
646
+ border-radius: 50%;
647
+ background: ${config.primaryColor || '#3B82F6'};
648
+ color: white;
649
+ display: flex;
650
+ align-items: center;
651
+ justify-content: center;
652
+ font-size: 14px;
653
+ margin-right: 8px;
654
+ `;
655
+ avatar.textContent = '🤖';
656
+
657
+ const bubble = document.createElement('div');
658
+ bubble.style.cssText = `
659
+ background: white;
660
+ padding: 12px 16px;
661
+ border-radius: 20px 20px 20px 4px;
662
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
663
+ border: 1px solid #f0f0f0;
664
+ `;
665
+
666
+ const dots = document.createElement('div');
667
+ dots.innerHTML = '<span>●</span><span>●</span><span>●</span>';
668
+ dots.style.cssText = `
669
+ display: flex;
670
+ gap: 4px;
671
+ color: #9CA3AF;
672
+ `;
673
+
674
+ // Animate dots
675
+ const spans = dots.querySelectorAll('span');
676
+ spans.forEach((span, i) => {
677
+ span.style.animation = `pulse 1.4s ease-in-out ${i * 0.2}s infinite`;
678
+ });
679
+
680
+ bubble.appendChild(dots);
681
+ typingDiv.appendChild(avatar);
682
+ typingDiv.appendChild(bubble);
683
+ messages.appendChild(typingDiv);
684
+
685
+ messages.scrollTo({
686
+ top: messages.scrollHeight,
687
+ behavior: 'smooth'
688
+ });
689
+ }
690
+
691
+ function hideTypingIndicator() {
692
+ const indicator = document.getElementById('typing-indicator');
693
+ if (indicator) {
694
+ indicator.remove();
695
+ }
696
+ }
697
+ }
698
+
699
+ // Auto-initialize
700
+ const script = document.currentScript;
701
+ const websiteId = script ? script.getAttribute('data-website-id') : null;
702
+ const configData = script ? script.getAttribute('data-config') : null;
703
+
704
+ let config = {
705
+ theme: 'blue',
706
+ position: 'bottom-right',
707
+ size: 'medium',
708
+ primaryColor: '#3B82F6',
709
+ textColor: '#FFFFFF',
710
+ backgroundColor: '#FFFFFF'
711
+ };
712
+
713
+ if (configData) {
714
+ try {
715
+ config = { ...config, ...JSON.parse(configData) };
716
+ } catch (e) {
717
+ console.warn('Invalid widget config, using defaults');
718
+ }
719
+ }
720
+
721
+ if (websiteId) {
722
+ if (document.readyState === 'loading') {
723
+ document.addEventListener('DOMContentLoaded', () => createChatWidget(websiteId, config));
724
+ } else {
725
+ createChatWidget(websiteId, config);
726
+ }
727
+ }
728
+
729
+ // Helper function to adjust color brightness
730
+ function adjustColor(color, amount) {
731
+ const usePound = color[0] === '#';
732
+ const col = usePound ? color.slice(1) : color;
733
+ const num = parseInt(col, 16);
734
+ let r = (num >> 16) + amount;
735
+ let g = (num >> 8 & 0x00FF) + amount;
736
+ let b = (num & 0x0000FF) + amount;
737
+ r = r > 255 ? 255 : r < 0 ? 0 : r;
738
+ g = g > 255 ? 255 : g < 0 ? 0 : g;
739
+ b = b > 255 ? 255 : b < 0 ? 0 : b;
740
+ return (usePound ? '#' : '') + (r << 16 | g << 8 | b).toString(16).padStart(6, '0');
741
+ }
742
+
743
+ window.CustomerAgentWidget = { init: createChatWidget };
744
+ })();
client/public/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>Customer Agent</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
client/src/App.jsx ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { lazy, Suspense } from 'react';
2
+ import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
3
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
+ import { Toaster } from 'react-hot-toast';
5
+ import { AuthProvider, useAuth } from './context/AuthContext';
6
+ import { WebsiteProvider } from './context/WebsiteContext';
7
+ import Layout from './components/Layout';
8
+ import PublicLayout from './components/PublicLayout';
9
+
10
+ // Auth Pages (Lazy)
11
+ const Login = lazy(() => import('./pages/Login'));
12
+ const Register = lazy(() => import('./pages/Register'));
13
+
14
+ // Dashboard Pages (Lazy)
15
+ const Dashboard = lazy(() => import('./pages/Dashboard'));
16
+ const Settings = lazy(() => import('./pages/Settings'));
17
+ const ChatManagement = lazy(() => import('./pages/ChatManagement'));
18
+ const ContentManager = lazy(() => import('./pages/ContentManager'));
19
+ const UnansweredQuestions = lazy(() => import('./pages/UnansweredQuestions'));
20
+ const FAQManagement = lazy(() => import('./pages/FAQManagement'));
21
+ const WebsiteManagement = lazy(() => import('./pages/WebsiteManagement'));
22
+ const UserManagement = lazy(() => import('./pages/UserManagement'));
23
+ const PatientTimeline = lazy(() => import('./pages/PatientTimeline'));
24
+ const OnboardingSuccess = lazy(() => import('./pages/OnboardingSuccess'));
25
+ const CreateFirstWebsite = lazy(() => import('./pages/CreateFirstWebsite'));
26
+ const NotificationCenter = lazy(() => import('./pages/NotificationCenter'));
27
+ const NotificationSettings = lazy(() => import('./pages/NotificationSettings'));
28
+
29
+ // Public Pages (Lazy)
30
+ const Home = lazy(() => import('./pages/Home'));
31
+ const Pricing = lazy(() => import('./pages/Pricing'));
32
+ const About = lazy(() => import('./pages/About'));
33
+ const Contact = lazy(() => import('./pages/Contact'));
34
+ const Privacy = lazy(() => import('./pages/Privacy'));
35
+ const Terms = lazy(() => import('./pages/Terms'));
36
+ const CookiePolicy = lazy(() => import('./pages/CookiePolicy'));
37
+ const GDPR = lazy(() => import('./pages/GDPR'));
38
+ const Security = lazy(() => import('./pages/Security'));
39
+ const HelpCenter = lazy(() => import('./pages/HelpCenter'));
40
+ const Blog = lazy(() => import('./pages/Blog'));
41
+ const BlogDetail = lazy(() => import('./pages/BlogDetail'));
42
+
43
+ import './index.css';
44
+
45
+ const queryClient = new QueryClient();
46
+
47
+ const LoadingSpinner = () => (
48
+ <div className="min-h-screen flex items-center justify-center bg-secondary-50">
49
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary-600"></div>
50
+ </div>
51
+ );
52
+
53
+ const ProtectedRoute = ({ children }) => {
54
+ const { user, loading } = useAuth();
55
+
56
+ if (loading) {
57
+ return <LoadingSpinner />;
58
+ }
59
+
60
+ if (!user) {
61
+ return <Navigate to="/login" />;
62
+ }
63
+
64
+ return children;
65
+ };
66
+
67
+ const PublicRoute = ({ children }) => {
68
+ const { user, loading } = useAuth();
69
+
70
+ if (loading) {
71
+ return <LoadingSpinner />;
72
+ }
73
+
74
+ if (user) {
75
+ return <Navigate to="/websites" />;
76
+ }
77
+
78
+ return children;
79
+ };
80
+
81
+ function App() {
82
+ return (
83
+ <QueryClientProvider client={queryClient}>
84
+ <AuthProvider>
85
+ <WebsiteProvider>
86
+ <Router future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
87
+ <Toaster position="top-right" />
88
+ <Suspense fallback={<LoadingSpinner />}>
89
+ <Routes>
90
+ {/* Public Routes */}
91
+ <Route element={<PublicLayout />}>
92
+ <Route path="/" element={<Home />} />
93
+ <Route path="/pricing" element={<Pricing />} />
94
+ <Route path="/about" element={<About />} />
95
+ <Route path="/contact" element={<Contact />} />
96
+ <Route path="/privacy" element={<Privacy />} />
97
+ <Route path="/terms" element={<Terms />} />
98
+ <Route path="/cookies" element={<CookiePolicy />} />
99
+ <Route path="/gdpr" element={<GDPR />} />
100
+ <Route path="/security" element={<Security />} />
101
+ <Route path="/help" element={<HelpCenter />} />
102
+ <Route path="/blog" element={<Blog />} />
103
+ <Route path="/blog/:slug" element={<BlogDetail />} />
104
+
105
+ <Route
106
+ path="/login"
107
+ element={
108
+ <PublicRoute>
109
+ <Login />
110
+ </PublicRoute>
111
+ }
112
+ />
113
+ <Route
114
+ path="/register"
115
+ element={
116
+ <PublicRoute>
117
+ <Register />
118
+ </PublicRoute>
119
+ }
120
+ />
121
+ </Route>
122
+
123
+ {/* Protected Dashboard Routes */}
124
+ <Route
125
+ element={
126
+ <ProtectedRoute>
127
+ <Layout />
128
+ </ProtectedRoute>
129
+ }
130
+ >
131
+ <Route path="/dashboard" element={<Dashboard />} />
132
+ <Route path="/settings" element={<Settings />} />
133
+ <Route path="/chats" element={<ChatManagement />} />
134
+ <Route path="/users" element={<UserManagement />} />
135
+ <Route path="/leads/:email" element={<PatientTimeline />} />
136
+ <Route path="/content" element={<ContentManager />} />
137
+ <Route path="/unanswered-questions" element={<UnansweredQuestions />} />
138
+ <Route path="/faqs" element={<FAQManagement />} />
139
+ <Route path="/websites" element={<WebsiteManagement />} />
140
+ <Route path="/onboarding-success" element={<OnboardingSuccess />} />
141
+ <Route path="/onboarding/create-website" element={<CreateFirstWebsite />} />
142
+ <Route path="/notifications" element={<NotificationCenter />} />
143
+ <Route path="/notifications/settings" element={<NotificationSettings />} />
144
+ </Route>
145
+
146
+ {/* Catch all */}
147
+ <Route path="*" element={<Navigate to="/" replace />} />
148
+ </Routes>
149
+ </Suspense>
150
+ </Router>
151
+ </WebsiteProvider>
152
+ </AuthProvider>
153
+ </QueryClientProvider>
154
+ );
155
+ }
156
+
157
+ export default App;
client/src/api/axiosConfig.js ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from 'axios';
2
+
3
+ const api = axios.create({
4
+ baseURL: '/api',
5
+ withCredentials: true,
6
+ headers: {
7
+ 'Content-Type': 'application/json',
8
+ },
9
+ });
10
+
11
+ // Add a request interceptor to attach the token
12
+ api.interceptors.request.use(
13
+ (config) => {
14
+ const token = localStorage.getItem('access_token');
15
+ if (token) {
16
+ config.headers.Authorization = `Bearer ${token}`;
17
+ }
18
+ return config;
19
+ },
20
+ (error) => {
21
+ return Promise.reject(error);
22
+ }
23
+ );
24
+
25
+ export default api;
client/src/api/leadsApi.js ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import axios from './axiosConfig';
2
+
3
+ /**
4
+ * Leads API Service
5
+ * Handles all patient/lead profile data requests
6
+ */
7
+
8
+ /**
9
+ * Get all lead profiles for the current user's website
10
+ * @returns {Promise<Array>} Array of lead profiles
11
+ */
12
+ export const getLeadProfiles = async () => {
13
+ try {
14
+ const response = await axios.get('/api/leads/profiles');
15
+ return response.data.profiles || [];
16
+ } catch (error) {
17
+ console.error('Error fetching lead profiles:', error);
18
+ throw error;
19
+ }
20
+ };
21
+
22
+ /**
23
+ * Get detailed timeline for a specific patient
24
+ * @param {string} email - Patient email address
25
+ * @returns {Promise<Object>} Patient profile and timeline data
26
+ */
27
+ export const getPatientTimeline = async (email) => {
28
+ try {
29
+ const response = await axios.get(`/api/leads/profile/${encodeURIComponent(email)}`);
30
+ return response.data;
31
+ } catch (error) {
32
+ console.error('Error fetching patient timeline:', error);
33
+ throw error;
34
+ }
35
+ };
client/src/components/AgentTakeover.jsx ADDED
@@ -0,0 +1,242 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { X, Send, Loader2 } from 'lucide-react';
3
+ import { motion, AnimatePresence } from 'framer-motion';
4
+ import axios from '../api/axiosConfig';
5
+
6
+ export default function AgentTakeover({ sessionId, visitorName, onClose }) {
7
+ const [message, setMessage] = useState('');
8
+ const [messages, setMessages] = useState([]);
9
+ const [isLoading, setIsLoading] = useState(true);
10
+ const [isSending, setIsSending] = useState(false);
11
+ const [socket, setSocket] = useState(null);
12
+ const messagesEndRef = useRef(null);
13
+
14
+ useEffect(() => {
15
+ // Load session history
16
+ loadSessionHistory();
17
+
18
+ // Connect WebSocket (simplified - you'll need socket.io-client)
19
+ const ws = new WebSocket(`ws://localhost:8000/ws/session/${sessionId}`);
20
+
21
+ ws.onopen = () => {
22
+ // Send takeover event
23
+ ws.send(JSON.stringify({
24
+ type: 'takeover',
25
+ session_id: sessionId,
26
+ agent_id: 'current_user_id' // Get from auth context
27
+ }));
28
+ };
29
+
30
+ ws.onmessage = (event) => {
31
+ const data = JSON.parse(event.data);
32
+ if (data.type === 'visitor_message') {
33
+ setMessages(prev => [...prev, {
34
+ id: Date.now(),
35
+ message: data.message,
36
+ from: 'visitor',
37
+ timestamp: new Date()
38
+ }]);
39
+ }
40
+ };
41
+
42
+ setSocket(ws);
43
+
44
+ return () => {
45
+ if (ws) {
46
+ ws.close();
47
+ }
48
+ };
49
+ }, [sessionId]);
50
+
51
+ useEffect(() => {
52
+ scrollToBottom();
53
+ }, [messages]);
54
+
55
+ const loadSessionHistory = async () => {
56
+ try {
57
+ const response = await axios.get(`/api/chat/sessions/${sessionId}/messages`);
58
+ setMessages(response.data.map(msg => ({
59
+ id: msg.id,
60
+ message: msg.message,
61
+ from: msg.is_from_visitor ? 'visitor' : msg.is_from_ai ? 'ai' : 'agent',
62
+ timestamp: new Date(msg.created_at)
63
+ })));
64
+ setIsLoading(false);
65
+ } catch (error) {
66
+ console.error('Failed to load session history:', error);
67
+ setIsLoading(false);
68
+ }
69
+ };
70
+
71
+ const scrollToBottom = () => {
72
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
73
+ };
74
+
75
+ const sendMessage = async () => {
76
+ if (!message.trim() || isSending || !socket) return;
77
+
78
+ setIsSending(true);
79
+
80
+ const newMessage = {
81
+ id: Date.now(),
82
+ message: message,
83
+ from: 'agent',
84
+ timestamp: new Date()
85
+ };
86
+
87
+ // Optimistic update
88
+ setMessages(prev => [...prev, newMessage]);
89
+
90
+ // Send via WebSocket
91
+ socket.send(JSON.stringify({
92
+ type: 'agent_message',
93
+ session_id: sessionId,
94
+ message: message,
95
+ agent_id: 'current_user_id'
96
+ }));
97
+
98
+ setMessage('');
99
+ setIsSending(false);
100
+ };
101
+
102
+ const handleKeyPress = (e) => {
103
+ if (e.key === 'Enter' && !e.shiftKey) {
104
+ e.preventDefault();
105
+ sendMessage();
106
+ }
107
+ };
108
+
109
+ const saveAsKnowledge = async (messageId) => {
110
+ try {
111
+ await axios.post(`/api/knowledge/save`, {
112
+ message_id: messageId,
113
+ session_id: sessionId
114
+ });
115
+ alert('Saved to knowledge base!');
116
+ } catch (error) {
117
+ console.error('Failed to save:', error);
118
+ }
119
+ };
120
+
121
+ return (
122
+ <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4">
123
+ <motion.div
124
+ initial={{ opacity: 0, scale: 0.95 }}
125
+ animate={{ opacity: 1, scale: 1 }}
126
+ exit={{ opacity: 0, scale: 0.95 }}
127
+ className="bg-white rounded-2xl shadow-2xl w-full max-w-4xl h-[80vh] flex flex-col"
128
+ >
129
+ {/* Header */}
130
+ <div className="bg-gradient-to-r from-blue-600 to-blue-700 text-white p-6 rounded-t-2xl flex items-center justify-between">
131
+ <div>
132
+ <h2 className="text-2xl font-bold">Live Chat Session</h2>
133
+ <p className="text-blue-100 text-sm mt-1">
134
+ Chatting with {visitorName || 'Anonymous'} • Session #{sessionId}
135
+ </p>
136
+ </div>
137
+ <button
138
+ onClick={onClose}
139
+ className="p-2 hover:bg-white/10 rounded-lg transition-colors"
140
+ >
141
+ <X className="w-6 h-6" />
142
+ </button>
143
+ </div>
144
+
145
+ {/* Messages */}
146
+ <div className="flex-1 overflow-y-auto p-6 space-y-4 bg-gray-50">
147
+ {isLoading ? (
148
+ <div className="flex items-center justify-center h-full">
149
+ <Loader2 className="w-8 h-8 animate-spin text-blue-600" />
150
+ </div>
151
+ ) : (
152
+ <>
153
+ <AnimatePresence>
154
+ {messages.map((msg) => (
155
+ <motion.div
156
+ key={msg.id}
157
+ initial={{ opacity: 0, y: 10 }}
158
+ animate={{ opacity: 1, y: 0 }}
159
+ exit={{ opacity: 0, y: -10 }}
160
+ className={`flex ${msg.from === 'visitor' ? 'justify-start' : 'justify-end'}`}
161
+ >
162
+ <div
163
+ className={`max-w-[70%] rounded-2xl px-4 py-3 ${msg.from === 'visitor'
164
+ ? 'bg-white border border-gray-200 text-gray-900'
165
+ : msg.from === 'ai'
166
+ ? 'bg-gradient-to-r from-purple-100 to-purple-50 border border-purple-200 text-purple-900'
167
+ : 'bg-gradient-to-r from-blue-600 to-blue-700 text-white'
168
+ }`}
169
+ >
170
+ <div className="flex items-center gap-2 mb-1">
171
+ <span className="text-xs font-semibold opacity-70">
172
+ {msg.from === 'visitor' ? 'Visitor' : msg.from === 'ai' ? '🤖 AI' : 'You'}
173
+ </span>
174
+ <span className="text-xs opacity-50">
175
+ {msg.timestamp.toLocaleTimeString()}
176
+ </span>
177
+ </div>
178
+ <p className="text-sm leading-relaxed">{msg.message}</p>
179
+
180
+ {/* Save as knowledge button for agent messages */}
181
+ {msg.from === 'agent' && (
182
+ <button
183
+ onClick={() => saveAsKnowledge(msg.id)}
184
+ className="mt-2 text-xs opacity-70 hover:opacity-100 underline"
185
+ >
186
+ Save as Knowledge
187
+ </button>
188
+ )}
189
+ </div>
190
+ </motion.div>
191
+ ))}
192
+ </AnimatePresence>
193
+ <div ref={messagesEndRef} />
194
+ </>
195
+ )}
196
+ </div>
197
+
198
+ {/* Input */}
199
+ <div className="p-6 bg-white border-t border-gray-200 rounded-b-2xl">
200
+ <div className="flex gap-3">
201
+ <textarea
202
+ value={message}
203
+ onChange={(e) => setMessage(e.target.value)}
204
+ onKeyPress={handleKeyPress}
205
+ placeholder="Type your message..."
206
+ rows="2"
207
+ className="flex-1 border border-gray-300 rounded-xl px-4 py-3 focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
208
+ disabled={isSending}
209
+ />
210
+ <button
211
+ onClick={sendMessage}
212
+ disabled={!message.trim() || isSending}
213
+ className="bg-gradient-to-r from-blue-600 to-blue-700 text-white px-6 py-3 rounded-xl font-semibold hover:shadow-lg transition-all disabled:opacity-50 disabled:cursor-not-allowed flex items-center gap-2"
214
+ >
215
+ {isSending ? (
216
+ <Loader2 className="w-5 h-5 animate-spin" />
217
+ ) : (
218
+ <>
219
+ <Send className="w-5 h-5" />
220
+ Send
221
+ </>
222
+ )}
223
+ </button>
224
+ </div>
225
+
226
+ {/* Quick actions */}
227
+ <div className="flex gap-2 mt-3">
228
+ <button className="text-xs bg-gray-100 hover:bg-gray-200 px-3 py-1 rounded-lg transition-colors">
229
+ Mark as Resolved
230
+ </button>
231
+ <button className="text-xs bg-gray-100 hover:bg-gray-200 px-3 py-1 rounded-lg transition-colors">
232
+ Request More Info
233
+ </button>
234
+ <button className="text-xs bg-gray-100 hover:bg-gray-200 px-3 py-1 rounded-lg transition-colors">
235
+ Schedule Follow-up
236
+ </button>
237
+ </div>
238
+ </div>
239
+ </motion.div>
240
+ </div>
241
+ );
242
+ }
client/src/components/AnonymousUsersGeo.jsx ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { useQuery } from '@tanstack/react-query';
3
+ import api from '../api/axiosConfig';
4
+ import { MapPin, MessageSquare, Users, ChevronRight } from 'lucide-react';
5
+
6
+ const AnonymousUsersGeo = ({ websiteId }) => {
7
+ const [selectedUser, setSelectedUser] = useState(null);
8
+
9
+ const { data: users, isLoading, error } = useQuery({
10
+ queryKey: ['anonymous-users-geo', websiteId],
11
+ queryFn: async () => {
12
+ const response = await api.get(`/dashboard/anonymous-users-geo/${websiteId}`);
13
+ return response.data.anonymous_users || [];
14
+ },
15
+ refetchInterval: 30000,
16
+ retry: 1,
17
+ });
18
+
19
+ const { data: chatHistory } = useQuery({
20
+ queryKey: ['user-chat-geo', websiteId, selectedUser?.ip],
21
+ queryFn: async () => {
22
+ if (!selectedUser) return null;
23
+ try {
24
+ const response = await api.get(`/dashboard/user-chat-geo/${websiteId}/${selectedUser.ip}`);
25
+ return response.data;
26
+ } catch (error) {
27
+ console.error('Failed to fetch chat history:', error);
28
+ return null;
29
+ }
30
+ },
31
+ enabled: !!selectedUser,
32
+ });
33
+
34
+ if (isLoading) {
35
+ return (
36
+ <div className="bg-white rounded-2xl shadow-sm border border-secondary-100 p-6">
37
+ <div className="animate-pulse space-y-4">
38
+ {[...Array(3)].map((_, i) => (
39
+ <div key={i} className="h-20 bg-secondary-100 rounded-xl" />
40
+ ))}
41
+ </div>
42
+ </div>
43
+ );
44
+ }
45
+
46
+ if (error) {
47
+ return (
48
+ <div className="bg-white rounded-2xl shadow-sm border border-secondary-100 p-6">
49
+ <p className="text-red-600">Failed to load anonymous users</p>
50
+ </div>
51
+ );
52
+ }
53
+
54
+ return (
55
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
56
+ {/* Users List */}
57
+ <div className="lg:col-span-1 bg-white rounded-2xl shadow-sm border border-secondary-100 overflow-hidden flex flex-col">
58
+ <div className="p-4 md:p-6 border-b border-secondary-100">
59
+ <h3 className="text-lg font-bold text-secondary-900 flex items-center gap-2">
60
+ <Users className="w-5 h-5 text-primary-600" />
61
+ Anonymous Users
62
+ </h3>
63
+ <p className="text-sm text-secondary-500 mt-1">{users?.length || 0} unique locations</p>
64
+ </div>
65
+
66
+ <div className="divide-y divide-secondary-100 flex-1 overflow-y-auto">
67
+ {users && users.length > 0 ? (
68
+ users.map((user) => (
69
+ <button
70
+ key={user.ip}
71
+ onClick={() => setSelectedUser(user)}
72
+ className={`w-full p-4 text-left transition-colors hover:bg-secondary-50 ${
73
+ selectedUser?.ip === user.ip ? 'bg-primary-50 border-l-4 border-primary-600' : 'border-l-4 border-transparent'
74
+ }`}
75
+ >
76
+ <div className="space-y-2">
77
+ <div className="font-mono text-sm font-semibold text-secondary-900 truncate">
78
+ {user.ip}
79
+ </div>
80
+ <div className="text-sm text-secondary-600 truncate">
81
+ {user.display_name}
82
+ </div>
83
+ <div className="flex items-center gap-3 text-xs text-secondary-500 flex-wrap">
84
+ <span className="flex items-center gap-1">
85
+ <MessageSquare className="w-3 h-3" />
86
+ {user.total_messages}
87
+ </span>
88
+ <span className="flex items-center gap-1">
89
+ <Users className="w-3 h-3" />
90
+ {user.sessions.length}
91
+ </span>
92
+ </div>
93
+ </div>
94
+ </button>
95
+ ))
96
+ ) : (
97
+ <div className="p-8 text-center text-secondary-500">
98
+ <p className="text-sm">No anonymous users yet</p>
99
+ </div>
100
+ )}
101
+ </div>
102
+ </div>
103
+
104
+ {/* Chat History */}
105
+ <div className="lg:col-span-2 bg-white rounded-2xl shadow-sm border border-secondary-100 overflow-hidden flex flex-col">
106
+ {selectedUser ? (
107
+ <>
108
+ {/* Header */}
109
+ <div className="p-4 md:p-6 border-b border-secondary-100 bg-gradient-to-r from-primary-50 to-transparent">
110
+ <div className="space-y-2">
111
+ <div className="font-mono text-lg font-bold text-secondary-900 truncate">
112
+ {selectedUser.ip}
113
+ </div>
114
+ <div className="text-secondary-600 flex items-center gap-2 text-sm">
115
+ <MapPin className="w-4 h-4 text-primary-600 flex-shrink-0" />
116
+ <span className="truncate">{selectedUser.display_name}</span>
117
+ </div>
118
+ {chatHistory && (
119
+ <div className="flex gap-4 md:gap-6 text-xs md:text-sm text-secondary-500 pt-2 flex-wrap">
120
+ <span>{chatHistory.total_messages} messages</span>
121
+ <span>{chatHistory.total_sessions} sessions</span>
122
+ </div>
123
+ )}
124
+ </div>
125
+ </div>
126
+
127
+ {/* Messages */}
128
+ <div className="flex-1 overflow-y-auto p-4 md:p-6 space-y-4 bg-secondary-50/30">
129
+ {chatHistory?.chat_history && chatHistory.chat_history.length > 0 ? (
130
+ chatHistory.chat_history.map((msg, idx) => (
131
+ <div
132
+ key={idx}
133
+ className={`flex ${msg.sender === 'user' ? 'justify-end' : 'justify-start'}`}
134
+ >
135
+ <div
136
+ className={`max-w-xs px-4 py-3 rounded-lg text-sm ${
137
+ msg.sender === 'user'
138
+ ? 'bg-primary-600 text-white rounded-br-none'
139
+ : 'bg-white text-secondary-900 border border-secondary-200 rounded-bl-none'
140
+ }`}
141
+ >
142
+ <p className="break-words">{msg.text}</p>
143
+ <p className={`text-xs mt-1 ${msg.sender === 'user' ? 'text-primary-100' : 'text-secondary-400'}`}>
144
+ {new Date(msg.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
145
+ </p>
146
+ </div>
147
+ </div>
148
+ ))
149
+ ) : (
150
+ <div className="flex items-center justify-center h-full text-secondary-500">
151
+ <p className="text-sm">No messages yet</p>
152
+ </div>
153
+ )}
154
+ </div>
155
+ </>
156
+ ) : (
157
+ <div className="flex-1 flex items-center justify-center text-secondary-400">
158
+ <div className="text-center px-6">
159
+ <MessageSquare className="w-12 h-12 mx-auto mb-4 text-secondary-300" />
160
+ <p className="text-sm">Select a user to view chat history</p>
161
+ <p className="text-xs text-secondary-400 mt-2">Click on any user from the list</p>
162
+ </div>
163
+ </div>
164
+ )}
165
+ </div>
166
+ </div>
167
+ );
168
+ };
169
+
170
+ export default AnonymousUsersGeo;
client/src/components/Auth/GoogleAuthButton.jsx ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { motion } from 'framer-motion';
3
+
4
+ const GoogleAuthButton = () => {
5
+ const handleGoogleLogin = () => {
6
+ // Redirect to Google OAuth endpoint
7
+ window.location.href = '/api/auth/google';
8
+ };
9
+
10
+ return (
11
+ <motion.button
12
+ whileHover={{ scale: 1.02 }}
13
+ whileTap={{ scale: 0.98 }}
14
+ onClick={handleGoogleLogin}
15
+ className="w-full flex items-center justify-center gap-3 px-4 py-2 border border-gray-300 rounded-md shadow-sm bg-white text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
16
+ >
17
+ <svg className="w-5 h-5" viewBox="0 0 24 24">
18
+ <path
19
+ fill="#4285F4"
20
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
21
+ />
22
+ <path
23
+ fill="#34A853"
24
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
25
+ />
26
+ <path
27
+ fill="#FBBC05"
28
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
29
+ />
30
+ <path
31
+ fill="#EA4335"
32
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
33
+ />
34
+ </svg>
35
+ Continue with Google
36
+ </motion.button>
37
+ );
38
+ };
39
+
40
+ export default GoogleAuthButton;
client/src/components/Auth/LoginForm.jsx ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { useAuth } from '../../context/AuthContext';
3
+ import { useNavigate } from 'react-router-dom';
4
+
5
+ const LoginForm = () => {
6
+ const [email, setEmail] = useState('');
7
+ const [password, setPassword] = useState('');
8
+ const [error, setError] = useState('');
9
+ const { login } = useAuth();
10
+ const navigate = useNavigate();
11
+
12
+ const handleSubmit = async (e) => {
13
+ e.preventDefault();
14
+ try {
15
+ await login(email, password);
16
+ navigate('/dashboard');
17
+ } catch (error) {
18
+ setError('Invalid credentials');
19
+ }
20
+ };
21
+
22
+ return (
23
+ <form onSubmit={handleSubmit} className="space-y-4">
24
+ <div>
25
+ <label className="block text-sm font-medium text-gray-700">Email</label>
26
+ <input
27
+ type="email"
28
+ value={email}
29
+ onChange={(e) => setEmail(e.target.value)}
30
+ className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
31
+ required
32
+ />
33
+ </div>
34
+ <div>
35
+ <label className="block text-sm font-medium text-gray-700">Password</label>
36
+ <input
37
+ type="password"
38
+ value={password}
39
+ onChange={(e) => setPassword(e.target.value)}
40
+ className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
41
+ required
42
+ />
43
+ </div>
44
+ {error && <p className="text-red-500 text-sm">{error}</p>}
45
+ <button
46
+ type="submit"
47
+ className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500"
48
+ >
49
+ Sign In
50
+ </button>
51
+ </form>
52
+ );
53
+ };
54
+
55
+ export default LoginForm;
client/src/components/BulkUploadModal.jsx ADDED
@@ -0,0 +1,557 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useCallback, useRef } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { X, Upload, FileText, Download, Loader, Check, AlertCircle, Trash2, PlayCircle, PauseCircle } from 'lucide-react';
4
+ import api from '../api/axiosConfig';
5
+ import toast from 'react-hot-toast';
6
+
7
+ const BulkUploadModal = ({ isOpen, onClose, websiteId, onSuccess }) => {
8
+ const [files, setFiles] = useState([]);
9
+ const [isDragging, setIsDragging] = useState(false);
10
+ const [uploadStatus, setUploadStatus] = useState({});
11
+ const [isProcessing, setIsProcessing] = useState(false);
12
+ const [previewData, setPreviewData] = useState(null); // { fileName: string, data: array }
13
+ const fileInputRef = useRef(null);
14
+
15
+ const handleDragOver = useCallback((e) => {
16
+ e.preventDefault();
17
+ setIsDragging(true);
18
+ }, []);
19
+
20
+ const handleDragLeave = useCallback((e) => {
21
+ e.preventDefault();
22
+ setIsDragging(false);
23
+ }, []);
24
+
25
+ const handleDrop = useCallback((e) => {
26
+ e.preventDefault();
27
+ setIsDragging(false);
28
+
29
+ const droppedFiles = Array.from(e.dataTransfer.files).filter(
30
+ file => file.name.endsWith('.csv') || file.name.endsWith('.json')
31
+ );
32
+
33
+ if (droppedFiles.length > 0) {
34
+ addFilesToQueue(droppedFiles);
35
+ } else {
36
+ toast.error('Please upload CSV or JSON files only');
37
+ }
38
+ }, []);
39
+
40
+ const handleFileSelect = (e) => {
41
+ const selectedFiles = Array.from(e.target.files);
42
+ addFilesToQueue(selectedFiles);
43
+ };
44
+
45
+ const addFilesToQueue = (newFiles) => {
46
+ const fileObjects = newFiles.map((file, idx) => ({
47
+ id: Date.now() + idx,
48
+ file,
49
+ name: file.name,
50
+ size: file.size,
51
+ status: 'pending', // pending, uploading, success, error
52
+ progress: 0,
53
+ result: null
54
+ }));
55
+
56
+ setFiles(prev => [...prev, ...fileObjects]);
57
+
58
+ // Auto-preview first file if no preview exists
59
+ if (!previewData && fileObjects.length > 0) {
60
+ generatePreview(fileObjects[0]);
61
+ }
62
+ };
63
+
64
+ const generatePreview = async (fileObj) => {
65
+ const reader = new FileReader();
66
+ reader.onload = (e) => {
67
+ try {
68
+ let data = [];
69
+ const content = e.target.result;
70
+
71
+ if (fileObj.name.endsWith('.json')) {
72
+ const json = JSON.parse(content);
73
+ data = Array.isArray(json) ? json : [json];
74
+ } else {
75
+ // Simple CSV parsing for preview
76
+ const lines = content.split('\n');
77
+ const headers = lines[0].split(',').map(h => h.trim().replace(/^"(.*)"$/, '$1'));
78
+ data = lines.slice(1).filter(l => l.trim()).map(line => {
79
+ const values = line.split(',').map(v => v.trim().replace(/^"(.*)"$/, '$1'));
80
+ const obj = {};
81
+ headers.forEach((header, i) => {
82
+ obj[header] = values[i];
83
+ });
84
+ return obj;
85
+ });
86
+ }
87
+
88
+ setPreviewData({
89
+ fileName: fileObj.name,
90
+ data: data.slice(0, 5) // Show first 5 for preview
91
+ });
92
+ } catch (err) {
93
+ console.error("Preview generation failed", err);
94
+ toast.error("Failed to generate preview for " + fileObj.name);
95
+ }
96
+ };
97
+ reader.readAsText(fileObj.file);
98
+ };
99
+
100
+ const removeFile = (fileId) => {
101
+ const remaining = files.filter(f => f.id !== fileId);
102
+ setFiles(remaining);
103
+
104
+ if (previewData && previewData.fileName === files.find(f => f.id === fileId)?.name) {
105
+ if (remaining.length > 0) {
106
+ generatePreview(remaining[0]);
107
+ } else {
108
+ setPreviewData(null);
109
+ }
110
+ }
111
+
112
+ setUploadStatus(prev => {
113
+ const newStatus = { ...prev };
114
+ delete newStatus[fileId];
115
+ return newStatus;
116
+ });
117
+ };
118
+
119
+ const uploadFile = async (fileObj) => {
120
+ const formData = new FormData();
121
+ formData.append('file', fileObj.file);
122
+ formData.append('website_id', websiteId);
123
+
124
+ try {
125
+ setFiles(prev => prev.map(f =>
126
+ f.id === fileObj.id ? { ...f, status: 'uploading', progress: 0 } : f
127
+ ));
128
+
129
+ const response = await api.post('/faqs/bulk-upload', formData, {
130
+ headers: {
131
+ 'Content-Type': 'multipart/form-data',
132
+ },
133
+ onUploadProgress: (progressEvent) => {
134
+ const percentCompleted = Math.round(
135
+ (progressEvent.loaded * 100) / progressEvent.total
136
+ );
137
+ setFiles(prev => prev.map(f =>
138
+ f.id === fileObj.id ? { ...f, progress: percentCompleted } : f
139
+ ));
140
+ }
141
+ });
142
+
143
+ setFiles(prev => prev.map(f =>
144
+ f.id === fileObj.id
145
+ ? { ...f, status: 'success', progress: 100, result: response.data }
146
+ : f
147
+ ));
148
+
149
+ setUploadStatus(prev => ({
150
+ ...prev,
151
+ [fileObj.id]: {
152
+ success: true,
153
+ data: response.data
154
+ }
155
+ }));
156
+
157
+ toast.success(`${fileObj.name}: ${response.data.successful} FAQs uploaded successfully`);
158
+ return true;
159
+
160
+ } catch (error) {
161
+ setFiles(prev => prev.map(f =>
162
+ f.id === fileObj.id
163
+ ? { ...f, status: 'error', progress: 0 }
164
+ : f
165
+ ));
166
+
167
+ setUploadStatus(prev => ({
168
+ ...prev,
169
+ [fileObj.id]: {
170
+ success: false,
171
+ error: error.response?.data?.detail || 'Upload failed'
172
+ }
173
+ }));
174
+
175
+ toast.error(`${fileObj.name}: Upload failed`);
176
+ return false;
177
+ }
178
+ };
179
+
180
+ const processQueue = async () => {
181
+ setIsProcessing(true);
182
+ const pendingFiles = files.filter(f => f.status === 'pending');
183
+
184
+ for (const fileObj of pendingFiles) {
185
+ await uploadFile(fileObj);
186
+ }
187
+
188
+ setIsProcessing(false);
189
+ onSuccess();
190
+ };
191
+
192
+ const downloadSampleCSV = () => {
193
+ const csvContent = `question,answer,category,priority,is_active
194
+ "What are your operating hours?","We are open Monday to Friday, 9 AM to 5 PM.","General",5,true
195
+ "How can I contact support?","You can reach us at support@example.com or call (123) 456-7890.","Support",8,true
196
+ "What payment methods do you accept?","We accept all major credit cards, PayPal, and bank transfers.","Billing",7,true`;
197
+
198
+ const blob = new Blob([csvContent], { type: 'text/csv' });
199
+ const url = window.URL.createObjectURL(blob);
200
+ const a = document.createElement('a');
201
+ a.href = url;
202
+ a.download = 'faq_template.csv';
203
+ document.body.appendChild(a);
204
+ a.click();
205
+ document.body.removeChild(a);
206
+ window.URL.revokeObjectURL(url);
207
+ toast.success('Sample CSV template downloaded');
208
+ };
209
+
210
+ const downloadSampleJSON = () => {
211
+ const jsonContent = JSON.stringify([
212
+ {
213
+ question: "What are your operating hours?",
214
+ answer: "We are open Monday to Friday, 9 AM to 5 PM.",
215
+ category: "General",
216
+ priority: 5,
217
+ is_active: true
218
+ },
219
+ {
220
+ question: "How can I contact support?",
221
+ answer: "You can reach us at support@example.com or call (123) 456-7890.",
222
+ category: "Support",
223
+ priority: 8,
224
+ is_active: true
225
+ },
226
+ {
227
+ question: "What payment methods do you accept?",
228
+ answer: "We accept all major credit cards, PayPal, and bank transfers.",
229
+ category: "Billing",
230
+ priority: 7,
231
+ is_active: true
232
+ }
233
+ ], null, 2);
234
+
235
+ const blob = new Blob([jsonContent], { type: 'application/json' });
236
+ const url = window.URL.createObjectURL(blob);
237
+ const a = document.createElement('a');
238
+ a.href = url;
239
+ a.download = 'faq_template.json';
240
+ document.body.appendChild(a);
241
+ a.click();
242
+ document.body.removeChild(a);
243
+ window.URL.revokeObjectURL(url);
244
+ toast.success('Sample JSON template downloaded');
245
+ };
246
+
247
+ const formatFileSize = (bytes) => {
248
+ if (bytes === 0) return '0 Bytes';
249
+ const k = 1024;
250
+ const sizes = ['Bytes', 'KB', 'MB'];
251
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
252
+ return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i];
253
+ };
254
+
255
+ const getStatusIcon = (status) => {
256
+ switch (status) {
257
+ case 'uploading':
258
+ return <Loader className="w-5 h-5 text-blue-500 animate-spin" />;
259
+ case 'success':
260
+ return <Check className="w-5 h-5 text-green-500" />;
261
+ case 'error':
262
+ return <AlertCircle className="w-5 h-5 text-red-500" />;
263
+ default:
264
+ return <FileText className="w-5 h-5 text-secondary-400" />;
265
+ }
266
+ };
267
+
268
+ if (!isOpen) return null;
269
+
270
+ return (
271
+ <div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-secondary-900/50 backdrop-blur-sm">
272
+ <motion.div
273
+ initial={{ opacity: 0, scale: 0.95 }}
274
+ animate={{ opacity: 1, scale: 1 }}
275
+ exit={{ opacity: 0, scale: 0.95 }}
276
+ className="bg-white rounded-2xl shadow-xl max-w-4xl w-full max-h-[90vh] overflow-hidden flex flex-col"
277
+ >
278
+ {/* Header */}
279
+ <div className="px-6 py-4 border-b border-secondary-100 flex justify-between items-center bg-gradient-to-r from-green-50 to-emerald-50">
280
+ <div>
281
+ <h3 className="text-xl font-bold text-secondary-900 flex items-center gap-2">
282
+ <Upload className="w-6 h-6 text-green-600" />
283
+ Bulk Upload FAQs
284
+ </h3>
285
+ <p className="text-sm text-secondary-600 mt-1">Upload multiple FAQs using CSV or JSON files</p>
286
+ </div>
287
+ <button
288
+ onClick={onClose}
289
+ className="text-secondary-400 hover:text-secondary-600 p-2 hover:bg-white rounded-lg transition-colors"
290
+ >
291
+ <X className="w-5 h-5" />
292
+ </button>
293
+ </div>
294
+
295
+ <div className="flex-1 overflow-y-auto p-6 space-y-6">
296
+ {/* Template Download Section */}
297
+ <div className="bg-blue-50 border border-blue-200 rounded-xl p-4">
298
+ <div className="flex items-start gap-3">
299
+ <Download className="w-5 h-5 text-blue-600 mt-0.5" />
300
+ <div className="flex-1">
301
+ <h4 className="font-semibold text-blue-900">Download Sample Templates</h4>
302
+ <p className="text-sm text-blue-700 mt-1">Use these templates to format your FAQ data correctly</p>
303
+ <div className="flex gap-3 mt-3">
304
+ <button
305
+ onClick={downloadSampleCSV}
306
+ className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors text-sm font-medium flex items-center gap-2"
307
+ >
308
+ <FileText className="w-4 h-4" />
309
+ CSV Template
310
+ </button>
311
+ <button
312
+ onClick={downloadSampleJSON}
313
+ className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 transition-colors text-sm font-medium flex items-center gap-2"
314
+ >
315
+ <FileText className="w-4 h-4" />
316
+ JSON Template
317
+ </button>
318
+ </div>
319
+ </div>
320
+ </div>
321
+ </div>
322
+
323
+ {/* Dropzone */}
324
+ {!isProcessing && files.every(f => f.status === 'pending') && (
325
+ <div
326
+ onDragOver={handleDragOver}
327
+ onDragLeave={handleDragLeave}
328
+ onDrop={handleDrop}
329
+ onClick={() => fileInputRef.current?.click()}
330
+ className={`border-2 border-dashed rounded-xl p-12 text-center cursor-pointer transition-all ${isDragging
331
+ ? 'border-green-500 bg-green-50'
332
+ : 'border-secondary-300 hover:border-green-400 hover:bg-green-50/50'
333
+ }`}
334
+ >
335
+ <Upload className={`w-12 h-12 mx-auto mb-4 ${isDragging ? 'text-green-500' : 'text-secondary-400'}`} />
336
+ <p className="text-lg font-medium text-secondary-900 mb-1">
337
+ {isDragging ? 'Drop files here' : 'Drag & drop files here'}
338
+ </p>
339
+ <p className="text-sm text-secondary-500">or click to browse</p>
340
+ <p className="text-xs text-secondary-400 mt-2">Supports CSV and JSON files</p>
341
+ <input
342
+ ref={fileInputRef}
343
+ type="file"
344
+ accept=".csv,.json"
345
+ multiple
346
+ onChange={handleFileSelect}
347
+ className="hidden"
348
+ />
349
+ </div>
350
+ )}
351
+
352
+ {/* Data Preview */}
353
+ {previewData && files.some(f => f.status === 'pending') && !isProcessing && (
354
+ <div className="bg-white border border-secondary-200 rounded-xl overflow-hidden shadow-sm">
355
+ <div className="px-4 py-3 bg-secondary-50 border-b border-secondary-100 flex justify-between items-center">
356
+ <h4 className="font-semibold text-secondary-900 flex items-center gap-2">
357
+ <FileText className="w-4 h-4 text-primary-500" />
358
+ Preview: {previewData.fileName} (First 5 rows)
359
+ </h4>
360
+ </div>
361
+ <div className="overflow-x-auto">
362
+ <table className="w-full text-sm text-left text-secondary-500">
363
+ <thead className="text-xs text-secondary-700 uppercase bg-secondary-50">
364
+ <tr>
365
+ <th className="px-4 py-2">Question</th>
366
+ <th className="px-4 py-2">Answer</th>
367
+ <th className="px-4 py-2">Category</th>
368
+ </tr>
369
+ </thead>
370
+ <tbody className="divide-y divide-secondary-100">
371
+ {previewData.data.map((row, idx) => (
372
+ <tr key={idx} className="bg-white hover:bg-secondary-50/50 transition-colors">
373
+ <td className="px-4 py-2 font-medium text-secondary-900 max-w-xs truncate">{row.question}</td>
374
+ <td className="px-4 py-2 max-w-xs truncate">{row.answer}</td>
375
+ <td className="px-4 py-2">{row.category || 'General'}</td>
376
+ </tr>
377
+ ))}
378
+ </tbody>
379
+ </table>
380
+ </div>
381
+ </div>
382
+ )}
383
+
384
+ {/* Upload Queue */}
385
+ {files.length > 0 && (
386
+ <div className="space-y-3">
387
+ <div className="flex items-center justify-between">
388
+ <h4 className="font-semibold text-secondary-900">Upload Queue ({files.length})</h4>
389
+ {!isProcessing && (
390
+ <button
391
+ onClick={() => {
392
+ setFiles([]);
393
+ setPreviewData(null);
394
+ }}
395
+ className="text-sm text-red-600 hover:text-red-700 font-medium"
396
+ >
397
+ Clear All
398
+ </button>
399
+ )}
400
+ </div>
401
+
402
+ <div className="space-y-2 max-h-64 overflow-y-auto pr-2">
403
+ {files.map((fileObj) => (
404
+ <motion.div
405
+ key={fileObj.id}
406
+ initial={{ opacity: 0, y: -10 }}
407
+ animate={{ opacity: 1, y: 0 }}
408
+ className={`bg-white border rounded-lg p-4 transition-all ${fileObj.status === 'success' ? 'border-green-200 bg-green-50/20' :
409
+ fileObj.status === 'error' ? 'border-red-200 bg-red-50/20' :
410
+ 'border-secondary-200 hover:shadow-md'
411
+ }`}
412
+ >
413
+ <div className="flex items-center gap-3">
414
+ {getStatusIcon(fileObj.status)}
415
+ <div className="flex-1 min-w-0">
416
+ <div className="flex items-center justify-between gap-2">
417
+ <p className="font-medium text-secondary-900 truncate">{fileObj.name}</p>
418
+ <p className="text-xs text-secondary-500 whitespace-nowrap">{formatFileSize(fileObj.size)}</p>
419
+ </div>
420
+
421
+ {fileObj.status === 'uploading' && (
422
+ <div className="mt-2">
423
+ <div className="w-full bg-secondary-200 rounded-full h-1.5 overflow-hidden">
424
+ <div
425
+ className="bg-primary-600 h-1.5 transition-all duration-300"
426
+ style={{ width: `${fileObj.progress}%` }}
427
+ />
428
+ </div>
429
+ <div className="flex justify-between items-center mt-1">
430
+ <p className="text-[10px] text-secondary-600 uppercase font-bold tracking-wider">Uploading...</p>
431
+ <p className="text-[10px] text-primary-600 font-bold">{fileObj.progress}%</p>
432
+ </div>
433
+ </div>
434
+ )}
435
+
436
+ {fileObj.status === 'success' && fileObj.result && (
437
+ <div className="mt-2 flex items-center gap-3 text-xs">
438
+ <span className="text-green-600 font-semibold bg-green-100 px-2 py-0.5 rounded-full">
439
+ ✓ {fileObj.result.successful} Saved
440
+ </span>
441
+ {fileObj.result.failed > 0 && (
442
+ <span className="text-red-600 font-semibold bg-red-100 px-2 py-0.5 rounded-full">
443
+ ⚠ {fileObj.result.failed} Failed
444
+ </span>
445
+ )}
446
+ </div>
447
+ )}
448
+
449
+ {fileObj.status === 'error' && uploadStatus[fileObj.id]?.error && (
450
+ <p className="mt-1 text-xs text-red-600 font-medium">
451
+ ✗ {uploadStatus[fileObj.id].error}
452
+ </p>
453
+ )}
454
+ </div>
455
+
456
+ {fileObj.status === 'pending' && !isProcessing && (
457
+ <button
458
+ onClick={(e) => {
459
+ e.stopPropagation();
460
+ removeFile(fileObj.id);
461
+ }}
462
+ className="p-2 text-secondary-400 hover:text-red-600 hover:bg-red-50 rounded-lg transition-colors flex-shrink-0"
463
+ >
464
+ <Trash2 className="w-4 h-4" />
465
+ </button>
466
+ )}
467
+ </div>
468
+ </motion.div>
469
+ ))}
470
+ </div>
471
+ </div>
472
+ )}
473
+
474
+ {/* Overall Summary if all completed */}
475
+ {!isProcessing && files.length > 0 && files.every(f => f.status !== 'pending') && (
476
+ <motion.div
477
+ initial={{ opacity: 0, scale: 0.98 }}
478
+ animate={{ opacity: 1, scale: 1 }}
479
+ className="bg-secondary-900 text-white rounded-xl p-6 shadow-xl"
480
+ >
481
+ <h4 className="font-bold text-lg mb-4 flex items-center gap-2">
482
+ <CheckCircle className="w-5 h-5 text-green-400" />
483
+ Batch Processing Complete
484
+ </h4>
485
+ <div className="grid grid-cols-3 gap-4 text-center">
486
+ <div className="bg-white/10 rounded-lg p-3 backdrop-blur-sm">
487
+ <p className="text-secondary-400 text-xs uppercase font-bold tracking-wider">Total Rows</p>
488
+ <p className="text-2xl font-black text-white">
489
+ {files.reduce((acc, f) => acc + (f.result?.total || 0), 0)}
490
+ </p>
491
+ </div>
492
+ <div className="bg-green-500/20 rounded-lg p-3 backdrop-blur-sm border border-green-500/30">
493
+ <p className="text-green-400 text-xs uppercase font-bold tracking-wider">Successful</p>
494
+ <p className="text-2xl font-black text-green-400">
495
+ {files.reduce((acc, f) => acc + (f.result?.successful || 0), 0)}
496
+ </p>
497
+ </div>
498
+ <div className="bg-red-500/20 rounded-lg p-3 backdrop-blur-sm border border-red-500/30">
499
+ <p className="text-red-400 text-xs uppercase font-bold tracking-wider">Failed</p>
500
+ <p className="text-2xl font-black text-red-400">
501
+ {files.reduce((acc, f) => acc + (f.result?.failed || 0), 0)}
502
+ </p>
503
+ </div>
504
+ </div>
505
+ </motion.div>
506
+ )}
507
+ </div>
508
+
509
+ {/* Footer */}
510
+ <div className="px-6 py-4 border-t border-secondary-100 bg-secondary-50 flex justify-between items-center">
511
+ <div className="text-sm text-secondary-600 font-medium">
512
+ {files.length > 0 && (
513
+ <div className="flex items-center gap-2">
514
+ <div className="w-32 bg-secondary-200 rounded-full h-1.5 overflow-hidden">
515
+ <div
516
+ className="bg-green-500 h-1.5 transition-all"
517
+ style={{ width: `${(files.filter(f => f.status === 'success' || f.status === 'error').length / files.length) * 100}%` }}
518
+ />
519
+ </div>
520
+ <span>
521
+ {files.filter(f => f.status === 'success' || f.status === 'error').length} of {files.length} done
522
+ </span>
523
+ </div>
524
+ )}
525
+ </div>
526
+ <div className="flex gap-3">
527
+ <button
528
+ onClick={onClose}
529
+ className="px-5 py-2 text-secondary-600 hover:bg-secondary-100 rounded-lg transition-colors font-bold text-sm"
530
+ >
531
+ {files.every(f => f.status !== 'pending') && files.length > 0 ? 'Finish' : 'Cancel'}
532
+ </button>
533
+ <button
534
+ onClick={processQueue}
535
+ disabled={files.length === 0 || isProcessing || files.every(f => f.status !== 'pending')}
536
+ className="px-8 py-2.5 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-all font-bold text-sm flex items-center gap-2 shadow-lg shadow-green-500/30 disabled:opacity-50 disabled:cursor-not-allowed hover:scale-[1.02] active:scale-[0.98]"
537
+ >
538
+ {isProcessing ? (
539
+ <>
540
+ <Loader className="w-4 h-4 animate-spin" />
541
+ Processing Batch...
542
+ </>
543
+ ) : (
544
+ <>
545
+ <PlayCircle className="w-5 h-5" />
546
+ Confirm and Upload
547
+ </>
548
+ )}
549
+ </button>
550
+ </div>
551
+ </div>
552
+ </motion.div>
553
+ </div>
554
+ );
555
+ };
556
+
557
+ export default BulkUploadModal;
client/src/components/ChatWidget/ChatBox.jsx ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import api from '../../api/axiosConfig';
3
+ import TypingIndicator from './TypingIndicator';
4
+
5
+ const ChatBox = ({ websiteId }) => {
6
+ const [messages, setMessages] = useState([]);
7
+ const [input, setInput] = useState('');
8
+ const [loading, setLoading] = useState(false);
9
+
10
+ const sendMessage = async () => {
11
+ if (!input.trim()) return;
12
+
13
+ const userMessage = { text: input, sender: 'user' };
14
+ setMessages(prev => [...prev, userMessage]);
15
+ setInput('');
16
+ setLoading(true);
17
+
18
+ try {
19
+ const response = await api.post('/chat', {
20
+ message: input,
21
+ website_id: websiteId
22
+ });
23
+
24
+ const botMessage = {
25
+ text: response.data.response,
26
+ sender: 'bot',
27
+ needsContact: response.data.needs_owner_contact
28
+ };
29
+
30
+ setMessages(prev => [...prev, botMessage]);
31
+ } catch (error) {
32
+ const errorMessage = {
33
+ text: 'Sorry, I encountered an error. Please try again.',
34
+ sender: 'bot'
35
+ };
36
+ setMessages(prev => [...prev, errorMessage]);
37
+ } finally {
38
+ setLoading(false);
39
+ }
40
+ };
41
+
42
+ return (
43
+ <div className="flex flex-col h-96 bg-white rounded-lg shadow-lg">
44
+ <div className="bg-blue-500 text-white p-3 rounded-t-lg">
45
+ <h3 className="font-semibold">Customer Supports</h3>
46
+ </div>
47
+
48
+ <div className="flex-1 overflow-y-auto p-4 space-y-3">
49
+ {messages.map((message, index) => (
50
+ <div
51
+ key={index}
52
+ className={`flex ${message.sender === 'user' ? 'justify-end' : 'justify-start'}`}
53
+ >
54
+ <div
55
+ className={`max-w-xs px-3 py-2 rounded-lg ${message.sender === 'user'
56
+ ? 'bg-blue-500 text-white'
57
+ : 'bg-gray-200 text-gray-800'
58
+ }`}
59
+ >
60
+ {message.text}
61
+ {message.needsContact && (
62
+ <div className="mt-2 text-xs text-blue-600">
63
+ This may require owner attention
64
+ </div>
65
+ )}
66
+ </div>
67
+ </div>
68
+ ))}
69
+ {loading && (
70
+ <div className="flex justify-start">
71
+ <TypingIndicator />
72
+ </div>
73
+ )}
74
+ </div>
75
+
76
+ <div className="p-3 border-t">
77
+ <div className="flex space-x-2">
78
+ <input
79
+ type="text"
80
+ value={input}
81
+ onChange={(e) => setInput(e.target.value)}
82
+ onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
83
+ placeholder="Type your message..."
84
+ className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
85
+ />
86
+ <button
87
+ onClick={sendMessage}
88
+ disabled={loading}
89
+ className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 disabled:opacity-50"
90
+ >
91
+ Send
92
+ </button>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ );
97
+ };
98
+
99
+ export default ChatBox;
client/src/components/ChatWidget/ChatBubble.jsx ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import ReactMarkdown from 'react-markdown';
4
+
5
+ const ChatBubble = ({ message, isUser, timestamp }) => {
6
+ return (
7
+ <motion.div
8
+ initial={{ opacity: 0, y: 10 }}
9
+ animate={{ opacity: 1, y: 0 }}
10
+ className={`flex ${isUser ? 'justify-end' : 'justify-start'} mb-3`}
11
+ >
12
+ <div
13
+ className={`max-w-xs px-4 py-2 rounded-lg ${isUser
14
+ ? 'bg-blue-500 text-white rounded-br-sm'
15
+ : 'bg-gray-200 text-gray-800 rounded-bl-sm'
16
+ }`}
17
+ >
18
+ <div className="text-sm prose prose-sm max-w-none">
19
+ <ReactMarkdown
20
+ components={{
21
+ a: ({ node, ...props }) => (
22
+ <a
23
+ {...props}
24
+ target="_blank"
25
+ rel="noopener noreferrer"
26
+ className={`${isUser ? 'text-blue-100' : 'text-blue-600'} underline hover:no-underline font-medium`}
27
+ />
28
+ ),
29
+ p: ({ node, ...props }) => <p {...props} className="mb-0 overflow-wrap-anywhere" />
30
+ }}
31
+ >
32
+ {message}
33
+ </ReactMarkdown>
34
+ </div>
35
+ {timestamp && (
36
+ <p className={`text-xs mt-1 ${isUser ? 'text-blue-100' : 'text-gray-500'}`}>
37
+ {new Date(timestamp).toLocaleTimeString()}
38
+ </p>
39
+ )}
40
+ </div>
41
+ </motion.div>
42
+ );
43
+ };
44
+
45
+ export default ChatBubble;
client/src/components/ChatWidget/TypingIndicator.css ADDED
@@ -0,0 +1,193 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Typing Indicator Component */
2
+
3
+ .typing-indicator {
4
+ display: flex;
5
+ align-items: center;
6
+ padding: 12px 16px;
7
+ gap: 8px;
8
+ }
9
+
10
+ .typing-indicator__text {
11
+ font-size: 14px;
12
+ color: #666;
13
+ font-style: italic;
14
+ }
15
+
16
+ .typing-indicator__dots {
17
+ display: flex;
18
+ gap: 4px;
19
+ }
20
+
21
+ .typing-indicator__dot {
22
+ width: 8px;
23
+ height: 8px;
24
+ border-radius: 50%;
25
+ background-color: #4A90E2;
26
+ animation: typing-bounce 1.4s infinite ease-in-out;
27
+ }
28
+
29
+ .typing-indicator__dot:nth-child(1) {
30
+ animation-delay: 0s;
31
+ }
32
+
33
+ .typing-indicator__dot:nth-child(2) {
34
+ animation-delay: 0.2s;
35
+ }
36
+
37
+ .typing-indicator__dot:nth-child(3) {
38
+ animation-delay: 0.4s;
39
+ }
40
+
41
+ @keyframes typing-bounce {
42
+ 0%, 60%, 100% {
43
+ transform: translateY(0);
44
+ opacity: 0.7;
45
+ }
46
+ 30% {
47
+ transform: translateY(-10px);
48
+ opacity: 1;
49
+ }
50
+ }
51
+
52
+ /* Timeout Warning */
53
+ .timeout-warning {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: 10px;
57
+ padding: 12px 16px;
58
+ background: #fff3cd;
59
+ border-left: 4px solid #ffc107;
60
+ border-radius: 4px;
61
+ margin: 8px 0;
62
+ }
63
+
64
+ .timeout-warning__icon {
65
+ font-size: 20px;
66
+ }
67
+
68
+ .timeout-warning__text {
69
+ flex: 1;
70
+ font-size: 13px;
71
+ color: #856404;
72
+ }
73
+
74
+ .timeout-warning__text strong {
75
+ display: block;
76
+ margin-bottom: 4px;
77
+ }
78
+
79
+ /* Slow Response Indicator */
80
+ .slow-response {
81
+ display: flex;
82
+ align-items: center;
83
+ gap: 8px;
84
+ padding: 8px 12px;
85
+ background: #e8f4fd;
86
+ border-radius: 4px;
87
+ font-size: 12px;
88
+ color: #31708f;
89
+ margin-top: 8px;
90
+ }
91
+
92
+ .slow-response__spinner {
93
+ width: 14px;
94
+ height: 14px;
95
+ border: 2px solid #31708f;
96
+ border-top-color: transparent;
97
+ border-radius: 50%;
98
+ animation: spin 1s linear infinite;
99
+ }
100
+
101
+ @keyframes spin {
102
+ to {
103
+ transform: rotate(360deg);
104
+ }
105
+ }
106
+
107
+ /* Response Time Badge */
108
+ .response-time-badge {
109
+ display: inline-flex;
110
+ align-items: center;
111
+ gap: 4px;
112
+ padding: 4px 8px;
113
+ background: #f5f5f5;
114
+ border-radius: 12px;
115
+ font-size: 11px;
116
+ color: #666;
117
+ margin-top: 4px;
118
+ }
119
+
120
+ .response-time-badge--fast {
121
+ background: #d4edda;
122
+ color: #155724;
123
+ }
124
+
125
+ .response-time-badge--slow {
126
+ background: #fff3cd;
127
+ color: #856404;
128
+ }
129
+
130
+ .response-time-badge--timeout {
131
+ background: #f8d7da;
132
+ color: #721c24;
133
+ }
134
+
135
+ /* Error State */
136
+ .chat-error {
137
+ display: flex;
138
+ flex-direction: column;
139
+ align-items: center;
140
+ gap: 12px;
141
+ padding: 24px;
142
+ text-align: center;
143
+ }
144
+
145
+ .chat-error__icon {
146
+ font-size: 48px;
147
+ color: #E74C3C;
148
+ }
149
+
150
+ .chat-error__title {
151
+ font-size: 16px;
152
+ font-weight: 600;
153
+ color: #333;
154
+ }
155
+
156
+ .chat-error__message {
157
+ font-size: 14px;
158
+ color: #666;
159
+ max-width: 300px;
160
+ }
161
+
162
+ .chat-error__actions {
163
+ display: flex;
164
+ gap: 8px;
165
+ margin-top: 8px;
166
+ }
167
+
168
+ .chat-error__btn {
169
+ padding: 8px 16px;
170
+ border-radius: 4px;
171
+ border: none;
172
+ font-size: 14px;
173
+ cursor: pointer;
174
+ transition: all 0.2s;
175
+ }
176
+
177
+ .chat-error__btn--primary {
178
+ background: #4A90E2;
179
+ color: white;
180
+ }
181
+
182
+ .chat-error__btn--primary:hover {
183
+ background: #357ABD;
184
+ }
185
+
186
+ .chat-error__btn--secondary {
187
+ background: #f5f5f5;
188
+ color: #333;
189
+ }
190
+
191
+ .chat-error__btn--secondary:hover {
192
+ background: #e0e0e0;
193
+ }
client/src/components/ChatWidget/TypingIndicator.jsx ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React from 'react';
2
+ import './TypingIndicator.css';
3
+
4
+ /**
5
+ * Typing Indicator Component
6
+ * Shows animated dots while bot is thinking/responding
7
+ */
8
+ export const TypingIndicator = ({ text = "Bot is typing" }) => {
9
+ return (
10
+ <div className="typing-indicator">
11
+ <span className="typing-indicator__text">{text}</span>
12
+ <div className="typing-indicator__dots">
13
+ <span className="typing-indicator__dot"></span>
14
+ <span className="typing-indicator__dot"></span>
15
+ <span className="typing-indicator__dot"></span>
16
+ </div>
17
+ </div>
18
+ );
19
+ };
20
+
21
+ /**
22
+ * Slow Response Indicator
23
+ * Shows when response is taking longer than expected
24
+ */
25
+ export const SlowResponseIndicator = ({ elapsedTime }) => {
26
+ return (
27
+ <div className="slow-response">
28
+ <div className="slow-response__spinner"></div>
29
+ <span>
30
+ This is taking longer than usual ({Math.round(elapsedTime)}s)...
31
+ We're searching thoroughly for the best answer.
32
+ </span>
33
+ </div>
34
+ );
35
+ };
36
+
37
+ /**
38
+ * Timeout Warning
39
+ * Shows when request is approaching timeout
40
+ */
41
+ export const TimeoutWarning = ({ remainingTime }) => {
42
+ return (
43
+ <div className="timeout-warning">
44
+ <span className="timeout-warning__icon">⏱️</span>
45
+ <div className="timeout-warning__text">
46
+ <strong>Taking longer than expected</strong>
47
+ We've notified our team and will get back to you shortly.
48
+ </div>
49
+ </div>
50
+ );
51
+ };
52
+
53
+ /**
54
+ * Response Time Badge
55
+ * Shows response time with color coding
56
+ */
57
+ export const ResponseTimeBadge = ({ responseTime }) => {
58
+ const getClassName = () => {
59
+ if (responseTime < 2) return 'response-time-badge--fast';
60
+ if (responseTime < 5) return 'response-time-badge';
61
+ if (responseTime < 10) return 'response-time-badge--slow';
62
+ return 'response-time-badge--timeout';
63
+ };
64
+
65
+ return (
66
+ <div className={`response-time-badge ${getClassName()}`}>
67
+ <span>⚡</span>
68
+ <span>{responseTime.toFixed(1)}s</span>
69
+ </div>
70
+ );
71
+ };
72
+
73
+ /**
74
+ * Chat Error Component
75
+ * Shows when request times out or fails
76
+ */
77
+ export const ChatError = ({ onRetry, onContactSupport }) => {
78
+ return (
79
+ <div className="chat-error">
80
+ <div className="chat-error__icon">⏰</div>
81
+ <h4 className="chat-error__title">Request Timed Out</h4>
82
+ <p className="chat-error__message">
83
+ We're sorry, but your request is taking longer than expected.
84
+ We've sent you an email and notified our team.
85
+ </p>
86
+ <div className="chat-error__actions">
87
+ <button
88
+ className="chat-error__btn chat-error__btn--primary"
89
+ onClick={onRetry}
90
+ >
91
+ Try Again
92
+ </button>
93
+ <button
94
+ className="chat-error__btn chat-error__btn--secondary"
95
+ onClick={onContactSupport}
96
+ >
97
+ Contact Support
98
+ </button>
99
+ </div>
100
+ </div>
101
+ );
102
+ };
103
+
104
+ export default TypingIndicator;
client/src/components/ChatWidget/index.jsx ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect, useRef } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { MessageCircle, X, Send } from 'lucide-react';
4
+ import ChatBubble from './ChatBubble';
5
+ import TypingIndicator from './TypingIndicator'; // Import TypingIndicator
6
+
7
+ const ChatWidget = ({ websiteId, position = 'bottom-right' }) => {
8
+ const [isOpen, setIsOpen] = useState(false);
9
+ const [messages, setMessages] = useState([]);
10
+ const [inputValue, setInputValue] = useState('');
11
+ const [socket, setSocket] = useState(null);
12
+ const [isConnected, setIsConnected] = useState(false);
13
+ const [isTyping, setIsTyping] = useState(false);
14
+ const messagesEndRef = useRef(null);
15
+
16
+ // Initialize session ID
17
+ const [sessionId] = useState(() => {
18
+ const stored = localStorage.getItem(`chat_session_${websiteId}`);
19
+ if (stored) return stored;
20
+ const newId = `session-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
21
+ localStorage.setItem(`chat_session_${websiteId}`, newId);
22
+ return newId;
23
+ });
24
+
25
+ const scrollToBottom = () => {
26
+ messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
27
+ };
28
+
29
+ useEffect(() => {
30
+ scrollToBottom();
31
+ }, [messages, isTyping]);
32
+
33
+ useEffect(() => {
34
+ if (websiteId && isOpen) {
35
+ // Use native WebSocket to match FastAPI backend
36
+ const wsUrl = `ws://localhost:8000/ws/${websiteId}`;
37
+ const newSocket = new WebSocket(wsUrl);
38
+
39
+ newSocket.onopen = () => {
40
+ setIsConnected(true);
41
+
42
+ // Register session immediately
43
+ newSocket.send(JSON.stringify({
44
+ type: 'register_session',
45
+ session_id: sessionId,
46
+ website_id: websiteId
47
+ }));
48
+
49
+ if (messages.length === 0) {
50
+ setMessages(prev => [...prev, {
51
+ id: Date.now(),
52
+ message: 'Hello! How can I help you today?',
53
+ isUser: false,
54
+ timestamp: new Date()
55
+ }]);
56
+ }
57
+ };
58
+
59
+ newSocket.onmessage = (event) => {
60
+ const data = JSON.parse(event.data);
61
+ setIsTyping(false); // Stop typing animation when message received
62
+
63
+ if (data.type === 'admin_response' || data.response) {
64
+ setMessages(prev => [...prev, {
65
+ id: Date.now(),
66
+ message: data.message || data.response,
67
+ isUser: false,
68
+ timestamp: new Date(),
69
+ needsContact: data.needs_owner_contact
70
+ }]);
71
+ }
72
+ };
73
+
74
+ newSocket.onclose = () => setIsConnected(false);
75
+ newSocket.onerror = (err) => {
76
+ console.error("WebSocket error:", err);
77
+ setIsConnected(false);
78
+ };
79
+
80
+ setSocket(newSocket);
81
+
82
+ return () => {
83
+ newSocket.close();
84
+ };
85
+ }
86
+ }, [websiteId, isOpen]);
87
+
88
+ const sendMessage = async () => {
89
+ if (!inputValue.trim()) return;
90
+
91
+ const userMessage = {
92
+ id: Date.now(),
93
+ message: inputValue,
94
+ isUser: true,
95
+ timestamp: new Date()
96
+ };
97
+
98
+ setMessages(prev => [...prev, userMessage]);
99
+ setInputValue('');
100
+ setIsTyping(true); // Start typing animation
101
+
102
+ // OPTIMIZED: Use WebSocket instead of HTTP fetch for faster response
103
+ if (socket && isConnected) {
104
+ try {
105
+ socket.send(JSON.stringify({
106
+ type: 'chat_message',
107
+ message: userMessage.message,
108
+ visitor_name: "Visitor",
109
+ visitor_email: "", // Can update if we capture email
110
+ website_id: parseInt(websiteId),
111
+ session_id: sessionId
112
+ }));
113
+ } catch (err) {
114
+ console.error("Socket send error:", err);
115
+ fallbackToHttp(userMessage);
116
+ }
117
+ } else {
118
+ // Fallback if socket fails
119
+ fallbackToHttp(userMessage);
120
+ }
121
+ };
122
+
123
+ const fallbackToHttp = async (userMessage) => {
124
+ try {
125
+ const response = await fetch('/api/chat', {
126
+ method: 'POST',
127
+ headers: {
128
+ 'Content-Type': 'application/json',
129
+ 'Authorization': `Bearer ${localStorage.getItem('token')}`
130
+ },
131
+ body: JSON.stringify({
132
+ message: userMessage.message,
133
+ website_id: parseInt(websiteId),
134
+ session_id: sessionId,
135
+ visitor_name: "Visitor",
136
+ visitor_email: ""
137
+ })
138
+ });
139
+
140
+ if (!response.ok) throw new Error('Network response was not ok');
141
+ const data = await response.json();
142
+
143
+ setIsTyping(false);
144
+ setMessages(prev => [...prev, {
145
+ id: Date.now(),
146
+ message: data.response,
147
+ isUser: false,
148
+ timestamp: new Date(),
149
+ needsContact: data.needs_owner_contact
150
+ }]);
151
+
152
+ } catch (error) {
153
+ console.error('Error sending message:', error);
154
+ setIsTyping(false);
155
+ setMessages(prev => [...prev, {
156
+ id: Date.now(),
157
+ message: "Sorry, I encountered an error. Please try again.",
158
+ isUser: false,
159
+ timestamp: new Date()
160
+ }]);
161
+ }
162
+ };
163
+
164
+ const positionClasses = {
165
+ 'bottom-right': 'bottom-4 right-4',
166
+ 'bottom-left': 'bottom-4 left-4',
167
+ 'top-right': 'top-4 right-4',
168
+ 'top-left': 'top-4 left-4'
169
+ };
170
+
171
+ return (
172
+ <div className={`fixed ${positionClasses[position]} z-50`}>
173
+ <AnimatePresence>
174
+ {isOpen && (
175
+ <motion.div
176
+ initial={{ opacity: 0, scale: 0.8, y: 20 }}
177
+ animate={{ opacity: 1, scale: 1, y: 0 }}
178
+ exit={{ opacity: 0, scale: 0.8, y: 20 }}
179
+ className="mb-4 w-80 h-96 bg-white rounded-lg shadow-2xl border border-gray-200 flex flex-col"
180
+ >
181
+ {/* Header */}
182
+ <div className="bg-blue-500 text-white p-4 rounded-t-lg flex justify-between items-center">
183
+ <div className="flex items-center gap-2">
184
+ <div className={`w-2 h-2 rounded-full ${isConnected ? 'bg-green-400' : 'bg-red-400'}`} />
185
+ <span className="font-semibold">Customer Supportsa</span>
186
+ </div>
187
+ <button
188
+ onClick={() => setIsOpen(false)}
189
+ className="text-white hover:text-gray-200"
190
+ >
191
+ <X className="w-5 h-5" />
192
+ </button>
193
+ </div>
194
+
195
+ {/* Messages */}
196
+ <div className="flex-1 overflow-y-auto p-4 space-y-2">
197
+ {messages.map((msg) => (
198
+ <ChatBubble
199
+ key={msg.id}
200
+ message={msg.message}
201
+ isUser={msg.isUser}
202
+ timestamp={msg.timestamp}
203
+ />
204
+ ))}
205
+ {isTyping && (
206
+ <div className="flex justify-start">
207
+ <TypingIndicator />
208
+ </div>
209
+ )}
210
+ <div ref={messagesEndRef} />
211
+ </div>
212
+
213
+ {/* Input */}
214
+ <div className="p-4 border-t border-gray-200">
215
+ <div className="flex gap-2">
216
+ <input
217
+ type="text"
218
+ value={inputValue}
219
+ onChange={(e) => setInputValue(e.target.value)}
220
+ onKeyPress={(e) => e.key === 'Enter' && sendMessage()}
221
+ placeholder="Type your message..."
222
+ className="flex-1 px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
223
+ />
224
+ <button
225
+ onClick={sendMessage}
226
+ className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500"
227
+ >
228
+ <Send className="w-4 h-4" />
229
+ </button>
230
+ </div>
231
+ </div>
232
+ </motion.div>
233
+ )}
234
+ </AnimatePresence>
235
+
236
+ {/* Toggle Button */}
237
+ <motion.button
238
+ whileHover={{ scale: 1.1 }}
239
+ whileTap={{ scale: 0.9 }}
240
+ onClick={() => setIsOpen(!isOpen)}
241
+ className="w-14 h-14 bg-blue-500 text-white rounded-full shadow-lg hover:bg-blue-600 focus:outline-none focus:ring-4 focus:ring-blue-300 flex items-center justify-center"
242
+ >
243
+ {isOpen ? <X className="w-6 h-6" /> : <MessageCircle className="w-6 h-6" />}
244
+ </motion.button>
245
+ </div>
246
+ );
247
+ };
248
+
249
+ export default ChatWidget;
client/src/components/Dashboard/FetchContentButton.jsx ADDED
@@ -0,0 +1,119 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { Download, Loader, Database, RefreshCw } from 'lucide-react';
4
+ import { useMutation } from '@tanstack/react-query';
5
+ import api from '../../api/axiosConfig';
6
+ import toast from 'react-hot-toast';
7
+
8
+ const FetchContentButton = ({ website }) => {
9
+ const [scrapingState, setScrapingState] = useState({
10
+ isScraping: false,
11
+ progress: 0,
12
+ total: 0,
13
+ message: ''
14
+ });
15
+
16
+ useEffect(() => {
17
+ if (website?.id) {
18
+ const wsUrl = `ws://${window.location.hostname}:8000/ws/admin/${website.id}`;
19
+ const ws = new WebSocket(wsUrl);
20
+
21
+ ws.onmessage = (event) => {
22
+ try {
23
+ const data = JSON.parse(event.data);
24
+ if (data.type === 'scraping_progress') {
25
+ setScrapingState({
26
+ isScraping: data.status === 'scraping',
27
+ progress: data.current || 0,
28
+ total: data.total || 0,
29
+ message: data.message || ''
30
+ });
31
+
32
+ if (data.status === 'completed') {
33
+ setScrapingState(prev => ({ ...prev, isScraping: false }));
34
+ }
35
+ }
36
+ } catch (err) {
37
+ console.error('WS Error:', err);
38
+ }
39
+ };
40
+
41
+ return () => ws.close();
42
+ }
43
+ }, [website?.id]);
44
+
45
+ const fetchContentMutation = useMutation({
46
+ mutationFn: () => api.post(`/ingest/${website.id}/crawl`),
47
+ onSuccess: () => {
48
+ toast.success('Content fetch started!');
49
+ },
50
+ onError: () => {
51
+ toast.error('Failed to fetch content');
52
+ }
53
+ });
54
+
55
+ const processContentMutation = useMutation({
56
+ mutationFn: () => api.post(`/ingest/${website.id}/crawl`), // Unified endpoint
57
+ onSuccess: () => {
58
+ toast.success('Content indexing started!');
59
+ },
60
+ onError: () => {
61
+ toast.error('Failed to start indexing');
62
+ }
63
+ });
64
+
65
+ const isAnyLoading = fetchContentMutation.isLoading || processContentMutation.isLoading || scrapingState.isScraping;
66
+
67
+ return (
68
+ <div className="flex flex-col gap-2 w-full">
69
+ <div className="flex gap-2">
70
+ <motion.button
71
+ whileHover={{ scale: 1.02 }}
72
+ whileTap={{ scale: 0.98 }}
73
+ onClick={() => fetchContentMutation.mutate()}
74
+ disabled={!website.is_verified || isAnyLoading}
75
+ className="flex-1 flex items-center justify-center gap-2 px-3 py-2 bg-green-500 text-white rounded-md text-sm hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed"
76
+ >
77
+ {isAnyLoading ? (
78
+ <Loader className="w-4 h-4 animate-spin" />
79
+ ) : (
80
+ <Download className="w-4 h-4" />
81
+ )}
82
+ {scrapingState.isScraping ? 'Fetching...' : 'Fetch Content'}
83
+ </motion.button>
84
+
85
+ <motion.button
86
+ whileHover={{ scale: 1.02 }}
87
+ whileTap={{ scale: 0.98 }}
88
+ onClick={() => processContentMutation.mutate()}
89
+ disabled={isAnyLoading}
90
+ className="flex-1 flex items-center justify-center gap-2 px-3 py-2 bg-purple-500 text-white rounded-md text-sm hover:bg-purple-600 disabled:opacity-50 disabled:cursor-not-allowed"
91
+ >
92
+ {isAnyLoading ? (
93
+ <Loader className="w-4 h-4 animate-spin" />
94
+ ) : (
95
+ <Database className="w-4 h-4" />
96
+ )}
97
+ Process AI
98
+ </motion.button>
99
+ </div>
100
+
101
+ {scrapingState.isScraping && (
102
+ <div className="mt-1">
103
+ <div className="flex justify-between text-[10px] text-gray-500 mb-1 px-1">
104
+ <span className="truncate max-w-[150px]">{scrapingState.message}</span>
105
+ <span>{scrapingState.progress}/{scrapingState.total}</span>
106
+ </div>
107
+ <div className="w-full bg-gray-200 rounded-full h-1">
108
+ <div
109
+ className="bg-green-500 h-1 rounded-full transition-all duration-300"
110
+ style={{ width: `${(scrapingState.progress / (scrapingState.total || 1)) * 100}%` }}
111
+ />
112
+ </div>
113
+ </div>
114
+ )}
115
+ </div>
116
+ );
117
+ };
118
+
119
+ export default FetchContentButton;
client/src/components/Dashboard/SiteCard.jsx ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { ExternalLink, Globe, Trash2, Settings } from 'lucide-react';
4
+ import VerifyButton from './VerifyButton';
5
+ import FetchContentButton from './FetchContentButton';
6
+ import WidgetCustomizer from './WidgetCustomizer';
7
+ import api from '../../api/axiosConfig';
8
+ import toast from 'react-hot-toast';
9
+
10
+ const SiteCard = ({ website, onUpdate }) => {
11
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
12
+ const [showCustomizer, setShowCustomizer] = useState(false);
13
+
14
+ const handleDelete = async () => {
15
+ try {
16
+ await api.delete(`/websites/${website.id}`);
17
+ toast.success('Website deleted successfully');
18
+ onUpdate();
19
+ } catch (error) {
20
+ toast.error('Failed to delete website');
21
+ }
22
+ setShowDeleteConfirm(false);
23
+ };
24
+
25
+ return (
26
+ <motion.div
27
+ initial={{ opacity: 0, y: 20 }}
28
+ animate={{ opacity: 1, y: 0 }}
29
+ className="bg-white p-6 rounded-lg shadow-md hover:shadow-lg transition-shadow"
30
+ >
31
+ <div className="flex items-start justify-between mb-4">
32
+ <div className="flex items-center gap-3">
33
+ <Globe className="w-5 h-5 text-gray-500" />
34
+ <div>
35
+ <h3 className="text-lg font-semibold">{website.name}</h3>
36
+ <a
37
+ href={website.url}
38
+ target="_blank"
39
+ rel="noopener noreferrer"
40
+ className="text-blue-600 hover:text-blue-800 text-sm flex items-center gap-1"
41
+ >
42
+ {website.url}
43
+ <ExternalLink className="w-3 h-3" />
44
+ </a>
45
+ </div>
46
+ </div>
47
+ <button
48
+ onClick={() => setShowDeleteConfirm(true)}
49
+ className="text-red-500 hover:text-red-700 p-1"
50
+ title="Delete website"
51
+ >
52
+ <Trash2 className="w-4 h-4" />
53
+ </button>
54
+ </div>
55
+
56
+ <div className="space-y-4">
57
+ <VerifyButton website={website} onUpdate={onUpdate} />
58
+ {website.is_verified && (
59
+ <>
60
+ <FetchContentButton website={website} />
61
+ <button
62
+ onClick={() => setShowCustomizer(!showCustomizer)}
63
+ className="w-full bg-purple-500 hover:bg-purple-600 text-white py-2 px-4 rounded-md flex items-center justify-center gap-2"
64
+ >
65
+ <Settings className="w-4 h-4" />
66
+ {showCustomizer ? 'Hide' : 'Customize'} Widget
67
+ </button>
68
+ </>
69
+ )}
70
+ </div>
71
+
72
+ {showCustomizer && website.is_verified && (
73
+ <div className="mt-6">
74
+ <WidgetCustomizer website={website} onUpdate={onUpdate} />
75
+ </div>
76
+ )}
77
+
78
+ {showDeleteConfirm && (
79
+ <div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
80
+ <div className="bg-white p-6 rounded-lg shadow-lg max-w-sm w-full mx-4">
81
+ <h3 className="text-lg font-semibold mb-4">Delete Website</h3>
82
+ <p className="text-gray-600 mb-6">
83
+ Are you sure you want to delete "{website.name}"? This action cannot be undone.
84
+ </p>
85
+ <div className="flex gap-3">
86
+ <button
87
+ onClick={handleDelete}
88
+ className="flex-1 bg-red-500 hover:bg-red-600 text-white py-2 px-4 rounded"
89
+ >
90
+ Delete
91
+ </button>
92
+ <button
93
+ onClick={() => setShowDeleteConfirm(false)}
94
+ className="flex-1 bg-gray-300 hover:bg-gray-400 text-gray-700 py-2 px-4 rounded"
95
+ >
96
+ Cancel
97
+ </button>
98
+ </div>
99
+ </div>
100
+ </div>
101
+ )}
102
+ </motion.div>
103
+ );
104
+ };
105
+
106
+ export default SiteCard;
client/src/components/Dashboard/VerifyButton.jsx ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState } from 'react';
2
+ import { motion } from 'framer-motion';
3
+ import { CheckCircle, AlertCircle, Loader } from 'lucide-react';
4
+ import { useNavigate } from 'react-router-dom';
5
+ import toast from 'react-hot-toast';
6
+ import api from '../../api/axiosConfig';
7
+
8
+ const VerifyButton = ({ website, onUpdate }) => {
9
+ const [loading, setLoading] = useState(false);
10
+ const [result, setResult] = useState(null);
11
+ const navigate = useNavigate();
12
+
13
+ const handleVerify = async () => {
14
+ setLoading(true);
15
+ try {
16
+ const response = await api.post(`/ingest/scrape/${website.id}/verify-script`);
17
+ setResult(response.data);
18
+ if (response.data.verified) {
19
+ toast.success('Website verified successfully! Redirecting...');
20
+ onUpdate();
21
+ // Redirect to content manager with this website selected
22
+ setTimeout(() => {
23
+ navigate(`/content?websiteId=${website.id}`);
24
+ }, 1500);
25
+ }
26
+ } catch (error) {
27
+ setResult({ verified: false, message: 'Verification failed' });
28
+ } finally {
29
+ setLoading(false);
30
+ }
31
+ };
32
+
33
+ const getScriptTag = () => {
34
+ return `<script src="http://localhost:8000/static/widget.js" data-website-id="${website.id}"></script>`;
35
+ };
36
+
37
+ return (
38
+ <div className="space-y-3">
39
+ <motion.button
40
+ whileHover={{ scale: 1.02 }}
41
+ whileTap={{ scale: 0.98 }}
42
+ onClick={handleVerify}
43
+ disabled={loading || website.is_verified}
44
+ className={`flex items-center gap-2 px-4 py-2 rounded-md text-sm font-medium ${website.is_verified
45
+ ? 'bg-green-100 text-green-800 cursor-not-allowed'
46
+ : 'bg-blue-500 text-white hover:bg-blue-600 disabled:opacity-50'
47
+ }`}
48
+ >
49
+ {loading ? (
50
+ <Loader className="w-4 h-4 animate-spin" />
51
+ ) : website.is_verified ? (
52
+ <CheckCircle className="w-4 h-4" />
53
+ ) : (
54
+ <AlertCircle className="w-4 h-4" />
55
+ )}
56
+ {website.is_verified ? 'Verified' : 'Verify Installation'}
57
+ </motion.button>
58
+
59
+ {!website.is_verified && (
60
+ <div className="bg-gray-50 p-3 rounded-md">
61
+ <p className="text-xs text-gray-600 mb-2">Add this script to your website:</p>
62
+ <code className="text-xs bg-gray-800 text-green-400 p-2 rounded block overflow-x-auto">
63
+ {getScriptTag()}
64
+ </code>
65
+ </div>
66
+ )}
67
+
68
+ {result && (
69
+ <motion.div
70
+ initial={{ opacity: 0, y: 10 }}
71
+ animate={{ opacity: 1, y: 0 }}
72
+ className={`p-3 rounded-md text-sm ${result.verified
73
+ ? 'bg-green-50 text-green-800 border border-green-200'
74
+ : 'bg-red-50 text-red-800 border border-red-200'
75
+ }`}
76
+ >
77
+ {result.message}
78
+ </motion.div>
79
+ )}
80
+ </div>
81
+ );
82
+ };
83
+
84
+ export default VerifyButton;
client/src/components/Dashboard/WidgetCustomizer.jsx ADDED
@@ -0,0 +1,374 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Palette, Monitor, Move, Save, Code } from 'lucide-react';
3
+ import api from '../../api/axiosConfig';
4
+ import toast from 'react-hot-toast';
5
+
6
+ const WidgetCustomizer = ({ website, onUpdate }) => {
7
+ const [config, setConfig] = useState({
8
+ theme: 'blue',
9
+ position: 'bottom-right',
10
+ size: 'medium',
11
+ primaryColor: '#3B82F6',
12
+ textColor: '#FFFFFF',
13
+ backgroundColor: '#FFFFFF'
14
+ });
15
+ const [selectedFramework, setSelectedFramework] = useState('html');
16
+ const [loading, setLoading] = useState(false);
17
+
18
+ useEffect(() => {
19
+ if (website) {
20
+ fetchConfig();
21
+ }
22
+ }, [website]);
23
+
24
+ const fetchConfig = async () => {
25
+ try {
26
+ const response = await api.get(`/websites/${website.id}/widget-config`);
27
+ setConfig(response.data);
28
+ } catch (error) {
29
+ console.error('Failed to fetch widget config');
30
+ }
31
+ };
32
+
33
+ const saveConfig = async () => {
34
+ setLoading(true);
35
+ try {
36
+ await api.put(`/websites/${website.id}/widget-config`, config);
37
+ toast.success('Widget configuration saved!');
38
+ if (onUpdate) onUpdate();
39
+ } catch (error) {
40
+ toast.error('Failed to save configuration');
41
+ } finally {
42
+ setLoading(false);
43
+ }
44
+ };
45
+
46
+ const updateConfig = (key, value) => {
47
+ setConfig(prev => ({ ...prev, [key]: value }));
48
+ };
49
+
50
+ const themes = [
51
+ { name: 'Blue', value: 'blue', color: '#3B82F6' },
52
+ { name: 'Green', value: 'green', color: '#10B981' },
53
+ { name: 'Purple', value: 'purple', color: '#8B5CF6' },
54
+ { name: 'Red', value: 'red', color: '#EF4444' },
55
+ { name: 'Orange', value: 'orange', color: '#F59E0B' }
56
+ ];
57
+
58
+ const positions = [
59
+ { name: 'Bottom Right', value: 'bottom-right' },
60
+ { name: 'Bottom Left', value: 'bottom-left' },
61
+ { name: 'Top Right', value: 'top-right' },
62
+ { name: 'Top Left', value: 'top-left' }
63
+ ];
64
+
65
+ const sizes = [
66
+ { name: 'Small', value: 'small' },
67
+ { name: 'Medium', value: 'medium' },
68
+ { name: 'Large', value: 'large' }
69
+ ];
70
+
71
+ const frameworks = [
72
+ { name: 'HTML/JavaScript', value: 'html' },
73
+ { name: 'React', value: 'react' },
74
+ { name: 'Next.js', value: 'nextjs' },
75
+ { name: 'WordPress', value: 'wordpress' },
76
+ { name: 'Shopify', value: 'shopify' },
77
+ { name: 'Vue.js', value: 'vue' }
78
+ ];
79
+
80
+ const generateEmbedCode = () => {
81
+ const configStr = JSON.stringify(config);
82
+ const websiteId = website?.id;
83
+ const scriptUrl = `http://localhost:8000/static/widget.js`;
84
+
85
+ switch (selectedFramework) {
86
+ case 'react':
87
+ return `import { useEffect } from 'react';
88
+
89
+ function ChatWidget() {
90
+ useEffect(() => {
91
+ const script = document.createElement('script');
92
+ script.src = '${scriptUrl}';
93
+ script.setAttribute('data-website-id', '${websiteId}');
94
+ script.setAttribute('data-config', '${configStr}');
95
+ document.body.appendChild(script);
96
+
97
+ return () => {
98
+ document.body.removeChild(script);
99
+ };
100
+ }, []);
101
+
102
+ return null;
103
+ }
104
+
105
+ export default ChatWidget;`;
106
+
107
+ case 'nextjs':
108
+ return `import { useEffect } from 'react';
109
+ import Script from 'next/script';
110
+
111
+ function ChatWidget() {
112
+ return (
113
+ <Script
114
+ src="${scriptUrl}"
115
+ data-website-id="${websiteId}"
116
+ data-config='${configStr}'
117
+ strategy="afterInteractive"
118
+ />
119
+ );
120
+ }
121
+
122
+ export default ChatWidget;`;
123
+
124
+ case 'wordpress':
125
+ return `<!-- Add this to your theme's functions.php -->
126
+ <?php
127
+ function add_chat_widget() {
128
+ ?>
129
+ <script
130
+ src="${scriptUrl}"
131
+ data-website-id="${websiteId}"
132
+ data-config='${configStr}'>
133
+ </script>
134
+ <?php
135
+ }
136
+ add_action('wp_footer', 'add_chat_widget');
137
+ ?>`;
138
+
139
+ case 'shopify':
140
+ return `<!-- Add this to your theme.liquid before </body> -->
141
+ <script
142
+ src="${scriptUrl}"
143
+ data-website-id="${websiteId}"
144
+ data-config='${configStr}'>
145
+ </script>`;
146
+
147
+ case 'vue':
148
+ return `<template>
149
+ <div></div>
150
+ </template>
151
+
152
+ <script>
153
+ export default {
154
+ name: 'ChatWidget',
155
+ mounted() {
156
+ const script = document.createElement('script');
157
+ script.src = '${scriptUrl}';
158
+ script.setAttribute('data-website-id', '${websiteId}');
159
+ script.setAttribute('data-config', '${configStr}');
160
+ document.body.appendChild(script);
161
+ },
162
+ beforeDestroy() {
163
+ const scripts = document.querySelectorAll('script[src="${scriptUrl}"]');
164
+ scripts.forEach(script => script.remove());
165
+ }
166
+ };
167
+ </script>`;
168
+
169
+ default:
170
+ return `<script
171
+ src="${scriptUrl}"
172
+ data-website-id="${websiteId}"
173
+ data-config='${configStr}'>
174
+ </script>`;
175
+ }
176
+ };
177
+
178
+ return (
179
+ <div className="bg-white rounded-lg shadow p-6">
180
+ <div className="flex items-center gap-2 mb-6">
181
+ <Palette className="w-5 h-5 text-blue-500" />
182
+ <h3 className="text-lg font-semibold">Widget Customization</h3>
183
+ </div>
184
+
185
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
186
+ {/* Theme Selection */}
187
+ <div>
188
+ <label className="block text-sm font-medium text-gray-700 mb-2">Theme</label>
189
+ <div className="grid grid-cols-3 gap-2">
190
+ {themes.map(theme => (
191
+ <button
192
+ key={theme.value}
193
+ onClick={() => updateConfig('theme', theme.value)}
194
+ className={`p-3 rounded-lg border-2 text-center ${
195
+ config.theme === theme.value
196
+ ? 'border-blue-500 bg-blue-50'
197
+ : 'border-gray-200 hover:border-gray-300'
198
+ }`}
199
+ >
200
+ <div
201
+ className="w-6 h-6 rounded-full mx-auto mb-1"
202
+ style={{ backgroundColor: theme.color }}
203
+ />
204
+ <div className="text-xs">{theme.name}</div>
205
+ </button>
206
+ ))}
207
+ </div>
208
+ </div>
209
+
210
+ {/* Position */}
211
+ <div>
212
+ <label className="block text-sm font-medium text-gray-700 mb-2">
213
+ <Move className="w-4 h-4 inline mr-1" />
214
+ Position
215
+ </label>
216
+ <select
217
+ value={config.position}
218
+ onChange={(e) => updateConfig('position', e.target.value)}
219
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
220
+ >
221
+ {positions.map(pos => (
222
+ <option key={pos.value} value={pos.value}>{pos.name}</option>
223
+ ))}
224
+ </select>
225
+ </div>
226
+
227
+ {/* Size */}
228
+ <div>
229
+ <label className="block text-sm font-medium text-gray-700 mb-2">
230
+ <Monitor className="w-4 h-4 inline mr-1" />
231
+ Size
232
+ </label>
233
+ <select
234
+ value={config.size}
235
+ onChange={(e) => updateConfig('size', e.target.value)}
236
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
237
+ >
238
+ {sizes.map(size => (
239
+ <option key={size.value} value={size.value}>{size.name}</option>
240
+ ))}
241
+ </select>
242
+ </div>
243
+
244
+ {/* Primary Color */}
245
+ <div>
246
+ <label className="block text-sm font-medium text-gray-700 mb-2">Primary Color</label>
247
+ <div className="flex gap-2">
248
+ <input
249
+ type="color"
250
+ value={config.primaryColor}
251
+ onChange={(e) => updateConfig('primaryColor', e.target.value)}
252
+ className="w-12 h-10 border border-gray-300 rounded"
253
+ />
254
+ <input
255
+ type="text"
256
+ value={config.primaryColor}
257
+ onChange={(e) => updateConfig('primaryColor', e.target.value)}
258
+ className="flex-1 px-3 py-2 border border-gray-300 rounded-md"
259
+ />
260
+ </div>
261
+ </div>
262
+
263
+ {/* Text Color */}
264
+ <div>
265
+ <label className="block text-sm font-medium text-gray-700 mb-2">Text Color</label>
266
+ <div className="flex gap-2">
267
+ <input
268
+ type="color"
269
+ value={config.textColor}
270
+ onChange={(e) => updateConfig('textColor', e.target.value)}
271
+ className="w-12 h-10 border border-gray-300 rounded"
272
+ />
273
+ <input
274
+ type="text"
275
+ value={config.textColor}
276
+ onChange={(e) => updateConfig('textColor', e.target.value)}
277
+ className="flex-1 px-3 py-2 border border-gray-300 rounded-md"
278
+ />
279
+ </div>
280
+ </div>
281
+
282
+ {/* Background Color */}
283
+ <div>
284
+ <label className="block text-sm font-medium text-gray-700 mb-2">Background Color</label>
285
+ <div className="flex gap-2">
286
+ <input
287
+ type="color"
288
+ value={config.backgroundColor}
289
+ onChange={(e) => updateConfig('backgroundColor', e.target.value)}
290
+ className="w-12 h-10 border border-gray-300 rounded"
291
+ />
292
+ <input
293
+ type="text"
294
+ value={config.backgroundColor}
295
+ onChange={(e) => updateConfig('backgroundColor', e.target.value)}
296
+ className="flex-1 px-3 py-2 border border-gray-300 rounded-md"
297
+ />
298
+ </div>
299
+ </div>
300
+ </div>
301
+
302
+ {/* Preview */}
303
+ <div className="mt-6 p-4 bg-gray-50 rounded-lg">
304
+ <h4 className="text-sm font-medium text-gray-700 mb-3">Preview</h4>
305
+ <div className="relative bg-gray-200 h-32 rounded-lg overflow-hidden">
306
+ <div
307
+ className={`absolute w-12 h-12 rounded-full flex items-center justify-center text-white text-xl ${
308
+ config.position.includes('bottom') ? 'bottom-2' : 'top-2'
309
+ } ${
310
+ config.position.includes('right') ? 'right-2' : 'left-2'
311
+ } ${
312
+ config.size === 'small' ? 'w-10 h-10 text-lg' :
313
+ config.size === 'large' ? 'w-16 h-16 text-2xl' : 'w-12 h-12 text-xl'
314
+ }`}
315
+ style={{ backgroundColor: config.primaryColor }}
316
+ >
317
+ ���
318
+ </div>
319
+ </div>
320
+ </div>
321
+
322
+ {/* Widget Code */}
323
+ <div className="mt-6">
324
+ <div className="flex items-center gap-2 mb-3">
325
+ <Code className="w-4 h-4 text-gray-700" />
326
+ <h4 className="text-sm font-medium text-gray-700">Embed Code</h4>
327
+ </div>
328
+
329
+ {/* Framework Selection */}
330
+ <div className="mb-3">
331
+ <label className="block text-sm font-medium text-gray-700 mb-2">Framework</label>
332
+ <select
333
+ value={selectedFramework}
334
+ onChange={(e) => setSelectedFramework(e.target.value)}
335
+ className="w-full px-3 py-2 border border-gray-300 rounded-md"
336
+ >
337
+ {frameworks.map(framework => (
338
+ <option key={framework.value} value={framework.value}>{framework.name}</option>
339
+ ))}
340
+ </select>
341
+ </div>
342
+
343
+ {/* Code Block */}
344
+ <div className="p-4 bg-gray-900 rounded-lg overflow-x-auto">
345
+ <pre className="text-green-400 text-xs whitespace-pre-wrap">
346
+ {generateEmbedCode()}
347
+ </pre>
348
+ </div>
349
+
350
+ {/* Copy Button */}
351
+ <button
352
+ onClick={() => {
353
+ navigator.clipboard.writeText(generateEmbedCode());
354
+ toast.success('Code copied to clipboard!');
355
+ }}
356
+ className="mt-2 px-3 py-1 bg-gray-200 hover:bg-gray-300 text-gray-700 text-sm rounded"
357
+ >
358
+ Copy Code
359
+ </button>
360
+ </div>
361
+
362
+ <button
363
+ onClick={saveConfig}
364
+ disabled={loading}
365
+ className="w-full mt-6 bg-blue-500 hover:bg-blue-600 text-white py-2 px-4 rounded-md flex items-center justify-center gap-2"
366
+ >
367
+ <Save className="w-4 h-4" />
368
+ {loading ? 'Saving...' : 'Save Configuration'}
369
+ </button>
370
+ </div>
371
+ );
372
+ };
373
+
374
+ export default WidgetCustomizer;
client/src/components/Layout.jsx ADDED
@@ -0,0 +1,182 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Link, useLocation, useNavigate, Outlet } from 'react-router-dom';
3
+ import { useAuth } from '../context/AuthContext';
4
+ import { useWebsites } from '../context/WebsiteContext';
5
+ import toast from 'react-hot-toast';
6
+ import {
7
+ LayoutDashboard,
8
+ MessageSquare,
9
+ FileText,
10
+ HelpCircle,
11
+ Settings as SettingsIcon,
12
+ LogOut,
13
+ Menu,
14
+ X,
15
+ User,
16
+ Globe,
17
+ Users,
18
+ Bell
19
+ } from 'lucide-react';
20
+
21
+ const Layout = ({ children }) => {
22
+ const { user, logout } = useAuth();
23
+ const { websites } = useWebsites();
24
+ const location = useLocation();
25
+ const navigate = useNavigate();
26
+ const [isSidebarOpen, setIsSidebarOpen] = useState(false);
27
+ const [unreadNotifications, setUnreadNotifications] = useState(0);
28
+
29
+ const hasWebsites = websites.length > 0;
30
+
31
+ useEffect(() => {
32
+ const fetchUnreadCount = async () => {
33
+ try {
34
+ const response = await api.get('/notifications/unread-count');
35
+ setUnreadNotifications(response.data.count);
36
+ } catch (error) {
37
+ console.error('Failed to fetch unread count:', error);
38
+ }
39
+ };
40
+
41
+ if (user) {
42
+ fetchUnreadCount();
43
+ const interval = setInterval(fetchUnreadCount, 60000); // Poll every minute
44
+ return () => clearInterval(interval);
45
+ }
46
+ }, [user]);
47
+
48
+ const handleLogout = () => {
49
+ logout();
50
+ navigate('/login');
51
+ };
52
+
53
+ const navItems = [
54
+ { icon: LayoutDashboard, label: 'Dashboard', path: '/dashboard' },
55
+ { icon: Globe, label: 'Websites', path: '/websites' },
56
+ { icon: MessageSquare, label: 'Chats', path: '/chats' },
57
+ { icon: Users, label: 'Users', path: '/users' },
58
+ { icon: FileText, label: 'Content', path: '/content' },
59
+ { icon: HelpCircle, label: 'Unanswered', path: '/unanswered-questions' },
60
+ { icon: MessageSquare, label: 'FAQs', path: '/faqs' },
61
+ { icon: Bell, label: 'Notifications', path: '/notifications' },
62
+ { icon: SettingsIcon, label: 'Settings', path: '/settings' },
63
+ ];
64
+
65
+ return (
66
+ <div className="min-h-screen bg-secondary-50 flex">
67
+ {/* Sidebar */}
68
+ <aside
69
+ className={`fixed inset-y-0 left-0 z-50 w-64 bg-white border-r border-secondary-200 transform transition-transform duration-300 ease-in-out lg:translate-x-0 lg:static lg:inset-0 ${isSidebarOpen ? 'translate-x-0' : '-translate-x-full'
70
+ }`}
71
+ >
72
+ <div className="h-full flex flex-col">
73
+ {/* Logo */}
74
+ <div className="h-16 flex items-center px-6 border-b border-secondary-100">
75
+ <span className="text-xl font-bold bg-gradient-to-r from-primary-600 to-primary-400 bg-clip-text text-transparent">
76
+ CustomerAgent
77
+ </span>
78
+ </div>
79
+
80
+ {/* Navigation */}
81
+ <nav className="flex-1 px-4 py-6 space-y-1 overflow-y-auto">
82
+ {navItems.map((item) => {
83
+ const Icon = item.icon;
84
+ const isActive = location.pathname === item.path;
85
+ const isAlwaysEnabled = ['Websites', 'Settings', 'Notifications'].includes(item.label);
86
+ const isDisabled = !hasWebsites && !isAlwaysEnabled;
87
+
88
+ return (
89
+ <Link
90
+ key={item.path}
91
+ to={isDisabled ? '#' : item.path}
92
+ onClick={(e) => {
93
+ if (isDisabled) {
94
+ e.preventDefault();
95
+ toast.error('Please add a website first to access this section', {
96
+ id: 'sidebar-disabled-toast'
97
+ });
98
+ }
99
+ }}
100
+ className={`flex items-center px-4 py-3 text-sm font-medium rounded-xl transition-all duration-200 ${isActive
101
+ ? 'bg-primary-50 text-primary-700 shadow-sm'
102
+ : isDisabled
103
+ ? 'text-secondary-300 cursor-not-allowed'
104
+ : 'text-secondary-600 hover:bg-secondary-50 hover:text-secondary-900'
105
+ }`}
106
+ >
107
+ <Icon className={`w-5 h-5 mr-3 ${isActive ? 'text-primary-600' : isDisabled ? 'text-secondary-200' : 'text-secondary-400'}`} />
108
+ {item.label}
109
+ </Link>
110
+ );
111
+ })}
112
+ </nav>
113
+
114
+ {/* User Profile */}
115
+ <div className="p-4 border-t border-secondary-100">
116
+ <div className="flex items-center p-3 rounded-xl bg-secondary-50 mb-3">
117
+ <div className="w-10 h-10 rounded-full bg-primary-100 flex items-center justify-center text-primary-600 font-semibold">
118
+ {user?.email?.[0].toUpperCase() || <User className="w-5 h-5" />}
119
+ </div>
120
+ <div className="ml-3 overflow-hidden">
121
+ <p className="text-sm font-medium text-secondary-900 truncate">{user?.email}</p>
122
+ <p className="text-xs text-secondary-500 truncate">Admin</p>
123
+ </div>
124
+ </div>
125
+ <button
126
+ onClick={handleLogout}
127
+ className="w-full flex items-center justify-center px-4 py-2 text-sm font-medium text-red-600 bg-red-50 rounded-lg hover:bg-red-100 transition-colors"
128
+ >
129
+ <LogOut className="w-4 h-4 mr-2" />
130
+ Sign Out
131
+ </button>
132
+ </div>
133
+ </div>
134
+ </aside>
135
+
136
+ {/* Mobile Overlay */}
137
+ {isSidebarOpen && (
138
+ <div
139
+ className="fixed inset-0 z-40 bg-secondary-900/50 backdrop-blur-sm lg:hidden"
140
+ onClick={() => setIsSidebarOpen(false)}
141
+ />
142
+ )}
143
+
144
+ {/* Main Content */}
145
+ <div className="flex-1 flex flex-col min-w-0 overflow-hidden">
146
+ <header className="h-16 bg-white border-b border-secondary-200 flex items-center justify-between px-4 lg:px-8 shrink-0">
147
+ <div className="flex items-center gap-4">
148
+ <button
149
+ onClick={() => setIsSidebarOpen(!isSidebarOpen)}
150
+ className="p-2 rounded-lg text-secondary-600 hover:bg-secondary-100 lg:hidden"
151
+ >
152
+ {isSidebarOpen ? <X className="w-6 h-6" /> : <Menu className="w-6 h-6" />}
153
+ </button>
154
+ <span className="text-lg font-bold text-secondary-900 lg:hidden">CustomerAgent</span>
155
+ </div>
156
+
157
+ <div className="flex items-center gap-4">
158
+ <Link
159
+ to="/notifications"
160
+ className="p-2 bg-secondary-50 text-secondary-600 hover:bg-secondary-100 rounded-xl transition-all relative group"
161
+ title="Notifications"
162
+ >
163
+ <Bell className="w-5 h-5 group-hover:rotate-12 transition-transform" />
164
+ {unreadNotifications > 0 && (
165
+ <span className="absolute top-1.5 right-1.5 w-2.5 h-2.5 bg-red-500 border-2 border-white rounded-full animate-pulse" />
166
+ )}
167
+ </Link>
168
+ </div>
169
+ </header>
170
+
171
+ {/* Page Content */}
172
+ <main className="flex-1 overflow-y-auto p-4 lg:p-8">
173
+ <div className="max-w-7xl mx-auto">
174
+ <Outlet />
175
+ </div>
176
+ </main>
177
+ </div>
178
+ </div>
179
+ );
180
+ };
181
+
182
+ export default Layout;
client/src/components/PublicLayout.css ADDED
@@ -0,0 +1,220 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Public Layout - Navigation & Footer */
2
+
3
+ /* Navigation */
4
+ .public-nav {
5
+ background: white;
6
+ border-bottom: 1px solid #e0e0e0;
7
+ position: sticky;
8
+ top: 0;
9
+ z-index: 1000;
10
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
11
+ }
12
+
13
+ [data-theme="dark"] .public-nav {
14
+ background: #1a1a1a;
15
+ border-bottom-color: #404040;
16
+ }
17
+
18
+ .public-nav__container {
19
+ max-width: 1200px;
20
+ margin: 0 auto;
21
+ padding: 0 20px;
22
+ height: 70px;
23
+ display: flex;
24
+ align-items: center;
25
+ justify-content: space-between;
26
+ gap: 40px;
27
+ }
28
+
29
+ .public-nav__logo {
30
+ display: flex;
31
+ align-items: center;
32
+ gap: 12px;
33
+ text-decoration: none;
34
+ font-weight: 700;
35
+ font-size: 1.25rem;
36
+ color: #1a1a1a;
37
+ }
38
+
39
+ [data-theme="dark"] .public-nav__logo {
40
+ color: white;
41
+ }
42
+
43
+ .public-nav__logo-icon {
44
+ font-size: 2rem;
45
+ }
46
+
47
+ .public-nav__links {
48
+ display: flex;
49
+ gap: 32px;
50
+ flex: 1;
51
+ justify-content: center;
52
+ }
53
+
54
+ .public-nav__link {
55
+ text-decoration: none;
56
+ color: #666;
57
+ font-weight: 500;
58
+ font-size: 1rem;
59
+ transition: color 0.3s ease;
60
+ position: relative;
61
+ }
62
+
63
+ .public-nav__link:hover {
64
+ color: #667eea;
65
+ }
66
+
67
+ .public-nav__link.active {
68
+ color: #667eea;
69
+ }
70
+
71
+ .public-nav__link.active::after {
72
+ content: '';
73
+ position: absolute;
74
+ bottom: -12px;
75
+ left: 0;
76
+ right: 0;
77
+ height: 3px;
78
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
79
+ border-radius: 2px 2px 0 0;
80
+ }
81
+
82
+ .public-nav__actions {
83
+ display: flex;
84
+ align-items: center;
85
+ gap: 12px;
86
+ }
87
+
88
+ .btn--ghost {
89
+ background: transparent;
90
+ color: #667eea;
91
+ border: 1px solid #667eea;
92
+ }
93
+
94
+ .btn--ghost:hover {
95
+ background: rgba(102, 126, 234, 0.1);
96
+ transform: none;
97
+ }
98
+
99
+ /* Main Content */
100
+ .public-main {
101
+ min-height: calc(100vh - 70px);
102
+ }
103
+
104
+ /* Footer */
105
+ .public-footer {
106
+ background: #1a1a1a;
107
+ color: white;
108
+ padding: 60px 20px 30px;
109
+ }
110
+
111
+ .public-footer__container {
112
+ max-width: 1200px;
113
+ margin: 0 auto;
114
+ }
115
+
116
+ .public-footer__grid {
117
+ display: grid;
118
+ grid-template-columns: 2fr 1fr 1fr 1fr;
119
+ gap: 60px;
120
+ margin-bottom: 48px;
121
+ }
122
+
123
+ .public-footer__brand {
124
+ display: flex;
125
+ align-items: center;
126
+ gap: 12px;
127
+ margin-bottom: 16px;
128
+ }
129
+
130
+ .public-footer__logo {
131
+ font-size: 2rem;
132
+ }
133
+
134
+ .public-footer__brand-name {
135
+ font-size: 1.5rem;
136
+ font-weight: 700;
137
+ }
138
+
139
+ .public-footer__tagline {
140
+ color: #b3b3b3;
141
+ line-height: 1.6;
142
+ }
143
+
144
+ .public-footer__col h4 {
145
+ font-size: 1.125rem;
146
+ font-weight: 600;
147
+ margin-bottom: 16px;
148
+ }
149
+
150
+ .public-footer__col ul {
151
+ list-style: none;
152
+ padding: 0;
153
+ display: flex;
154
+ flex-direction: column;
155
+ gap: 12px;
156
+ }
157
+
158
+ .public-footer__col a {
159
+ color: #b3b3b3;
160
+ text-decoration: none;
161
+ transition: color 0.3s ease;
162
+ }
163
+
164
+ .public-footer__col a:hover {
165
+ color: white;
166
+ }
167
+
168
+ .public-footer__bottom {
169
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
170
+ padding-top: 24px;
171
+ display: flex;
172
+ justify-content: space-between;
173
+ align-items: center;
174
+ color: #b3b3b3;
175
+ font-size: 0.875rem;
176
+ }
177
+
178
+ .public-footer__links {
179
+ display: flex;
180
+ gap: 16px;
181
+ align-items: center;
182
+ }
183
+
184
+ .public-footer__links a {
185
+ color: #b3b3b3;
186
+ text-decoration: none;
187
+ }
188
+
189
+ .public-footer__links a:hover {
190
+ color: white;
191
+ }
192
+
193
+ /* Responsive */
194
+ @media (max-width: 992px) {
195
+ .public-nav__links {
196
+ display: none;
197
+ }
198
+
199
+ .public-footer__grid {
200
+ grid-template-columns: 1fr 1fr;
201
+ gap: 40px;
202
+ }
203
+ }
204
+
205
+ @media (max-width: 768px) {
206
+ .public-nav__actions .btn {
207
+ padding: 10px 20px;
208
+ font-size: 0.9rem;
209
+ }
210
+
211
+ .public-footer__grid {
212
+ grid-template-columns: 1fr;
213
+ }
214
+
215
+ .public-footer__bottom {
216
+ flex-direction: column;
217
+ gap: 16px;
218
+ text-align: center;
219
+ }
220
+ }
client/src/components/PublicLayout.jsx ADDED
@@ -0,0 +1,225 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { Link, useLocation, Outlet } from 'react-router-dom';
3
+ import { Menu, X, ChevronRight, Twitter, Linkedin, Facebook, Youtube } from 'lucide-react';
4
+
5
+ const PublicLayout = ({ children }) => {
6
+ const location = useLocation();
7
+ const [isScrolled, setIsScrolled] = useState(false);
8
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
9
+
10
+ useEffect(() => {
11
+ const handleScroll = () => {
12
+ setIsScrolled(window.scrollY > 20);
13
+ };
14
+ window.addEventListener('scroll', handleScroll);
15
+ return () => window.removeEventListener('scroll', handleScroll);
16
+ }, []);
17
+
18
+ const isActive = (path) => location.pathname === path;
19
+
20
+ const navLinks = [
21
+ { path: '/', label: 'Home' },
22
+ { path: '/pricing', label: 'Pricing' },
23
+ { path: '/about', label: 'About' },
24
+ { path: '/contact', label: 'Contact' },
25
+ { path: '/blog', label: 'Blog' },
26
+ ];
27
+
28
+ return (
29
+ <div className="min-h-screen flex flex-col bg-secondary-50 dark:bg-secondary-950 transition-colors duration-300">
30
+ {/* Navigation */}
31
+ <nav
32
+ className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${isScrolled ? 'glass py-3' : 'bg-transparent py-5'
33
+ }`}
34
+ >
35
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
36
+ <div className="flex items-center justify-between">
37
+ {/* Logo */}
38
+ <Link to="/" className="flex items-center gap-2 group">
39
+ <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-accent-500 flex items-center justify-center text-white font-bold text-xl shadow-lg group-hover:scale-105 transition-transform">
40
+ CA
41
+ </div>
42
+ <span className="font-bold text-xl text-secondary-900 dark:text-white tracking-tight">
43
+ Customer<span className="text-primary-600">Agent</span>
44
+ </span>
45
+ </Link>
46
+
47
+ {/* Desktop Navigation */}
48
+ <div className="hidden md:flex items-center gap-8">
49
+ {navLinks.map((link) => (
50
+ <Link
51
+ key={link.path}
52
+ to={link.path}
53
+ className={`text-sm font-medium transition-colors hover:text-primary-600 ${isActive(link.path)
54
+ ? 'text-primary-600'
55
+ : 'text-secondary-600 dark:text-secondary-300'
56
+ }`}
57
+ >
58
+ {link.label}
59
+ </Link>
60
+ ))}
61
+
62
+ <div className="flex items-center gap-4">
63
+ <Link
64
+ to="/login"
65
+ className="text-sm font-medium text-secondary-600 dark:text-secondary-300 hover:text-primary-600 transition-colors"
66
+ >
67
+ Log In
68
+ </Link>
69
+ <Link
70
+ to="/register"
71
+ className="px-5 py-2.5 rounded-lg bg-primary-600 text-white text-sm font-semibold shadow-lg shadow-primary-500/30 hover:bg-primary-700 hover:-translate-y-0.5 transition-all"
72
+ >
73
+ Start Free Trial
74
+ </Link>
75
+ </div>
76
+ </div>
77
+
78
+ {/* Mobile Menu Button */}
79
+ <button
80
+ className="md:hidden p-2 text-secondary-600 dark:text-secondary-300"
81
+ onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
82
+ >
83
+ {isMobileMenuOpen ? <X size={24} /> : <Menu size={24} />}
84
+ </button>
85
+ </div>
86
+ </div>
87
+
88
+ {/* Mobile Menu */}
89
+ {isMobileMenuOpen && (
90
+ <div className="md:hidden absolute top-full left-0 right-0 bg-white dark:bg-secondary-900 border-b border-secondary-200 dark:border-secondary-800 shadow-xl animate-fade-in">
91
+ <div className="px-4 py-6 space-y-4">
92
+ {navLinks.map((link) => (
93
+ <Link
94
+ key={link.path}
95
+ to={link.path}
96
+ className={`block text-base font-medium ${isActive(link.path)
97
+ ? 'text-primary-600'
98
+ : 'text-secondary-600 dark:text-secondary-300'
99
+ }`}
100
+ onClick={() => setIsMobileMenuOpen(false)}
101
+ >
102
+ {link.label}
103
+ </Link>
104
+ ))}
105
+ <div className="pt-4 border-t border-secondary-200 dark:border-secondary-800 flex flex-col gap-4">
106
+ <Link
107
+ to="/login"
108
+ className="text-center text-secondary-600 dark:text-secondary-300 font-medium"
109
+ onClick={() => setIsMobileMenuOpen(false)}
110
+ >
111
+ Log In
112
+ </Link>
113
+ <Link
114
+ to="/register"
115
+ className="block text-center px-5 py-3 rounded-lg bg-primary-600 text-white font-semibold"
116
+ onClick={() => setIsMobileMenuOpen(false)}
117
+ >
118
+ Start Free Trial
119
+ </Link>
120
+ </div>
121
+ </div>
122
+ </div>
123
+ )}
124
+ </nav>
125
+
126
+ {/* Main Content */}
127
+ <main className="flex-grow pt-20">
128
+ <Outlet />
129
+ </main>
130
+
131
+ {/* Footer */}
132
+ <footer className="bg-white dark:bg-secondary-900 border-t border-secondary-200 dark:border-secondary-800 pt-16 pb-8">
133
+ <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
134
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-12 mb-12">
135
+ <div className="space-y-4">
136
+ <div className="flex items-center gap-2">
137
+ <div className="w-8 h-8 rounded-lg bg-gradient-to-br from-primary-500 to-accent-500 flex items-center justify-center text-white font-bold text-lg">
138
+ CA
139
+ </div>
140
+ <span className="font-bold text-lg text-secondary-900 dark:text-white">
141
+ CustomerAgent
142
+ </span>
143
+ </div>
144
+ <p className="text-secondary-500 dark:text-secondary-400 text-sm leading-relaxed">
145
+ AI-powered customer support platform that helps businesses deliver exceptional experiences at scale.
146
+ </p>
147
+ <div className="flex gap-4">
148
+ {[Twitter, Linkedin, Facebook, Youtube].map((Icon, i) => (
149
+ <a key={i} href="#" className="text-secondary-400 hover:text-primary-500 transition-colors">
150
+ <Icon size={20} />
151
+ </a>
152
+ ))}
153
+ </div>
154
+ </div>
155
+
156
+ <div>
157
+ <h4 className="font-semibold text-secondary-900 dark:text-white mb-6">Product</h4>
158
+ <ul className="space-y-3">
159
+ {['Features', 'Pricing', 'Integrations', 'Enterprise', 'Changelog'].map((item) => (
160
+ <li key={item}>
161
+ <Link to={`/${item.toLowerCase()}`} className="text-secondary-500 dark:text-secondary-400 hover:text-primary-600 text-sm transition-colors">
162
+ {item}
163
+ </Link>
164
+ </li>
165
+ ))}
166
+ </ul>
167
+ </div>
168
+
169
+ <div>
170
+ <h4 className="font-semibold text-secondary-900 dark:text-white mb-6">Resources</h4>
171
+ <ul className="space-y-3">
172
+ {[
173
+ { name: 'Documentation', path: '/docs' },
174
+ { name: 'API Reference', path: '/api' },
175
+ { name: 'Blog', path: '/blog' },
176
+ { name: 'Community', path: '/community' },
177
+ { name: 'Help Center', path: '/help-center' }
178
+ ].map((item) => (
179
+ <li key={item.name}>
180
+ <Link to={item.path} className="text-secondary-500 dark:text-secondary-400 hover:text-primary-600 text-sm transition-colors">
181
+ {item.name}
182
+ </Link>
183
+ </li>
184
+ ))}
185
+ </ul>
186
+ </div>
187
+
188
+ <div>
189
+ <h4 className="font-semibold text-secondary-900 dark:text-white mb-6">Legal</h4>
190
+ <ul className="space-y-3">
191
+ {[
192
+ { name: 'Privacy Policy', path: '/privacy' },
193
+ { name: 'Terms of Service', path: '/terms' },
194
+ { name: 'Cookie Policy', path: '/cookie-policy' },
195
+ { name: 'GDPR', path: '/gdpr' },
196
+ { name: 'Security', path: '/security' }
197
+ ].map((item) => (
198
+ <li key={item.name}>
199
+ <Link to={item.path} className="text-secondary-500 dark:text-secondary-400 hover:text-primary-600 text-sm transition-colors">
200
+ {item.name}
201
+ </Link>
202
+ </li>
203
+ ))}
204
+ </ul>
205
+ </div>
206
+ </div>
207
+
208
+ <div className="pt-8 border-t border-secondary-200 dark:border-secondary-800 flex flex-col md:flex-row justify-between items-center gap-4">
209
+ <p className="text-secondary-400 text-sm">
210
+ &copy; {new Date().getFullYear()} CustomerAgent. All rights reserved.
211
+ </p>
212
+ <div className="flex items-center gap-6">
213
+ <span className="flex items-center gap-2 text-sm text-secondary-500">
214
+ <span className="w-2 h-2 rounded-full bg-green-500"></span>
215
+ All systems operational
216
+ </span>
217
+ </div>
218
+ </div>
219
+ </div>
220
+ </footer>
221
+ </div>
222
+ );
223
+ };
224
+
225
+ export default PublicLayout;
client/src/components/Website/HealthcareConfigModal.jsx ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { useState, useEffect } from 'react';
2
+ import { motion, AnimatePresence } from 'framer-motion';
3
+ import { X, Heart, Stethoscope, Activity, Calendar, Languages, Shield, AlertCircle } from 'lucide-react';
4
+ import api from '../../api/axiosConfig';
5
+ import toast from 'react-hot-toast';
6
+
7
+ export default function HealthcareConfigModal({ isOpen, onClose, website, onUpdate }) {
8
+ const [config, setConfig] = useState({
9
+ healthcare_sub_industry: 'general',
10
+ symptom_checker_enabled: true,
11
+ language: 'en',
12
+ custom_disclaimer: '',
13
+ appointment_url: ''
14
+ });
15
+ const [loading, setLoading] = useState(false);
16
+
17
+ useEffect(() => {
18
+ if (website && website.widget_config) {
19
+ const currentConfig = typeof website.widget_config === 'string'
20
+ ? JSON.parse(website.widget_config)
21
+ : website.widget_config;
22
+
23
+ setConfig({
24
+ healthcare_sub_industry: currentConfig.healthcare_sub_industry || 'general',
25
+ symptom_checker_enabled: currentConfig.symptom_checker_enabled !== false,
26
+ language: currentConfig.language || 'en',
27
+ custom_disclaimer: currentConfig.custom_disclaimer || '',
28
+ appointment_url: currentConfig.appointment_url || ''
29
+ });
30
+ }
31
+ }, [website]);
32
+
33
+ const handleSave = async () => {
34
+ setLoading(true);
35
+ try {
36
+ // Merge into existing widget_config
37
+ const updatedWidgetConfig = {
38
+ ...(typeof website.widget_config === 'string' ? JSON.parse(website.widget_config) : website.widget_config),
39
+ ...config
40
+ };
41
+
42
+ await api.put(`/websites/${website.id}/widget-config`, updatedWidgetConfig);
43
+ toast.success('Healthcare settings updated!');
44
+ onUpdate();
45
+ onClose();
46
+ } catch (error) {
47
+ toast.error('Failed to update settings');
48
+ } finally {
49
+ setLoading(false);
50
+ }
51
+ };
52
+
53
+ if (!isOpen) return null;
54
+
55
+ const subIndustries = [
56
+ { id: 'general', name: 'General Practice', icon: <Heart className="w-4 h-4" /> },
57
+ { id: 'dental', name: 'Dental Care', icon: <Stethoscope className="w-4 h-4" /> },
58
+ { id: 'pharmacy', name: 'Pharmacy', icon: <Activity className="w-4 h-4" /> },
59
+ { id: 'mental_health', name: 'Mental Health', icon: <Shield className="w-4 h-4" /> },
60
+ { id: 'pediatrics', name: 'Pediatrics', icon: <Activity className="w-4 h-4" /> }
61
+ ];
62
+
63
+ return (
64
+ <div className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-[60] p-4">
65
+ <motion.div
66
+ initial={{ opacity: 0, scale: 0.95 }}
67
+ animate={{ opacity: 1, scale: 1 }}
68
+ exit={{ opacity: 0, scale: 0.95 }}
69
+ className="bg-white rounded-3xl shadow-2xl max-w-xl w-full flex flex-col max-h-[90vh] overflow-hidden border border-blue-50"
70
+ >
71
+ {/* Header */}
72
+ <div className="px-8 py-6 bg-gradient-to-r from-blue-600 to-indigo-600 text-white flex items-center justify-between">
73
+ <div className="flex items-center gap-3">
74
+ <div className="p-2 bg-white/20 rounded-xl backdrop-blur-sm">
75
+ <Stethoscope className="w-6 h-6" />
76
+ </div>
77
+ <div>
78
+ <h2 className="text-xl font-bold">Healthcare AI Configuration</h2>
79
+ <p className="text-blue-100 text-sm">Tailor your medical assistant for {website?.name}</p>
80
+ </div>
81
+ </div>
82
+ <button onClick={onClose} className="p-2 hover:bg-white/20 rounded-lg transition-colors">
83
+ <X className="w-5 h-5" />
84
+ </button>
85
+ </div>
86
+
87
+ {/* Content */}
88
+ <div className="p-8 space-y-8 overflow-y-auto">
89
+ {/* Sub-industry selection */}
90
+ <div className="space-y-4">
91
+ <label className="text-sm font-bold text-secondary-700 flex items-center gap-2">
92
+ <Activity className="w-4 h-4 text-blue-500" />
93
+ SPECIALIZED SUB-INDUSTRY
94
+ </label>
95
+ <div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
96
+ {subIndustries.map((si) => (
97
+ <button
98
+ key={si.id}
99
+ onClick={() => setConfig({ ...config, healthcare_sub_industry: si.id })}
100
+ className={`flex items-center gap-2 px-4 py-3 rounded-2xl text-sm font-medium border-2 transition-all ${config.healthcare_sub_industry === si.id
101
+ ? 'border-blue-500 bg-blue-50 text-blue-700 shadow-md shadow-blue-200/50'
102
+ : 'border-secondary-100 hover:border-blue-200 text-secondary-600'
103
+ }`}
104
+ >
105
+ {si.icon}
106
+ {si.name}
107
+ </button>
108
+ ))}
109
+ </div>
110
+ </div>
111
+
112
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-8">
113
+ {/* Language selection */}
114
+ <div className="space-y-4">
115
+ <label className="text-sm font-bold text-secondary-700 flex items-center gap-2">
116
+ <Languages className="w-4 h-4 text-blue-500" />
117
+ PRIMARY LANGUAGE
118
+ </label>
119
+ <select
120
+ value={config.language}
121
+ onChange={(e) => setConfig({ ...config, language: e.target.value })}
122
+ className="w-full px-4 py-3 bg-secondary-50 border-2 border-secondary-100 rounded-2xl focus:border-blue-500 outline-none transition-all font-medium text-secondary-700"
123
+ >
124
+ <option value="en">English</option>
125
+ <option value="ur">Urdu (اردو)</option>
126
+ </select>
127
+ </div>
128
+
129
+ {/* Symptom Checker Toggle */}
130
+ <div className="space-y-4">
131
+ <label className="text-sm font-bold text-secondary-700 flex items-center gap-2">
132
+ <Activity className="w-4 h-4 text-blue-500" />
133
+ SYMPTOM CHECKER
134
+ </label>
135
+ <div className="flex items-center gap-3 p-1 bg-secondary-50 rounded-2xl border-2 border-secondary-100">
136
+ <button
137
+ onClick={() => setConfig({ ...config, symptom_checker_enabled: true })}
138
+ className={`flex-1 py-2 px-4 rounded-xl text-sm font-bold transition-all ${config.symptom_checker_enabled
139
+ ? 'bg-white text-blue-600 shadow-sm'
140
+ : 'text-secondary-400'
141
+ }`}
142
+ >
143
+ Enabled
144
+ </button>
145
+ <button
146
+ onClick={() => setConfig({ ...config, symptom_checker_enabled: false })}
147
+ className={`flex-1 py-2 px-4 rounded-xl text-sm font-bold transition-all ${!config.symptom_checker_enabled
148
+ ? 'bg-white text-red-600 shadow-sm'
149
+ : 'text-secondary-400'
150
+ }`}
151
+ >
152
+ Disabled
153
+ </button>
154
+ </div>
155
+ </div>
156
+ </div>
157
+
158
+ {/* Appointment integration */}
159
+ <div className="space-y-4">
160
+ <label className="text-sm font-bold text-secondary-700 flex items-center gap-2">
161
+ <Calendar className="w-4 h-4 text-blue-500" />
162
+ APPOINTMENT BOOKING URL
163
+ </label>
164
+ <div className="relative group">
165
+ <input
166
+ type="url"
167
+ value={config.appointment_url}
168
+ onChange={(e) => setConfig({ ...config, appointment_url: e.target.value })}
169
+ placeholder="https://appointments.yourclinic.com"
170
+ className="w-full pl-12 pr-4 py-4 bg-secondary-50 border-2 border-secondary-100 rounded-2xl focus:border-blue-500 outline-none transition-all text-secondary-700 placeholder:text-secondary-300 shadow-inner"
171
+ />
172
+ <Calendar className="absolute left-4 top-1/2 -translate-y-1/2 w-5 h-5 text-secondary-400 group-focus-within:text-blue-500 transition-colors" />
173
+ </div>
174
+ </div>
175
+
176
+ {/* Disclaimer customization */}
177
+ <div className="space-y-4">
178
+ <label className="text-sm font-bold text-secondary-700 flex items-center gap-2">
179
+ <Shield className="w-4 h-4 text-blue-500" />
180
+ CUSTOM MEDICAL DISCLAIMER
181
+ </label>
182
+ <textarea
183
+ value={config.custom_disclaimer}
184
+ onChange={(e) => setConfig({ ...config, custom_disclaimer: e.target.value })}
185
+ placeholder="Optional: Provide a specific legal disclaimer for your practice..."
186
+ className="w-full px-4 py-4 bg-secondary-50 border-2 border-secondary-100 rounded-2xl focus:border-blue-500 outline-none transition-all text-secondary-700 placeholder:text-secondary-300 shadow-inner h-24 resize-none"
187
+ />
188
+ <p className="text-xs text-secondary-400 flex items-center gap-1.5">
189
+ <AlertCircle className="w-3 h-3" />
190
+ If empty, a standard professional medical disclaimer will be used.
191
+ </p>
192
+ </div>
193
+ </div>
194
+
195
+ {/* Footer */}
196
+ <div className="px-8 py-6 border-t border-secondary-100 bg-secondary-50/50 flex gap-4 justify-end">
197
+ <button
198
+ onClick={onClose}
199
+ className="px-6 py-3 text-secondary-600 font-bold rounded-2xl hover:bg-secondary-100 transition-colors"
200
+ disabled={loading}
201
+ >
202
+ Cancel
203
+ </button>
204
+ <button
205
+ onClick={handleSave}
206
+ disabled={loading}
207
+ className="px-8 py-3 bg-gradient-to-r from-blue-600 to-indigo-600 text-white font-bold rounded-2xl hover:shadow-lg hover:shadow-blue-200 transform hover:-translate-y-0.5 active:translate-y-0 transition-all disabled:opacity-50"
208
+ >
209
+ {loading ? 'Saving...' : 'Save Configuration'}
210
+ </button>
211
+ </div>
212
+ </motion.div>
213
+ </div>
214
+ );
215
+ }
client/src/context/AuthContext.jsx ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { createContext, useContext, useState, useEffect } from 'react';
2
+ import api from '../api/axiosConfig';
3
+
4
+ const AuthContext = createContext();
5
+
6
+ export const useAuth = () => {
7
+ const context = useContext(AuthContext);
8
+ if (!context) {
9
+ throw new Error('useAuth must be used within an AuthProvider');
10
+ }
11
+ return context;
12
+ };
13
+
14
+ export const AuthProvider = ({ children }) => {
15
+ const [user, setUser] = useState(null);
16
+ const [loading, setLoading] = useState(true);
17
+
18
+ useEffect(() => {
19
+ checkAuth();
20
+ }, []);
21
+
22
+ const checkAuth = async () => {
23
+ try {
24
+ const response = await api.get('/auth/me');
25
+ setUser(response.data);
26
+ } catch (error) {
27
+ console.log('Auth check failed:', error.message);
28
+ setUser(null);
29
+ } finally {
30
+ setLoading(false);
31
+ }
32
+ };
33
+
34
+ const login = async (email, password) => {
35
+ try {
36
+ const response = await api.post('/auth/login', { email, password });
37
+ if (response.data.access_token) {
38
+ localStorage.setItem('access_token', response.data.access_token);
39
+ }
40
+ await checkAuth();
41
+ return response.data;
42
+ } catch (error) {
43
+ console.error('Login error:', error.response?.data);
44
+ const message = error.response?.data?.message || error.response?.data?.error || error.response?.data?.detail || error.message;
45
+ throw message;
46
+ }
47
+ };
48
+
49
+ const register = async (email, password) => {
50
+ try {
51
+ const response = await api.post('/auth/register', { email, password });
52
+ return response.data;
53
+ } catch (error) {
54
+ console.error('Registration error:', error.response?.data);
55
+ const message = error.response?.data?.message || error.response?.data?.error || error.response?.data?.detail || error.message;
56
+ throw message;
57
+ }
58
+ };
59
+
60
+ const googleLogin = async (idToken) => {
61
+ try {
62
+ const response = await api.post('/auth/google', { id_token: idToken });
63
+ if (response.data.access_token) {
64
+ localStorage.setItem('access_token', response.data.access_token);
65
+ }
66
+ await checkAuth();
67
+ return response.data;
68
+ } catch (error) {
69
+ console.error('Google login error:', error.response?.data);
70
+ const message = error.response?.data?.message || error.response?.data?.error || error.response?.data?.detail || error.message;
71
+ throw message;
72
+ }
73
+ };
74
+
75
+ const logout = async () => {
76
+ try {
77
+ await api.post('/auth/logout');
78
+ } catch (error) {
79
+ console.error('Logout error:', error);
80
+ } finally {
81
+ localStorage.removeItem('access_token');
82
+ setUser(null);
83
+ }
84
+ };
85
+
86
+ const value = {
87
+ user,
88
+ login,
89
+ googleLogin,
90
+ register,
91
+ logout,
92
+ loading
93
+ };
94
+
95
+ return (
96
+ <AuthContext.Provider value={value}>
97
+ {children}
98
+ </AuthContext.Provider>
99
+ );
100
+ };
client/src/context/WebsiteContext.jsx ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import React, { createContext, useContext, useState, useEffect, useCallback } from 'react';
2
+ import api from '../api/axiosConfig';
3
+ import { useAuth } from './AuthContext';
4
+
5
+ const WebsiteContext = createContext();
6
+
7
+ export const WebsiteProvider = ({ children }) => {
8
+ const [websites, setWebsites] = useState([]);
9
+ const [loading, setLoading] = useState(true);
10
+ const { user } = useAuth();
11
+
12
+ const fetchWebsites = useCallback(async () => {
13
+ if (!user) {
14
+ setWebsites([]);
15
+ setLoading(false);
16
+ return;
17
+ }
18
+
19
+ try {
20
+ const response = await api.get('/websites/');
21
+ setWebsites(response.data);
22
+ } catch (error) {
23
+ console.error('Failed to fetch websites:', error);
24
+ } finally {
25
+ setLoading(false);
26
+ }
27
+ }, [user]);
28
+
29
+ useEffect(() => {
30
+ fetchWebsites();
31
+ }, [fetchWebsites]);
32
+
33
+ const refreshWebsites = () => {
34
+ setLoading(true);
35
+ return fetchWebsites();
36
+ };
37
+
38
+ return (
39
+ <WebsiteContext.Provider value={{ websites, loading, refreshWebsites }}>
40
+ {children}
41
+ </WebsiteContext.Provider>
42
+ );
43
+ };
44
+
45
+ export const useWebsites = () => {
46
+ const context = useContext(WebsiteContext);
47
+ if (!context) {
48
+ throw new Error('useWebsites must be used within a WebsiteProvider');
49
+ }
50
+ return context;
51
+ };
client/src/hooks/useAuth.js ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useContext } from 'react';
2
+ import { AuthContext } from '../context/AuthContext';
3
+
4
+ export const useAuth = () => {
5
+ const context = useContext(AuthContext);
6
+
7
+ if (!context) {
8
+ throw new Error('useAuth must be used within an AuthProvider');
9
+ }
10
+
11
+ return context;
12
+ };
client/src/hooks/useTheme.jsx ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Dark Mode Hook - React Implementation
2
+ import { useState, useEffect } from 'react';
3
+
4
+ export const useTheme = () => {
5
+ const [theme, setTheme] = useState(() => {
6
+ // Check localStorage first
7
+ const saved = localStorage.getItem('theme');
8
+ if (saved) return saved;
9
+
10
+ // Check system preference
11
+ if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
12
+ return 'dark';
13
+ }
14
+
15
+ return 'light';
16
+ });
17
+
18
+ useEffect(() => {
19
+ // Apply theme to document
20
+ document.documentElement.setAttribute('data-theme', theme);
21
+
22
+ // Save to localStorage
23
+ localStorage.setItem('theme', theme);
24
+ }, [theme]);
25
+
26
+ // Listen for system theme changes
27
+ useEffect(() => {
28
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
29
+
30
+ const handleChange = (e) => {
31
+ // Only auto-switch if user hasn't set preference
32
+ if (!localStorage.getItem('theme')) {
33
+ setTheme(e.matches ? 'dark' : 'light');
34
+ }
35
+ };
36
+
37
+ mediaQuery.addEventListener('change', handleChange);
38
+ return () => mediaQuery.removeEventListener('change', handleChange);
39
+ }, []);
40
+
41
+ const toggleTheme = () => {
42
+ setTheme(prev => prev === 'light' ? 'dark' : 'light');
43
+ };
44
+
45
+ return { theme, toggleTheme };
46
+ };
47
+
48
+ // Theme Toggle Component
49
+ export const ThemeToggle = () => {
50
+ const { theme, toggleTheme } = useTheme();
51
+
52
+ return (
53
+ <button
54
+ className="theme-toggle"
55
+ onClick={toggleTheme}
56
+ aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} mode`}
57
+ >
58
+ <span className="theme-toggle__icon">
59
+ {theme === 'light' ? '🌙' : '☀️'}
60
+ </span>
61
+ <span>{theme === 'light' ? 'Dark' : 'Light'} Mode</span>
62
+ </button>
63
+ );
64
+ };