Spaces:
Running
Running
Cleanup and Fixes
Browse files- .streamlit/config.toml +9 -0
- API_DOCUMENTATION.md +658 -0
- agents/demystifier_agent.py +115 -35
- agents/general_assistant_agent.py +37 -17
- agents/legal_agent.py +45 -15
- agents/scheme_chatbot.py +18 -9
- components/video_recorder.py +192 -119
- core_utils/core_model_loaders.py +2 -2
- debug_models.py +19 -0
- main_fastapi.py +371 -117
- main_streamlit.py +132 -123
- requirements.txt +2 -1
- run_app.py +0 -106
.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
|
| 10 |
from langchain_community.vectorstores import FAISS
|
| 11 |
-
from
|
| 12 |
-
from
|
| 13 |
-
from
|
| 14 |
|
| 15 |
# LangGraph Imports
|
| 16 |
from langgraph.graph import StateGraph, END, START
|
| 17 |
|
| 18 |
-
# --- Tool and
|
| 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
|
| 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
|
| 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 |
-
|
| 49 |
-
|
| 50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 67 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
explained_terms_list.append(ExplainedTerm(term=term, explanation=explanation, resource_link=link))
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 99 |
def create_rag_chain(retriever):
|
| 100 |
"""Creates the Q&A chain for the interactive chat."""
|
| 101 |
-
prompt_template = """You are a helpful assistant
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
# Configure the API key from the .env file
|
| 7 |
try:
|
| 8 |
-
genai.
|
| 9 |
-
#
|
| 10 |
-
model = genai.GenerativeModel('gemini-1.5-flash')
|
| 11 |
except Exception as e:
|
| 12 |
-
print(f"Error configuring Google
|
| 13 |
-
|
|
|
|
| 14 |
|
| 15 |
def ask_gemini(prompt: str) -> str:
|
| 16 |
"""
|
| 17 |
-
Sends a prompt directly to the Google
|
| 18 |
-
|
| 19 |
"""
|
| 20 |
-
if
|
| 21 |
-
return "Error: The
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
|
| 23 |
-
|
| 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
|
| 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
|
| 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,42 +23,72 @@ class LegalTriviaOutput(BaseModel):
|
|
| 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: LegalTriviaOutput
|
| 34 |
|
| 35 |
-
# --- LangGraph Nodes
|
| 36 |
def generate_legal_doc(state: LegalAgentState):
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
| 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 |
-
|
| 54 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
return {"legal_trivia": structured_trivia}
|
| 56 |
|
| 57 |
-
# --- Build Graph
|
| 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
|
| 5 |
-
from
|
| 6 |
from pydantic import BaseModel, Field
|
| 7 |
from langchain_core.output_parsers import PydanticOutputParser
|
| 8 |
from typing import List
|
| 9 |
|
| 10 |
-
# --- Tool and
|
| 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,24 +24,33 @@ class SchemeOutput(BaseModel):
|
|
| 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 |
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
|
| 43 |
def get_search_results(query: dict):
|
| 44 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
| 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 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 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 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
| 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 |
-
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
|
| 126 |
-
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
return None
|
| 130 |
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
|
| 136 |
-
#
|
| 137 |
-
|
| 138 |
-
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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="
|
| 18 |
|
| 19 |
def load_gemini_llm():
|
| 20 |
"""Loads the Gemini LLM without any Streamlit dependencies."""
|
| 21 |
-
return ChatGoogleGenerativeAI(model="gemini-
|
|
|
|
| 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 |
-
#
|
|
|
|
| 2 |
|
| 3 |
import os
|
| 4 |
import uuid
|
| 5 |
import tempfile
|
| 6 |
import json
|
| 7 |
-
|
| 8 |
-
from
|
| 9 |
-
from fastapi
|
|
|
|
| 10 |
from fastapi.middleware.cors import CORSMiddleware
|
| 11 |
-
from pydantic import BaseModel, Field
|
| 12 |
import io
|
| 13 |
import shutil
|
|
|
|
|
|
|
| 14 |
|
| 15 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 23 |
app = FastAPI(
|
| 24 |
-
title="Jan-Contract
|
| 25 |
description="""
|
| 26 |
-
|
|
|
|
|
|
|
| 27 |
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
🎥 **Media Processing**: Audio/video consent recording and processing
|
| 33 |
|
| 34 |
Built with FastAPI, LangChain, and modern AI technologies.
|
| 35 |
""",
|
| 36 |
-
version="2.
|
| 37 |
contact={
|
| 38 |
"name": "Jan-Contract Team",
|
| 39 |
"email": "support@jan-contract.com"
|
|
@@ -44,7 +56,7 @@ app = FastAPI(
|
|
| 44 |
}
|
| 45 |
)
|
| 46 |
|
| 47 |
-
#
|
| 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 |
-
#
|
|
|
|
|
|
|
|
|
|
| 57 |
class ContractRequest(BaseModel):
|
| 58 |
-
user_request: str = Field(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
class SchemeRequest(BaseModel):
|
| 61 |
-
user_profile: str = Field(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
class ChatRequest(BaseModel):
|
| 64 |
-
session_id: str = Field(
|
| 65 |
-
|
| 66 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 67 |
class GeneralChatRequest(BaseModel):
|
| 68 |
-
question: str = Field(
|
| 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 |
-
#
|
| 75 |
class ApiResponse(BaseModel):
|
| 76 |
success: bool
|
| 77 |
message: str
|
| 78 |
-
data: Optional[
|
| 79 |
error: Optional[str] = None
|
|
|
|
| 80 |
|
| 81 |
class HealthCheck(BaseModel):
|
| 82 |
status: str
|
| 83 |
version: str
|
| 84 |
timestamp: str
|
| 85 |
-
services:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
-
# --- 5. State Management ---
|
| 88 |
SESSION_CACHE = {}
|
| 89 |
CONTRACT_CACHE = {}
|
| 90 |
|
| 91 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 126 |
timestamp=datetime.datetime.now().isoformat(),
|
| 127 |
services={
|
| 128 |
"directories": directories,
|
| 129 |
-
"modules": modules
|
|
|
|
| 130 |
}
|
| 131 |
)
|
| 132 |
|
| 133 |
-
#
|
|
|
|
|
|
|
| 134 |
|
| 135 |
-
@app.post("/
|
| 136 |
async def generate_contract(request: ContractRequest):
|
| 137 |
"""
|
| 138 |
Generate a digital contract from plain text description.
|
| 139 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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] =
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
"
|
| 156 |
}
|
| 157 |
)
|
| 158 |
except Exception as e:
|
|
|
|
| 159 |
raise HTTPException(status_code=500, detail=f"Contract generation failed: {str(e)}")
|
| 160 |
|
| 161 |
-
@app.post("/
|
| 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=
|
| 176 |
)
|
| 177 |
except Exception as e:
|
|
|
|
| 178 |
raise HTTPException(status_code=500, detail=f"PDF generation failed: {str(e)}")
|
| 179 |
|
| 180 |
-
@app.get("/
|
| 181 |
async def get_contract(contract_id: str):
|
| 182 |
"""Retrieve a previously generated contract by ID"""
|
| 183 |
-
|
| 184 |
-
raise HTTPException(status_code=404, detail="Contract not found")
|
| 185 |
|
| 186 |
return ApiResponse(
|
| 187 |
success=True,
|
| 188 |
message="Contract retrieved successfully",
|
| 189 |
-
data=
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 190 |
)
|
| 191 |
|
| 192 |
-
#
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
|
|
|
|
|
|
| 211 |
|
| 212 |
-
@app.post("/demystify/upload", tags=["
|
| 213 |
async def demystify_upload(file: UploadFile = File(...)):
|
| 214 |
"""
|
| 215 |
Upload a PDF document for AI-powered analysis.
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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":
|
|
|
|
| 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=["
|
| 257 |
async def demystify_chat(request: ChatRequest):
|
| 258 |
"""
|
| 259 |
Ask follow-up questions about an uploaded document.
|
| 260 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
"""
|
| 262 |
-
session_data =
|
| 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={
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
)
|
| 275 |
except Exception as e:
|
|
|
|
| 276 |
raise HTTPException(status_code=500, detail=f"Chat processing failed: {str(e)}")
|
| 277 |
|
| 278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
|
| 280 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 281 |
async def general_chat(request: GeneralChatRequest):
|
| 282 |
"""
|
| 283 |
Get AI-powered assistance for general questions.
|
| 284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
"""
|
| 286 |
try:
|
|
|
|
|
|
|
| 287 |
response = ask_gemini(request.question)
|
|
|
|
| 288 |
return ApiResponse(
|
| 289 |
success=True,
|
| 290 |
message="Response generated successfully",
|
| 291 |
-
data={
|
|
|
|
|
|
|
|
|
|
| 292 |
)
|
| 293 |
except Exception as e:
|
|
|
|
| 294 |
raise HTTPException(status_code=500, detail=f"AI response generation failed: {str(e)}")
|
| 295 |
|
| 296 |
-
#
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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":
|
| 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 |
-
#
|
| 375 |
-
|
| 376 |
-
|
| 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
|
| 417 |
return {
|
| 418 |
-
"message": "Jan-Contract
|
| 419 |
-
"version": "2.
|
| 420 |
"description": "Comprehensive API for India's informal workforce",
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 421 |
"endpoints": {
|
| 422 |
"health": "/health",
|
| 423 |
-
"contracts": "/
|
| 424 |
-
"schemes": "/schemes/find",
|
| 425 |
-
"demystify": "/demystify/upload",
|
| 426 |
-
"assistant": "/assistant/chat",
|
| 427 |
-
"media": "/media/upload-video"
|
| 428 |
},
|
| 429 |
-
"docs": "/docs"
|
|
|
|
| 430 |
}
|
| 431 |
|
| 432 |
-
#
|
|
|
|
|
|
|
| 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
|
| 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 |
# --- 1. Initial Setup ---
|
| 14 |
load_dotenv()
|
| 15 |
-
st.set_page_config(layout="wide", page_title="Jan-Contract Unified Assistant")
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 23 |
-
"
|
| 24 |
-
"
|
| 25 |
-
"
|
| 26 |
-
"🤖 **General Assistant**"
|
| 27 |
])
|
| 28 |
|
| 29 |
# --- TAB 1: Contract Generator ---
|
| 30 |
with tab1:
|
| 31 |
-
st.header("
|
| 32 |
-
st.write("
|
| 33 |
|
| 34 |
-
st.
|
| 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 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
pdf_bytes = generate_formatted_pdf(result['legal_doc'])
|
| 60 |
-
st.download_button(label="
|
| 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 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
st.write("Could not retrieve structured legal trivia.")
|
| 72 |
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
saved_video_path = record_consent_video()
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
|
| 85 |
-
|
| 86 |
-
|
| 87 |
-
|
| 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
|
| 93 |
with tab2:
|
| 94 |
-
st.header("
|
| 95 |
-
st.write("
|
| 96 |
|
| 97 |
-
user_profile = st.text_input("Enter your profile...", key="scheme_profile")
|
| 98 |
|
| 99 |
-
if st.button("
|
| 100 |
if user_profile:
|
| 101 |
-
with st.spinner("
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
|
|
|
|
|
|
|
|
|
| 105 |
else:
|
| 106 |
-
st.
|
| 107 |
|
| 108 |
if 'scheme_response' in st.session_state:
|
| 109 |
response = st.session_state.scheme_response
|
| 110 |
-
st.subheader(f"
|
| 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(
|
| 116 |
-
st.
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
# --- TAB 3: Demystifier & Chat ---
|
| 119 |
with tab3:
|
| 120 |
-
st.header("
|
| 121 |
-
st.
|
| 122 |
|
| 123 |
-
uploaded_file = st.file_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("
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 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 |
-
|
| 146 |
-
|
|
|
|
|
|
|
|
|
|
| 147 |
st.write(report.summary)
|
| 148 |
-
st.divider()
|
| 149 |
|
| 150 |
-
st.subheader("
|
| 151 |
for term in report.key_terms:
|
| 152 |
-
with st.expander(f"
|
| 153 |
st.write(term.explanation)
|
| 154 |
-
st.markdown(f"[Learn More
|
| 155 |
-
st.divider()
|
| 156 |
|
| 157 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|