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):
|