Nischal Subedi commited on
Commit
7c7cb71
·
1 Parent(s): dee52be

updated UI and example table with enhancement in response logic

Browse files
Files changed (1) hide show
  1. app.py +155 -89
app.py CHANGED
@@ -10,7 +10,11 @@ from langchain.chains import LLMChain
10
  from vector_db import VectorDatabase
11
  import re
12
 
13
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
 
 
 
14
 
15
  class RAGSystem:
16
  def __init__(self, vector_db: Optional[VectorDatabase] = None):
@@ -18,25 +22,26 @@ class RAGSystem:
18
 
19
  self.vector_db = vector_db if vector_db else VectorDatabase()
20
 
21
- # LLM and chain will be initialized later with user-provided API key
22
  self.llm = None
23
  self.chain = None
24
 
25
- # Prompt template for statute-grounded answers
26
  self.prompt_template = PromptTemplate(
27
  input_variables=["query", "context", "state", "statutes"],
28
- template="""You are a legal assistant specializing in tenant rights and landlord-tenant laws. Your goal is to provide accurate, detailed, and helpful answers that are explicitly grounded in the statutes provided in the context. Only use general knowledge to supplement the answer if the context lacks sufficient detail to fully answer the question, and clearly indicate when you are doing so.
 
29
  Instructions:
30
- - Use the context information and the provided statutes as the primary source to answer the question.
31
- - Explicitly cite the relevant statute(s) (e.g., (AS § 34.03.220(a)(2))) in your answer to ground your response in the legal text.
32
- - If multiple statutes are relevant, cite all that apply.
33
- - If the context does not contain a relevant statute to answer the question, state that no specific statute was found and provide a general answer, clearly marking it as general knowledge.
34
- - Provide detailed answers with practical examples or scenarios when possible.
35
- - Use bullet points or numbered lists for clarity when applicable.
 
36
  - Maintain a professional and neutral tone.
 
37
  Question: {query}
38
  State: {state}
39
- Statutes found in context:
40
  {statutes}
41
  Context information:
42
  {context}
@@ -44,7 +49,6 @@ Answer:"""
44
  )
45
 
46
  def initialize_llm(self, openai_api_key: str):
47
- """Initialize the LLM and chain with the provided API key."""
48
  if not openai_api_key:
49
  raise ValueError("OpenAI API key is required.")
50
 
@@ -64,52 +68,60 @@ Answer:"""
64
  logging.error(f"Failed to initialize OpenAI LLM: {str(e)}")
65
  raise
66
 
67
- def extract_statutes(self, context: str) -> str:
68
  """
69
- Extract statute citations from the context using a regex pattern.
70
- Returns a string of statutes, one per line, or a message if none are found.
71
  """
72
- statute_pattern = r'\([A-Za-z0-9§\.\s-]+\)'
73
- statutes = re.findall(statute_pattern, context)
74
- if statutes:
75
- return "\n".join(statutes)
 
 
 
 
 
 
 
 
76
  return "No statutes found in the context."
77
 
78
  @lru_cache(maxsize=100)
79
  def process_query(self, query: str, state: str, openai_api_key: str, n_results: int = 5) -> Dict[str, any]:
80
- logging.info(f"Processing query: '{query}' for state: {state}")
81
 
82
  if not state:
 
83
  return {
84
  "answer": "Please select a state to proceed with your query.",
85
- "context_used": "N/A",
86
- "statutes_found": "N/A"
87
  }
88
 
89
  if not openai_api_key:
 
90
  return {
91
  "answer": "Please provide an OpenAI API key to proceed.",
92
- "context_used": "N/A",
93
- "statutes_found": "N/A"
94
  }
95
 
96
- # Initialize LLM with the provided API key if not already initialized
97
  if not self.llm or not self.chain:
98
  try:
99
  self.initialize_llm(openai_api_key)
100
  except Exception as e:
 
101
  return {
102
  "answer": f"Failed to initialize LLM with the provided API key: {str(e)}",
103
- "context_used": "N/A",
104
- "statutes_found": "N/A"
105
  }
106
 
 
107
  try:
108
  results = self.vector_db.query(query, state=state, n_results=n_results)
109
  logging.info("Vector database query successful")
 
110
  except Exception as e:
111
  logging.error(f"Vector database query failed: {str(e)}")
112
- # Safeguard: Fallback to empty results if vector DB query fails
113
  results = {
114
  "document_results": {"documents": [[]], "metadatas": [[]]},
115
  "state_results": {"documents": [[]], "metadatas": [[]]}
@@ -118,51 +130,72 @@ Answer:"""
118
 
119
  context_parts = []
120
 
 
121
  if results["document_results"]["documents"] and results["document_results"]["documents"][0]:
122
  for i, doc in enumerate(results["document_results"]["documents"][0]):
123
  metadata = results["document_results"]["metadatas"][0][i]
124
  context_parts.append(f"[{metadata['state']} - Chunk {metadata.get('chunk_id', 'N/A')}] {doc}")
 
 
125
 
 
126
  if results["state_results"]["documents"] and results["state_results"]["documents"][0]:
127
  for i, doc in enumerate(results["state_results"]["documents"][0]):
128
  metadata = results["state_results"]["metadatas"][0][i]
129
  context_parts.append(f"[{metadata['state']} - Summary] {doc}")
 
 
130
 
131
  context = "\n\n---\n\n".join(context_parts) if context_parts else "No relevant context found."
132
 
 
 
133
  if not context_parts:
134
  logging.info("No relevant context found for query")
135
- return {
136
- "answer": "I don't have sufficient information in my database to answer this question accurately. However, I can provide some general information about tenant rights.",
137
- "context_used": context,
138
- "statutes_found": "N/A"
139
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
140
 
141
- # Extract statutes from the context
142
- statutes = self.extract_statutes(context)
143
 
144
  try:
145
  answer = self.chain.invoke({
146
  "query": query,
147
  "context": context,
148
  "state": state,
149
- "statutes": statutes
150
  })
151
  logging.info("LLM generated answer successfully")
 
152
  except Exception as e:
153
  logging.error(f"LLM processing failed: {str(e)}")
154
  return {
155
  "answer": "An error occurred while generating the answer. Please try again.",
156
- "context_used": context,
157
- "statutes_found": statutes
158
  }
159
 
160
  return {
161
  "answer": answer['text'].strip(),
162
- "context_used": context,
163
- "statutes_found": statutes
164
  }
165
-
166
  def get_states(self) -> List[str]:
167
  try:
168
  states = self.vector_db.get_states()
@@ -181,17 +214,42 @@ Answer:"""
181
  logging.error(f"Failed to load PDF: {str(e)}")
182
  return 0
183
 
184
- def gradio_interface(self) -> gr.Interface:
185
  def query_interface(api_key: str, query: str, state: str) -> str:
186
  if not api_key:
 
187
  return "⚠️ **Error:** Please provide an OpenAI API key to proceed."
188
  if not state:
 
189
  return "⚠️ **Error:** Please select a state to proceed with your query."
190
  result = self.process_query(query, state=state, openai_api_key=api_key)
191
- return f"### Answer:\n{result['answer']}\n\n### Statutes Found:\n{result['statutes_found']}"
 
192
 
193
  states = self.get_states()
194
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  example_queries = [
196
  ["What is the rent due date law?", "California"],
197
  ["What are the rules for security deposit returns?", "New York"],
@@ -200,9 +258,7 @@ Answer:"""
200
  ["Are there rent control laws?", "Oregon"]
201
  ]
202
 
203
- # Custom CSS for a modern, readable, and responsive UI
204
  custom_css = """
205
- /* General container styling */
206
  .gr-form {
207
  max-width: 900px;
208
  margin: 0 auto;
@@ -211,10 +267,8 @@ Answer:"""
211
  border-radius: 15px;
212
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
213
  }
214
-
215
- /* Title and description */
216
  .gr-title {
217
- font-size: 3em; /* Increased font size for the title */
218
  font-weight: bold;
219
  color: #2c3e50;
220
  text-align: center;
@@ -226,8 +280,6 @@ Answer:"""
226
  text-align: center;
227
  margin-bottom: 30px;
228
  }
229
-
230
- /* Footnote styling */
231
  .footnote {
232
  font-size: 1.1em;
233
  color: #34495e;
@@ -245,8 +297,6 @@ Answer:"""
245
  color: #2980b9;
246
  text-decoration: underline;
247
  }
248
-
249
- /* Input fields */
250
  .gr-textbox, .gr-dropdown {
251
  border: 1px solid #dcdcdc !important;
252
  border-radius: 8px !important;
@@ -263,8 +313,6 @@ Answer:"""
263
  color: #34495e;
264
  margin-bottom: 8px;
265
  }
266
-
267
- /* Buttons */
268
  .gr-button-primary {
269
  background-color: #3498db !important;
270
  border: none !important;
@@ -290,11 +338,9 @@ Answer:"""
290
  .gr-button-secondary:hover {
291
  background-color: #7f8c8d !important;
292
  }
293
-
294
- /* Output area */
295
  .output-markdown {
296
  background-color: #f9f9f9 !important;
297
- color: #2c3e50 !important; /* Dark text for readability */
298
  padding: 25px !important;
299
  border-radius: 10px !important;
300
  border: 1px solid #e0e0e0 !important;
@@ -302,8 +348,6 @@ Answer:"""
302
  line-height: 1.8 !important;
303
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
304
  }
305
-
306
- /* Examples section */
307
  .gr-examples {
308
  background-color: #ecf0f1;
309
  padding: 15px;
@@ -313,8 +357,6 @@ Answer:"""
313
  .gr-examples table {
314
  background-color: transparent !important;
315
  }
316
-
317
- /* Dark mode */
318
  @media (prefers-color-scheme: dark) {
319
  .gr-form {
320
  background-color: #2c3e50;
@@ -353,20 +395,18 @@ Answer:"""
353
  background-color: #3e5367;
354
  }
355
  }
356
-
357
- /* Responsive design */
358
  @media (max-width: 600px) {
359
  .gr-form {
360
  padding: 15px;
361
  }
362
  .gr-title {
363
- font-size: 2.2em; /* Adjusted for smaller screens */
364
  }
365
  .gr-description {
366
  font-size: 1em;
367
  }
368
  .footnote {
369
- font-size: 1em; /* Slightly smaller but still larger than before */
370
  }
371
  .gr-textbox, .gr-dropdown {
372
  font-size: 0.9em !important;
@@ -382,40 +422,66 @@ Answer:"""
382
  }
383
  """
384
 
385
- interface = gr.Interface(
386
- fn=query_interface,
387
- inputs=[
388
- gr.Textbox(
389
- label="Enter your OpenAI API Key",
 
 
 
 
 
 
 
 
390
  type="password",
391
  placeholder="e.g., sk-abc123",
392
  elem_classes="input-field"
393
- ),
394
- gr.Textbox(
395
- label="Enter your question about Landlord-Tenant laws",
396
  placeholder="e.g., What are the eviction rules?",
397
  lines=3,
398
  elem_classes="input-field"
399
- ),
400
- gr.Dropdown(
401
  label="Select a state (required)",
402
  choices=states,
403
  value=None,
404
  allow_custom_value=False,
405
  elem_classes="input-field"
406
  )
407
- ],
408
- outputs=gr.Markdown(
409
- label="Response",
410
- elem_classes="output-markdown"
411
- ),
412
- title="🏠 Landlord-Tenant Rights Bot",
413
- description="Ask questions about tenant rights and landlord-tenant laws based on state-specific legal documents. Provide your OpenAI API key, select a state, and enter your question below. You can get an API key from [OpenAI](https://platform.openai.com/api-keys).\n\n<div class='footnote'>Developed by Nischal Subedi. Follow me on <a href='https://www.linkedin.com/in/nischal1/' target='_blank'>LinkedIn</a> or read my insights on <a href='https://datascientistinsights.substack.com/' target='_blank'>Substack</a>.</div>",
414
- examples=example_queries,
415
- theme=gr.themes.Default(),
416
- css=custom_css
417
- )
418
- return interface
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
419
 
420
  if __name__ == "__main__":
421
  try:
@@ -424,8 +490,8 @@ if __name__ == "__main__":
424
  pdf_path = "data/tenant-landlord.pdf"
425
  rag.load_pdf(pdf_path)
426
 
427
- interface = rag.gradio_interface()
428
- interface.launch(share=True)
429
 
430
  except Exception as e:
431
  logging.error(f"Main execution failed: {str(e)}")
 
10
  from vector_db import VectorDatabase
11
  import re
12
 
13
+ # Enhanced logging for better debugging
14
+ logging.basicConfig(
15
+ level=logging.INFO,
16
+ format='%(asctime)s - %(levelname)s - [%(filename)s:%(lineno)d] - %(message)s'
17
+ )
18
 
19
  class RAGSystem:
20
  def __init__(self, vector_db: Optional[VectorDatabase] = None):
 
22
 
23
  self.vector_db = vector_db if vector_db else VectorDatabase()
24
 
 
25
  self.llm = None
26
  self.chain = None
27
 
 
28
  self.prompt_template = PromptTemplate(
29
  input_variables=["query", "context", "state", "statutes"],
30
+ template="""You are a legal assistant specializing in tenant rights and landlord-tenant laws. Your goal is to provide accurate, detailed, and helpful answers grounded in legal authority. Use the provided statutes as the primary source when available. If no relevant statutes are found in the context, rely on your general knowledge to provide a pertinent and practical response, clearly indicating when you are doing so and prioritizing state-specific information over federal laws for state-specific queries.
31
+
32
  Instructions:
33
+ - Use the context and statutes as the primary basis for your answer when available.
34
+ - For state-specific queries, prioritize statutes or legal principles from the specified state over federal laws.
35
+ - Cite relevant statutes (e.g., (AS § 34.03.220(a)(2))) explicitly in your answer when applicable.
36
+ - If multiple statutes apply, list all relevant ones.
37
+ - If no specific statute is found in the context, state this clearly (e.g., 'No specific statute was found in the provided context'), then provide a general answer based on common legal principles or practices, marked as such.
38
+ - Include practical examples or scenarios to enhance clarity and usefulness.
39
+ - Use bullet points or numbered lists for readability when appropriate.
40
  - Maintain a professional and neutral tone.
41
+
42
  Question: {query}
43
  State: {state}
44
+ Statutes from context:
45
  {statutes}
46
  Context information:
47
  {context}
 
49
  )
50
 
51
  def initialize_llm(self, openai_api_key: str):
 
52
  if not openai_api_key:
53
  raise ValueError("OpenAI API key is required.")
54
 
 
68
  logging.error(f"Failed to initialize OpenAI LLM: {str(e)}")
69
  raise
70
 
71
+ def extract_statutes(self, text: str) -> str:
72
  """
73
+ Extract statute citations from the given text using a refined regex pattern.
74
+ Returns a string of valid statutes, one per line, or a message if none are found.
75
  """
76
+ statute_pattern = r'\((?:[A-Za-z\s]+\s*(?:Code|Laws|Statutes|CCP)\s*§\s*[0-9-]+(?:\([a-z0-9]+\))?|[A-Za-z0-9\s]+\s*§\s*[0-9-]+(?:\([a-z0-9]+\))?|[A-Z]{2,3}\s*§\s*[0-9-]+(?:\([a-z0-9]+\))?|[0-9]+\s*ILCS\s*[0-9]+/[0-9-]+(?:\([a-z0-9]+\))?|Title\s*[0-9]+\s*USC\s*§\s*[0-9]+-[0-9]+|[A-Za-z\s]+\s*Laws\s*[0-9]+\s*§\s*[0-9-]+(?:\([a-z0-9]+\))?|[A-Za-z\s]+\s*CCP\s*§\s*[0-9-]+(?:\([a-z0-9]+\))?)\)'
77
+ statutes = re.findall(statute_pattern, text)
78
+
79
+ valid_statutes = []
80
+ for statute in statutes:
81
+ if '§' in statute and any(char.isdigit() for char in statute) and not re.match(r'\([a-z]\)', statute) and 'found here' not in statute:
82
+ valid_statutes.append(statute)
83
+
84
+ if valid_statutes:
85
+ seen = set()
86
+ unique_statutes = [statute for statute in valid_statutes if not (statute in seen or seen.add(statute))]
87
+ return "\n".join(unique_statutes)
88
  return "No statutes found in the context."
89
 
90
  @lru_cache(maxsize=100)
91
  def process_query(self, query: str, state: str, openai_api_key: str, n_results: int = 5) -> Dict[str, any]:
92
+ logging.info(f"Processing query: '{query}' for state: '{state}' with n_results={n_results}")
93
 
94
  if not state:
95
+ logging.warning("No state provided for query")
96
  return {
97
  "answer": "Please select a state to proceed with your query.",
98
+ "context_used": "N/A"
 
99
  }
100
 
101
  if not openai_api_key:
102
+ logging.warning("No OpenAI API key provided")
103
  return {
104
  "answer": "Please provide an OpenAI API key to proceed.",
105
+ "context_used": "N/A"
 
106
  }
107
 
 
108
  if not self.llm or not self.chain:
109
  try:
110
  self.initialize_llm(openai_api_key)
111
  except Exception as e:
112
+ logging.error(f"Failed to initialize LLM: {str(e)}")
113
  return {
114
  "answer": f"Failed to initialize LLM with the provided API key: {str(e)}",
115
+ "context_used": "N/A"
 
116
  }
117
 
118
+ # Query the vector database
119
  try:
120
  results = self.vector_db.query(query, state=state, n_results=n_results)
121
  logging.info("Vector database query successful")
122
+ logging.debug(f"Query results: {json.dumps(results, indent=2)}")
123
  except Exception as e:
124
  logging.error(f"Vector database query failed: {str(e)}")
 
125
  results = {
126
  "document_results": {"documents": [[]], "metadatas": [[]]},
127
  "state_results": {"documents": [[]], "metadatas": [[]]}
 
130
 
131
  context_parts = []
132
 
133
+ # Process document results
134
  if results["document_results"]["documents"] and results["document_results"]["documents"][0]:
135
  for i, doc in enumerate(results["document_results"]["documents"][0]):
136
  metadata = results["document_results"]["metadatas"][0][i]
137
  context_parts.append(f"[{metadata['state']} - Chunk {metadata.get('chunk_id', 'N/A')}] {doc}")
138
+ else:
139
+ logging.warning("No document results found in query response")
140
 
141
+ # Process state summary results
142
  if results["state_results"]["documents"] and results["state_results"]["documents"][0]:
143
  for i, doc in enumerate(results["state_results"]["documents"][0]):
144
  metadata = results["state_results"]["metadatas"][0][i]
145
  context_parts.append(f"[{metadata['state']} - Summary] {doc}")
146
+ else:
147
+ logging.warning("No state summary results found in query response")
148
 
149
  context = "\n\n---\n\n".join(context_parts) if context_parts else "No relevant context found."
150
 
151
+ logging.info(f"Raw context for query: {context}")
152
+
153
  if not context_parts:
154
  logging.info("No relevant context found for query")
155
+ # Fallback to general knowledge
156
+ statutes_from_context = "No statutes found in the context."
157
+ try:
158
+ answer = self.chain.invoke({
159
+ "query": query,
160
+ "context": "No specific legal documents available.",
161
+ "state": state,
162
+ "statutes": statutes_from_context
163
+ })
164
+ return {
165
+ "answer": answer['text'].strip(),
166
+ "context_used": context
167
+ }
168
+ except Exception as e:
169
+ logging.error(f"LLM fallback processing failed: {str(e)}")
170
+ return {
171
+ "answer": "I don’t have sufficient information to answer this accurately, and an error occurred while generating a general response. Please try again.",
172
+ "context_used": context
173
+ }
174
 
175
+ statutes_from_context = self.extract_statutes(context)
176
+ logging.info(f"Statutes extracted from context: {statutes_from_context}")
177
 
178
  try:
179
  answer = self.chain.invoke({
180
  "query": query,
181
  "context": context,
182
  "state": state,
183
+ "statutes": statutes_from_context
184
  })
185
  logging.info("LLM generated answer successfully")
186
+ logging.debug(f"Raw answer text: {answer['text']}")
187
  except Exception as e:
188
  logging.error(f"LLM processing failed: {str(e)}")
189
  return {
190
  "answer": "An error occurred while generating the answer. Please try again.",
191
+ "context_used": context
 
192
  }
193
 
194
  return {
195
  "answer": answer['text'].strip(),
196
+ "context_used": context
 
197
  }
198
+
199
  def get_states(self) -> List[str]:
200
  try:
201
  states = self.vector_db.get_states()
 
214
  logging.error(f"Failed to load PDF: {str(e)}")
215
  return 0
216
 
217
+ def gradio_interface(self):
218
  def query_interface(api_key: str, query: str, state: str) -> str:
219
  if not api_key:
220
+ logging.warning("No OpenAI API key provided in interface")
221
  return "⚠️ **Error:** Please provide an OpenAI API key to proceed."
222
  if not state:
223
+ logging.warning("No state selected in interface")
224
  return "⚠️ **Error:** Please select a state to proceed with your query."
225
  result = self.process_query(query, state=state, openai_api_key=api_key)
226
+
227
+ return f"### Answer:\n{result['answer']}"
228
 
229
  states = self.get_states()
230
 
231
+ # Define the inputs
232
+ api_key_input = gr.Textbox(
233
+ label="Open AI API Key",
234
+ type="password",
235
+ placeholder="e.g., sk-abc123",
236
+ elem_classes="input-field"
237
+ )
238
+ query_input = gr.Textbox(
239
+ label="Query",
240
+ placeholder="e.g., What are the eviction rules?",
241
+ lines=3,
242
+ elem_classes="input-field"
243
+ )
244
+ state_input = gr.Dropdown(
245
+ label="Select a state (required)",
246
+ choices=states,
247
+ value=None,
248
+ allow_custom_value=False,
249
+ elem_classes="input-field"
250
+ )
251
+
252
+ # Define the example queries (only for query and state)
253
  example_queries = [
254
  ["What is the rent due date law?", "California"],
255
  ["What are the rules for security deposit returns?", "New York"],
 
258
  ["Are there rent control laws?", "Oregon"]
259
  ]
260
 
 
261
  custom_css = """
 
262
  .gr-form {
263
  max-width: 900px;
264
  margin: 0 auto;
 
267
  border-radius: 15px;
268
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
269
  }
 
 
270
  .gr-title {
271
+ font-size: 3em;
272
  font-weight: bold;
273
  color: #2c3e50;
274
  text-align: center;
 
280
  text-align: center;
281
  margin-bottom: 30px;
282
  }
 
 
283
  .footnote {
284
  font-size: 1.1em;
285
  color: #34495e;
 
297
  color: #2980b9;
298
  text-decoration: underline;
299
  }
 
 
300
  .gr-textbox, .gr-dropdown {
301
  border: 1px solid #dcdcdc !important;
302
  border-radius: 8px !important;
 
313
  color: #34495e;
314
  margin-bottom: 8px;
315
  }
 
 
316
  .gr-button-primary {
317
  background-color: #3498db !important;
318
  border: none !important;
 
338
  .gr-button-secondary:hover {
339
  background-color: #7f8c8d !important;
340
  }
 
 
341
  .output-markdown {
342
  background-color: #f9f9f9 !important;
343
+ color: #2c3e50 !important;
344
  padding: 25px !important;
345
  border-radius: 10px !important;
346
  border: 1px solid #e0e0e0 !important;
 
348
  line-height: 1.8 !important;
349
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
350
  }
 
 
351
  .gr-examples {
352
  background-color: #ecf0f1;
353
  padding: 15px;
 
357
  .gr-examples table {
358
  background-color: transparent !important;
359
  }
 
 
360
  @media (prefers-color-scheme: dark) {
361
  .gr-form {
362
  background-color: #2c3e50;
 
395
  background-color: #3e5367;
396
  }
397
  }
 
 
398
  @media (max-width: 600px) {
399
  .gr-form {
400
  padding: 15px;
401
  }
402
  .gr-title {
403
+ font-size: 2.2em;
404
  }
405
  .gr-description {
406
  font-size: 1em;
407
  }
408
  .footnote {
409
+ font-size: 1em;
410
  }
411
  .gr-textbox, .gr-dropdown {
412
  font-size: 0.9em !important;
 
422
  }
423
  """
424
 
425
+ with gr.Blocks(css=custom_css, theme=gr.themes.Default()) as demo:
426
+ gr.Markdown(
427
+ """
428
+ # 🏠 Landlord-Tenant Rights Bot
429
+ Ask questions about tenant rights and landlord-tenant laws based on state-specific legal documents. Provide your OpenAI API key, select a state, and enter your question below. You can get an API key from [OpenAI](https://platform.openai.com/api-keys).
430
+
431
+ # <div class='footnote'>Developed by Nischal Subedi. Follow me on <a href='https://www.linkedin.com/in/nischal1/' target='_blank'>LinkedIn</a> or read my insights on <a href='https://datascientistinsights.substack.com/' target='_blank'>Substack</a>.</div>
432
+ """
433
+ )
434
+
435
+ with gr.Column(elem_classes="gr-form"):
436
+ api_key_input = gr.Textbox(
437
+ label="Open AI API Key",
438
  type="password",
439
  placeholder="e.g., sk-abc123",
440
  elem_classes="input-field"
441
+ )
442
+ query_input = gr.Textbox(
443
+ label="Query",
444
  placeholder="e.g., What are the eviction rules?",
445
  lines=3,
446
  elem_classes="input-field"
447
+ )
448
+ state_input = gr.Dropdown(
449
  label="Select a state (required)",
450
  choices=states,
451
  value=None,
452
  allow_custom_value=False,
453
  elem_classes="input-field"
454
  )
455
+
456
+ with gr.Row():
457
+ clear_button = gr.Button("Clear", variant="secondary")
458
+ submit_button = gr.Button("Submit", variant="primary")
459
+
460
+ output = gr.Markdown(
461
+ label="Response",
462
+ elem_classes="output-markdown"
463
+ )
464
+
465
+ gr.Examples(
466
+ examples=example_queries,
467
+ inputs=[query_input, state_input],
468
+ outputs=output,
469
+ fn=query_interface,
470
+ examples_per_page=5
471
+ )
472
+
473
+ submit_button.click(
474
+ fn=query_interface,
475
+ inputs=[api_key_input, query_input, state_input],
476
+ outputs=output
477
+ )
478
+ clear_button.click(
479
+ fn=lambda: ("", "", None, ""),
480
+ inputs=[],
481
+ outputs=[api_key_input, query_input, state_input, output]
482
+ )
483
+
484
+ return demo
485
 
486
  if __name__ == "__main__":
487
  try:
 
490
  pdf_path = "data/tenant-landlord.pdf"
491
  rag.load_pdf(pdf_path)
492
 
493
+ demo = rag.gradio_interface()
494
+ demo.launch(share=True)
495
 
496
  except Exception as e:
497
  logging.error(f"Main execution failed: {str(e)}")