Amodit commited on
Commit
66c7ada
·
1 Parent(s): 865e378

Cleanup and Fixes

Browse files
.streamlit/config.toml ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ [theme]
2
+ primaryColor = "#1A73E8"
3
+ backgroundColor = "#FFFFFF"
4
+ secondaryBackgroundColor = "#F8F9FA"
5
+ textColor = "#202124"
6
+ font = "sans serif"
7
+
8
+ [server]
9
+ headless = true
API_DOCUMENTATION.md ADDED
@@ -0,0 +1,658 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Jan-Contract Enhanced API Documentation
2
+
3
+ ## Overview
4
+
5
+ The Jan-Contract Enhanced API provides comprehensive services for India's informal workforce, including contract generation, scheme discovery, document analysis, and AI-powered assistance.
6
+
7
+ **Base URL:** `http://localhost:8000`
8
+ **API Version:** 2.1.0
9
+ **Documentation:** `/docs` (Swagger UI) or `/redoc` (ReDoc)
10
+
11
+ ---
12
+
13
+ ## Table of Contents
14
+
15
+ 1. [Authentication & Setup](#authentication--setup)
16
+ 2. [Contract Generator API](#contract-generator-api)
17
+ 3. [Scheme Finder API](#scheme-finder-api)
18
+ 4. [PDF Demystifier API](#pdf-demystifier-api)
19
+ 5. [General Assistant API](#general-assistant-api)
20
+ 6. [Media Processing API](#media-processing-api)
21
+ 7. [System Endpoints](#system-endpoints)
22
+ 8. [Error Handling](#error-handling)
23
+ 9. [Testing Examples](#testing-examples)
24
+
25
+ ---
26
+
27
+ ## Authentication & Setup
28
+
29
+ ### Environment Variables Required
30
+
31
+ ```bash
32
+ GOOGLE_API_KEY=your_google_api_key
33
+ GROQ_API_KEY=your_groq_api_key
34
+ TAVILY_API_KEY=your_tavily_api_key
35
+ ```
36
+
37
+ ### Health Check
38
+
39
+ **Endpoint:** `GET /health`
40
+
41
+ **Response:**
42
+ ```json
43
+ {
44
+ "status": "healthy",
45
+ "version": "2.1.0",
46
+ "timestamp": "2024-01-15T10:30:00.000Z",
47
+ "services": {
48
+ "directories": {
49
+ "video_consents": true,
50
+ "pdfs_demystify": true
51
+ },
52
+ "modules": {
53
+ "streamlit_webrtc": "✅",
54
+ "av": "✅",
55
+ "speech_recognition": "✅"
56
+ },
57
+ "api_keys": {
58
+ "GOOGLE_API_KEY": "✅",
59
+ "GROQ_API_KEY": "✅",
60
+ "TAVILY_API_KEY": "✅"
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Contract Generator API
69
+
70
+ ### 1. Generate Contract
71
+
72
+ **Endpoint:** `POST /api/v1/contracts/generate`
73
+
74
+ **Description:** Generate a digital contract from plain text description.
75
+
76
+ **Request Payload:**
77
+ ```json
78
+ {
79
+ "user_request": "I need a contract for hiring a domestic helper for 6 months with weekly payment of Rs. 3000"
80
+ }
81
+ ```
82
+
83
+ **Response:**
84
+ ```json
85
+ {
86
+ "success": true,
87
+ "message": "Contract generated successfully",
88
+ "data": {
89
+ "contract_id": "123e4567-e89b-12d3-a456-426614174000",
90
+ "contract": "DOMESTIC HELPER EMPLOYMENT AGREEMENT\n\nThis agreement is made between...",
91
+ "legal_trivia": {
92
+ "trivia": [
93
+ {
94
+ "point": "Minimum wage rights for domestic workers",
95
+ "explanation": "Domestic workers are entitled to minimum wages as per state regulations",
96
+ "source_url": "https://labour.gov.in/sites/default/files/domestic_workers_act.pdf"
97
+ }
98
+ ]
99
+ },
100
+ "created_at": "2024-01-15T10:30:00.000Z"
101
+ },
102
+ "timestamp": "2024-01-15T10:30:00.000Z"
103
+ }
104
+ ```
105
+
106
+ ### 2. Generate Contract PDF
107
+
108
+ **Endpoint:** `POST /api/v1/contracts/generate-pdf`
109
+
110
+ **Description:** Generate a contract and return it as a downloadable PDF file.
111
+
112
+ **Request Payload:**
113
+ ```json
114
+ {
115
+ "user_request": "I need a contract for hiring a domestic helper for 6 months with weekly payment of Rs. 3000"
116
+ }
117
+ ```
118
+
119
+ **Response:** PDF file download with headers:
120
+ ```
121
+ Content-Type: application/pdf
122
+ Content-Disposition: attachment;filename=contract_20240115_103000.pdf
123
+ ```
124
+
125
+ ### 3. Get Contract
126
+
127
+ **Endpoint:** `GET /api/v1/contracts/{contract_id}`
128
+
129
+ **Description:** Retrieve a previously generated contract by ID.
130
+
131
+ **Response:**
132
+ ```json
133
+ {
134
+ "success": true,
135
+ "message": "Contract retrieved successfully",
136
+ "data": {
137
+ "legal_doc": "DOMESTIC HELPER EMPLOYMENT AGREEMENT\n\nThis agreement is made between...",
138
+ "legal_trivia": {
139
+ "trivia": [...]
140
+ },
141
+ "created_at": "2024-01-15T10:30:00.000Z",
142
+ "user_request": "I need a contract for hiring a domestic helper..."
143
+ },
144
+ "timestamp": "2024-01-15T10:30:00.000Z"
145
+ }
146
+ ```
147
+
148
+ ### 4. List Contracts
149
+
150
+ **Endpoint:** `GET /api/v1/contracts`
151
+
152
+ **Description:** List all generated contracts with summaries.
153
+
154
+ **Response:**
155
+ ```json
156
+ {
157
+ "success": true,
158
+ "message": "Found 2 contract(s)",
159
+ "data": {
160
+ "contracts": [
161
+ {
162
+ "id": "123e4567-e89b-12d3-a456-426614174000",
163
+ "summary": "DOMESTIC HELPER EMPLOYMENT AGREEMENT\n\nThis agreement is made between...",
164
+ "created_at": "2024-01-15T10:30:00.000Z",
165
+ "user_request": "I need a contract for hiring a domestic helper for 6 months..."
166
+ }
167
+ ]
168
+ },
169
+ "timestamp": "2024-01-15T10:30:00.000Z"
170
+ }
171
+ ```
172
+
173
+ ### 5. Delete Contract
174
+
175
+ **Endpoint:** `DELETE /api/v1/contracts/{contract_id}`
176
+
177
+ **Description:** Delete a specific contract and its associated data.
178
+
179
+ **Response:**
180
+ ```json
181
+ {
182
+ "success": true,
183
+ "message": "Contract and associated data deleted successfully",
184
+ "timestamp": "2024-01-15T10:30:00.000Z"
185
+ }
186
+ ```
187
+
188
+ ---
189
+
190
+ ## Scheme Finder API
191
+
192
+ ### Find Government Schemes
193
+
194
+ **Endpoint:** `POST /api/v1/schemes/find`
195
+
196
+ **Description:** Find relevant government schemes based on user profile.
197
+
198
+ **Request Payload:**
199
+ ```json
200
+ {
201
+ "user_profile": "I am a 35-year-old woman from rural Maharashtra, working as a daily wage laborer, looking for financial assistance schemes"
202
+ }
203
+ ```
204
+
205
+ **Response:**
206
+ ```json
207
+ {
208
+ "success": true,
209
+ "message": "Schemes found successfully",
210
+ "data": {
211
+ "schemes": [
212
+ {
213
+ "scheme_name": "Pradhan Mantri Jan Dhan Yojana",
214
+ "description": "Financial inclusion program providing basic banking services to unbanked households",
215
+ "target_audience": "Unbanked households, especially women",
216
+ "official_link": "https://pmjdy.gov.in/"
217
+ },
218
+ {
219
+ "scheme_name": "Mahila Shakti Kendra",
220
+ "description": "Women empowerment scheme providing support for rural women",
221
+ "target_audience": "Rural women",
222
+ "official_link": "https://wcd.nic.in/schemes/mahila-shakti-kendra"
223
+ }
224
+ ]
225
+ },
226
+ "timestamp": "2024-01-15T10:30:00.000Z"
227
+ }
228
+ ```
229
+
230
+ ---
231
+
232
+ ## PDF Demystifier API
233
+
234
+ ### 1. Upload Document
235
+
236
+ **Endpoint:** `POST /api/v1/demystify/upload`
237
+
238
+ **Description:** Upload a PDF document for AI-powered analysis.
239
+
240
+ **Request:** Multipart form data
241
+ - `file`: PDF file (max 50MB)
242
+
243
+ **Response:**
244
+ ```json
245
+ {
246
+ "success": true,
247
+ "message": "Document uploaded and analyzed successfully",
248
+ "data": {
249
+ "session_id": "456e7890-e89b-12d3-a456-426614174001",
250
+ "report": {
251
+ "summary": "This is a rental agreement for a residential property...",
252
+ "key_terms": [
253
+ {
254
+ "term": "Security Deposit",
255
+ "explanation": "A refundable amount paid by tenant to cover potential damages",
256
+ "resource_link": "https://housing.com/guides/security-deposit"
257
+ }
258
+ ],
259
+ "overall_advice": "This is an automated analysis. For critical matters, please consult with a qualified legal professional."
260
+ },
261
+ "filename": "rental_agreement.pdf",
262
+ "upload_time": "2024-01-15T10:30:00.000Z"
263
+ },
264
+ "timestamp": "2024-01-15T10:30:00.000Z"
265
+ }
266
+ ```
267
+
268
+ ### 2. Chat with Document
269
+
270
+ **Endpoint:** `POST /api/v1/demystify/chat`
271
+
272
+ **Description:** Ask follow-up questions about an uploaded document.
273
+
274
+ **Request Payload:**
275
+ ```json
276
+ {
277
+ "session_id": "456e7890-e89b-12d3-a456-426614174001",
278
+ "question": "What are the key terms I should be aware of in this contract?"
279
+ }
280
+ ```
281
+
282
+ **Response:**
283
+ ```json
284
+ {
285
+ "success": true,
286
+ "message": "Question answered successfully",
287
+ "data": {
288
+ "answer": "Based on the document, the key terms you should be aware of include: 1. Security Deposit - A refundable amount...",
289
+ "session_id": "456e7890-e89b-12d3-a456-426614174001",
290
+ "question": "What are the key terms I should be aware of in this contract?"
291
+ },
292
+ "timestamp": "2024-01-15T10:30:00.000Z"
293
+ }
294
+ ```
295
+
296
+ ### 3. List Sessions
297
+
298
+ **Endpoint:** `GET /api/v1/demystify/sessions`
299
+
300
+ **Description:** List all active document analysis sessions.
301
+
302
+ **Response:**
303
+ ```json
304
+ {
305
+ "success": true,
306
+ "message": "Found 1 active session(s)",
307
+ "data": {
308
+ "sessions": [
309
+ {
310
+ "session_id": "456e7890-e89b-12d3-a456-426614174001",
311
+ "filename": "rental_agreement.pdf",
312
+ "upload_time": "2024-01-15T10:30:00.000Z"
313
+ }
314
+ ]
315
+ },
316
+ "timestamp": "2024-01-15T10:30:00.000Z"
317
+ }
318
+ ```
319
+
320
+ ### 4. Delete Session
321
+
322
+ **Endpoint:** `DELETE /api/v1/demystify/sessions/{session_id}`
323
+
324
+ **Description:** Delete a document analysis session and its associated files.
325
+
326
+ **Response:**
327
+ ```json
328
+ {
329
+ "success": true,
330
+ "message": "Session and associated files deleted successfully",
331
+ "timestamp": "2024-01-15T10:30:00.000Z"
332
+ }
333
+ ```
334
+
335
+ ---
336
+
337
+ ## General Assistant API
338
+
339
+ ### Chat with AI Assistant
340
+
341
+ **Endpoint:** `POST /api/v1/assistant/chat`
342
+
343
+ **Description:** Get AI-powered assistance for general questions.
344
+
345
+ **Request Payload:**
346
+ ```json
347
+ {
348
+ "question": "What are my rights as a domestic worker in India?"
349
+ }
350
+ ```
351
+
352
+ **Response:**
353
+ ```json
354
+ {
355
+ "success": true,
356
+ "message": "Response generated successfully",
357
+ "data": {
358
+ "response": "As a domestic worker in India, you have several important rights: 1. Right to minimum wages as per state regulations...",
359
+ "question": "What are my rights as a domestic worker in India?"
360
+ },
361
+ "timestamp": "2024-01-15T10:30:00.000Z"
362
+ }
363
+ ```
364
+
365
+ ---
366
+
367
+ ## Media Processing API
368
+
369
+ ### 1. Upload Video Consent
370
+
371
+ **Endpoint:** `POST /api/v1/media/upload-video`
372
+
373
+ **Description:** Upload a video consent file for a specific contract.
374
+
375
+ **Request:** Multipart form data
376
+ - `file`: Video file (MP4, AVI, MOV - max 100MB)
377
+ - `contract_id`: Contract identifier
378
+ - `consent_text`: Text of the consent being recorded
379
+
380
+ **Response:**
381
+ ```json
382
+ {
383
+ "success": true,
384
+ "message": "Video consent uploaded successfully",
385
+ "data": {
386
+ "video_path": "video_consents/consent_123e4567-e89b-12d3-a456-426614174000_789.mp4",
387
+ "contract_id": "123e4567-e89b-12d3-a456-426614174000",
388
+ "filename": "consent_123e4567-e89b-12d3-a456-426614174000_789.mp4",
389
+ "size": 2048576,
390
+ "consent_text": "I agree to the terms and conditions of this employment contract"
391
+ },
392
+ "timestamp": "2024-01-15T10:30:00.000Z"
393
+ }
394
+ ```
395
+
396
+ ### 2. Get Contract Videos
397
+
398
+ **Endpoint:** `GET /api/v1/media/videos/{contract_id}`
399
+
400
+ **Description:** Get all video consents for a specific contract.
401
+
402
+ **Response:**
403
+ ```json
404
+ {
405
+ "success": true,
406
+ "message": "Found 1 video(s) for contract",
407
+ "data": {
408
+ "videos": [
409
+ {
410
+ "filename": "consent_123e4567-e89b-12d3-a456-426614174000_789.mp4",
411
+ "path": "video_consents/consent_123e4567-e89b-12d3-a456-426614174000_789.mp4",
412
+ "size": 2048576,
413
+ "created": "2024-01-15T10:30:00.000Z"
414
+ }
415
+ ]
416
+ },
417
+ "timestamp": "2024-01-15T10:30:00.000Z"
418
+ }
419
+ ```
420
+
421
+ ---
422
+
423
+ ## System Endpoints
424
+
425
+ ### Root Endpoint
426
+
427
+ **Endpoint:** `GET /`
428
+
429
+ **Description:** API root endpoint with comprehensive information.
430
+
431
+ **Response:**
432
+ ```json
433
+ {
434
+ "message": "Jan-Contract Enhanced API",
435
+ "version": "2.1.0",
436
+ "description": "Comprehensive API for India's informal workforce",
437
+ "features": [
438
+ "Contract Generation",
439
+ "Scheme Discovery",
440
+ "Document Analysis",
441
+ "AI Assistant",
442
+ "Media Processing"
443
+ ],
444
+ "endpoints": {
445
+ "health": "/health",
446
+ "contracts": "/api/v1/contracts/generate",
447
+ "schemes": "/api/v1/schemes/find",
448
+ "demystify": "/api/v1/demystify/upload",
449
+ "assistant": "/api/v1/assistant/chat",
450
+ "media": "/api/v1/media/upload-video"
451
+ },
452
+ "docs": "/docs",
453
+ "redoc": "/redoc"
454
+ }
455
+ ```
456
+
457
+ ---
458
+
459
+ ## Error Handling
460
+
461
+ ### Standard Error Response Format
462
+
463
+ ```json
464
+ {
465
+ "success": false,
466
+ "message": "Request failed",
467
+ "error": "Detailed error message",
468
+ "timestamp": "2024-01-15T10:30:00.000Z"
469
+ }
470
+ ```
471
+
472
+ ### Common HTTP Status Codes
473
+
474
+ - `200 OK`: Request successful
475
+ - `400 Bad Request`: Invalid request data
476
+ - `404 Not Found`: Resource not found
477
+ - `422 Unprocessable Entity`: Validation error
478
+ - `500 Internal Server Error`: Server error
479
+
480
+ ### Validation Errors
481
+
482
+ ```json
483
+ {
484
+ "success": false,
485
+ "message": "Request failed",
486
+ "error": [
487
+ {
488
+ "loc": ["body", "user_request"],
489
+ "msg": "ensure this value has at least 10 characters",
490
+ "type": "value_error.any_str.min_length"
491
+ }
492
+ ],
493
+ "timestamp": "2024-01-15T10:30:00.000Z"
494
+ }
495
+ ```
496
+
497
+ ---
498
+
499
+ ## Testing Examples
500
+
501
+ ### Using cURL
502
+
503
+ #### 1. Generate Contract
504
+ ```bash
505
+ curl -X POST "http://localhost:8000/api/v1/contracts/generate" \
506
+ -H "Content-Type: application/json" \
507
+ -d '{
508
+ "user_request": "I need a contract for hiring a domestic helper for 6 months with weekly payment of Rs. 3000"
509
+ }'
510
+ ```
511
+
512
+ #### 2. Find Schemes
513
+ ```bash
514
+ curl -X POST "http://localhost:8000/api/v1/schemes/find" \
515
+ -H "Content-Type: application/json" \
516
+ -d '{
517
+ "user_profile": "I am a 35-year-old woman from rural Maharashtra, working as a daily wage laborer, looking for financial assistance schemes"
518
+ }'
519
+ ```
520
+
521
+ #### 3. Upload Document
522
+ ```bash
523
+ curl -X POST "http://localhost:8000/api/v1/demystify/upload" \
524
+ -F "file=@/path/to/document.pdf"
525
+ ```
526
+
527
+ #### 4. Chat with Assistant
528
+ ```bash
529
+ curl -X POST "http://localhost:8000/api/v1/assistant/chat" \
530
+ -H "Content-Type: application/json" \
531
+ -d '{
532
+ "question": "What are my rights as a domestic worker in India?"
533
+ }'
534
+ ```
535
+
536
+ ### Using Python requests
537
+
538
+ ```python
539
+ import requests
540
+ import json
541
+
542
+ # Base URL
543
+ BASE_URL = "http://localhost:8000"
544
+
545
+ # 1. Generate Contract
546
+ contract_data = {
547
+ "user_request": "I need a contract for hiring a domestic helper for 6 months with weekly payment of Rs. 3000"
548
+ }
549
+ response = requests.post(f"{BASE_URL}/api/v1/contracts/generate", json=contract_data)
550
+ print(response.json())
551
+
552
+ # 2. Find Schemes
553
+ scheme_data = {
554
+ "user_profile": "I am a 35-year-old woman from rural Maharashtra, working as a daily wage laborer, looking for financial assistance schemes"
555
+ }
556
+ response = requests.post(f"{BASE_URL}/api/v1/schemes/find", json=scheme_data)
557
+ print(response.json())
558
+
559
+ # 3. Chat with Assistant
560
+ chat_data = {
561
+ "question": "What are my rights as a domestic worker in India?"
562
+ }
563
+ response = requests.post(f"{BASE_URL}/api/v1/assistant/chat", json=chat_data)
564
+ print(response.json())
565
+
566
+ # 4. Upload Document
567
+ with open("document.pdf", "rb") as f:
568
+ files = {"file": f}
569
+ response = requests.post(f"{BASE_URL}/api/v1/demystify/upload", files=files)
570
+ print(response.json())
571
+ ```
572
+
573
+ ### Using JavaScript/Fetch
574
+
575
+ ```javascript
576
+ const BASE_URL = "http://localhost:8000";
577
+
578
+ // 1. Generate Contract
579
+ async function generateContract() {
580
+ const response = await fetch(`${BASE_URL}/api/v1/contracts/generate`, {
581
+ method: 'POST',
582
+ headers: {
583
+ 'Content-Type': 'application/json',
584
+ },
585
+ body: JSON.stringify({
586
+ user_request: "I need a contract for hiring a domestic helper for 6 months with weekly payment of Rs. 3000"
587
+ })
588
+ });
589
+ const data = await response.json();
590
+ console.log(data);
591
+ }
592
+
593
+ // 2. Find Schemes
594
+ async function findSchemes() {
595
+ const response = await fetch(`${BASE_URL}/api/v1/schemes/find`, {
596
+ method: 'POST',
597
+ headers: {
598
+ 'Content-Type': 'application/json',
599
+ },
600
+ body: JSON.stringify({
601
+ user_profile: "I am a 35-year-old woman from rural Maharashtra, working as a daily wage laborer, looking for financial assistance schemes"
602
+ })
603
+ });
604
+ const data = await response.json();
605
+ console.log(data);
606
+ }
607
+
608
+ // 3. Chat with Assistant
609
+ async function chatWithAssistant() {
610
+ const response = await fetch(`${BASE_URL}/api/v1/assistant/chat`, {
611
+ method: 'POST',
612
+ headers: {
613
+ 'Content-Type': 'application/json',
614
+ },
615
+ body: JSON.stringify({
616
+ question: "What are my rights as a domestic worker in India?"
617
+ })
618
+ });
619
+ const data = await response.json();
620
+ console.log(data);
621
+ }
622
+ ```
623
+
624
+ ---
625
+
626
+ ## Rate Limits & Best Practices
627
+
628
+ ### Rate Limits
629
+ - No explicit rate limits implemented
630
+ - Recommended: 100 requests per minute per IP
631
+ - Large file uploads may take longer processing time
632
+
633
+ ### Best Practices
634
+ 1. **Always check the health endpoint** before making requests
635
+ 2. **Use appropriate content types** for different endpoints
636
+ 3. **Handle errors gracefully** with proper error checking
637
+ 4. **Store session IDs** for document chat functionality
638
+ 5. **Validate file sizes** before upload (50MB for PDFs, 100MB for videos)
639
+ 6. **Use HTTPS in production** for security
640
+
641
+ ### File Upload Guidelines
642
+ - **PDF files**: Maximum 50MB, only PDF format
643
+ - **Video files**: Maximum 100MB, formats: MP4, AVI, MOV
644
+ - **File naming**: Avoid special characters, use alphanumeric names
645
+
646
+ ---
647
+
648
+ ## Support & Contact
649
+
650
+ - **API Documentation**: `/docs` (Swagger UI)
651
+ - **Alternative Docs**: `/redoc` (ReDoc)
652
+ - **Health Check**: `/health`
653
+ - **Support Email**: support@jan-contract.com
654
+ - **Version**: 2.1.0
655
+
656
+ ---
657
+
658
+ *This documentation is automatically generated and updated with each API version release.*
agents/demystifier_agent.py CHANGED
@@ -6,16 +6,16 @@ from pydantic import BaseModel, Field
6
 
7
  # --- Core LangChain & Document Processing Imports ---
8
  from langchain_community.document_loaders import PyMuPDFLoader
9
- from langchain.text_splitter import RecursiveCharacterTextSplitter
10
  from langchain_community.vectorstores import FAISS
11
- from langchain.prompts import PromptTemplate
12
- from langchain.schema.runnable import RunnablePassthrough
13
- from langchain.schema.output_parser import StrOutputParser
14
 
15
  # LangGraph Imports
16
  from langgraph.graph import StateGraph, END, START
17
 
18
- # --- Tool and NEW Core Model Loader Imports ---
19
  from tools.legal_tools import legal_search
20
  from core_utils.core_model_loaders import load_groq_llm, load_embedding_model
21
 
@@ -24,7 +24,7 @@ from core_utils.core_model_loaders import load_groq_llm, load_embedding_model
24
  groq_llm = load_groq_llm()
25
  embedding_model = load_embedding_model()
26
 
27
- # --- Pydantic Models (No Changes) ---
28
  class ExplainedTerm(BaseModel):
29
  term: str = Field(description="The legal term or jargon identified.")
30
  explanation: str = Field(description="A simple, plain-English explanation of the term.")
@@ -35,7 +35,7 @@ class DemystifyReport(BaseModel):
35
  key_terms: List[ExplainedTerm] = Field(description="A list of the most important explained legal terms.")
36
  overall_advice: str = Field(description="A concluding sentence of general advice.")
37
 
38
- # --- 2. LangGraph for Document Analysis (No Changes) ---
39
  class DemystifyState(TypedDict):
40
  document_chunks: List[str]
41
  summary: str
@@ -45,43 +45,110 @@ class DemystifyState(TypedDict):
45
  def summarize_node(state: DemystifyState):
46
  """Takes all document chunks and creates a high-level summary."""
47
  print("---NODE (Demystify): Generating Summary---")
48
- context = "\n\n".join(state["document_chunks"])
49
- prompt = f"You are a paralegal expert... Document Content:\n{context}"
50
- summary = groq_llm.invoke(prompt).content
 
 
 
 
 
 
 
 
 
 
51
  return {"summary": summary}
52
 
53
  def identify_terms_node(state: DemystifyState):
54
  """Identifies the most critical and potentially confusing legal terms in the document."""
55
  print("---NODE (Demystify): Identifying Key Terms---")
56
- context = "\n\n".join(state["document_chunks"])
57
- prompt = f"Based on the following legal document, identify the 3-5 most critical legal terms... Document Content:\n{context}"
58
- terms_string = groq_llm.invoke(prompt).content
59
- identified_terms = [term.strip() for term in terms_string.split(',') if term.strip()]
60
- return {"identified_terms": identified_terms}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
  def generate_report_node(state: DemystifyState):
63
  """Combines the summary and terms into a final, structured report with enriched explanations."""
64
  print("---NODE (Demystify): Generating Final Report---")
65
  explained_terms_list = []
66
- document_context = "\n\n".join(state["document_chunks"])
67
- for term in state["identified_terms"]:
 
 
 
 
 
 
 
 
 
68
  print(f" - Researching term: {term}")
69
- search_results = legal_search.invoke(f"simple explanation of legal term '{term}' in Indian law")
70
- prompt = f"""A user is reading a legal document that contains the term "{term}".
71
- Overall document context is: {document_context[:2000]}
72
- Web search results for "{term}" are: {search_results}
73
- Format your response strictly as:
74
- Explanation: [Your simple, one-sentence explanation here]
75
- URL: [The best, full, working URL from the search results]"""
76
- response = groq_llm.invoke(prompt).content
77
  try:
78
- explanation = response.split("Explanation:")[1].split("URL:")[0].strip()
79
- link = response.split("URL:")[-1].strip()
80
- except IndexError:
81
- explanation = "Could not generate a simple explanation for this term."
82
- link = "No link found."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  explained_terms_list.append(ExplainedTerm(term=term, explanation=explanation, resource_link=link))
84
- final_report = DemystifyReport(summary=state["summary"], key_terms=explained_terms_list, overall_advice="This is an automated analysis. For critical matters, please consult with a qualified legal professional.")
 
 
 
 
 
 
 
 
 
 
85
  return {"final_report": final_report}
86
 
87
  # Compile the analysis graph
@@ -95,29 +162,42 @@ graph_builder.add_edge("identify_terms", "generate_report")
95
  graph_builder.add_edge("generate_report", END)
96
  demystifier_agent_graph = graph_builder.compile()
97
 
98
- # --- 3. Helper Function to Create the RAG Chain (No Changes) ---
99
  def create_rag_chain(retriever):
100
  """Creates the Q&A chain for the interactive chat."""
101
- prompt_template = """You are a helpful assistant... CONTEXT: {context} QUESTION: {question} ANSWER:"""
 
 
 
102
  prompt = PromptTemplate.from_template(prompt_template)
103
  rag_chain = ({"context": retriever, "question": RunnablePassthrough()} | prompt | groq_llm | StrOutputParser())
104
  return rag_chain
105
 
106
- # --- 4. The Master "Controller" Function (No Changes) ---
107
  def process_document_for_demystification(file_path: str):
108
  """Loads a PDF, runs the full analysis, creates a RAG chain, and returns both."""
109
  print(f"--- Processing document: {file_path} ---")
 
110
  loader = PyMuPDFLoader(file_path)
111
  documents = loader.load()
 
 
 
 
112
  splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
113
  chunks = splitter.split_documents(documents)
 
114
  print("--- Creating FAISS vector store for Q&A ---")
115
  vectorstore = FAISS.from_documents(chunks, embedding=embedding_model)
116
  retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
117
  rag_chain = create_rag_chain(retriever)
 
118
  print("--- Running analysis graph for the report ---")
119
  chunk_contents = [chunk.page_content for chunk in chunks]
120
- graph_input = {"document_chunks": chunk_contents}
 
 
121
  result = demystifier_agent_graph.invoke(graph_input)
122
  report = result.get("final_report")
 
123
  return {"report": report, "rag_chain": rag_chain}
 
6
 
7
  # --- Core LangChain & Document Processing Imports ---
8
  from langchain_community.document_loaders import PyMuPDFLoader
9
+ from langchain_text_splitters import RecursiveCharacterTextSplitter
10
  from langchain_community.vectorstores import FAISS
11
+ from langchain_core.prompts import PromptTemplate
12
+ from langchain_core.runnables import RunnablePassthrough
13
+ from langchain_core.output_parsers import StrOutputParser
14
 
15
  # LangGraph Imports
16
  from langgraph.graph import StateGraph, END, START
17
 
18
+ # --- Tool and Core Model Loader Imports ---
19
  from tools.legal_tools import legal_search
20
  from core_utils.core_model_loaders import load_groq_llm, load_embedding_model
21
 
 
24
  groq_llm = load_groq_llm()
25
  embedding_model = load_embedding_model()
26
 
27
+ # --- Pydantic Models ---
28
  class ExplainedTerm(BaseModel):
29
  term: str = Field(description="The legal term or jargon identified.")
30
  explanation: str = Field(description="A simple, plain-English explanation of the term.")
 
35
  key_terms: List[ExplainedTerm] = Field(description="A list of the most important explained legal terms.")
36
  overall_advice: str = Field(description="A concluding sentence of general advice.")
37
 
38
+ # --- 2. LangGraph for Document Analysis ---
39
  class DemystifyState(TypedDict):
40
  document_chunks: List[str]
41
  summary: str
 
45
  def summarize_node(state: DemystifyState):
46
  """Takes all document chunks and creates a high-level summary."""
47
  print("---NODE (Demystify): Generating Summary---")
48
+ chunks = state.get("document_chunks", [])
49
+ if not chunks:
50
+ return {"summary": "No content to summarize."}
51
+
52
+ context = "\n\n".join(chunks)
53
+ prompt = f"You are a paralegal expert for the Indian legal system. Summarize the following document clearly for a layman:\n\n{context}"
54
+ try:
55
+ response = groq_llm.invoke(prompt)
56
+ summary = response.content if response and response.content else "Summary generation failed."
57
+ except Exception as e:
58
+ print(f"Summary generation error: {e}")
59
+ summary = "Summary generation failed due to an error."
60
+
61
  return {"summary": summary}
62
 
63
  def identify_terms_node(state: DemystifyState):
64
  """Identifies the most critical and potentially confusing legal terms in the document."""
65
  print("---NODE (Demystify): Identifying Key Terms---")
66
+ try:
67
+ context = "\n\n".join(state.get("document_chunks", []))
68
+ if not context:
69
+ print("Warning: No document context found for term identification.")
70
+ return {"identified_terms": []}
71
+
72
+ prompt = f"Identify the 3-5 most critical complex legal terms in the following document that a layman would not understand. Return only the terms separated by commas.\n\n{context}"
73
+ response = groq_llm.invoke(prompt)
74
+
75
+ if not response or not response.content:
76
+ print("Warning: Empty response from LLM for term identification.")
77
+ return {"identified_terms": []}
78
+
79
+ terms_string = response.content
80
+ identified_terms = [term.strip() for term in terms_string.split(',') if term.strip()]
81
+ return {"identified_terms": identified_terms}
82
+ except Exception as e:
83
+ print(f"Error in identify_terms_node: {e}")
84
+ return {"identified_terms": []}
85
 
86
  def generate_report_node(state: DemystifyState):
87
  """Combines the summary and terms into a final, structured report with enriched explanations."""
88
  print("---NODE (Demystify): Generating Final Report---")
89
  explained_terms_list = []
90
+
91
+ # Handle None or empty document_chunks
92
+ chunks = state.get("document_chunks", [])
93
+ document_context = "\n\n".join(chunks) if chunks else ""
94
+
95
+ # Handle None identified_terms
96
+ terms = state.get("identified_terms", [])
97
+ if terms is None:
98
+ terms = []
99
+
100
+ for term in terms:
101
  print(f" - Researching term: {term}")
 
 
 
 
 
 
 
 
102
  try:
103
+ search_results = legal_search.invoke(f"simple explanation of legal term '{term}' in Indian law")
104
+ except Exception as e:
105
+ print(f"Search failed for term '{term}': {e}")
106
+ search_results = "Search unavailable."
107
+
108
+ prompt = f"""
109
+ A user is reading a legal document containing the term "{term}".
110
+ Context: {document_context[:2000]}...
111
+ Search Results: {search_results}
112
+
113
+ Provide a simple one-sentence explanation and a valid URL if found.
114
+ Format:
115
+ Explanation: [Explanation]
116
+ URL: [URL]
117
+ """
118
+ try:
119
+ response = groq_llm.invoke(prompt)
120
+ if response and response.content:
121
+ content = response.content
122
+ try:
123
+ if "Explanation:" in content and "URL:" in content:
124
+ explanation = content.split("Explanation:")[1].split("URL:")[0].strip()
125
+ link = content.split("URL:")[-1].strip()
126
+ else:
127
+ explanation = content.strip()
128
+ link = "https://kanoon.nearlaw.com/"
129
+ except Exception:
130
+ explanation = f"Legal term '{term}' identified."
131
+ link = "https://kanoon.nearlaw.com/"
132
+ else:
133
+ explanation = "Explanation unavailable."
134
+ link = "https://kanoon.nearlaw.com/"
135
+ except Exception as e:
136
+ print(f"LLM failed for term '{term}': {e}")
137
+ explanation = "Explanation unavailable."
138
+ link = "https://kanoon.nearlaw.com/"
139
+
140
  explained_terms_list.append(ExplainedTerm(term=term, explanation=explanation, resource_link=link))
141
+
142
+ # Ensure summary is not None
143
+ summary_text = state.get("summary", "Summary unavailable.")
144
+ if summary_text is None:
145
+ summary_text = "Summary unavailable."
146
+
147
+ final_report = DemystifyReport(
148
+ summary=summary_text,
149
+ key_terms=explained_terms_list,
150
+ overall_advice="This AI analysis is for informational purposes only. Consult a lawyer for binding advice."
151
+ )
152
  return {"final_report": final_report}
153
 
154
  # Compile the analysis graph
 
162
  graph_builder.add_edge("generate_report", END)
163
  demystifier_agent_graph = graph_builder.compile()
164
 
165
+ # --- 3. Helper Function to Create the RAG Chain ---
166
  def create_rag_chain(retriever):
167
  """Creates the Q&A chain for the interactive chat."""
168
+ prompt_template = """You are a helpful legal assistant. Answer based on the context only.
169
+ CONTEXT: {context}
170
+ QUESTION: {question}
171
+ ANSWER:"""
172
  prompt = PromptTemplate.from_template(prompt_template)
173
  rag_chain = ({"context": retriever, "question": RunnablePassthrough()} | prompt | groq_llm | StrOutputParser())
174
  return rag_chain
175
 
176
+ # --- 4. The Master "Controller" Function ---
177
  def process_document_for_demystification(file_path: str):
178
  """Loads a PDF, runs the full analysis, creates a RAG chain, and returns both."""
179
  print(f"--- Processing document: {file_path} ---")
180
+
181
  loader = PyMuPDFLoader(file_path)
182
  documents = loader.load()
183
+
184
+ if not documents:
185
+ raise ValueError("No content found in PDF.")
186
+
187
  splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=100)
188
  chunks = splitter.split_documents(documents)
189
+
190
  print("--- Creating FAISS vector store for Q&A ---")
191
  vectorstore = FAISS.from_documents(chunks, embedding=embedding_model)
192
  retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
193
  rag_chain = create_rag_chain(retriever)
194
+
195
  print("--- Running analysis graph for the report ---")
196
  chunk_contents = [chunk.page_content for chunk in chunks]
197
+ # Limit context to avoid token limits if document is huge
198
+ graph_input = {"document_chunks": chunk_contents[:10]}
199
+
200
  result = demystifier_agent_graph.invoke(graph_input)
201
  report = result.get("final_report")
202
+
203
  return {"report": report, "rag_chain": rag_chain}
agents/general_assistant_agent.py CHANGED
@@ -1,27 +1,47 @@
1
- # D:\jan-contract\agents\general_assistant_agent.py
2
-
3
  import os
4
- import google.generativeai as genai
 
 
 
5
 
6
  # Configure the API key from the .env file
7
  try:
8
- genai.configure(api_key=os.getenv("GOOGLE_API_KEY"))
9
- # Use a specific, robust model name
10
- model = genai.GenerativeModel('gemini-1.5-flash')
11
  except Exception as e:
12
- print(f"Error configuring Google Generative AI: {e}")
13
- model = None
 
14
 
15
  def ask_gemini(prompt: str) -> str:
16
  """
17
- Sends a prompt directly to the Google Gemini API and returns the text response.
18
- This is the core logic from your script, adapted for our application.
19
  """
20
- if model is None:
21
- return "Error: The Generative AI model is not configured. Please check your API key."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- try:
24
- response = model.generate_content(prompt)
25
- return response.text
26
- except Exception as e:
27
- return f"An error occurred while communicating with the Gemini API: {str(e)}"
 
 
 
1
  import os
2
+ import time
3
+ import random
4
+ from google import genai
5
+ from google.genai import types
6
 
7
  # Configure the API key from the .env file
8
  try:
9
+ client = genai.Client(api_key=os.getenv("GOOGLE_API_KEY"))
10
+ model_name = "gemini-2.0-flash-exp" # Using the user's preferred model
 
11
  except Exception as e:
12
+ print(f"Error configuring Google Gen AI Client: {e}")
13
+ client = None
14
+ model_name = None
15
 
16
  def ask_gemini(prompt: str) -> str:
17
  """
18
+ Sends a prompt directly to the Google Gen AI API using the new SDK.
19
+ Includes robust retry logic for 429 Resource Exhausted errors.
20
  """
21
+ if client is None:
22
+ return "Error: The Gen AI client is not configured. Please check your API key."
23
+
24
+ max_retries = 5
25
+ base_delay = 2 # seconds
26
+
27
+ for attempt in range(max_retries):
28
+ try:
29
+ response = client.models.generate_content(
30
+ model=model_name,
31
+ contents=prompt
32
+ )
33
+ return response.text
34
+ except Exception as e:
35
+ error_str = str(e)
36
+ if "429" in error_str or "RESOURCE_EXHAUSTED" in error_str:
37
+ if attempt == max_retries - 1:
38
+ return f"Error: Rate limit exceeded after {max_retries} attempts. Please try again later."
39
+
40
+ # Exponential backoff with jitter
41
+ delay = (base_delay * (2 ** attempt)) + random.uniform(0, 1)
42
+ print(f"Rate limit hit. Retrying in {delay:.2f} seconds...")
43
+ time.sleep(delay)
44
+ else:
45
+ return f"An error occurred while communicating with the Gemini API: {str(e)}"
46
 
47
+ return "Error: Failed to get response from Gemini API."
 
 
 
 
agents/legal_agent.py CHANGED
@@ -1,17 +1,17 @@
1
  # D:\jan-contract\agents\legal_agent.py
2
 
3
  import os
4
- from langchain.prompts import PromptTemplate
5
  from langgraph.graph import StateGraph, END
6
- from typing import List, TypedDict
7
  from pydantic import BaseModel, Field
8
  from langchain_core.output_parsers import PydanticOutputParser
9
 
10
- # --- Tool and NEW Core Model Loader Imports ---
11
  from tools.legal_tools import legal_search
12
  from core_utils.core_model_loaders import load_gemini_llm
13
 
14
- # --- Pydantic Models (No Changes) ---
15
  class LegalTriviaItem(BaseModel):
16
  point: str = Field(description="A concise summary of the legal point or right.")
17
  explanation: str = Field(description="A brief explanation of what the point means for the user.")
@@ -23,42 +23,72 @@ class LegalTriviaOutput(BaseModel):
23
  # --- Setup Models and Parsers ---
24
  parser = PydanticOutputParser(pydantic_object=LegalTriviaOutput)
25
 
26
- # --- Initialize the LLM by calling the backend-safe loader function ---
27
  llm = load_gemini_llm()
28
 
29
- # --- LangGraph State (No Changes) ---
30
  class LegalAgentState(TypedDict):
31
  user_request: str
32
  legal_doc: str
33
- legal_trivia: LegalTriviaOutput
34
 
35
- # --- LangGraph Nodes (No Changes) ---
36
  def generate_legal_doc(state: LegalAgentState):
37
- prompt_text = f"Based on the user's request, generate a simple legal document text for an informal agreement in India. Keep it clear and simple.\n\nUser Request: {state['user_request']}"
38
- legal_doc_text = llm.invoke(prompt_text).content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  return {"legal_doc": legal_doc_text}
40
 
41
  def get_legal_trivia(state: LegalAgentState):
 
 
42
  prompt = PromptTemplate(
43
  template="""
44
- You are a specialized legal assistant for India's informal workforce...
 
 
45
  User's situation: {user_request}
46
  Web search results: {search_results}
 
47
  {format_instructions}
48
  """,
49
  input_variables=["user_request", "search_results"],
50
  partial_variables={"format_instructions": parser.get_format_instructions()},
51
  )
52
  chain = prompt | llm | parser
53
- search_results = legal_search.invoke(state["user_request"])
54
- structured_trivia = chain.invoke({"user_request": state["user_request"], "search_results": search_results})
 
 
 
 
 
 
 
 
 
 
 
55
  return {"legal_trivia": structured_trivia}
56
 
57
- # --- Build Graph (No Changes) ---
58
  workflow = StateGraph(LegalAgentState)
59
  workflow.add_node("generate_legal_doc", generate_legal_doc)
60
  workflow.add_node("get_legal_trivia", get_legal_trivia)
61
  workflow.set_entry_point("generate_legal_doc")
62
  workflow.add_edge("generate_legal_doc", "get_legal_trivia")
63
  workflow.add_edge("get_legal_trivia", END)
64
- legal_agent = workflow.compile()
 
1
  # D:\jan-contract\agents\legal_agent.py
2
 
3
  import os
4
+ from langchain_core.prompts import PromptTemplate
5
  from langgraph.graph import StateGraph, END
6
+ from typing import List, TypedDict, Optional
7
  from pydantic import BaseModel, Field
8
  from langchain_core.output_parsers import PydanticOutputParser
9
 
10
+ # --- Tool and Core Model Loader Imports ---
11
  from tools.legal_tools import legal_search
12
  from core_utils.core_model_loaders import load_gemini_llm
13
 
14
+ # --- Pydantic Models ---
15
  class LegalTriviaItem(BaseModel):
16
  point: str = Field(description="A concise summary of the legal point or right.")
17
  explanation: str = Field(description="A brief explanation of what the point means for the user.")
 
23
  # --- Setup Models and Parsers ---
24
  parser = PydanticOutputParser(pydantic_object=LegalTriviaOutput)
25
 
26
+ # --- Initialize the LLM ---
27
  llm = load_gemini_llm()
28
 
29
+ # --- LangGraph State ---
30
  class LegalAgentState(TypedDict):
31
  user_request: str
32
  legal_doc: str
33
+ legal_trivia: Optional[LegalTriviaOutput]
34
 
35
+ # --- LangGraph Nodes ---
36
  def generate_legal_doc(state: LegalAgentState):
37
+ """Generates the legal document based on user request."""
38
+ print("---NODE: Generating Legal Document---")
39
+ prompt_text = (
40
+ f"You are a professional legal drafter for the Indian context. "
41
+ f"Create a simple, clear, and legally valid digital agreement based on the request below. "
42
+ f"Do not use emojis. Use professional formatting (Markdown). "
43
+ f"Focus on clarity for informal workers.\n\n"
44
+ f"User Request: {state['user_request']}"
45
+ )
46
+ try:
47
+ response = llm.invoke(prompt_text)
48
+ legal_doc_text = response.content if response and response.content else "Error: Failed to generate contract."
49
+ except Exception as e:
50
+ print(f"Contract generation error: {e}")
51
+ legal_doc_text = "Error: Failed to generate contract due to an internal error."
52
+
53
  return {"legal_doc": legal_doc_text}
54
 
55
  def get_legal_trivia(state: LegalAgentState):
56
+ """Fetches relevant legal trivia to educate the user."""
57
+ print("---NODE: Fetching Legal Trivia---")
58
  prompt = PromptTemplate(
59
  template="""
60
+ You are a specialized legal assistant for India's workforce.
61
+ Based on the user's situation, provide 3 important legal rights or points they should be aware of.
62
+
63
  User's situation: {user_request}
64
  Web search results: {search_results}
65
+
66
  {format_instructions}
67
  """,
68
  input_variables=["user_request", "search_results"],
69
  partial_variables={"format_instructions": parser.get_format_instructions()},
70
  )
71
  chain = prompt | llm | parser
72
+
73
+ try:
74
+ search_results = legal_search.invoke(state["user_request"])
75
+ except Exception as e:
76
+ print(f"Legal search failed: {e}")
77
+ search_results = "Search unavailable."
78
+
79
+ try:
80
+ structured_trivia = chain.invoke({"user_request": state["user_request"], "search_results": search_results})
81
+ except Exception as e:
82
+ print(f"Trivia generation failed: {e}")
83
+ structured_trivia = LegalTriviaOutput(trivia=[])
84
+
85
  return {"legal_trivia": structured_trivia}
86
 
87
+ # --- Build Graph ---
88
  workflow = StateGraph(LegalAgentState)
89
  workflow.add_node("generate_legal_doc", generate_legal_doc)
90
  workflow.add_node("get_legal_trivia", get_legal_trivia)
91
  workflow.set_entry_point("generate_legal_doc")
92
  workflow.add_edge("generate_legal_doc", "get_legal_trivia")
93
  workflow.add_edge("get_legal_trivia", END)
94
+ legal_agent = workflow.compile()
agents/scheme_chatbot.py CHANGED
@@ -1,17 +1,17 @@
1
  # D:\jan-contract\agents\scheme_chatbot.py
2
 
3
  import os
4
- from langchain.prompts import PromptTemplate
5
- from langchain.schema.runnable import RunnablePassthrough
6
  from pydantic import BaseModel, Field
7
  from langchain_core.output_parsers import PydanticOutputParser
8
  from typing import List
9
 
10
- # --- Tool and NEW Core Model Loader Imports ---
11
  from tools.scheme_tools import scheme_search
12
  from core_utils.core_model_loaders import load_gemini_llm
13
 
14
- # --- Pydantic Models (No Changes) ---
15
  class GovernmentScheme(BaseModel):
16
  scheme_name: str = Field(description="The official name of the government scheme.")
17
  description: str = Field(description="A concise summary of the scheme's objectives and benefits.")
@@ -24,24 +24,33 @@ class SchemeOutput(BaseModel):
24
  # --- Setup Models and Parsers ---
25
  parser = PydanticOutputParser(pydantic_object=SchemeOutput)
26
 
27
- # --- Initialize the LLM by calling the backend-safe loader function ---
28
  llm = load_gemini_llm()
29
 
30
- # --- Prompt Template (No Changes) ---
31
  prompt = PromptTemplate(
32
  template="""
33
- You are an expert assistant for Indian government schemes...
 
 
 
34
  User Profile: {user_profile}
35
  Web search results: {search_results}
 
36
  {format_instructions}
37
  """,
38
  input_variables=["user_profile", "search_results"],
39
  partial_variables={"format_instructions": parser.get_format_instructions()},
40
  )
41
 
42
- # --- Build Chain (No Changes) ---
43
  def get_search_results(query: dict):
44
- return scheme_search.invoke(query["user_profile"])
 
 
 
 
 
45
 
46
  scheme_chatbot = (
47
  {"search_results": get_search_results, "user_profile": RunnablePassthrough()}
 
1
  # D:\jan-contract\agents\scheme_chatbot.py
2
 
3
  import os
4
+ from langchain_core.prompts import PromptTemplate
5
+ from langchain_core.runnables import RunnablePassthrough
6
  from pydantic import BaseModel, Field
7
  from langchain_core.output_parsers import PydanticOutputParser
8
  from typing import List
9
 
10
+ # --- Tool and Core Model Loader Imports ---
11
  from tools.scheme_tools import scheme_search
12
  from core_utils.core_model_loaders import load_gemini_llm
13
 
14
+ # --- Pydantic Models ---
15
  class GovernmentScheme(BaseModel):
16
  scheme_name: str = Field(description="The official name of the government scheme.")
17
  description: str = Field(description="A concise summary of the scheme's objectives and benefits.")
 
24
  # --- Setup Models and Parsers ---
25
  parser = PydanticOutputParser(pydantic_object=SchemeOutput)
26
 
27
+ # --- Initialize the LLM ---
28
  llm = load_gemini_llm()
29
 
30
+ # --- Prompt Template ---
31
  prompt = PromptTemplate(
32
  template="""
33
+ You are an expert assistant for Indian government schemes.
34
+ Find the most relevant official government schemes for the profile below.
35
+ Focus on accuracy and official sources.
36
+
37
  User Profile: {user_profile}
38
  Web search results: {search_results}
39
+
40
  {format_instructions}
41
  """,
42
  input_variables=["user_profile", "search_results"],
43
  partial_variables={"format_instructions": parser.get_format_instructions()},
44
  )
45
 
46
+ # --- Build Chain ---
47
  def get_search_results(query: dict):
48
+ print(f"---NODE: Searching Schemes for profile: {query['user_profile']}---")
49
+ try:
50
+ return scheme_search.invoke(query["user_profile"])
51
+ except Exception as e:
52
+ print(f"Scheme search failed: {e}")
53
+ return "Search unavailable."
54
 
55
  scheme_chatbot = (
56
  {"search_results": get_search_results, "user_profile": RunnablePassthrough()}
components/video_recorder.py CHANGED
@@ -3,138 +3,211 @@
3
  import os
4
  import streamlit as st
5
  import datetime
6
- import av
7
- import numpy as np
8
- from typing import Optional
9
-
10
- from streamlit_webrtc import webrtc_streamer, WebRtcMode
11
 
12
  VIDEO_CONSENT_DIR = "video_consents"
13
  os.makedirs(VIDEO_CONSENT_DIR, exist_ok=True)
14
 
15
  def record_consent_video():
16
  """
17
- Improved video recording component with better error handling and reliability.
18
-
19
- Returns:
20
- str | None: The file path of the saved video, or None if not saved yet.
 
21
  """
22
- st.info("🎥 **Instructions:** Click START to begin recording, speak your consent, then click STOP to save.")
23
-
24
- # Initialize session state for video recording
25
- if "video_frames_buffer" not in st.session_state:
26
- st.session_state.video_frames_buffer = []
27
- if "video_recording" not in st.session_state:
28
- st.session_state.video_recording = False
29
- if "video_processed" not in st.session_state:
30
- st.session_state.video_processed = False
31
- if "recording_start_time" not in st.session_state:
32
- st.session_state.recording_start_time = None
33
 
34
- def video_frame_callback(frame: av.VideoFrame):
35
- """Callback to collect video frames during recording"""
36
- if st.session_state.video_recording:
37
- try:
38
- # Convert frame to numpy array for easier handling
39
- img = frame.to_ndarray(format="bgr24")
40
- st.session_state.video_frames_buffer.append(img)
41
- except Exception as e:
42
- st.error(f"Error processing video frame: {e}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
 
44
- # WebRTC streamer configuration
45
- webrtc_ctx = webrtc_streamer(
46
- key="video-consent-recorder",
47
- mode=WebRtcMode.SENDONLY,
48
- rtc_configuration={
49
- "iceServers": [
50
- {"urls": ["stun:stun.l.google.com:19302"]},
51
- {"urls": ["stun:stun1.l.google.com:19302"]}
52
- ]
53
- },
54
- media_stream_constraints={
55
- "video": {
56
- "width": {"ideal": 640},
57
- "height": {"ideal": 480},
58
- "frameRate": {"ideal": 30}
59
- },
60
- "audio": False
61
- },
62
- video_frame_callback=video_frame_callback,
63
- async_processing=True,
64
- )
65
 
66
- # Handle recording state
67
- if webrtc_ctx.state.playing and not st.session_state.video_recording:
68
- st.session_state.video_recording = True
69
- st.session_state.video_processed = False
70
- st.session_state.recording_start_time = datetime.datetime.now()
71
- st.session_state.video_frames_buffer = [] # Clear previous buffer
72
- st.success("🔴 **Recording started!** Speak your consent now...")
73
-
74
- elif webrtc_ctx.state.playing and st.session_state.video_recording:
75
- # Show recording progress
76
- frames_captured = len(st.session_state.video_frames_buffer)
77
- if st.session_state.recording_start_time:
78
- elapsed = (datetime.datetime.now() - st.session_state.recording_start_time).total_seconds()
79
- st.caption(f"📹 Recording... Frames: {frames_captured} | Duration: {elapsed:.1f}s")
 
 
 
 
 
 
 
 
 
80
 
81
- # Process video when recording stops
82
- if not webrtc_ctx.state.playing and st.session_state.video_recording and not st.session_state.video_processed:
83
- st.session_state.video_recording = False
84
- st.session_state.video_processed = True
85
-
86
- with st.spinner("💾 Processing and saving your recording..."):
87
- try:
88
- video_frames = st.session_state.video_frames_buffer.copy()
89
-
90
- # Enhanced validation
91
- if len(video_frames) < 30: # At least 1 second at 30fps
92
- st.warning(f"⚠️ Recording too short ({len(video_frames)} frames). Please record for at least 2-3 seconds.")
93
- st.session_state.video_frames_buffer = []
94
- return None
95
-
96
- # Generate unique filename
97
- timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
98
- video_filename = os.path.join(VIDEO_CONSENT_DIR, f"consent_{timestamp}.mp4")
99
-
100
- # Get video dimensions from first frame
101
- height, width = video_frames[0].shape[:2]
102
- fps = 30
103
-
104
- # Use OpenCV for more reliable video writing
105
- import cv2
106
- fourcc = cv2.VideoWriter_fourcc(*'mp4v')
107
- out = cv2.VideoWriter(video_filename, fourcc, fps, (width, height))
108
-
109
- # Write frames
110
- for frame in video_frames:
111
- out.write(frame)
112
-
113
- out.release()
114
-
115
- # Verify the video was created successfully
116
- if os.path.exists(video_filename) and os.path.getsize(video_filename) > 1000:
117
- # Clear the buffer
118
- st.session_state.video_frames_buffer = []
119
- st.session_state.video_filename = video_filename
120
 
121
- # Calculate duration
122
- duration = len(video_frames) / fps
123
- st.success(f"✅ **Video saved successfully!**")
124
- st.caption(f"📊 Duration: {duration:.1f}s | Frames: {len(video_frames)} | Size: {os.path.getsize(video_filename)/1024:.1f}KB")
125
 
126
- return video_filename
127
- else:
128
- st.error("❌ Failed to save video file properly.")
129
- return None
130
 
131
- except Exception as e:
132
- st.error(f"❌ Error saving video: {str(e)}")
133
- st.session_state.video_frames_buffer = []
134
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
 
136
- # Show recording status
137
- if st.session_state.video_recording:
138
- st.info("🎥 **Recording in progress...** Click STOP when finished.")
 
 
 
139
 
140
- return None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import os
4
  import streamlit as st
5
  import datetime
6
+ import streamlit.components.v1 as components
 
 
 
 
7
 
8
  VIDEO_CONSENT_DIR = "video_consents"
9
  os.makedirs(VIDEO_CONSENT_DIR, exist_ok=True)
10
 
11
  def record_consent_video():
12
  """
13
+ Production-grade Video Recorder using RecordRTC.
14
+ Features:
15
+ - Camera Selection (Fixes 'wrong camera' issues)
16
+ - RecordRTC Library (Handles cross-browser compatibility)
17
+ - Client-side Encoding (Works on Vercel/Heroku)
18
  """
 
 
 
 
 
 
 
 
 
 
 
19
 
20
+ st.markdown("### 📹 Record Video Consent")
21
+ st.info("Ensure you grant camera permissions when prompted by your browser.")
22
+
23
+ # We use RecordRTC via CDN for maximum robustness
24
+ html_code = """
25
+ <!DOCTYPE html>
26
+ <html lang="en">
27
+ <head>
28
+ <meta charset="UTF-8">
29
+ <script src="https://www.WebRTC-Experiment.com/RecordRTC.js"></script>
30
+ <style>
31
+ body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; background: transparent; }
32
+ .container { text-align: center; max-width: 640px; margin: auto; }
33
+
34
+ video {
35
+ width: 100%;
36
+ border-radius: 8px;
37
+ background: #000;
38
+ margin-bottom: 10px;
39
+ box-shadow: 0 2px 10px rgba(0,0,0,0.2);
40
+ }
41
+
42
+ select {
43
+ padding: 8px;
44
+ border-radius: 4px;
45
+ border: 1px solid #ccc;
46
+ margin-bottom: 15px;
47
+ width: 100%;
48
+ font-size: 14px;
49
+ }
50
+
51
+ .btn-group { display: flex; gap: 10px; justify-content: center; margin-top: 10px; }
52
+
53
+ button {
54
+ padding: 10px 20px;
55
+ border: none;
56
+ border-radius: 4px;
57
+ color: white;
58
+ font-weight: 600;
59
+ cursor: pointer;
60
+ }
61
+
62
+ #btn-start { background: #28a745; }
63
+ #btn-stop { background: #dc3545; }
64
+ #btn-download { background: #007bff; display: none; }
65
+
66
+ button:disabled { opacity: 0.5; cursor: not-allowed; }
67
+ #status { margin-top: 10px; font-size: 13px; color: #555; }
68
+ </style>
69
+ </head>
70
+ <body>
71
+ <div class="container">
72
+ <select id="video-source"><option value="">Loading cameras...</option></select>
73
+ <video id="preview" autoplay muted playsinline></video>
74
+
75
+ <div class="btn-group">
76
+ <button id="btn-start">Start Recording</button>
77
+ <button id="btn-stop" disabled>Stop</button>
78
+ <button id="btn-download">Save Video</button>
79
+ </div>
80
+ <div id="status">Ready. Select camera and click Start.</div>
81
+ </div>
82
+
83
+ <script>
84
+ const videoElement = document.getElementById('preview');
85
+ const videoSelect = document.getElementById('video-source');
86
+ const btnStart = document.getElementById('btn-start');
87
+ const btnStop = document.getElementById('btn-stop');
88
+ const btnDownload = document.getElementById('btn-download');
89
+ const status = document.getElementById('status');
90
+
91
+ let recorder;
92
+ let stream;
93
+
94
+ // 1. Enumerate Cameras
95
+ async function getCameras() {
96
+ try {
97
+ await navigator.mediaDevices.getUserMedia({ video: true }); // Request permission first
98
+ const devices = await navigator.mediaDevices.enumerateDevices();
99
+ const videoDevices = devices.filter(device => device.kind === 'videoinput');
100
+
101
+ videoSelect.innerHTML = '';
102
+ videoDevices.forEach(device => {
103
+ const option = document.createElement('option');
104
+ option.value = device.deviceId;
105
+ option.text = device.label || `Camera ${videoSelect.length + 1}`;
106
+ videoSelect.appendChild(option);
107
+ });
108
+
109
+ if(videoDevices.length === 0) {
110
+ videoSelect.innerHTML = '<option>No cameras found</option>';
111
+ status.innerText = "Error: No camera devices detected.";
112
+ }
113
+ } catch (err) {
114
+ status.innerText = "Error: Permission denied or no camera. " + err.message;
115
+ videoSelect.innerHTML = '<option>Camera Access Denied</option>';
116
+ }
117
+ }
118
+ getCameras();
119
 
120
+ // 2. Start Recording
121
+ btnStart.onclick = async () => {
122
+ const deviceId = videoSelect.value;
123
+ const constraints = {
124
+ video: { deviceId: deviceId ? { exact: deviceId } : undefined },
125
+ audio: true
126
+ };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ try {
129
+ stream = await navigator.mediaDevices.getUserMedia(constraints);
130
+ videoElement.srcObject = stream;
131
+ videoElement.muted = true; // Avoid feedback
132
+
133
+ recorder = new RecordRTC(stream, {
134
+ type: 'video',
135
+ mimeType: 'video/webm;codecs=vp8',
136
+ disableLogs: false
137
+ });
138
+
139
+ recorder.startRecording();
140
+
141
+ btnStart.disabled = true;
142
+ btnStop.disabled = false;
143
+ btnDownload.style.display = 'none';
144
+ status.innerText = "Recording... Speak clearly.";
145
+
146
+ } catch (err) {
147
+ status.innerText = "Failed to start: " + err.message;
148
+ console.error(err);
149
+ }
150
+ };
151
 
152
+ // 3. Stop Recording
153
+ btnStop.onclick = () => {
154
+ recorder.stopRecording(() => {
155
+ const blob = recorder.getBlob();
156
+ const url = URL.createObjectURL(blob);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
+ btnStart.disabled = false;
159
+ btnStop.disabled = true;
160
+ btnDownload.style.display = 'inline-block';
161
+ status.innerText = "Recording finished. Download to save.";
162
 
163
+ // Stop stream
164
+ stream.getTracks().forEach(track => track.stop());
165
+ videoElement.srcObject = null;
 
166
 
167
+ // Setup Download
168
+ btnDownload.onclick = () => {
169
+ const a = document.createElement('a');
170
+ a.style.display = 'none';
171
+ a.href = url;
172
+ a.download = 'recorded_consent.webm';
173
+ document.body.appendChild(a);
174
+ a.click();
175
+ setTimeout(() => {
176
+ document.body.removeChild(a);
177
+ window.URL.revokeObjectURL(url);
178
+ }, 100);
179
+ status.innerText = "File kept. Now upload below.";
180
+ };
181
+ });
182
+ };
183
+ </script>
184
+ </body>
185
+ </html>
186
+ """
187
 
188
+ # Height 600 to accommodate camera dropdown
189
+ components.html(html_code, height=600)
190
+
191
+ st.write("---")
192
+ st.markdown("### 📤 Upload Your Recording")
193
+ st.caption("Once you've saved the video above, upload it here to confirm.")
194
 
195
+ uploaded_file = st.file_uploader("Drop your recorded video here", type=["webm", "mp4", "mov"])
196
+ if uploaded_file is not None:
197
+ try:
198
+ # Process the uploaded file
199
+ timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
200
+ ext = os.path.splitext(uploaded_file.name)[1] or ".webm"
201
+ video_filename = os.path.join(VIDEO_CONSENT_DIR, f"consent_upload_{timestamp}{ext}")
202
+
203
+ with open(video_filename, "wb") as f:
204
+ f.write(uploaded_file.getbuffer())
205
+
206
+ st.success("✅ Consent Video Received!")
207
+ st.video(video_filename)
208
+ return video_filename
209
+ except Exception as e:
210
+ st.error(f"Error saving file: {e}")
211
+ return None
212
+
213
+ return None
core_utils/core_model_loaders.py CHANGED
@@ -14,8 +14,8 @@ def load_embedding_model():
14
 
15
  def load_groq_llm():
16
  """Loads the Groq LLM without any Streamlit dependencies."""
17
- return ChatGroq(temperature=0, model="llama3-8b-8192", api_key=os.getenv("GROQ_API_KEY"))
18
 
19
  def load_gemini_llm():
20
  """Loads the Gemini LLM without any Streamlit dependencies."""
21
- return ChatGoogleGenerativeAI(model="gemini-1.5-flash", temperature=0)
 
14
 
15
  def load_groq_llm():
16
  """Loads the Groq LLM without any Streamlit dependencies."""
17
+ return ChatGroq(temperature=0, model="meta-llama/llama-4-scout-17b-16e-instruct", api_key=os.getenv("GROQ_API_KEY"))
18
 
19
  def load_gemini_llm():
20
  """Loads the Gemini LLM without any Streamlit dependencies."""
21
+ return ChatGoogleGenerativeAI(model="gemini-2.5-flash", temperature=0, max_retries=5)
debug_models.py ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import os
3
+ import google.generativeai as genai
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+ api_key = os.getenv("GOOGLE_API_KEY")
8
+
9
+ if not api_key:
10
+ print("Error: GOOGLE_API_KEY not found in environment.")
11
+ else:
12
+ genai.configure(api_key=api_key)
13
+ print("Listing available models...")
14
+ try:
15
+ for m in genai.list_models():
16
+ if 'generateContent' in m.supported_generation_methods:
17
+ print(m.name)
18
+ except Exception as e:
19
+ print(f"Error listing models: {e}")
main_fastapi.py CHANGED
@@ -1,39 +1,51 @@
1
- # D:\jan-contract\main_fastapi.py
 
2
 
3
  import os
4
  import uuid
5
  import tempfile
6
  import json
7
- from typing import Optional, List
8
- from fastapi import FastAPI, UploadFile, File, HTTPException, Form, BackgroundTasks
9
- from fastapi.responses import StreamingResponse, JSONResponse
 
10
  from fastapi.middleware.cors import CORSMiddleware
11
- from pydantic import BaseModel, Field
12
  import io
13
  import shutil
 
 
14
 
15
- # --- Import all our backend logic and agents ---
 
 
 
 
 
 
 
16
  from agents.legal_agent import legal_agent
17
  from agents.scheme_chatbot import scheme_chatbot
18
  from agents.demystifier_agent import process_document_for_demystification
19
  from agents.general_assistant_agent import ask_gemini
20
  from utils.pdf_generator import generate_formatted_pdf
21
 
22
- # --- 1. Initialize FastAPI App ---
23
  app = FastAPI(
24
- title="Jan-Contract Unified API",
25
  description="""
26
- A comprehensive API for India's informal workforce providing:
 
 
27
 
28
- 🏗️ **Contract Generation**: Create digital agreements from plain text
29
- 🏦 **Scheme Discovery**: Find relevant government schemes and benefits
30
- 📜 **Document Analysis**: Demystify legal documents with AI-powered insights
31
- 🤖 **General Assistant**: AI-powered guidance and support
32
- 🎥 **Media Processing**: Audio/video consent recording and processing
33
 
34
  Built with FastAPI, LangChain, and modern AI technologies.
35
  """,
36
- version="2.0.0",
37
  contact={
38
  "name": "Jan-Contract Team",
39
  "email": "support@jan-contract.com"
@@ -44,7 +56,7 @@ app = FastAPI(
44
  }
45
  )
46
 
47
- # --- 2. CORS Middleware ---
48
  app.add_middleware(
49
  CORSMiddleware,
50
  allow_origins=["*"], # Configure appropriately for production
@@ -53,46 +65,113 @@ app.add_middleware(
53
  allow_headers=["*"],
54
  )
55
 
56
- # --- 3. Pydantic Models for Request Bodies ---
 
 
 
57
  class ContractRequest(BaseModel):
58
- user_request: str = Field(..., description="Plain text description of the agreement needed", min_length=10)
 
 
 
 
 
 
59
 
 
 
 
 
 
 
60
  class SchemeRequest(BaseModel):
61
- user_profile: str = Field(..., description="Description of user's situation, needs, or profile", min_length=10)
 
 
 
 
 
 
62
 
 
 
 
 
 
 
63
  class ChatRequest(BaseModel):
64
- session_id: str = Field(..., description="Unique session identifier for document chat")
65
- question: str = Field(..., description="Question about the uploaded document", min_length=1)
66
-
 
 
 
 
 
 
 
 
 
 
67
  class GeneralChatRequest(BaseModel):
68
- question: str = Field(..., description="General question for AI assistant", min_length=1)
69
-
 
 
 
 
 
 
70
  class VideoConsentRequest(BaseModel):
71
  contract_id: str = Field(..., description="Identifier for the contract this consent applies to")
72
  consent_text: str = Field(..., description="Text of the consent being recorded", min_length=1)
73
 
74
- # --- 4. Response Models ---
75
  class ApiResponse(BaseModel):
76
  success: bool
77
  message: str
78
- data: Optional[dict] = None
79
  error: Optional[str] = None
 
80
 
81
  class HealthCheck(BaseModel):
82
  status: str
83
  version: str
84
  timestamp: str
85
- services: dict
 
 
 
 
86
 
87
- # --- 5. State Management ---
88
  SESSION_CACHE = {}
89
  CONTRACT_CACHE = {}
90
 
91
- # --- 6. Health Check Endpoint ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
  @app.get("/health", tags=["System"], response_model=HealthCheck)
93
  async def health_check():
94
  """Check the health status of the API and its dependencies"""
95
- import datetime
96
 
97
  # Check if required directories exist
98
  directories = {
@@ -120,30 +199,57 @@ async def health_check():
120
  except:
121
  modules["speech_recognition"] = "❌"
122
 
 
 
 
 
 
 
 
123
  return HealthCheck(
124
  status="healthy",
125
- version="2.0.0",
126
  timestamp=datetime.datetime.now().isoformat(),
127
  services={
128
  "directories": directories,
129
- "modules": modules
 
130
  }
131
  )
132
 
133
- # --- 7. Contract Generation Endpoints ---
 
 
134
 
135
- @app.post("/contract/generate", tags=["Contract Generator"], response_model=ApiResponse)
136
  async def generate_contract(request: ContractRequest):
137
  """
138
  Generate a digital contract from plain text description.
139
- Returns structured JSON with contract text and legal trivia.
 
 
 
 
 
 
 
 
 
 
 
140
  """
141
  try:
 
 
142
  result = legal_agent.invoke({"user_request": request.user_request})
143
 
144
  # Cache the contract for later use
145
  contract_id = str(uuid.uuid4())
146
- CONTRACT_CACHE[contract_id] = result
 
 
 
 
147
 
148
  return ApiResponse(
149
  success=True,
@@ -152,68 +258,151 @@ async def generate_contract(request: ContractRequest):
152
  "contract_id": contract_id,
153
  "contract": result.get('legal_doc', ''),
154
  "legal_trivia": result.get('legal_trivia', {}),
155
- "timestamp": str(uuid.uuid4())
156
  }
157
  )
158
  except Exception as e:
 
159
  raise HTTPException(status_code=500, detail=f"Contract generation failed: {str(e)}")
160
 
161
- @app.post("/contract/generate-pdf", tags=["Contract Generator"])
162
  async def generate_contract_pdf(request: ContractRequest):
163
  """
164
  Generate a contract and return it as a downloadable PDF file.
 
 
 
 
 
 
165
  """
166
  try:
 
 
167
  result = legal_agent.invoke({"user_request": request.user_request})
168
  contract_text = result.get('legal_doc', "Error: Could not generate document text.")
169
 
170
  pdf_bytes = generate_formatted_pdf(contract_text)
171
 
 
 
172
  return StreamingResponse(
173
  io.BytesIO(pdf_bytes),
174
  media_type="application/pdf",
175
- headers={"Content-Disposition": f"attachment;filename=digital_agreement_{uuid.uuid4()}.pdf"}
176
  )
177
  except Exception as e:
 
178
  raise HTTPException(status_code=500, detail=f"PDF generation failed: {str(e)}")
179
 
180
- @app.get("/contract/{contract_id}", tags=["Contract Generator"], response_model=ApiResponse)
181
  async def get_contract(contract_id: str):
182
  """Retrieve a previously generated contract by ID"""
183
- if contract_id not in CONTRACT_CACHE:
184
- raise HTTPException(status_code=404, detail="Contract not found")
185
 
186
  return ApiResponse(
187
  success=True,
188
  message="Contract retrieved successfully",
189
- data=CONTRACT_CACHE[contract_id]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  )
191
 
192
- # --- 8. Scheme Finder Endpoints ---
 
 
193
 
194
- @app.post("/schemes/find", tags=["Scheme Finder"], response_model=ApiResponse)
195
  async def find_schemes(request: SchemeRequest):
196
  """
197
  Find relevant government schemes based on user profile.
198
- Returns list of schemes with descriptions and official links.
 
 
 
 
 
 
 
 
 
 
 
 
199
  """
200
  try:
 
 
201
  response = scheme_chatbot.invoke({"user_profile": request.user_profile})
 
202
  return ApiResponse(
203
  success=True,
204
  message="Schemes found successfully",
205
  data=response
206
  )
207
  except Exception as e:
 
208
  raise HTTPException(status_code=500, detail=f"Scheme search failed: {str(e)}")
209
 
210
- # --- 9. Document Demystifier Endpoints ---
 
 
211
 
212
- @app.post("/demystify/upload", tags=["Document Demystifier"], response_model=ApiResponse)
213
  async def demystify_upload(file: UploadFile = File(...)):
214
  """
215
  Upload a PDF document for AI-powered analysis.
216
- Returns analysis report and session ID for follow-up questions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  """
218
  if file.content_type != "application/pdf":
219
  raise HTTPException(status_code=400, detail="Invalid file type. Please upload a PDF.")
@@ -222,6 +411,8 @@ async def demystify_upload(file: UploadFile = File(...)):
222
  raise HTTPException(status_code=400, detail="File too large. Maximum size is 50MB.")
223
 
224
  try:
 
 
225
  # Save to project directory
226
  upload_dir = "pdfs_demystify"
227
  os.makedirs(upload_dir, exist_ok=True)
@@ -238,7 +429,8 @@ async def demystify_upload(file: UploadFile = File(...)):
238
  SESSION_CACHE[session_id] = {
239
  "rag_chain": analysis_result["rag_chain"],
240
  "file_path": file_path,
241
- "upload_time": str(uuid.uuid4())
 
242
  }
243
 
244
  return ApiResponse(
@@ -247,55 +439,128 @@ async def demystify_upload(file: UploadFile = File(...)):
247
  data={
248
  "session_id": session_id,
249
  "report": analysis_result["report"],
250
- "filename": file.filename
 
251
  }
252
  )
253
  except Exception as e:
 
254
  raise HTTPException(status_code=500, detail=f"Document processing failed: {str(e)}")
255
 
256
- @app.post("/demystify/chat", tags=["Document Demystifier"], response_model=ApiResponse)
257
  async def demystify_chat(request: ChatRequest):
258
  """
259
  Ask follow-up questions about an uploaded document.
260
- Requires valid session ID from upload endpoint.
 
 
 
 
 
 
 
 
 
261
  """
262
- session_data = SESSION_CACHE.get(request.session_id)
263
- if not session_data:
264
- raise HTTPException(status_code=404, detail="Session not found. Please upload the document again.")
265
 
266
  try:
 
 
267
  rag_chain = session_data["rag_chain"]
268
  response = rag_chain.invoke(request.question)
269
 
270
  return ApiResponse(
271
  success=True,
272
  message="Question answered successfully",
273
- data={"answer": response}
 
 
 
 
274
  )
275
  except Exception as e:
 
276
  raise HTTPException(status_code=500, detail=f"Chat processing failed: {str(e)}")
277
 
278
- # --- 10. General Assistant Endpoints ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
- @app.post("/assistant/chat", tags=["General Assistant"], response_model=ApiResponse)
 
 
 
 
281
  async def general_chat(request: GeneralChatRequest):
282
  """
283
  Get AI-powered assistance for general questions.
284
- Uses Gemini AI model for responses.
 
 
 
 
 
 
 
 
 
 
 
285
  """
286
  try:
 
 
287
  response = ask_gemini(request.question)
 
288
  return ApiResponse(
289
  success=True,
290
  message="Response generated successfully",
291
- data={"response": response}
 
 
 
292
  )
293
  except Exception as e:
 
294
  raise HTTPException(status_code=500, detail=f"AI response generation failed: {str(e)}")
295
 
296
- # --- 11. Media Processing Endpoints ---
 
 
297
 
298
- @app.post("/media/upload-video", tags=["Media Processing"], response_model=ApiResponse)
299
  async def upload_video_consent(
300
  file: UploadFile = File(...),
301
  contract_id: str = Form(...),
@@ -303,7 +568,16 @@ async def upload_video_consent(
303
  ):
304
  """
305
  Upload a video consent file for a specific contract.
306
- Supports MP4, AVI, MOV formats.
 
 
 
 
 
 
 
 
 
307
  """
308
  allowed_types = ["video/mp4", "video/avi", "video/quicktime", "video/x-msvideo"]
309
 
@@ -317,6 +591,8 @@ async def upload_video_consent(
317
  raise HTTPException(status_code=400, detail="Video too large. Maximum size is 100MB.")
318
 
319
  try:
 
 
320
  # Save video to project directory
321
  upload_dir = "video_consents"
322
  os.makedirs(upload_dir, exist_ok=True)
@@ -334,13 +610,15 @@ async def upload_video_consent(
334
  "video_path": video_path,
335
  "contract_id": contract_id,
336
  "filename": video_filename,
337
- "size": file.size
 
338
  }
339
  )
340
  except Exception as e:
 
341
  raise HTTPException(status_code=500, detail=f"Video upload failed: {str(e)}")
342
 
343
- @app.get("/media/videos/{contract_id}", tags=["Media Processing"], response_model=ApiResponse)
344
  async def get_contract_videos(contract_id: str):
345
  """Get all video consents for a specific contract"""
346
  try:
@@ -360,7 +638,7 @@ async def get_contract_videos(contract_id: str):
360
  "filename": filename,
361
  "path": file_path,
362
  "size": os.path.getsize(file_path),
363
- "created": str(uuid.uuid4())
364
  })
365
 
366
  return ApiResponse(
@@ -369,67 +647,42 @@ async def get_contract_videos(contract_id: str):
369
  data={"videos": videos}
370
  )
371
  except Exception as e:
 
372
  raise HTTPException(status_code=500, detail=f"Video retrieval failed: {str(e)}")
373
 
374
- # --- 12. Utility Endpoints ---
375
-
376
- @app.get("/contracts", tags=["Utilities"], response_model=ApiResponse)
377
- async def list_contracts():
378
- """List all generated contracts"""
379
- contracts = []
380
- for contract_id, contract_data in CONTRACT_CACHE.items():
381
- contracts.append({
382
- "id": contract_id,
383
- "summary": contract_data.get('legal_doc', '')[:100] + "...",
384
- "timestamp": str(uuid.uuid4())
385
- })
386
-
387
- return ApiResponse(
388
- success=True,
389
- message=f"Found {len(contracts)} contract(s)",
390
- data={"contracts": contracts}
391
- )
392
-
393
- @app.delete("/contracts/{contract_id}", tags=["Utilities"], response_model=ApiResponse)
394
- async def delete_contract(contract_id: str):
395
- """Delete a specific contract and its associated data"""
396
- if contract_id not in CONTRACT_CACHE:
397
- raise HTTPException(status_code=404, detail="Contract not found")
398
-
399
- # Remove contract
400
- del CONTRACT_CACHE[contract_id]
401
-
402
- # Remove associated videos
403
- video_dir = "video_consents"
404
- if os.path.exists(video_dir):
405
- for filename in os.listdir(video_dir):
406
- if filename.startswith(f"consent_{contract_id}_"):
407
- os.remove(os.path.join(video_dir, filename))
408
-
409
- return ApiResponse(
410
- success=True,
411
- message="Contract and associated data deleted successfully"
412
- )
413
 
414
  @app.get("/", tags=["System"])
415
  async def root():
416
- """API root endpoint with basic information"""
417
  return {
418
- "message": "Jan-Contract Unified API",
419
- "version": "2.0.0",
420
  "description": "Comprehensive API for India's informal workforce",
 
 
 
 
 
 
 
421
  "endpoints": {
422
  "health": "/health",
423
- "contracts": "/contract/generate",
424
- "schemes": "/schemes/find",
425
- "demystify": "/demystify/upload",
426
- "assistant": "/assistant/chat",
427
- "media": "/media/upload-video"
428
  },
429
- "docs": "/docs"
 
430
  }
431
 
432
- # --- 13. Error Handlers ---
 
 
433
 
434
  @app.exception_handler(HTTPException)
435
  async def http_exception_handler(request, exc):
@@ -444,6 +697,7 @@ async def http_exception_handler(request, exc):
444
 
445
  @app.exception_handler(Exception)
446
  async def general_exception_handler(request, exc):
 
447
  return JSONResponse(
448
  status_code=500,
449
  content=ApiResponse(
@@ -455,4 +709,4 @@ async def general_exception_handler(request, exc):
455
 
456
  if __name__ == "__main__":
457
  import uvicorn
458
- uvicorn.run(app, host="0.0.0.0", port=8000)
 
1
+ # Enhanced FastAPI Application for Jan-Contract
2
+ # Comprehensive API for India's informal workforce
3
 
4
  import os
5
  import uuid
6
  import tempfile
7
  import json
8
+ import datetime
9
+ from typing import Optional, List, Dict, Any
10
+ from fastapi import FastAPI, UploadFile, File, HTTPException, Form, BackgroundTasks, Depends
11
+ from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
12
  from fastapi.middleware.cors import CORSMiddleware
13
+ from pydantic import BaseModel, Field, validator
14
  import io
15
  import shutil
16
+ import logging
17
+ from dotenv import load_dotenv
18
 
19
+ # Load environment variables from .env file
20
+ load_dotenv()
21
+
22
+ # Configure logging
23
+ logging.basicConfig(level=logging.INFO)
24
+ logger = logging.getLogger(__name__)
25
+
26
+ # Import all backend logic and agents
27
  from agents.legal_agent import legal_agent
28
  from agents.scheme_chatbot import scheme_chatbot
29
  from agents.demystifier_agent import process_document_for_demystification
30
  from agents.general_assistant_agent import ask_gemini
31
  from utils.pdf_generator import generate_formatted_pdf
32
 
33
+ # Initialize FastAPI App
34
  app = FastAPI(
35
+ title="Jan-Contract Enhanced API",
36
  description="""
37
+ 🏗️ **Enhanced API for India's Informal Workforce**
38
+
39
+ This comprehensive API provides four core functionalities:
40
 
41
+ 1. **Contract Generator**: Create digital agreements from plain text descriptions
42
+ 2. **Scheme Finder**: Discover relevant government schemes and benefits
43
+ 3. **PDF Demystifier**: AI-powered analysis and explanation of legal documents
44
+ 4. **General Chatbot**: AI-powered assistance for general queries
 
45
 
46
  Built with FastAPI, LangChain, and modern AI technologies.
47
  """,
48
+ version="2.1.0",
49
  contact={
50
  "name": "Jan-Contract Team",
51
  "email": "support@jan-contract.com"
 
56
  }
57
  )
58
 
59
+ # CORS Middleware
60
  app.add_middleware(
61
  CORSMiddleware,
62
  allow_origins=["*"], # Configure appropriately for production
 
65
  allow_headers=["*"],
66
  )
67
 
68
+ # =============================================================================
69
+ # PYDANTIC MODELS FOR REQUEST/RESPONSE VALIDATION
70
+ # =============================================================================
71
+
72
  class ContractRequest(BaseModel):
73
+ user_request: str = Field(
74
+ ...,
75
+ description="Plain text description of the agreement needed",
76
+ min_length=10,
77
+ max_length=2000,
78
+ example="I need a contract for hiring a domestic helper for 6 months with weekly payment of Rs. 3000"
79
+ )
80
 
81
+ @validator('user_request')
82
+ def validate_request(cls, v):
83
+ if len(v.strip()) < 10:
84
+ raise ValueError('Request must be at least 10 characters long')
85
+ return v.strip()
86
+
87
  class SchemeRequest(BaseModel):
88
+ user_profile: str = Field(
89
+ ...,
90
+ description="Description of user's situation, needs, or profile",
91
+ min_length=10,
92
+ max_length=2000,
93
+ example="I am a 35-year-old woman from rural Maharashtra, working as a daily wage laborer, looking for financial assistance schemes"
94
+ )
95
 
96
+ @validator('user_profile')
97
+ def validate_profile(cls, v):
98
+ if len(v.strip()) < 10:
99
+ raise ValueError('Profile must be at least 10 characters long')
100
+ return v.strip()
101
+
102
  class ChatRequest(BaseModel):
103
+ session_id: str = Field(
104
+ ...,
105
+ description="Unique session identifier for document chat",
106
+ example="123e4567-e89b-12d3-a456-426614174000"
107
+ )
108
+ question: str = Field(
109
+ ...,
110
+ description="Question about the uploaded document",
111
+ min_length=1,
112
+ max_length=1000,
113
+ example="What are the key terms I should be aware of in this contract?"
114
+ )
115
+
116
  class GeneralChatRequest(BaseModel):
117
+ question: str = Field(
118
+ ...,
119
+ description="General question for AI assistant",
120
+ min_length=1,
121
+ max_length=1000,
122
+ example="What are my rights as a domestic worker in India?"
123
+ )
124
+
125
  class VideoConsentRequest(BaseModel):
126
  contract_id: str = Field(..., description="Identifier for the contract this consent applies to")
127
  consent_text: str = Field(..., description="Text of the consent being recorded", min_length=1)
128
 
129
+ # Response Models
130
  class ApiResponse(BaseModel):
131
  success: bool
132
  message: str
133
+ data: Optional[Dict[str, Any]] = None
134
  error: Optional[str] = None
135
+ timestamp: str = Field(default_factory=lambda: datetime.datetime.now().isoformat())
136
 
137
  class HealthCheck(BaseModel):
138
  status: str
139
  version: str
140
  timestamp: str
141
+ services: Dict[str, Any]
142
+
143
+ # =============================================================================
144
+ # STATE MANAGEMENT
145
+ # =============================================================================
146
 
 
147
  SESSION_CACHE = {}
148
  CONTRACT_CACHE = {}
149
 
150
+ # =============================================================================
151
+ # UTILITY FUNCTIONS
152
+ # =============================================================================
153
+
154
+ def get_session_data(session_id: str):
155
+ """Get session data or raise 404 if not found"""
156
+ session_data = SESSION_CACHE.get(session_id)
157
+ if not session_data:
158
+ raise HTTPException(status_code=404, detail="Session not found. Please upload the document again.")
159
+ return session_data
160
+
161
+ def get_contract_data(contract_id: str):
162
+ """Get contract data or raise 404 if not found"""
163
+ contract_data = CONTRACT_CACHE.get(contract_id)
164
+ if not contract_data:
165
+ raise HTTPException(status_code=404, detail="Contract not found")
166
+ return contract_data
167
+
168
+ # =============================================================================
169
+ # HEALTH CHECK ENDPOINT
170
+ # =============================================================================
171
+
172
  @app.get("/health", tags=["System"], response_model=HealthCheck)
173
  async def health_check():
174
  """Check the health status of the API and its dependencies"""
 
175
 
176
  # Check if required directories exist
177
  directories = {
 
199
  except:
200
  modules["speech_recognition"] = "❌"
201
 
202
+ # Check API keys
203
+ api_keys = {
204
+ "GOOGLE_API_KEY": "✅" if os.getenv("GOOGLE_API_KEY") else "❌",
205
+ "GROQ_API_KEY": "✅" if os.getenv("GROQ_API_KEY") else "❌",
206
+ "TAVILY_API_KEY": "✅" if os.getenv("TAVILY_API_KEY") else "❌"
207
+ }
208
+
209
  return HealthCheck(
210
  status="healthy",
211
+ version="2.1.0",
212
  timestamp=datetime.datetime.now().isoformat(),
213
  services={
214
  "directories": directories,
215
+ "modules": modules,
216
+ "api_keys": api_keys
217
  }
218
  )
219
 
220
+ # =============================================================================
221
+ # 1. CONTRACT GENERATOR ENDPOINTS
222
+ # =============================================================================
223
 
224
+ @app.post("/api/v1/contracts/generate", tags=["Contract Generator"], response_model=ApiResponse)
225
  async def generate_contract(request: ContractRequest):
226
  """
227
  Generate a digital contract from plain text description.
228
+
229
+ **Features:**
230
+ - Creates structured legal documents
231
+ - Includes relevant legal trivia and rights
232
+ - Returns contract ID for future reference
233
+ - Caches contract for retrieval
234
+
235
+ **Use Cases:**
236
+ - Domestic worker agreements
237
+ - Service contracts
238
+ - Rental agreements
239
+ - Employment contracts
240
  """
241
  try:
242
+ logger.info(f"Generating contract for request: {request.user_request[:100]}...")
243
+
244
  result = legal_agent.invoke({"user_request": request.user_request})
245
 
246
  # Cache the contract for later use
247
  contract_id = str(uuid.uuid4())
248
+ CONTRACT_CACHE[contract_id] = {
249
+ **result,
250
+ "created_at": datetime.datetime.now().isoformat(),
251
+ "user_request": request.user_request
252
+ }
253
 
254
  return ApiResponse(
255
  success=True,
 
258
  "contract_id": contract_id,
259
  "contract": result.get('legal_doc', ''),
260
  "legal_trivia": result.get('legal_trivia', {}),
261
+ "created_at": datetime.datetime.now().isoformat()
262
  }
263
  )
264
  except Exception as e:
265
+ logger.error(f"Contract generation failed: {str(e)}")
266
  raise HTTPException(status_code=500, detail=f"Contract generation failed: {str(e)}")
267
 
268
+ @app.post("/api/v1/contracts/generate-pdf", tags=["Contract Generator"])
269
  async def generate_contract_pdf(request: ContractRequest):
270
  """
271
  Generate a contract and return it as a downloadable PDF file.
272
+
273
+ **Features:**
274
+ - Creates formatted PDF document
275
+ - Includes all contract terms and legal trivia
276
+ - Returns downloadable file
277
+ - Auto-generates filename with timestamp
278
  """
279
  try:
280
+ logger.info(f"Generating PDF contract for request: {request.user_request[:100]}...")
281
+
282
  result = legal_agent.invoke({"user_request": request.user_request})
283
  contract_text = result.get('legal_doc', "Error: Could not generate document text.")
284
 
285
  pdf_bytes = generate_formatted_pdf(contract_text)
286
 
287
+ filename = f"contract_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf"
288
+
289
  return StreamingResponse(
290
  io.BytesIO(pdf_bytes),
291
  media_type="application/pdf",
292
+ headers={"Content-Disposition": f"attachment;filename={filename}"}
293
  )
294
  except Exception as e:
295
+ logger.error(f"PDF generation failed: {str(e)}")
296
  raise HTTPException(status_code=500, detail=f"PDF generation failed: {str(e)}")
297
 
298
+ @app.get("/api/v1/contracts/{contract_id}", tags=["Contract Generator"], response_model=ApiResponse)
299
  async def get_contract(contract_id: str):
300
  """Retrieve a previously generated contract by ID"""
301
+ contract_data = get_contract_data(contract_id)
 
302
 
303
  return ApiResponse(
304
  success=True,
305
  message="Contract retrieved successfully",
306
+ data=contract_data
307
+ )
308
+
309
+ @app.get("/api/v1/contracts", tags=["Contract Generator"], response_model=ApiResponse)
310
+ async def list_contracts():
311
+ """List all generated contracts with summaries"""
312
+ contracts = []
313
+ for contract_id, contract_data in CONTRACT_CACHE.items():
314
+ contracts.append({
315
+ "id": contract_id,
316
+ "summary": contract_data.get('legal_doc', '')[:100] + "...",
317
+ "created_at": contract_data.get('created_at', 'Unknown'),
318
+ "user_request": contract_data.get('user_request', '')[:100] + "..."
319
+ })
320
+
321
+ return ApiResponse(
322
+ success=True,
323
+ message=f"Found {len(contracts)} contract(s)",
324
+ data={"contracts": contracts}
325
+ )
326
+
327
+ @app.delete("/api/v1/contracts/{contract_id}", tags=["Contract Generator"], response_model=ApiResponse)
328
+ async def delete_contract(contract_id: str):
329
+ """Delete a specific contract and its associated data"""
330
+ contract_data = get_contract_data(contract_id)
331
+
332
+ # Remove contract
333
+ del CONTRACT_CACHE[contract_id]
334
+
335
+ # Remove associated videos
336
+ video_dir = "video_consents"
337
+ if os.path.exists(video_dir):
338
+ for filename in os.listdir(video_dir):
339
+ if filename.startswith(f"consent_{contract_id}_"):
340
+ os.remove(os.path.join(video_dir, filename))
341
+
342
+ return ApiResponse(
343
+ success=True,
344
+ message="Contract and associated data deleted successfully"
345
  )
346
 
347
+ # =============================================================================
348
+ # 2. SCHEME FINDER ENDPOINTS
349
+ # =============================================================================
350
 
351
+ @app.post("/api/v1/schemes/find", tags=["Scheme Finder"], response_model=ApiResponse)
352
  async def find_schemes(request: SchemeRequest):
353
  """
354
  Find relevant government schemes based on user profile.
355
+
356
+ **Features:**
357
+ - Searches official government portals
358
+ - Returns structured scheme information
359
+ - Includes official links and descriptions
360
+ - Targets specific user demographics
361
+
362
+ **Use Cases:**
363
+ - Financial assistance programs
364
+ - Healthcare schemes
365
+ - Education benefits
366
+ - Employment support
367
+ - Women's empowerment programs
368
  """
369
  try:
370
+ logger.info(f"Finding schemes for profile: {request.user_profile[:100]}...")
371
+
372
  response = scheme_chatbot.invoke({"user_profile": request.user_profile})
373
+
374
  return ApiResponse(
375
  success=True,
376
  message="Schemes found successfully",
377
  data=response
378
  )
379
  except Exception as e:
380
+ logger.error(f"Scheme search failed: {str(e)}")
381
  raise HTTPException(status_code=500, detail=f"Scheme search failed: {str(e)}")
382
 
383
+ # =============================================================================
384
+ # 3. PDF DEMYSTIFIER ENDPOINTS
385
+ # =============================================================================
386
 
387
+ @app.post("/api/v1/demystify/upload", tags=["PDF Demystifier"], response_model=ApiResponse)
388
  async def demystify_upload(file: UploadFile = File(...)):
389
  """
390
  Upload a PDF document for AI-powered analysis.
391
+
392
+ **Features:**
393
+ - Analyzes legal documents with AI
394
+ - Generates comprehensive reports
395
+ - Creates interactive Q&A session
396
+ - Explains complex legal terms
397
+
398
+ **Supported Formats:**
399
+ - PDF files only
400
+ - Maximum size: 50MB
401
+
402
+ **Analysis Includes:**
403
+ - Document summary
404
+ - Key legal terms explanation
405
+ - Overall advice and recommendations
406
  """
407
  if file.content_type != "application/pdf":
408
  raise HTTPException(status_code=400, detail="Invalid file type. Please upload a PDF.")
 
411
  raise HTTPException(status_code=400, detail="File too large. Maximum size is 50MB.")
412
 
413
  try:
414
+ logger.info(f"Processing document: {file.filename}")
415
+
416
  # Save to project directory
417
  upload_dir = "pdfs_demystify"
418
  os.makedirs(upload_dir, exist_ok=True)
 
429
  SESSION_CACHE[session_id] = {
430
  "rag_chain": analysis_result["rag_chain"],
431
  "file_path": file_path,
432
+ "upload_time": datetime.datetime.now().isoformat(),
433
+ "filename": file.filename
434
  }
435
 
436
  return ApiResponse(
 
439
  data={
440
  "session_id": session_id,
441
  "report": analysis_result["report"],
442
+ "filename": file.filename,
443
+ "upload_time": datetime.datetime.now().isoformat()
444
  }
445
  )
446
  except Exception as e:
447
+ logger.error(f"Document processing failed: {str(e)}")
448
  raise HTTPException(status_code=500, detail=f"Document processing failed: {str(e)}")
449
 
450
+ @app.post("/api/v1/demystify/chat", tags=["PDF Demystifier"], response_model=ApiResponse)
451
  async def demystify_chat(request: ChatRequest):
452
  """
453
  Ask follow-up questions about an uploaded document.
454
+
455
+ **Features:**
456
+ - Interactive Q&A about uploaded documents
457
+ - Context-aware responses
458
+ - Legal term explanations
459
+ - Document-specific insights
460
+
461
+ **Requirements:**
462
+ - Valid session ID from upload endpoint
463
+ - Questions must be related to the uploaded document
464
  """
465
+ session_data = get_session_data(request.session_id)
 
 
466
 
467
  try:
468
+ logger.info(f"Processing question for session {request.session_id}: {request.question[:50]}...")
469
+
470
  rag_chain = session_data["rag_chain"]
471
  response = rag_chain.invoke(request.question)
472
 
473
  return ApiResponse(
474
  success=True,
475
  message="Question answered successfully",
476
+ data={
477
+ "answer": response,
478
+ "session_id": request.session_id,
479
+ "question": request.question
480
+ }
481
  )
482
  except Exception as e:
483
+ logger.error(f"Chat processing failed: {str(e)}")
484
  raise HTTPException(status_code=500, detail=f"Chat processing failed: {str(e)}")
485
 
486
+ @app.get("/api/v1/demystify/sessions", tags=["PDF Demystifier"], response_model=ApiResponse)
487
+ async def list_demystify_sessions():
488
+ """List all active document analysis sessions"""
489
+ sessions = []
490
+ for session_id, session_data in SESSION_CACHE.items():
491
+ sessions.append({
492
+ "session_id": session_id,
493
+ "filename": session_data.get("filename", "Unknown"),
494
+ "upload_time": session_data.get("upload_time", "Unknown")
495
+ })
496
+
497
+ return ApiResponse(
498
+ success=True,
499
+ message=f"Found {len(sessions)} active session(s)",
500
+ data={"sessions": sessions}
501
+ )
502
+
503
+ @app.delete("/api/v1/demystify/sessions/{session_id}", tags=["PDF Demystifier"], response_model=ApiResponse)
504
+ async def delete_demystify_session(session_id: str):
505
+ """Delete a document analysis session and its associated files"""
506
+ session_data = get_session_data(session_id)
507
+
508
+ # Remove session
509
+ del SESSION_CACHE[session_id]
510
+
511
+ # Remove associated file
512
+ file_path = session_data.get("file_path")
513
+ if file_path and os.path.exists(file_path):
514
+ os.remove(file_path)
515
+
516
+ return ApiResponse(
517
+ success=True,
518
+ message="Session and associated files deleted successfully"
519
+ )
520
 
521
+ # =============================================================================
522
+ # 4. GENERAL CHATBOT ENDPOINTS
523
+ # =============================================================================
524
+
525
+ @app.post("/api/v1/assistant/chat", tags=["General Assistant"], response_model=ApiResponse)
526
  async def general_chat(request: GeneralChatRequest):
527
  """
528
  Get AI-powered assistance for general questions.
529
+
530
+ **Features:**
531
+ - Uses Google Gemini AI model
532
+ - Provides helpful responses to general queries
533
+ - Supports various topics and questions
534
+ - Context-aware assistance
535
+
536
+ **Use Cases:**
537
+ - Legal rights information
538
+ - General guidance
539
+ - FAQ responses
540
+ - Educational content
541
  """
542
  try:
543
+ logger.info(f"Processing general chat question: {request.question[:50]}...")
544
+
545
  response = ask_gemini(request.question)
546
+
547
  return ApiResponse(
548
  success=True,
549
  message="Response generated successfully",
550
+ data={
551
+ "response": response,
552
+ "question": request.question
553
+ }
554
  )
555
  except Exception as e:
556
+ logger.error(f"AI response generation failed: {str(e)}")
557
  raise HTTPException(status_code=500, detail=f"AI response generation failed: {str(e)}")
558
 
559
+ # =============================================================================
560
+ # MEDIA PROCESSING ENDPOINTS (BONUS)
561
+ # =============================================================================
562
 
563
+ @app.post("/api/v1/media/upload-video", tags=["Media Processing"], response_model=ApiResponse)
564
  async def upload_video_consent(
565
  file: UploadFile = File(...),
566
  contract_id: str = Form(...),
 
568
  ):
569
  """
570
  Upload a video consent file for a specific contract.
571
+
572
+ **Features:**
573
+ - Supports multiple video formats
574
+ - Links to specific contracts
575
+ - Stores consent text metadata
576
+ - File size validation
577
+
578
+ **Supported Formats:**
579
+ - MP4, AVI, MOV
580
+ - Maximum size: 100MB
581
  """
582
  allowed_types = ["video/mp4", "video/avi", "video/quicktime", "video/x-msvideo"]
583
 
 
591
  raise HTTPException(status_code=400, detail="Video too large. Maximum size is 100MB.")
592
 
593
  try:
594
+ logger.info(f"Uploading video consent for contract {contract_id}")
595
+
596
  # Save video to project directory
597
  upload_dir = "video_consents"
598
  os.makedirs(upload_dir, exist_ok=True)
 
610
  "video_path": video_path,
611
  "contract_id": contract_id,
612
  "filename": video_filename,
613
+ "size": file.size,
614
+ "consent_text": consent_text
615
  }
616
  )
617
  except Exception as e:
618
+ logger.error(f"Video upload failed: {str(e)}")
619
  raise HTTPException(status_code=500, detail=f"Video upload failed: {str(e)}")
620
 
621
+ @app.get("/api/v1/media/videos/{contract_id}", tags=["Media Processing"], response_model=ApiResponse)
622
  async def get_contract_videos(contract_id: str):
623
  """Get all video consents for a specific contract"""
624
  try:
 
638
  "filename": filename,
639
  "path": file_path,
640
  "size": os.path.getsize(file_path),
641
+ "created": datetime.datetime.now().isoformat()
642
  })
643
 
644
  return ApiResponse(
 
647
  data={"videos": videos}
648
  )
649
  except Exception as e:
650
+ logger.error(f"Video retrieval failed: {str(e)}")
651
  raise HTTPException(status_code=500, detail=f"Video retrieval failed: {str(e)}")
652
 
653
+ # =============================================================================
654
+ # ROOT ENDPOINT
655
+ # =============================================================================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
 
657
  @app.get("/", tags=["System"])
658
  async def root():
659
+ """API root endpoint with comprehensive information"""
660
  return {
661
+ "message": "Jan-Contract Enhanced API",
662
+ "version": "2.1.0",
663
  "description": "Comprehensive API for India's informal workforce",
664
+ "features": [
665
+ "Contract Generation",
666
+ "Scheme Discovery",
667
+ "Document Analysis",
668
+ "AI Assistant",
669
+ "Media Processing"
670
+ ],
671
  "endpoints": {
672
  "health": "/health",
673
+ "contracts": "/api/v1/contracts/generate",
674
+ "schemes": "/api/v1/schemes/find",
675
+ "demystify": "/api/v1/demystify/upload",
676
+ "assistant": "/api/v1/assistant/chat",
677
+ "media": "/api/v1/media/upload-video"
678
  },
679
+ "docs": "/docs",
680
+ "redoc": "/redoc"
681
  }
682
 
683
+ # =============================================================================
684
+ # ERROR HANDLERS
685
+ # =============================================================================
686
 
687
  @app.exception_handler(HTTPException)
688
  async def http_exception_handler(request, exc):
 
697
 
698
  @app.exception_handler(Exception)
699
  async def general_exception_handler(request, exc):
700
+ logger.error(f"Unhandled exception: {str(exc)}")
701
  return JSONResponse(
702
  status_code=500,
703
  content=ApiResponse(
 
709
 
710
  if __name__ == "__main__":
711
  import uvicorn
712
+ uvicorn.run(app, host="0.0.0.0", port=8000, reload=True)
main_streamlit.py CHANGED
@@ -4,179 +4,188 @@ import os
4
  import streamlit as st
5
  from dotenv import load_dotenv
6
 
7
- # --- Agent and Component Imports (Cleaned up) ---
8
  from agents.demystifier_agent import process_document_for_demystification
9
  from components.video_recorder import record_consent_video
10
  from utils.pdf_generator import generate_formatted_pdf
11
  from components.chat_interface import chat_interface
12
- from agents.general_assistant_agent import ask_gemini
 
13
  # --- 1. Initial Setup ---
14
  load_dotenv()
15
- st.set_page_config(layout="wide", page_title="Jan-Contract Unified Assistant")
16
- st.title("Jan-Contract: Your Digital Workforce Assistant")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
 
18
  PDF_UPLOAD_DIR = "pdfs_demystify"
19
  os.makedirs(PDF_UPLOAD_DIR, exist_ok=True)
20
 
21
  # --- 2. Streamlit UI with Tabs ---
22
- tab1, tab2, tab3, tab4 = st.tabs([
23
- "📝 **Contract Generator**",
24
- "🏦 **Scheme Finder**",
25
- "📜 **Document Demystifier & Chat**",
26
- "🤖 **General Assistant**"
27
  ])
28
 
29
  # --- TAB 1: Contract Generator ---
30
  with tab1:
31
- st.header("Create a Simple Digital Agreement")
32
- st.write("Turn your everyday language into a clear agreement, then provide video consent.")
33
 
34
- st.subheader("Step 1: Describe and Generate Your Agreement")
35
- user_request = st.text_area("Describe the agreement...", height=120, key="contract_request")
36
-
37
- # --- FIX: Added a unique key="b1" for consistency ---
38
- if st.button("Generate Document & Get Legal Info", type="primary", key="b1"):
39
- if user_request:
40
- with st.spinner("Generating document..."):
41
- from agents.legal_agent import legal_agent
42
- result = legal_agent.invoke({"user_request": user_request})
43
- st.session_state.legal_result = result
44
- # Reset video state for each new contract
45
- if 'video_path_from_component' in st.session_state:
46
- del st.session_state['video_path_from_component']
47
- if 'frames_buffer' in st.session_state:
48
- del st.session_state['frames_buffer']
49
- else:
50
- st.error("Please describe the agreement.")
51
 
52
- if 'legal_result' in st.session_state:
53
- result = st.session_state.legal_result
54
- col1, col2 = st.columns(2)
55
 
56
- with col1:
57
- st.subheader("Generated Digital Agreement")
58
- st.markdown(result['legal_doc'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  pdf_bytes = generate_formatted_pdf(result['legal_doc'])
60
- st.download_button(label="⬇️ Download Formatted PDF", data=pdf_bytes, file_name="agreement.pdf")
61
-
62
- with col2:
63
- st.subheader("Relevant Legal Trivia")
64
- # --- FIX: Restored the missing trivia display logic ---
65
  if result.get('legal_trivia') and result['legal_trivia'].trivia:
66
- for item in result['legal_trivia'].trivia:
67
- st.markdown(f"- **{item.point}**")
68
- st.caption(item.explanation)
69
- st.markdown(f"[Source Link]({item.source_url})")
70
- else:
71
- st.write("Could not retrieve structured legal trivia.")
72
 
73
- st.divider()
74
-
75
- st.subheader("Step 2: Record Video Consent for this Agreement")
76
-
77
- # Browser compatibility check
78
- st.info("🌐 **Browser Requirements:** This feature works best in Chrome, Firefox, or Edge. Make sure to allow camera access when prompted.")
79
-
80
- saved_video_path = record_consent_video()
81
 
82
- if saved_video_path:
83
- st.session_state.video_path_from_component = saved_video_path
84
 
85
- if st.session_state.get("video_path_from_component"):
86
- st.success(" Your consent has been recorded and saved!")
87
- st.video(st.session_state.video_path_from_component)
88
- st.info("This video is now linked to your generated agreement.")
89
- else:
90
- st.info("💡 **Tip:** If video recording isn't working, try refreshing the page and allowing camera permissions.")
91
 
92
- # --- TAB 2: Scheme Finder (Unchanged) ---
93
  with tab2:
94
- st.header("Find Relevant Government Schemes")
95
- st.write("Describe yourself or your situation to find government schemes that might apply to you.")
96
 
97
- user_profile = st.text_input("Enter your profile...", key="scheme_profile")
98
 
99
- if st.button("Find Schemes", type="primary", key="b2"):
100
  if user_profile:
101
- with st.spinner("Initializing models and searching for schemes..."):
102
- from agents.scheme_chatbot import scheme_chatbot
103
- response = scheme_chatbot.invoke({"user_profile": user_profile})
104
- st.session_state.scheme_response = response
 
 
 
105
  else:
106
- st.error("Please enter a profile.")
107
 
108
  if 'scheme_response' in st.session_state:
109
  response = st.session_state.scheme_response
110
- st.subheader(f"Potential Schemes for: '{user_profile}'")
111
  if response and response.schemes:
112
  for scheme in response.schemes:
113
  with st.container(border=True):
114
  st.markdown(f"#### {scheme.scheme_name}")
115
- st.write(f"**Description:** {scheme.description}")
116
- st.link_button("Go to Official Page ➡️", scheme.official_link)
 
 
 
117
 
118
  # --- TAB 3: Demystifier & Chat ---
119
  with tab3:
120
- st.header("📜 Simplify & Chat With Your Legal Document")
121
- st.markdown("Get a plain-English summary of your document, then ask questions using text or your voice.")
122
 
123
- uploaded_file = st.file_uploader("Choose a PDF document", type="pdf", key="demystify_uploader")
124
 
125
- # This button triggers the one-time analysis and embedding process
126
  if uploaded_file and st.button("Analyze Document", type="primary"):
127
- with st.spinner("Performing deep analysis and preparing for chat..."):
128
- # Save the uploaded file to a temporary location for processing
129
- temp_file_path = os.path.join(PDF_UPLOAD_DIR, uploaded_file.name)
130
- with open(temp_file_path, "wb") as f:
131
- f.write(uploaded_file.getbuffer())
132
-
133
- # Call the master controller function from the agent
134
- analysis_result = process_document_for_demystification(temp_file_path)
135
-
136
- # Store the two key results in the session state
137
- st.session_state.demystifier_report = analysis_result["report"]
138
- st.session_state.rag_chain = analysis_result["rag_chain"]
139
 
140
- # This UI section only appears after a document has been successfully analyzed
141
  if 'demystifier_report' in st.session_state:
142
  st.divider()
143
- st.header("Step 1: Automated Document Analysis")
144
  report = st.session_state.demystifier_report
145
- with st.container(border=True):
146
- st.subheader("📄 Document Summary")
 
 
 
147
  st.write(report.summary)
148
- st.divider()
149
 
150
- st.subheader("🔑 Key Terms Explained")
151
  for term in report.key_terms:
152
- with st.expander(f"**{term.term}**"):
153
  st.write(term.explanation)
154
- st.markdown(f"[Learn More Here]({term.resource_link})")
155
- st.divider()
156
 
157
- st.success(f"**Overall Advice:** {report.overall_advice}")
 
 
 
 
 
 
 
158
 
159
- st.divider()
160
 
161
- st.header("Step 2: Ask Follow-up Questions")
162
- # Call our reusable chat component, passing the RAG chain specific to this document.
163
- # The RAG chain's .invoke method is the handler function.
164
- chat_interface(
165
- handler_function=st.session_state.rag_chain.invoke,
166
- session_state_key="doc_chat_history" # Use a unique key for this chat's history
167
- )
168
-
169
- elif not uploaded_file:
170
- st.info("Upload a PDF document to begin analysis and enable chat.")
171
-
172
- # --- TAB 4: General Assistant (Complete) ---
173
- with tab4:
174
- st.header("🤖 General Assistant")
175
- st.markdown("Ask a general question and get a response directly from the Gemini AI model. You can use text or your voice.")
176
-
177
- # Call our reusable chat component.
178
- # This time, we pass the simple `ask_gemini` function as the handler.
179
- chat_interface(
180
- handler_function=ask_gemini,
181
- session_state_key="general_chat_history" # Use a different key for this chat's history
182
- )
 
4
  import streamlit as st
5
  from dotenv import load_dotenv
6
 
7
+ # --- Agent and Component Imports ---
8
  from agents.demystifier_agent import process_document_for_demystification
9
  from components.video_recorder import record_consent_video
10
  from utils.pdf_generator import generate_formatted_pdf
11
  from components.chat_interface import chat_interface
12
+
13
+
14
  # --- 1. Initial Setup ---
15
  load_dotenv()
16
+ st.set_page_config(layout="wide", page_title="Jan-Contract Unified Assistant", page_icon="⚖️")
17
+
18
+ # Custom CSS for a cleaner look
19
+ st.markdown("""
20
+ <style>
21
+ .reportview-container {
22
+ background: #f0f2f6;
23
+ }
24
+ .main-header {
25
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
26
+ color: #333;
27
+ }
28
+ h1 {
29
+ color: #1A73E8;
30
+ }
31
+ h2, h3 {
32
+ color: #424242;
33
+ }
34
+ .stButton>button {
35
+ color: #ffffff;
36
+ background-color: #1A73E8;
37
+ border-radius: 5px;
38
+ }
39
+ </style>
40
+ """, unsafe_allow_html=True)
41
+
42
+ st.title("Jan-Contract: Digital Workforce Assistant")
43
+ st.write("Empowering India's workforce with accessible legal tools and government scheme discovery.")
44
 
45
  PDF_UPLOAD_DIR = "pdfs_demystify"
46
  os.makedirs(PDF_UPLOAD_DIR, exist_ok=True)
47
 
48
  # --- 2. Streamlit UI with Tabs ---
49
+ tab1, tab2, tab3 = st.tabs([
50
+ "Contract Generator",
51
+ "Scheme Finder",
52
+ "Document Demystifier"
 
53
  ])
54
 
55
  # --- TAB 1: Contract Generator ---
56
  with tab1:
57
+ st.header("Digital Agreement Generator")
58
+ st.write("Create a clear digital agreement from plain text and record video consent.")
59
 
60
+ col1, col2 = st.columns([1, 1])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ with col1:
63
+ st.subheader("Agreement Details")
64
+ user_request = st.text_area("Describe the terms of the agreement...", height=150, key="contract_request", placeholder="E.g., I, Rajesh, agree to paint Mr. Sharma's house for 5000 rupees by next Tuesday.")
65
 
66
+ if st.button("Generate Agreement", type="primary", key="btn_generate_contract"):
67
+ if user_request:
68
+ with st.spinner("Drafting agreement..."):
69
+ try:
70
+ from agents.legal_agent import legal_agent
71
+ result = legal_agent.invoke({"user_request": user_request})
72
+ st.session_state.legal_result = result
73
+ # Reset video state for new contract
74
+ if 'video_path_from_component' in st.session_state:
75
+ del st.session_state['video_path_from_component']
76
+ except Exception as e:
77
+ st.error(f"An error occurred: {e}")
78
+ else:
79
+ st.warning("Please describe the agreement details.")
80
+
81
+ with col2:
82
+ if 'legal_result' in st.session_state:
83
+ result = st.session_state.legal_result
84
+
85
+ st.subheader("Drafted Agreement")
86
+ with st.container(border=True):
87
+ st.markdown(result['legal_doc'])
88
+
89
  pdf_bytes = generate_formatted_pdf(result['legal_doc'])
90
+ st.download_button(label="Download PDF", data=pdf_bytes, file_name="agreement.pdf", mime="application/pdf")
91
+
 
 
 
92
  if result.get('legal_trivia') and result['legal_trivia'].trivia:
93
+ with st.expander("Legal Insights"):
94
+ for item in result['legal_trivia'].trivia:
95
+ st.markdown(f"**{item.point}**")
96
+ st.caption(item.explanation)
97
+ st.markdown(f"[Source]({item.source_url})")
 
98
 
99
+ st.divider()
100
+
101
+ st.subheader("Video Consent Recording")
102
+ st.info("Please record a video stating your name and that you agree to the terms above.")
103
+
104
+ saved_video_path = record_consent_video()
 
 
105
 
106
+ if saved_video_path:
107
+ st.session_state.video_path_from_component = saved_video_path
108
 
109
+ if st.session_state.get("video_path_from_component"):
110
+ st.success("Consent recorded successfully.")
111
+ st.video(st.session_state.video_path_from_component)
 
 
 
112
 
113
+ # --- TAB 2: Scheme Finder ---
114
  with tab2:
115
+ st.header("Government Scheme Finder")
116
+ st.write("Find relevant government schemes based on your profile.")
117
 
118
+ user_profile = st.text_input("Enter your profile description...", key="scheme_profile", placeholder="E.g., A female farmer in Maharashtra owning 2 acres of land.")
119
 
120
+ if st.button("Search Schemes", type="primary", key="btn_find_schemes"):
121
  if user_profile:
122
+ with st.spinner("Searching for schemes..."):
123
+ try:
124
+ from agents.scheme_chatbot import scheme_chatbot
125
+ response = scheme_chatbot.invoke({"user_profile": user_profile})
126
+ st.session_state.scheme_response = response
127
+ except Exception as e:
128
+ st.error(f"An error occurred during search: {e}")
129
  else:
130
+ st.warning("Please enter a profile description.")
131
 
132
  if 'scheme_response' in st.session_state:
133
  response = st.session_state.scheme_response
134
+ st.subheader(f"Schemes for: '{user_profile}'")
135
  if response and response.schemes:
136
  for scheme in response.schemes:
137
  with st.container(border=True):
138
  st.markdown(f"#### {scheme.scheme_name}")
139
+ st.write(scheme.description)
140
+ st.write(f"**Target Audience:** {scheme.target_audience}")
141
+ st.markdown(f"[Official Website]({scheme.official_link})")
142
+ else:
143
+ st.info("No specific schemes found. Try a more detailed description.")
144
 
145
  # --- TAB 3: Demystifier & Chat ---
146
  with tab3:
147
+ st.header("Document Demystifier")
148
+ st.write("Upload a legal document to get a simplified summary and ask questions.")
149
 
150
+ uploaded_file = st.file_uploader("Upload PDF Document", type="pdf", key="demystify_uploader")
151
 
 
152
  if uploaded_file and st.button("Analyze Document", type="primary"):
153
+ with st.spinner("Analyzing document..."):
154
+ try:
155
+ temp_file_path = os.path.join(PDF_UPLOAD_DIR, uploaded_file.name)
156
+ with open(temp_file_path, "wb") as f:
157
+ f.write(uploaded_file.getbuffer())
158
+
159
+ analysis_result = process_document_for_demystification(temp_file_path)
160
+
161
+ st.session_state.demystifier_report = analysis_result["report"]
162
+ st.session_state.rag_chain = analysis_result["rag_chain"]
163
+ except Exception as e:
164
+ st.error(f"Analysis failed: {e}")
165
 
 
166
  if 'demystifier_report' in st.session_state:
167
  st.divider()
 
168
  report = st.session_state.demystifier_report
169
+
170
+ tab_summary, tab_chat = st.tabs(["Summary & Analysis", "Chat with Document"])
171
+
172
+ with tab_summary:
173
+ st.subheader("Document Summary")
174
  st.write(report.summary)
 
175
 
176
+ st.subheader("Key Terms Explained")
177
  for term in report.key_terms:
178
+ with st.expander(f"{term.term}"):
179
  st.write(term.explanation)
180
+ st.markdown(f"[Learn More]({term.resource_link})")
 
181
 
182
+ st.info(f"**Advice:** {report.overall_advice}")
183
+
184
+ with tab_chat:
185
+ st.subheader("Ask Questions")
186
+ chat_interface(
187
+ handler_function=st.session_state.rag_chain.invoke,
188
+ session_state_key="doc_chat_history"
189
+ )
190
 
 
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -5,6 +5,7 @@ langchain-core>=0.2.0
5
  langchain>=0.2.0
6
  langchain-community>=0.2.0
7
  langgraph>=0.2.0
 
8
 
9
  # LLM Integrations
10
  langchain_google_genai>=0.1.0
@@ -26,7 +27,7 @@ streamlit>=1.28.0
26
 
27
  # Video and Audio Processing
28
  streamlit-webrtc>=0.63.4
29
- opencv-python-headless>=4.8.0
30
  av>=14.0.0
31
  SpeechRecognition>=3.10.0
32
  gTTS>=2.4.0
 
5
  langchain>=0.2.0
6
  langchain-community>=0.2.0
7
  langgraph>=0.2.0
8
+ langchain-text-splitters>=0.2.0
9
 
10
  # LLM Integrations
11
  langchain_google_genai>=0.1.0
 
27
 
28
  # Video and Audio Processing
29
  streamlit-webrtc>=0.63.4
30
+ opencv-python>=4.8.0
31
  av>=14.0.0
32
  SpeechRecognition>=3.10.0
33
  gTTS>=2.4.0
run_app.py DELETED
@@ -1,106 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Jan-Contract App Launcher
4
- This script helps you run the Streamlit app with proper configuration.
5
- """
6
-
7
- import os
8
- import sys
9
- import subprocess
10
- import webbrowser
11
- import time
12
-
13
-
14
- def check_dependencies():
15
- """Check if all required dependencies are installed"""
16
- # Map human/package names to actual importable module names
17
- required_modules = [
18
- ("streamlit", "streamlit"),
19
- ("streamlit-webrtc", "streamlit_webrtc"),
20
- ("opencv-python-headless", "cv2"), # import cv2, not opencv_python_headless
21
- ("av", "av"),
22
- ("SpeechRecognition", "speech_recognition"),
23
- ("gTTS", "gtts"),
24
- ("numpy", "numpy"),
25
- ]
26
-
27
- missing = []
28
- for package_label, module_name in required_modules:
29
- try:
30
- __import__(module_name)
31
- except ImportError:
32
- missing.append(package_label)
33
-
34
- if missing:
35
- print("❌ Missing dependencies:")
36
- for package in missing:
37
- print(f" - {package}")
38
- print("\n💡 Install missing packages with:")
39
- print(" pip install -r requirements.txt")
40
- return False
41
-
42
- print("✅ All dependencies are installed!")
43
- return True
44
-
45
-
46
- def check_directories():
47
- """Check if required directories exist"""
48
- required_dirs = ['video_consents', 'pdfs_demystify']
49
-
50
- for dir_name in required_dirs:
51
- if not os.path.exists(dir_name):
52
- os.makedirs(dir_name, exist_ok=True)
53
- print(f"📁 Created directory: {dir_name}")
54
-
55
- print("✅ All directories are ready!")
56
-
57
-
58
- def main():
59
- print("🚀 Jan-Contract App Launcher")
60
- print("=" * 40)
61
-
62
- # Check dependencies
63
- if not check_dependencies():
64
- print("\n❌ Please install missing dependencies before running the app.")
65
- return
66
-
67
- # Check directories
68
- check_directories()
69
-
70
- print("\n🌐 Starting Streamlit app...")
71
- print("💡 The app will open in your default browser.")
72
- print("💡 If it doesn't open automatically, go to: http://localhost:8501")
73
- print("\n📋 Tips for best experience:")
74
- print(" - Use Chrome, Firefox, or Edge")
75
- print(" - Allow camera and microphone permissions")
76
- print(" - Record videos for at least 2-3 seconds")
77
- print(" - Speak clearly for voice input")
78
-
79
- # Start the Streamlit app using `python -m streamlit` so PATH is not required
80
- try:
81
- # Open browser after a short delay
82
- def open_browser():
83
- time.sleep(3)
84
- webbrowser.open('http://localhost:8501')
85
-
86
- import threading
87
- browser_thread = threading.Thread(target=open_browser)
88
- browser_thread.daemon = True
89
- browser_thread.start()
90
-
91
- # Run Streamlit
92
- subprocess.run([
93
- sys.executable, '-m', 'streamlit', 'run', 'main_streamlit.py',
94
- '--server.port', '8501',
95
- '--server.address', 'localhost'
96
- ])
97
-
98
- except KeyboardInterrupt:
99
- print("\n👋 App stopped by user.")
100
- except Exception as e:
101
- print(f"\n❌ Error starting app: {e}")
102
- print("💡 Try running manually: python -m streamlit run main_streamlit.py")
103
-
104
-
105
- if __name__ == "__main__":
106
- main()