jan-contract / main_streamlit.py
Amodit's picture
Fix Pydantic model conversion and remove Railway dependency
3aee12f
import os
import streamlit as st
import requests
from dotenv import load_dotenv
# --- 1. Initial Setup ---
load_dotenv()
st.set_page_config(layout="wide", page_title="Jan-Contract Unified Assistant", page_icon="⚖️")
# Backend Configuration
# Default to local server (run with: python -m uvicorn main:app --port 8000)
LOCAL_URL = "http://localhost:8000"
default_url = os.getenv("BACKEND_URL", LOCAL_URL)
with st.sidebar:
with st.expander("⚙️ Connection Settings"):
BACKEND_URL = st.text_input(
"Backend API URL",
value=default_url,
help="URL of the deployed Jan-Contract API"
)
# Test Connection
if st.button("Test Connection"):
try:
resp = requests.get(f"{BACKEND_URL}/health", timeout=10)
if resp.status_code == 200:
st.success("✅ Connected!")
else:
st.error(f"❌ Error: {resp.status_code}")
except Exception as e:
st.error(f"❌ Failed: {e}")
# Only strip trailing slash if it exists
if BACKEND_URL.endswith("/"):
BACKEND_URL = BACKEND_URL[:-1]
# --- Custom CSS ---
st.markdown("""
<style>
.main-header { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; color: #333; }
h1 { color: #1A73E8; }
h2, h3 { color: #424242; }
.stButton>button { color: #ffffff; background-color: #1A73E8; border-radius: 5px; }
.success-msg { color: #2e7d32; font-weight: bold; }
.error-msg { color: #c62828; font-weight: bold; }
</style>
""", unsafe_allow_html=True)
st.title("Jan-Contract: Digital Workforce Assistant")
st.caption(f"Connected to Backend: `{BACKEND_URL}`")
st.write("Empowering India's workforce with accessible legal tools and government scheme discovery.")
# --- 2. Streamlit UI with Tabs ---
tab1, tab2, tab3 = st.tabs([
"Contract Generator",
"Scheme Finder",
"Document Demystifier"
])
# --- TAB 1: Contract Generator ---
with tab1:
st.header("Digital Agreement Generator")
st.write("Create a clear digital agreement from plain text and record video consent.")
col1, col2 = st.columns([1, 1])
with col1:
st.subheader("Agreement Details")
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.")
if st.button("Generate Agreement", type="primary", key="btn_generate_contract"):
if user_request:
with st.spinner("Drafting agreement via API..."):
try:
payload = {"user_request": user_request}
response = requests.post(f"{BACKEND_URL}/api/v1/contracts/generate", json=payload)
if response.status_code == 200:
data = response.json()
if data.get("success"):
st.session_state.legal_result = data["data"]
if 'video_path_from_component' in st.session_state:
del st.session_state['video_path_from_component']
else:
st.error(f"API Error: {data.get('message')}")
else:
st.error(f"Server Error: {response.text}")
except Exception as e:
st.error(f"Connection failed: {e}")
else:
st.warning("Please describe the agreement details.")
with col2:
if 'legal_result' in st.session_state:
result = st.session_state.legal_result
contract_text = result.get('contract', '')
st.subheader("Drafted Agreement")
with st.container(border=True):
st.markdown(contract_text)
# PDF Download via API
if st.button("Download PDF"):
try:
pdf_Payload = {"user_request": result.get('user_request', user_request)}
pdf_resp = requests.post(f"{BACKEND_URL}/api/v1/contracts/generate-pdf", json=pdf_Payload)
if pdf_resp.status_code == 200:
st.download_button(label="Click to Save PDF", data=pdf_resp.content, file_name="agreement.pdf", mime="application/pdf")
else:
st.error("Failed to generate PDF")
except Exception as e:
st.error(f"Download failed: {e}")
if result.get('legal_trivia') and result['legal_trivia'].get('trivia'):
with st.expander("Legal Insights"):
for item in result['legal_trivia']['trivia']:
st.markdown(f"**{item['point']}**")
st.caption(item['explanation'])
st.markdown(f"[Source]({item['source_url']})")
st.divider()
from components.video_recorder import record_consent_video
st.subheader("Video Consent Recording")
saved_video_path = record_consent_video()
# Upload video to backend if recorded
if saved_video_path and 'legal_result' in st.session_state:
contract_id = st.session_state.legal_result.get('contract_id')
if contract_id:
if st.button("Upload Consent to Server"):
try:
with open(saved_video_path, 'rb') as f:
files = {'file': (os.path.basename(saved_video_path), f, 'video/mp4')}
data = {'contract_id': contract_id, 'consent_text': "I agree to the terms."}
up_resp = requests.post(f"{BACKEND_URL}/api/v1/media/upload-video", files=files, data=data)
if up_resp.status_code == 200:
st.success("Consent uploaded to server securely!")
else:
st.error(f"Upload failed: {up_resp.text}")
except Exception as e:
st.error(f"Upload error: {e}")
# --- TAB 2: Scheme Finder ---
with tab2:
st.header("Government Scheme Finder")
st.write("Find relevant government schemes based on your profile.")
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.")
if st.button("Search Schemes", type="primary", key="btn_find_schemes"):
if user_profile:
with st.spinner("Searching schemes via API..."):
try:
payload = {"user_profile": user_profile}
response = requests.post(f"{BACKEND_URL}/api/v1/schemes/find", json=payload)
if response.status_code == 200:
st.session_state.scheme_response = response.json().get('data', {})
else:
st.error(f"Error: {response.text}")
except Exception as e:
st.error(f"Connection error: {e}")
else:
st.warning("Please enter a profile description.")
if 'scheme_response' in st.session_state:
response = st.session_state.scheme_response
st.subheader(f"Results")
schemes = response.get('schemes', [])
if schemes:
for scheme in schemes:
with st.container(border=True):
st.markdown(f"#### {scheme.get('scheme_name')}")
st.write(scheme.get('description'))
st.write(f"**Target Audience:** {scheme.get('target_audience')}")
st.markdown(f"[Official Website]({scheme.get('official_link')})")
else:
st.info("No specific schemes found.")
# --- TAB 3: Demystifier ---
with tab3:
st.header("Document Demystifier")
st.write("Upload a legal document to get a simplified summary and ask questions.")
uploaded_file = st.file_uploader("Upload PDF Document", type="pdf", key="demystify_uploader")
if uploaded_file and st.button("Analyze Document", type="primary"):
with st.spinner("Uploading and analyzing..."):
try:
files = {"file": (uploaded_file.name, uploaded_file.getvalue(), "application/pdf")}
response = requests.post(f"{BACKEND_URL}/api/v1/demystify/upload", files=files)
if response.status_code == 200:
st.session_state.demystify_data = response.json().get('data', {})
st.session_state.session_id = st.session_state.demystify_data.get('session_id')
st.success("Analysis complete!")
else:
st.error(f"Analysis failed: {response.text}")
except Exception as e:
st.error(f"Connection error: {e}")
if 'demystify_data' in st.session_state:
st.divider()
report = st.session_state.demystify_data.get('report', {})
tab_summary, tab_chat = st.tabs(["Summary & Analysis", "Chat with Document"])
with tab_summary:
st.subheader("Document Summary")
st.write(report.get('summary', ''))
st.subheader("Key Terms Explained")
for term in report.get('key_terms', []):
with st.expander(f"{term.get('term')}"):
st.write(term.get('explanation'))
st.markdown(f"[Learn More]({term.get('resource_link')})")
st.info(f"**Advice:** {report.get('overall_advice')}")
with tab_chat:
st.subheader("Ask Questions")
# Simple Chat Interface for API
if "messages" not in st.session_state:
st.session_state.messages = []
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
if prompt := st.chat_input("Ask about the document..."):
st.session_state.messages.append({"role": "user", "content": prompt})
with st.chat_message("user"):
st.markdown(prompt)
with st.chat_message("assistant"):
with st.spinner("Thinking..."):
try:
payload = {
"session_id": st.session_state.session_id,
"question": prompt
}
chat_resp = requests.post(f"{BACKEND_URL}/api/v1/demystify/chat", json=payload)
if chat_resp.status_code == 200:
answer = chat_resp.json()['data']['answer']
st.markdown(answer)
st.session_state.messages.append({"role": "assistant", "content": answer})
else:
st.error("Failed to get answer.")
except Exception as e:
st.error(f"Error: {e}")