Nischal Subedi
commited on
Commit
·
4596ac3
1
Parent(s):
3c8fabc
UI v13
Browse files
app.py
CHANGED
|
@@ -1,28 +1,174 @@
|
|
| 1 |
import os
|
| 2 |
import logging
|
|
|
|
|
|
|
| 3 |
from typing import Dict, List, Optional
|
| 4 |
from functools import lru_cache
|
| 5 |
-
import re
|
| 6 |
|
| 7 |
import gradio as gr
|
| 8 |
-
import gradio.themes as themes # Import gradio.themes
|
| 9 |
|
|
|
|
| 10 |
try:
|
| 11 |
# Assuming vector_db.py exists in the same directory or is installed
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
except ImportError:
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
|
|
|
|
| 18 |
try:
|
| 19 |
from langchain_openai import ChatOpenAI
|
|
|
|
|
|
|
| 20 |
except ImportError:
|
| 21 |
-
|
| 22 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
|
| 24 |
-
from langchain.prompts import PromptTemplate
|
| 25 |
-
from langchain.chains import LLMChain
|
| 26 |
|
| 27 |
# Suppress warnings
|
| 28 |
import warnings
|
|
@@ -36,7 +182,7 @@ logging.basicConfig(
|
|
| 36 |
format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
|
| 37 |
)
|
| 38 |
|
| 39 |
-
# --- RAGSystem Class
|
| 40 |
class RAGSystem:
|
| 41 |
def __init__(self, vector_db: Optional[VectorDatabase] = None):
|
| 42 |
logging.info("Initializing RAGSystem")
|
|
@@ -223,7 +369,6 @@ Answer:"""
|
|
| 223 |
raise FileNotFoundError(f"PDF file not found: {pdf_path}")
|
| 224 |
try:
|
| 225 |
logging.info(f"Attempting to load/verify data from PDF: {pdf_path}")
|
| 226 |
-
# Assuming process_and_load_pdf is part of VectorDatabase and correctly implemented
|
| 227 |
num_states_processed = self.vector_db.process_and_load_pdf(pdf_path)
|
| 228 |
doc_count = self.vector_db.document_collection.count()
|
| 229 |
state_count = self.vector_db.state_collection.count()
|
|
@@ -241,7 +386,7 @@ Answer:"""
|
|
| 241 |
logging.error(f"Failed to load or process PDF '{pdf_path}': {str(e)}", exc_info=True)
|
| 242 |
raise RuntimeError(f"Failed to process PDF '{pdf_path}': {e}") from e
|
| 243 |
|
| 244 |
-
# --- GRADIO INTERFACE
|
| 245 |
def gradio_interface(self):
|
| 246 |
def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
|
| 247 |
# Basic client-side validation for immediate feedback (redundant but good UX)
|
|
@@ -278,7 +423,13 @@ Answer:"""
|
|
| 278 |
["Can a landlord enter my apartment without notice?", "New York"],
|
| 279 |
["My landlord hasn't made necessary repairs. What can I do?", "Texas"],
|
| 280 |
["How much notice must a landlord give to raise rent?", "Florida"],
|
| 281 |
-
["What is an implied warranty of habitability?", "Illinois"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 282 |
]
|
| 283 |
example_queries = []
|
| 284 |
if available_states_list and "Error" not in available_states_list[0] and len(available_states_list) > 0:
|
|
@@ -596,7 +747,7 @@ Answer:"""
|
|
| 596 |
}
|
| 597 |
|
| 598 |
.gr-button-primary:hover {
|
| 599 |
-
|
| 600 |
transform: translateY(-2px) scale(1.02) !important;
|
| 601 |
}
|
| 602 |
|
|
@@ -615,7 +766,7 @@ Answer:"""
|
|
| 615 |
background: var(--surface-accent) !important;
|
| 616 |
border-color: var(--accent-color) !important;
|
| 617 |
transform: translateY(-1px) !important;
|
| 618 |
-
|
| 619 |
}
|
| 620 |
|
| 621 |
/* Exceptional output styling */
|
|
@@ -675,7 +826,7 @@ Answer:"""
|
|
| 675 |
align-items: flex-start !important;
|
| 676 |
gap: var(--spacing-md) !important;
|
| 677 |
font-size: 0.9rem !important;
|
| 678 |
-
|
| 679 |
}
|
| 680 |
|
| 681 |
.error-icon {
|
|
@@ -714,7 +865,7 @@ Answer:"""
|
|
| 714 |
border-radius: var(--border-radius-sm) !important;
|
| 715 |
overflow: hidden !important;
|
| 716 |
margin-top: var(--spacing-lg) !important;
|
| 717 |
-
|
| 718 |
}
|
| 719 |
|
| 720 |
.examples-section .gr-samples-table th,
|
|
@@ -754,7 +905,7 @@ Answer:"""
|
|
| 754 |
padding: var(--spacing-lg) !important;
|
| 755 |
margin-top: var(--spacing-lg) !important;
|
| 756 |
text-align: center !important;
|
| 757 |
-
|
| 758 |
}
|
| 759 |
|
| 760 |
.app-footer p {
|
|
@@ -894,7 +1045,7 @@ Answer:"""
|
|
| 894 |
examples=example_queries,
|
| 895 |
inputs=[query_input, state_input],
|
| 896 |
examples_per_page=5,
|
| 897 |
-
label=""
|
| 898 |
)
|
| 899 |
else:
|
| 900 |
gr.Markdown("<div class='placeholder'>Sample questions could not be loaded.</div>")
|
|
@@ -914,15 +1065,15 @@ Answer:"""
|
|
| 914 |
fn=query_interface_wrapper,
|
| 915 |
inputs=[api_key_input, query_input, state_input],
|
| 916 |
outputs=output,
|
| 917 |
-
api_name="submit_query"
|
| 918 |
)
|
| 919 |
|
| 920 |
clear_button.click(
|
| 921 |
fn=lambda: (
|
| 922 |
-
"",
|
| 923 |
-
"",
|
| 924 |
-
initial_value,
|
| 925 |
-
"<div class='placeholder'>Inputs cleared. Ready for your next question.</div>"
|
| 926 |
),
|
| 927 |
inputs=[],
|
| 928 |
outputs=[api_key_input, query_input, state_input, output]
|
|
@@ -930,7 +1081,7 @@ Answer:"""
|
|
| 930 |
|
| 931 |
return demo
|
| 932 |
|
| 933 |
-
# --- Main Execution Block
|
| 934 |
if __name__ == "__main__":
|
| 935 |
logging.info("Starting Landlord-Tenant Rights Bot application...")
|
| 936 |
try:
|
|
@@ -938,35 +1089,44 @@ if __name__ == "__main__":
|
|
| 938 |
DEFAULT_PDF_PATH = os.path.join(SCRIPT_DIR, "tenant-landlord.pdf")
|
| 939 |
DEFAULT_DB_PATH = os.path.join(SCRIPT_DIR, "chroma_db")
|
| 940 |
|
|
|
|
| 941 |
PDF_PATH = os.getenv("PDF_PATH", DEFAULT_PDF_PATH)
|
| 942 |
VECTOR_DB_PATH = os.getenv("VECTOR_DB_PATH", DEFAULT_DB_PATH)
|
| 943 |
|
|
|
|
| 944 |
os.makedirs(os.path.dirname(VECTOR_DB_PATH), exist_ok=True)
|
| 945 |
|
| 946 |
logging.info(f"Attempting to load PDF from: {PDF_PATH}")
|
| 947 |
if not os.path.exists(PDF_PATH):
|
| 948 |
logging.error(f"FATAL: PDF file not found at the specified path: {PDF_PATH}")
|
| 949 |
print(f"\n--- CONFIGURATION ERROR ---\nPDF file ('{os.path.basename(PDF_PATH)}') not found at: {PDF_PATH}.\nPlease ensure it exists or set 'PDF_PATH' environment variable.\n---------------------------\n")
|
| 950 |
-
exit(1) #
|
| 951 |
|
| 952 |
if not os.access(PDF_PATH, os.R_OK):
|
| 953 |
logging.error(f"FATAL: PDF file at '{PDF_PATH}' exists but is not readable. Check file permissions.")
|
| 954 |
print(f"\n--- PERMISSION ERROR ---\nPDF file ('{os.path.basename(PDF_PATH)}') found but not readable at: {PDF_PATH}\nPlease check file permissions (e.g., using 'chmod +r' in terminal).\n---------------------------\n")
|
| 955 |
-
exit(1) #
|
| 956 |
|
| 957 |
logging.info(f"PDF file '{os.path.basename(PDF_PATH)}' found and is readable.")
|
| 958 |
|
|
|
|
| 959 |
vector_db_instance = VectorDatabase(persist_directory=VECTOR_DB_PATH)
|
| 960 |
rag = RAGSystem(vector_db=vector_db_instance)
|
| 961 |
|
|
|
|
| 962 |
rag.load_pdf(PDF_PATH)
|
| 963 |
|
|
|
|
| 964 |
app_interface = rag.gradio_interface()
|
| 965 |
-
|
|
|
|
|
|
|
| 966 |
|
| 967 |
logging.info(f"Launching Gradio app on http://0.0.0.0:{SERVER_PORT}")
|
| 968 |
print(f"\n--- Gradio App Running ---\nAccess at: http://localhost:{SERVER_PORT} or your public Spaces URL\n--------------------------\n")
|
| 969 |
-
|
|
|
|
|
|
|
| 970 |
|
| 971 |
except ModuleNotFoundError as e:
|
| 972 |
if "vector_db" in str(e):
|
|
|
|
| 1 |
import os
|
| 2 |
import logging
|
| 3 |
+
import re
|
| 4 |
+
import time # Added for time.sleep in placeholder functions
|
| 5 |
from typing import Dict, List, Optional
|
| 6 |
from functools import lru_cache
|
|
|
|
| 7 |
|
| 8 |
import gradio as gr
|
| 9 |
+
import gradio.themes as themes # Import gradio.themes (though not explicitly used in this exact UI, it's a good practice)
|
| 10 |
|
| 11 |
+
# --- Ensure vector_db.py is accessible ---
|
| 12 |
try:
|
| 13 |
# Assuming vector_db.py exists in the same directory or is installed
|
| 14 |
+
# Placeholder for VectorDatabase if the file is not provided
|
| 15 |
+
class VectorDatabase:
|
| 16 |
+
def __init__(self, persist_directory: str = "chroma_db"):
|
| 17 |
+
self.persist_directory = persist_directory
|
| 18 |
+
self.documents = {} # Simulating document storage
|
| 19 |
+
self.states = [] # Simulating state storage
|
| 20 |
+
logging.info(f"VectorDatabase initialized (placeholder) at {persist_directory}")
|
| 21 |
+
|
| 22 |
+
def process_and_load_pdf(self, pdf_path: str) -> int:
|
| 23 |
+
logging.info(f"Placeholder: Processing and loading PDF '{pdf_path}'...")
|
| 24 |
+
# Simulate parsing a PDF and extracting content
|
| 25 |
+
# In a real scenario, this would use PyPDFLoader, RecursiveCharacterTextSplitter, Chroma.from_documents
|
| 26 |
+
if not self.documents: # Only load once for simulation
|
| 27 |
+
self.documents = {
|
| 28 |
+
"doc1": "California Civil Code § 1950.5: Security deposit limit is two months' rent. Must be returned within 21 days.",
|
| 29 |
+
"doc2": "New York Real Property Law § 235-b: Implied Warranty of Habitability. Landlord must keep premises fit for human habitation.",
|
| 30 |
+
"doc3": "Texas Property Code § 92.056: Landlord's duty to repair or remedy. Tenant must give notice and time to repair.",
|
| 31 |
+
"doc4": "Florida Statutes § 83.56: Termination of rental agreement. Requires specific notice periods for rent increases or lease termination.",
|
| 32 |
+
"doc5": "Illinois Landlord and Tenant Act § 765 ILCS 705/1: Security Deposit Return Act. Landlord must return deposit within 45 days. ",
|
| 33 |
+
"doc6": "Washington RCW 59.18.230: Tenant's right to quiet enjoyment. Landlord may not interfere with tenant's privacy.",
|
| 34 |
+
"state_summary_ca": "California: Strong tenant protections, rent control, and strict security deposit rules.",
|
| 35 |
+
"state_summary_ny": "New York: Extensive habitability laws, rent stabilization in some areas, and detailed eviction procedures.",
|
| 36 |
+
"state_summary_tx": "Texas: More landlord-friendly, but still has rules on repairs, evictions, and security deposits.",
|
| 37 |
+
"state_summary_fl": "Florida: Clear statutes on lease termination, eviction, and security deposits.",
|
| 38 |
+
"state_summary_il": "Illinois: Rules on security deposits and landlord's duties, especially in Chicago.",
|
| 39 |
+
"state_summary_wa": "Washington: Just cause eviction, security deposit rules, and tenant privacy laws.",
|
| 40 |
+
}
|
| 41 |
+
self.states = ["California", "New York", "Texas", "Florida", "Illinois", "Washington", "Massachusetts", "Colorado", "Pennsylvania", "Ohio", "Georgia", "North Carolina", "Virginia", "Michigan", "Arizona"]
|
| 42 |
+
logging.info(f"Placeholder: Simulated loading {len(self.documents)} documents and {len(self.states)} states.")
|
| 43 |
+
return len(self.states)
|
| 44 |
+
|
| 45 |
+
def query(self, query_text: str, state: str = None, n_results: int = 5) -> Dict[str, any]:
|
| 46 |
+
logging.info(f"Placeholder: Querying DB for '{query_text[:50]}...' in state '{state}'")
|
| 47 |
+
# Simulate relevant document retrieval
|
| 48 |
+
doc_matches = []
|
| 49 |
+
for key, content in self.documents.items():
|
| 50 |
+
if state and state.lower() in key.lower() or query_text.lower() in content.lower():
|
| 51 |
+
doc_matches.append(content)
|
| 52 |
+
|
| 53 |
+
# Simple simulation: return up to n_results relevant docs and a state summary
|
| 54 |
+
documents_retrieved = []
|
| 55 |
+
metadatas_retrieved = []
|
| 56 |
+
for i, doc_content in enumerate(doc_matches):
|
| 57 |
+
if len(documents_retrieved) >= n_results:
|
| 58 |
+
break
|
| 59 |
+
|
| 60 |
+
# Extract state from content or use provided state
|
| 61 |
+
match_state = "Unknown"
|
| 62 |
+
for s in self.states:
|
| 63 |
+
if s.lower() in doc_content.lower():
|
| 64 |
+
match_state = s
|
| 65 |
+
break
|
| 66 |
+
if match_state == "Unknown" and state:
|
| 67 |
+
match_state = state # Fallback to query state if not found in content
|
| 68 |
+
|
| 69 |
+
documents_retrieved.append(doc_content)
|
| 70 |
+
metadatas_retrieved.append({"state": match_state, "chunk_id": f"sim_chunk_{i+1}"})
|
| 71 |
+
|
| 72 |
+
state_summary_doc = None
|
| 73 |
+
state_summary_metadata = None
|
| 74 |
+
if state:
|
| 75 |
+
for key, content in self.documents.items():
|
| 76 |
+
if f"state_summary_{state.lower()}" in key.lower().replace(" ", "_"):
|
| 77 |
+
state_summary_doc = content
|
| 78 |
+
state_summary_metadata = {"state": state, "type": "summary"}
|
| 79 |
+
break
|
| 80 |
+
|
| 81 |
+
results = {
|
| 82 |
+
"document_results": {"documents": [documents_retrieved], "metadatas": [metadatas_retrieved]},
|
| 83 |
+
"state_results": {"documents": [[state_summary_doc]] if state_summary_doc else [[]], "metadatas": [[state_summary_metadata]] if state_summary_metadata else [[]]}
|
| 84 |
+
}
|
| 85 |
+
logging.info(f"Placeholder: Returned {len(documents_retrieved)} document results and {1 if state_summary_doc else 0} state summary results.")
|
| 86 |
+
return results
|
| 87 |
+
|
| 88 |
+
def get_states(self) -> List[str]:
|
| 89 |
+
logging.info("Placeholder: Getting states from DB")
|
| 90 |
+
# Simulate loading states or return pre-defined ones
|
| 91 |
+
return sorted(list(set(self.states)))
|
| 92 |
+
|
| 93 |
+
def document_collection(self): # Simulates Chroma collection
|
| 94 |
+
return type('Collection', (object,), {'count': lambda: len(self.documents)})()
|
| 95 |
+
|
| 96 |
+
def state_collection(self): # Simulates Chroma collection
|
| 97 |
+
return type('Collection', (object,), {'count': lambda: len(self.states)})()
|
| 98 |
+
|
| 99 |
+
|
| 100 |
except ImportError:
|
| 101 |
+
logging.error("Error: Could not import VectorDatabase. Using a placeholder for demonstration. Please ensure vector_db.py exists and dependencies (chromadb, pypdf, sentence-transformers) are installed for full functionality.")
|
| 102 |
+
# Define a simple placeholder if vector_db.py is missing
|
| 103 |
+
class VectorDatabase:
|
| 104 |
+
def __init__(self, persist_directory: str = "chroma_db"):
|
| 105 |
+
logging.warning("Using placeholder VectorDatabase. Full functionality requires 'vector_db.py'.")
|
| 106 |
+
self.persist_directory = persist_directory
|
| 107 |
+
self.documents = {}
|
| 108 |
+
self.states = []
|
| 109 |
+
|
| 110 |
+
def process_and_load_pdf(self, pdf_path: str) -> int:
|
| 111 |
+
logging.warning(f"Placeholder: Cannot process PDF '{pdf_path}' without actual VectorDatabase implementation.")
|
| 112 |
+
self.documents = {
|
| 113 |
+
"doc1": "California Civil Code § 1950.5: Security deposit limit is two months' rent. Must be returned within 21 days.",
|
| 114 |
+
"doc2": "New York Real Property Law § 235-b: Implied Warranty of Habitability. Landlord must keep premises fit for human habitation.",
|
| 115 |
+
"doc3": "Texas Property Code § 92.056: Landlord's duty to repair or remedy. Tenant must give notice and time to repair.",
|
| 116 |
+
}
|
| 117 |
+
self.states = ["California", "New York", "Texas", "Florida", "Illinois"]
|
| 118 |
+
return len(self.states) # Simulate some states loaded
|
| 119 |
+
|
| 120 |
+
def query(self, query_text: str, state: str = None, n_results: int = 5) -> Dict[str, any]:
|
| 121 |
+
logging.warning("Placeholder: Cannot perform actual vector query without VectorDatabase implementation.")
|
| 122 |
+
# Simple dummy response
|
| 123 |
+
if state == "California":
|
| 124 |
+
return {"answer": f"Simulated response for California: Security deposits are governed by specific statutes like Civil Code § 1950.5.", "context_used": "Simulated context for CA"}
|
| 125 |
+
return {"answer": f"Simulated response for {state}: Landlord-tenant laws vary by state.", "context_used": "Simulated general context"}
|
| 126 |
+
|
| 127 |
+
def get_states(self) -> List[str]:
|
| 128 |
+
logging.warning("Placeholder: Getting states from dummy VectorDatabase.")
|
| 129 |
+
return ["California", "New York", "Texas", "Florida", "Illinois"]
|
| 130 |
+
|
| 131 |
+
def document_collection(self): # Simulates Chroma collection
|
| 132 |
+
return type('Collection', (object,), {'count': lambda: len(self.documents)})()
|
| 133 |
+
|
| 134 |
+
def state_collection(self): # Simulates Chroma collection
|
| 135 |
+
return type('Collection', (object,), {'count': lambda: len(self.states)})()
|
| 136 |
+
|
| 137 |
|
| 138 |
+
# --- Ensure langchain_openai is accessible ---
|
| 139 |
try:
|
| 140 |
from langchain_openai import ChatOpenAI
|
| 141 |
+
from langchain.prompts import PromptTemplate
|
| 142 |
+
from langchain.chains import LLMChain
|
| 143 |
except ImportError:
|
| 144 |
+
logging.error("Error: langchain-openai or langchain components not found. Please install them: pip install langchain-openai langchain.")
|
| 145 |
+
# Define placeholder classes if Langchain is missing
|
| 146 |
+
class ChatOpenAI:
|
| 147 |
+
def __init__(self, *args, **kwargs):
|
| 148 |
+
logging.warning("Using placeholder ChatOpenAI. Install 'langchain-openai' for actual LLM functionality.")
|
| 149 |
+
self.kwargs = kwargs
|
| 150 |
+
def invoke(self, messages):
|
| 151 |
+
if "fail" in messages.get("query", "").lower():
|
| 152 |
+
raise Exception("Simulated LLM error.")
|
| 153 |
+
return {"text": f"Placeholder LLM response for query: '{messages.get('query')}' in state '{messages.get('state')}'. Please install langchain-openai for real AI responses."}
|
| 154 |
+
|
| 155 |
+
class PromptTemplate:
|
| 156 |
+
def __init__(self, input_variables, template):
|
| 157 |
+
self.input_variables = input_variables
|
| 158 |
+
self.template = template
|
| 159 |
+
logging.warning("Using placeholder PromptTemplate.")
|
| 160 |
+
|
| 161 |
+
class LLMChain:
|
| 162 |
+
def __init__(self, llm, prompt):
|
| 163 |
+
self.llm = llm
|
| 164 |
+
self.prompt = prompt
|
| 165 |
+
logging.warning("Using placeholder LLMChain.")
|
| 166 |
+
def invoke(self, input_data):
|
| 167 |
+
# Simulate the prompt being filled and passed to LLM
|
| 168 |
+
filled_prompt = self.prompt.template.format(**input_data)
|
| 169 |
+
logging.info(f"Placeholder: LLMChain invoking with prompt: {filled_prompt[:100]}...")
|
| 170 |
+
return self.llm.invoke(input_data)
|
| 171 |
|
|
|
|
|
|
|
| 172 |
|
| 173 |
# Suppress warnings
|
| 174 |
import warnings
|
|
|
|
| 182 |
format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
|
| 183 |
)
|
| 184 |
|
| 185 |
+
# --- RAGSystem Class ---
|
| 186 |
class RAGSystem:
|
| 187 |
def __init__(self, vector_db: Optional[VectorDatabase] = None):
|
| 188 |
logging.info("Initializing RAGSystem")
|
|
|
|
| 369 |
raise FileNotFoundError(f"PDF file not found: {pdf_path}")
|
| 370 |
try:
|
| 371 |
logging.info(f"Attempting to load/verify data from PDF: {pdf_path}")
|
|
|
|
| 372 |
num_states_processed = self.vector_db.process_and_load_pdf(pdf_path)
|
| 373 |
doc_count = self.vector_db.document_collection.count()
|
| 374 |
state_count = self.vector_db.state_collection.count()
|
|
|
|
| 386 |
logging.error(f"Failed to load or process PDF '{pdf_path}': {str(e)}", exc_info=True)
|
| 387 |
raise RuntimeError(f"Failed to process PDF '{pdf_path}': {e}") from e
|
| 388 |
|
| 389 |
+
# --- GRADIO INTERFACE ---
|
| 390 |
def gradio_interface(self):
|
| 391 |
def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
|
| 392 |
# Basic client-side validation for immediate feedback (redundant but good UX)
|
|
|
|
| 423 |
["Can a landlord enter my apartment without notice?", "New York"],
|
| 424 |
["My landlord hasn't made necessary repairs. What can I do?", "Texas"],
|
| 425 |
["How much notice must a landlord give to raise rent?", "Florida"],
|
| 426 |
+
["What is an implied warranty of habitability?", "Illinois"],
|
| 427 |
+
["Can a landlord evict a tenant for not paying rent?", "California"],
|
| 428 |
+
["What is a fixed-term lease?", "New York"],
|
| 429 |
+
["Are emotional support animals allowed?", "Texas"],
|
| 430 |
+
["What is a notice to quit?", "Florida"],
|
| 431 |
+
["How do I break my lease early?", "Illinois"],
|
| 432 |
+
["What are the quiet enjoyment rights?", "Washington"],
|
| 433 |
]
|
| 434 |
example_queries = []
|
| 435 |
if available_states_list and "Error" not in available_states_list[0] and len(available_states_list) > 0:
|
|
|
|
| 747 |
}
|
| 748 |
|
| 749 |
.gr-button-primary:hover {
|
| 750 |
+
box_shadow: var(--shadow-medium) !important;
|
| 751 |
transform: translateY(-2px) scale(1.02) !important;
|
| 752 |
}
|
| 753 |
|
|
|
|
| 766 |
background: var(--surface-accent) !important;
|
| 767 |
border-color: var(--accent-color) !important;
|
| 768 |
transform: translateY(-1px) !important;
|
| 769 |
+
box_shadow: var(--shadow-soft) !important;
|
| 770 |
}
|
| 771 |
|
| 772 |
/* Exceptional output styling */
|
|
|
|
| 826 |
align-items: flex-start !important;
|
| 827 |
gap: var(--spacing-md) !important;
|
| 828 |
font-size: 0.9rem !important;
|
| 829 |
+
box_shadow: var(--shadow-soft) !important;
|
| 830 |
}
|
| 831 |
|
| 832 |
.error-icon {
|
|
|
|
| 865 |
border-radius: var(--border-radius-sm) !important;
|
| 866 |
overflow: hidden !important;
|
| 867 |
margin-top: var(--spacing-lg) !important;
|
| 868 |
+
box_shadow: var(--shadow-soft) !important;
|
| 869 |
}
|
| 870 |
|
| 871 |
.examples-section .gr-samples-table th,
|
|
|
|
| 905 |
padding: var(--spacing-lg) !important;
|
| 906 |
margin-top: var(--spacing-lg) !important;
|
| 907 |
text-align: center !important;
|
| 908 |
+
box_shadow: var(--shadow-soft) !important;
|
| 909 |
}
|
| 910 |
|
| 911 |
.app-footer p {
|
|
|
|
| 1045 |
examples=example_queries,
|
| 1046 |
inputs=[query_input, state_input],
|
| 1047 |
examples_per_page=5,
|
| 1048 |
+
label="" # Hide the default "Examples" label
|
| 1049 |
)
|
| 1050 |
else:
|
| 1051 |
gr.Markdown("<div class='placeholder'>Sample questions could not be loaded.</div>")
|
|
|
|
| 1065 |
fn=query_interface_wrapper,
|
| 1066 |
inputs=[api_key_input, query_input, state_input],
|
| 1067 |
outputs=output,
|
| 1068 |
+
api_name="submit_query" # API name for potential external calls
|
| 1069 |
)
|
| 1070 |
|
| 1071 |
clear_button.click(
|
| 1072 |
fn=lambda: (
|
| 1073 |
+
"", # Clear API key
|
| 1074 |
+
"", # Clear query input
|
| 1075 |
+
initial_value, # Reset state dropdown to initial value
|
| 1076 |
+
"<div class='placeholder'>Inputs cleared. Ready for your next question.</div>" # Reset output
|
| 1077 |
),
|
| 1078 |
inputs=[],
|
| 1079 |
outputs=[api_key_input, query_input, state_input, output]
|
|
|
|
| 1081 |
|
| 1082 |
return demo
|
| 1083 |
|
| 1084 |
+
# --- Main Execution Block ---
|
| 1085 |
if __name__ == "__main__":
|
| 1086 |
logging.info("Starting Landlord-Tenant Rights Bot application...")
|
| 1087 |
try:
|
|
|
|
| 1089 |
DEFAULT_PDF_PATH = os.path.join(SCRIPT_DIR, "tenant-landlord.pdf")
|
| 1090 |
DEFAULT_DB_PATH = os.path.join(SCRIPT_DIR, "chroma_db")
|
| 1091 |
|
| 1092 |
+
# Use environment variables for paths if available, otherwise use defaults
|
| 1093 |
PDF_PATH = os.getenv("PDF_PATH", DEFAULT_PDF_PATH)
|
| 1094 |
VECTOR_DB_PATH = os.getenv("VECTOR_DB_PATH", DEFAULT_DB_PATH)
|
| 1095 |
|
| 1096 |
+
# Ensure the directory for the vector database exists
|
| 1097 |
os.makedirs(os.path.dirname(VECTOR_DB_PATH), exist_ok=True)
|
| 1098 |
|
| 1099 |
logging.info(f"Attempting to load PDF from: {PDF_PATH}")
|
| 1100 |
if not os.path.exists(PDF_PATH):
|
| 1101 |
logging.error(f"FATAL: PDF file not found at the specified path: {PDF_PATH}")
|
| 1102 |
print(f"\n--- CONFIGURATION ERROR ---\nPDF file ('{os.path.basename(PDF_PATH)}') not found at: {PDF_PATH}.\nPlease ensure it exists or set 'PDF_PATH' environment variable.\n---------------------------\n")
|
| 1103 |
+
exit(1) # Exit if PDF not found
|
| 1104 |
|
| 1105 |
if not os.access(PDF_PATH, os.R_OK):
|
| 1106 |
logging.error(f"FATAL: PDF file at '{PDF_PATH}' exists but is not readable. Check file permissions.")
|
| 1107 |
print(f"\n--- PERMISSION ERROR ---\nPDF file ('{os.path.basename(PDF_PATH)}') found but not readable at: {PDF_PATH}\nPlease check file permissions (e.g., using 'chmod +r' in terminal).\n---------------------------\n")
|
| 1108 |
+
exit(1) # Exit if PDF not readable
|
| 1109 |
|
| 1110 |
logging.info(f"PDF file '{os.path.basename(PDF_PATH)}' found and is readable.")
|
| 1111 |
|
| 1112 |
+
# Initialize VectorDatabase and RAGSystem
|
| 1113 |
vector_db_instance = VectorDatabase(persist_directory=VECTOR_DB_PATH)
|
| 1114 |
rag = RAGSystem(vector_db=vector_db_instance)
|
| 1115 |
|
| 1116 |
+
# Load PDF into the vector database (or verify it's loaded if already persisted)
|
| 1117 |
rag.load_pdf(PDF_PATH)
|
| 1118 |
|
| 1119 |
+
# Get the Gradio interface object from the RAGSystem instance
|
| 1120 |
app_interface = rag.gradio_interface()
|
| 1121 |
+
|
| 1122 |
+
# Determine server port (for Gradio Spaces compatibility)
|
| 1123 |
+
SERVER_PORT = int(os.getenv("PORT", 7860))
|
| 1124 |
|
| 1125 |
logging.info(f"Launching Gradio app on http://0.0.0.0:{SERVER_PORT}")
|
| 1126 |
print(f"\n--- Gradio App Running ---\nAccess at: http://localhost:{SERVER_PORT} or your public Spaces URL\n--------------------------\n")
|
| 1127 |
+
|
| 1128 |
+
# Launch the Gradio interface
|
| 1129 |
+
app_interface.launch(server_name="0.0.0.0", server_port=SERVER_PORT, share=False)
|
| 1130 |
|
| 1131 |
except ModuleNotFoundError as e:
|
| 1132 |
if "vector_db" in str(e):
|