Nischal Subedi commited on
Commit
d55a911
·
1 Parent(s): a02417f

updated UI

Browse files
Files changed (1) hide show
  1. app.py +344 -463
app.py CHANGED
@@ -35,20 +35,12 @@ logging.basicConfig(
35
 
36
  # --- RAGSystem Class ---
37
  class RAGSystem:
38
- # (Keep the RAGSystem class exactly the same as in the previous version)
39
- # ... __init__ ...
40
- # ... extract_statutes ...
41
- # ... process_query_cached ...
42
- # ... process_query ...
43
- # ... get_states ...
44
- # ... load_pdf ...
45
  def __init__(self, vector_db: Optional[VectorDatabase] = None):
46
  logging.info("Initializing RAGSystem")
47
  self.vector_db = vector_db if vector_db else VectorDatabase()
48
  self.llm = None
49
  self.chain = None
50
  self.prompt_template_str = """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.
51
-
52
  Instructions:
53
  * Use the context and statutes as the primary basis for your answer when available.
54
  * For state-specific queries, prioritize statutes or legal principles from the specified state over federal laws.
@@ -58,17 +50,14 @@ Instructions:
58
  * Include practical examples or scenarios to enhance clarity and usefulness.
59
  * Use bullet points or numbered lists for readability when appropriate.
60
  * Maintain a professional and neutral tone.
61
-
62
  Question: {query}
63
  State: {state}
64
  Statutes from context:
65
  {statutes}
66
-
67
  Context information:
68
  --- START CONTEXT ---
69
  {context}
70
  --- END CONTEXT ---
71
-
72
  Answer:"""
73
  self.prompt_template = PromptTemplate(
74
  input_variables=["query", "context", "state", "statutes"],
@@ -248,463 +237,406 @@ Answer:"""
248
  logging.error(f"Failed to load or process PDF '{pdf_path}': {str(e)}", exc_info=True)
249
  raise RuntimeError(f"Failed to process PDF '{pdf_path}': {e}") from e
250
 
251
-
252
  # --- GRADIO INTERFACE ---
253
  def gradio_interface(self):
254
- # Wrapper function for the Gradio interface logic
255
  def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
256
  logging.info(f"Gradio interface received query: '{query[:50]}...', state: '{state}'")
257
-
258
- # Re-validate inputs robustly
259
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
260
- return "<div class='error-message'>Please provide a valid OpenAI API key (starting with 'sk-'). <a href='https://platform.openai.com/api-keys' target='_blank'>Get one here</a>.</div>"
261
  if not state or state == "Select a state..." or "Error" in state:
262
- return "<div class='error-message'>Please select a valid state from the dropdown.</div>"
263
  if not query or not query.strip():
264
- return "<div class='error-message'>Please enter your question in the text box.</div>"
265
-
266
- # Call the core processing logic
267
  result = self.process_query(query=query, state=state, openai_api_key=api_key)
268
-
269
- # Format the response for display
270
- answer = result.get("answer", "<div class='error-message'>An unexpected error occurred, and no answer was generated. Please check the logs or try again.</div>")
271
-
272
- # Add a header *only* if the answer is not an error message itself
273
  if not "<div class='error-message'>" in answer:
274
- formatted_response = f"<h3 class='response-header'>Response for {state}</h3><hr class='divider'>{answer}"
275
  else:
276
- formatted_response = answer # Pass through error messages directly
277
-
278
- # Log context length for debugging (optional)
279
- context_used = result.get("context_used", "N/A")
280
- if isinstance(context_used, str) and "N/A" not in context_used:
281
- logging.debug(f"Context length used for query: {len(context_used)} characters.")
282
- else:
283
- logging.debug(f"No context was used or available for this query ({context_used}).")
284
-
285
  return formatted_response
286
 
287
- # --- Get Available States for Dropdown ---
288
  try:
289
  available_states_list = self.get_states()
290
- if not available_states_list or "Error" in available_states_list[0]:
291
- dropdown_choices = ["Error: Could not load states"]
292
- initial_value = dropdown_choices[0]
293
- logging.error("Could not load states for dropdown. UI will show error.")
294
- else:
295
- dropdown_choices = ["Select a state..."] + available_states_list
296
- initial_value = dropdown_choices[0]
297
- except Exception as e:
298
- logging.error(f"Unexpected critical error getting states: {e}", exc_info=True)
299
  dropdown_choices = ["Error: Critical failure loading states"]
300
  initial_value = dropdown_choices[0]
301
 
302
- # --- Prepare Example Queries ---
303
  example_queries_base = [
304
  ["What are the rules for security deposit returns?", "California"],
305
  ["Can a landlord enter my apartment without notice?", "New York"],
306
  ["My landlord hasn't made necessary repairs. What can I do?", "Texas"],
307
- ["What are the limits on rent increases in my state?", "Florida"],
308
- ["Is my lease automatically renewed if I don't move out?", "Illinois"],
309
- ["What happens if I break my lease early?", "Washington"]
310
  ]
311
  example_queries = []
312
- if available_states_list and "Error" not in available_states_list[0]:
313
  loaded_states_set = set(available_states_list)
314
  example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
315
- if not example_queries:
316
- fallback_state = available_states_list[0] if available_states_list and "Error" not in available_states_list[0] else "California"
317
- example_queries.append(["What basic rights do tenants have?", fallback_state])
 
318
 
319
- # --- Refined Custom CSS ---
320
- # Focus: Unified background, distinct white cards, proper centering, refined examples table
321
  custom_css = """
322
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
 
324
- /* --- Base & Body --- */
325
  body, .gradio-container {
326
- font-family: 'Roboto', sans-serif !important;
327
- background-color: #F5F7FA !important; /* Light grey base background */
328
- color: #1F2A44;
329
- margin: 0;
330
- padding: 0;
331
- min-height: 100vh;
332
- font-size: 16px; /* Base font size */
333
- -webkit-font-smoothing: antialiased;
334
- -moz-osx-font-smoothing: grayscale;
335
- }
336
- * {
337
- box-sizing: border-box;
338
- }
339
-
340
- /* --- Main Content Container --- */
341
- .gradio-container > .flex.flex-col { /* Target the main content column */
342
- max-width: 960px; /* Slightly wider max-width */
343
- margin: 0 auto !important; /* Center the column */
344
- padding: 3rem 1.5rem !important; /* More vertical padding */
345
- gap: 2.5rem !important; /* Consistent gap between sections */
346
- background-color: transparent !important; /* Ensure container itself is transparent */
347
- }
348
-
349
- /* --- Card Styling (Applied to Groups) --- */
350
- .card-style {
351
- background-color: #FFFFFF !important; /* White background for cards */
352
- border: 1px solid #E5E7EB !important; /* Subtle border */
353
- border-radius: 12px !important;
354
- padding: 2rem !important; /* Consistent padding inside cards */
355
- box-shadow: 0 4px 12px rgba(101, 119, 134, 0.08) !important; /* Refined shadow */
356
- overflow: hidden; /* Prevent content spill */
357
- }
358
- /* Remove default Gradio Group padding if using custom padding */
359
- .gradio-group {
360
- padding: 0 !important;
361
- border: none !important;
362
- background: none !important;
363
- box-shadow: none !important;
364
- }
365
-
366
- /* --- Header Section --- */
367
- .header-section {
368
- background-color: transparent !important; /* Header blends */
369
- padding: 1rem 0 !important;
370
- text-align: center !important; /* Center align all content */
371
- border: none !important;
372
- box-shadow: none !important;
373
- }
374
- .header-logo {
375
- font-size: 2.8rem;
376
- color: #2563EB;
377
- margin-bottom: 0.75rem;
378
- display: block; /* Ensure centering */
379
- }
380
- .header-title {
381
- font-size: 2rem; /* Larger title */
382
- font-weight: 700;
383
- color: #111827; /* Darker title */
384
- margin: 0 0 0.25rem 0;
385
- }
386
- .header-tagline {
387
- font-size: 1.1rem;
388
- color: #4B5563;
389
- margin: 0;
390
- }
391
-
392
- /* --- Introduction Section --- */
393
- /* Uses card-style defined above */
394
- .intro-card h3 {
395
- font-size: 1.5rem;
396
- font-weight: 600;
397
- color: #0369A1; /* Blue heading */
398
- margin: 0 0 1rem 0;
399
- padding-bottom: 0.5rem;
400
- border-bottom: 1px solid #E0F2FE; /* Light blue underline */
401
- }
402
- .intro-card p {
403
- font-size: 1rem;
404
- line-height: 1.6;
405
- color: #374151; /* Standard text color */
406
- margin: 0 0 0.75rem 0;
407
- }
408
- .intro-card a {
409
- color: #0369A1;
410
- text-decoration: underline;
411
- font-weight: 500;
412
- }
413
- .intro-card a:hover { color: #0284C7; }
414
- .intro-card strong { font-weight: 600; color: #1F2A44; }
415
-
416
- /* --- Input Form Section --- */
417
- /* Uses card-style */
418
- .input-form-card h3 {
419
- font-size: 1.4rem;
420
- font-weight: 600;
421
- color: #1F2A44;
422
- margin: 0 0 1.75rem 0;
423
- padding-bottom: 0.75rem;
424
- border-bottom: 1px solid #E5E7EB;
425
- }
426
- .input-field-group { margin-bottom: 1.5rem; }
427
- .input-row { display: flex; gap: 1.5rem; flex-wrap: wrap; margin-bottom: 1.5rem; }
428
- .input-field { flex: 1; min-width: 220px; }
429
-
430
- /* Input Elements */
431
  .gradio-textbox textarea, .gradio-dropdown select, .gradio-textbox input[type=password] {
432
- border: 1px solid #D1D5DB !important;
433
- border-radius: 8px !important;
434
- padding: 0.8rem 1rem !important;
435
- font-size: 1rem !important; /* Make inputs slightly larger */
436
- background-color: #F9FAFB !important;
437
- color: #1F2A44 !important;
438
- transition: border-color 0.2s ease, box-shadow 0.2s ease;
439
- width: 100% !important;
440
- }
441
- .gradio-textbox textarea { min-height: 90px; }
442
  .gradio-textbox textarea:focus, .gradio-dropdown select:focus, .gradio-textbox input[type=password]:focus {
443
- border-color: #2563EB !important;
444
- box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15) !important;
445
- outline: none !important;
446
- background-color: #FFFFFF !important;
447
- }
448
- .gradio-input-label, .gradio-output-label { /* Label styling */
449
- font-size: 0.9rem !important;
450
- font-weight: 500 !important;
451
- color: #374151 !important;
452
- margin-bottom: 0.5rem !important;
453
- display: block !important;
454
- }
455
- .gradio-input-info { /* Info text */
456
- font-size: 0.85rem !important;
457
- color: #6B7280 !important;
458
- margin-top: 0.3rem;
459
- }
460
-
461
- /* Buttons */
462
- .button-row { display: flex; gap: 1rem; margin-top: 1.5rem; flex-wrap: wrap; justify-content: flex-end; }
 
 
 
 
 
463
  .gradio-button {
464
- border-radius: 8px !important; padding: 0.75rem 1.5rem !important; font-size: 0.95rem !important;
465
- font-weight: 500 !important; border: none !important; cursor: pointer;
466
- transition: background-color 0.2s ease, transform 0.1s ease, box-shadow 0.2s ease;
467
- }
468
- .gradio-button:hover:not(:disabled) { transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); }
469
- .gradio-button:active:not(:disabled) { transform: scale(0.98); box-shadow: none; }
470
- .gradio-button:disabled { background: #E5E7EB !important; color: #9CA3AF !important; cursor: not-allowed; }
471
- .gr-button-primary { background-color: #2563EB !important; color: #FFFFFF !important; }
472
- .gr-button-primary:hover:not(:disabled) { background-color: #1D4ED8 !important; }
473
- .gr-button-secondary { background-color: #F3F4F6 !important; color: #374151 !important; border: 1px solid #D1D5DB !important; }
474
- .gr-button-secondary:hover:not(:disabled) { background-color: #E5E7EB !important; border-color: #9CA3AF !important; }
475
-
476
- /* --- Output Section --- */
477
- /* Uses card-style */
478
- .output-card .response-header { /* Style the H3 we add in Python */
479
- font-size: 1.3rem;
480
- font-weight: 600;
481
- color: #1F2A44;
482
- margin: 0 0 0.75rem 0;
483
- }
484
- .output-card .divider { /* Style the HR we add */
485
- border: none; border-top: 1px solid #E5E7EB; margin: 1rem 0 1.5rem 0;
486
- }
487
- .output-card .output-content-wrapper { /* Wrapper for the markdown content */
488
- font-size: 1rem; line-height: 1.7; color: #374151;
489
- }
490
- .output-card .output-content-wrapper p { margin-bottom: 1rem; }
491
- .output-card .output-content-wrapper ul, .output-card .output-content-wrapper ol { margin-left: 1.5rem; margin-bottom: 1rem; padding-left: 1rem; }
492
- .output-card .output-content-wrapper li { margin-bottom: 0.5rem; }
493
- .output-card .output-content-wrapper strong, .output-card .output-content-wrapper b { font-weight: 600; color: #111827; }
494
- .output-card .output-content-wrapper a { color: #2563EB; text-decoration: underline; }
495
- .output-card .output-content-wrapper a:hover { color: #1D4ED8; }
496
-
497
- /* Error message styling */
498
- .output-card .error-message {
499
- background-color: #FEF2F2; border: 1px solid #FECACA; border-left: 4px solid #F87171;
500
- border-radius: 8px; padding: 1rem 1.25rem; color: #B91C1C; font-weight: 500; margin-top: 0.5rem;
501
- }
502
- .output-card .error-details { font-size: 0.9rem; color: #991B1B; margin-top: 0.5rem; font-style: italic; }
503
- /* Placeholder text */
504
- .output-card .placeholder { color: #9CA3AF; font-style: italic; text-align: center; padding: 2rem 1rem; display: block; }
505
-
506
- /* --- Examples Section --- */
507
- /* Uses card-style */
508
- .examples-card .gr-examples-header { /* Style the header Gradio adds */
509
- font-size: 1.3rem !important; font-weight: 600 !important; color: #1F2A44 !important;
510
- margin: 0 0 1.5rem 0 !important; padding-bottom: 0.75rem !important; border-bottom: 1px solid #E5E7EB !important;
511
- }
512
- /* Style the TABLE generated by gr.Examples */
513
- .examples-card .gr-examples-table { border-collapse: collapse !important; width: 100% !important; }
514
- .examples-card .gr-examples-table th,
515
- .examples-card .gr-examples-table td {
516
- text-align: left !important; padding: 0.75rem 1rem !important;
517
- border: 1px solid #E5E7EB !important; font-size: 0.95rem !important;
518
- color: #374151 !important; background-color: transparent !important;
519
- }
520
- .examples-card .gr-examples-table th {
521
- font-weight: 500 !important; background-color: #F9FAFB !important; color: #1F2A44 !important;
522
- }
523
- /* Style the example *rows* when clickable */
524
- .examples-card .gr-examples-table tr { cursor: pointer; transition: background-color 0.2s ease; }
525
- .examples-card .gr-examples-table tr:hover td { background-color: #F3F4F6 !important; }
526
-
527
- /* --- Footer Section --- */
528
- .footer-section {
529
- background-color: transparent !important;
530
- border-top: 1px solid #E5E7EB !important;
531
- padding: 2rem 1rem !important;
532
- margin-top: 1rem !important; /* Space above footer */
533
- text-align: center !important;
534
- color: #6B7280 !important;
535
- font-size: 0.9rem !important;
536
- line-height: 1.6 !important;
537
- box-shadow: none !important; border-radius: 0 !important;
538
- }
539
- .footer-section strong { color: #374151; font-weight: 500; }
540
- .footer-section a { color: #2563EB; text-decoration: none; font-weight: 500; }
541
- .footer-section a:hover { color: #1D4ED8; text-decoration: underline; }
542
-
543
- /* --- Accessibility & Focus --- */
544
- :focus-visible {
545
- outline: 2px solid #2563EB !important;
546
- outline-offset: 2px;
547
- box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2) !important;
548
- }
549
- /* Remove default Gradio focus on button internal span */
550
  .gradio-button span:focus { outline: none !important; }
551
 
552
- /* --- Responsive Adjustments --- */
553
  @media (max-width: 768px) {
554
- .gradio-container > .flex.flex-col { padding: 2rem 1rem !important; gap: 2rem !important; }
555
- .card-style { padding: 1.5rem !important; }
556
- .header-title { font-size: 1.8rem; }
557
- .header-tagline { font-size: 1rem; }
558
- .input-row { flex-direction: column; gap: 1rem; margin-bottom: 1rem; }
559
- .button-row { justify-content: center; }
 
 
560
  }
561
  @media (max-width: 480px) {
562
- body { font-size: 15px; }
563
- .gradio-container > .flex.flex-col { padding: 1.5rem 1rem !important; gap: 1.5rem !important; }
564
- .card-style { padding: 1.25rem !important; border-radius: 10px !important;}
565
- .header-logo { font-size: 2.5rem; margin-bottom: 0.5rem;}
566
- .header-title { font-size: 1.5rem; }
567
- .header-tagline { font-size: 0.95rem; }
568
- .intro-card h3, .input-form-card h3, .output-card .response-header, .examples-card .gr-examples-header { font-size: 1.2rem !important; margin-bottom: 1rem !important; }
569
- .gradio-textbox textarea, .gradio-dropdown select, .gradio-textbox input[type=password] { font-size: 0.95rem !important; padding: 0.75rem !important; }
570
- .gradio-button { width: 100%; padding: 0.7rem 1.2rem !important; font-size: 0.9rem !important; }
571
- .button-row { flex-direction: column; gap: 0.75rem; }
572
- .footer-section { font-size: 0.85rem; padding: 1.5rem 1rem !important; }
573
- .examples-card .gr-examples-table th, .examples-card .gr-examples-table td { padding: 0.6rem 0.8rem !important; font-size: 0.9rem !important;}
574
- }
575
-
576
- /* Gradio Specific Overrides (Use sparingly) */
577
- /* Force main container gap */
578
- .gradio-container > .flex { gap: 2.5rem !important; }
579
- /* Ensure no weird margins collapse */
580
- .gradio-markdown > *:first-child { margin-top: 0; }
581
- .gradio-markdown > *:last-child { margin-bottom: 0; }
582
- /* Remove border from dropdown wrapper if needed */
583
- .gradio-dropdown { border: none !important; padding: 0 !important; }
584
- /* Remove border from textbox wrapper */
585
- .gradio-textbox { border: none !important; padding: 0 !important; }
586
  """
587
 
588
- # --- Gradio Blocks Layout ---
589
- with gr.Blocks(theme = 'light',css=custom_css, title="Landlord-Tenant Rights Assistant") as demo:
590
- # The main container class is applied implicitly by Gradio, CSS targets it
 
 
 
 
 
 
 
 
 
591
 
592
- # Header Section (No Card Style)
593
- with gr.Group(elem_classes="header-section"): # Use Group for structure, styled via class
594
  gr.Markdown(
595
  """
596
- <span class="header-logo">⚖️</span>
597
- <h1 class="header-title">Landlord-Tenant Rights Assistant</h1>
598
- <p class="header-tagline">Your AI-powered guide to U.S. landlord-tenant laws</p>
599
- """, elem_id="app-title"
600
- )
601
-
602
- # Introduction Section (Card Style)
603
- with gr.Group(elem_classes="card-style intro-card"):
604
- gr.Markdown(
605
  """
606
- <h3 style="text-align: center;">Discover Your Rights</h3>
607
-
608
- <p>Get accurate, AI-powered answers to your questions about landlord-tenant laws. Select your state, provide an <strong>OpenAI API key</strong>, and ask your question below.</p>
609
- <p>Need an API key? <a href='https://platform.openai.com/api-keys' target='_blank'>Get one free here</a> from OpenAI.</p>
610
- <p><strong>Note:</strong> This tool is for informational purposes only. Always consult a licensed attorney for legal advice specific to your situation.</p>
611
- """,
612
- elem_id="app-description"
613
  )
614
 
615
- # Examples Section (Card Style)
616
-
617
- # Input Form Section (Card Style)
618
- with gr.Group(elem_classes="card-style input-form-card"):
619
- gr.Markdown("<h3>Query Section</h3>", elem_id="form-heading")
620
-
621
  with gr.Column(elem_classes="input-field-group"):
622
- api_key_input = gr.Textbox(
623
- label="OpenAI API Key", type="password",
624
- placeholder="Enter your API key (e.g., sk-...)",
625
- info="Required to process your question. Securely used per request, not stored.",
626
- elem_id="api-key-input", lines=1
627
- )
628
-
629
  with gr.Row(elem_classes="input-row"):
630
- with gr.Column(elem_classes="input-field"):
631
- query_input = gr.Textbox(
632
- label="Curious about landlord-tenant laws in your state? Ask away!",
633
- placeholder="E.g., What are the rules for security deposit returns in my state?",
634
- lines=4, max_lines=8, elem_id="query-input"
635
- )
636
- with gr.Column(elem_classes="input-field"):
637
  state_input = gr.Dropdown(
638
  label="Select State", choices=dropdown_choices, value=initial_value,
639
- allow_custom_value=False, elem_id="state-dropdown"
640
- )
641
-
642
  with gr.Row(elem_classes="button-row"):
643
- clear_button = gr.Button(
644
- "Clear Inputs", variant="secondary", elem_id="clear-button",
645
- elem_classes=["gr-button-secondary"]
646
- )
647
- submit_button = gr.Button(
648
- "Submit Question", variant="primary", elem_id="submit-button",
649
- elem_classes=["gr-button-primary"]
650
- )
651
 
652
- # Output Section (Card Style)
653
- with gr.Group(elem_classes="card-style output-card"):
654
- # Wrap the output markdown for better targeting if needed
655
- with gr.Column(): # Add column wrapper if needed for spacing/styling
656
- output = gr.Markdown(
657
- value="<div class='placeholder'>The response will appear here after submitting a question.</div>",
658
- elem_id="output-content",
659
- elem_classes="output-content-wrapper" # Apply styling to this wrapper
660
- )
661
 
662
- # Example Questions Section (Card Style)
663
  if example_queries:
664
- with gr.Group(elem_classes="card-style examples-card"):
665
- gr.Examples(
666
- examples=example_queries,
667
- inputs=[query_input, state_input],
668
- label="Example Sample Questions", # Uses .gr-examples-header class
669
- examples_per_page=6
670
- )
671
- else:
672
- with gr.Group(elem_classes="card-style examples-card"): # Still use card style for consistency
673
- gr.Markdown(
674
- "<div class='placeholder'>Sample questions could not be loaded. Please ensure states are available.</div>"
675
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
676
 
677
- # Footer Section (No Card Style)
678
- with gr.Group(elem_classes="footer-section"):
679
- gr.Markdown(
680
- """
681
- **Disclaimer**: This tool is for informational purposes only and does not constitute legal advice.
682
- <br><br>
683
- Developed by **Nischal Subedi**. Connect on <a href="https://www.linkedin.com/in/nischal1/" target="_blank">LinkedIn</a> or explore insights at <a href="https://datascientistinsights.substack.com/" target="_blank">Substack</a>.
684
- """, elem_id="app-footer"
685
- )
686
 
687
- # --- Event Listeners ---
688
  submit_button.click(
689
- fn=query_interface_wrapper,
690
- inputs=[api_key_input, query_input, state_input],
691
- outputs=output,
692
- api_name="submit_query"
693
  )
694
-
695
  clear_button.click(
696
- fn=lambda: (
697
- "", "", initial_value,
698
- "<div class='placeholder'>Inputs cleared. Ready for your next question.</div>"
699
- ),
700
- inputs=[],
701
- outputs=[api_key_input, query_input, state_input, output]
702
  )
703
-
704
- logging.info("Refined Gradio interface created successfully.")
705
  return demo
706
 
707
- # --- Main Execution Block ---
708
  if __name__ == "__main__":
709
  logging.info("Starting Landlord-Tenant Rights Bot application...")
710
  try:
@@ -718,73 +650,22 @@ if __name__ == "__main__":
718
  os.makedirs(os.path.dirname(VECTOR_DB_PATH), exist_ok=True)
719
  os.makedirs(os.path.dirname(PDF_PATH), exist_ok=True)
720
 
721
- logging.info(f"Using PDF path: {PDF_PATH}")
722
- logging.info(f"Using Vector DB path: {VECTOR_DB_PATH}")
723
-
724
  if not os.path.exists(PDF_PATH):
725
  logging.error(f"FATAL: PDF file not found at the specified path: {PDF_PATH}")
726
- print(f"\n--- CONFIGURATION ERROR ---")
727
- print(f"The required PDF file ('{os.path.basename(PDF_PATH)}') was not found at:")
728
- print(f" {PDF_PATH}")
729
- print(f"Please ensure the file exists or set 'PDF_PATH' environment variable.")
730
- print(f"---------------------------\n")
731
  exit(1)
732
 
733
- logging.info("Initializing Vector Database...")
734
  vector_db_instance = VectorDatabase(persist_directory=VECTOR_DB_PATH)
735
- logging.info("Initializing RAG System...")
736
  rag = RAGSystem(vector_db=vector_db_instance)
 
737
 
738
- logging.info(f"Loading/Verifying data from PDF: {PDF_PATH}")
739
- states_loaded_count = rag.load_pdf(PDF_PATH)
740
- doc_count = vector_db_instance.document_collection.count() if vector_db_instance.document_collection else 0
741
- state_count = vector_db_instance.state_collection.count() if vector_db_instance.state_collection else 0
742
- total_items = doc_count + state_count
743
-
744
- if total_items > 0:
745
- logging.info(f"Data loading/verification complete. Vector DB contains {total_items} items. Found {states_loaded_count} distinct states.")
746
- else:
747
- logging.warning("Potential issue: PDF processed but Vector DB appears empty. Check PDF content/format and logs.")
748
- print("\nWarning: No data loaded from PDF or found in DB. Application might not function correctly.\n")
749
-
750
- logging.info("Setting up Gradio interface...")
751
  app_interface = rag.gradio_interface()
752
-
753
  SERVER_PORT = 7860
754
  logging.info(f"Launching Gradio app on http://0.0.0.0:{SERVER_PORT}")
755
- print("\n--- Gradio App Running ---")
756
- print(f"Access the interface in your browser at: http://localhost:{SERVER_PORT} or http://<your-ip-address>:{SERVER_PORT}")
757
- print("--------------------------\n")
758
- app_interface.launch(
759
- server_name="0.0.0.0", server_port=SERVER_PORT,
760
- share=True,
761
-
762
- )
763
 
764
- except FileNotFoundError as fnf_error:
765
- logging.error(f"Initialization failed due to a missing file: {str(fnf_error)}", exc_info=True)
766
- print(f"\n--- STARTUP ERROR: File Not Found ---")
767
- print(f"{str(fnf_error)}")
768
- print(f"---------------------------------------\n")
769
- exit(1)
770
- except ImportError as import_error:
771
- logging.error(f"Import error: {str(import_error)}. Check dependencies.", exc_info=True)
772
- print(f"\n--- STARTUP ERROR: Missing Dependency ---")
773
- print(f"Import Error: {str(import_error)}")
774
- print(f"Please ensure required libraries are installed (e.g., pip install -r requirements.txt).")
775
- print(f"-----------------------------------------\n")
776
- exit(1)
777
- except RuntimeError as runtime_error:
778
- logging.error(f"A runtime error occurred during setup: {str(runtime_error)}", exc_info=True)
779
- print(f"\n--- STARTUP ERROR: Runtime Problem ---")
780
- print(f"Runtime Error: {str(runtime_error)}")
781
- print(f"Check logs for details, often related to data loading or DB setup.")
782
- print(f"--------------------------------------\n")
783
- exit(1)
784
  except Exception as e:
785
- logging.error(f"An unexpected error occurred during application startup: {str(e)}", exc_info=True)
786
- print(f"\n--- FATAL STARTUP ERROR ---")
787
- print(f"An unexpected error stopped the application: {str(e)}")
788
- print(f"Check logs for detailed traceback.")
789
- print(f"---------------------------\n")
790
  exit(1)
 
35
 
36
  # --- RAGSystem Class ---
37
  class RAGSystem:
 
 
 
 
 
 
 
38
  def __init__(self, vector_db: Optional[VectorDatabase] = None):
39
  logging.info("Initializing RAGSystem")
40
  self.vector_db = vector_db if vector_db else VectorDatabase()
41
  self.llm = None
42
  self.chain = None
43
  self.prompt_template_str = """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.
 
44
  Instructions:
45
  * Use the context and statutes as the primary basis for your answer when available.
46
  * For state-specific queries, prioritize statutes or legal principles from the specified state over federal laws.
 
50
  * Include practical examples or scenarios to enhance clarity and usefulness.
51
  * Use bullet points or numbered lists for readability when appropriate.
52
  * Maintain a professional and neutral tone.
 
53
  Question: {query}
54
  State: {state}
55
  Statutes from context:
56
  {statutes}
 
57
  Context information:
58
  --- START CONTEXT ---
59
  {context}
60
  --- END CONTEXT ---
 
61
  Answer:"""
62
  self.prompt_template = PromptTemplate(
63
  input_variables=["query", "context", "state", "statutes"],
 
237
  logging.error(f"Failed to load or process PDF '{pdf_path}': {str(e)}", exc_info=True)
238
  raise RuntimeError(f"Failed to process PDF '{pdf_path}': {e}") from e
239
 
 
240
  # --- GRADIO INTERFACE ---
241
  def gradio_interface(self):
 
242
  def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
243
  logging.info(f"Gradio interface received query: '{query[:50]}...', state: '{state}'")
 
 
244
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
245
+ return "<div class='error-message'><span class='error-icon'>⚠️</span>Please provide a valid OpenAI API key (starting with 'sk-'). <a href='https://platform.openai.com/api-keys' target='_blank'>Get one here</a>.</div>"
246
  if not state or state == "Select a state..." or "Error" in state:
247
+ return "<div class='error-message'><span class='error-icon'>⚠️</span>Please select a valid state from the dropdown.</div>"
248
  if not query or not query.strip():
249
+ return "<div class='error-message'><span class='error-icon'>⚠️</span>Please enter your question in the text box.</div>"
 
 
250
  result = self.process_query(query=query, state=state, openai_api_key=api_key)
251
+ answer = result.get("answer", "<div class='error-message'><span class='error-icon'>⚠️</span>An unexpected error occurred.</div>")
 
 
 
 
252
  if not "<div class='error-message'>" in answer:
253
+ formatted_response = f"<div class='response-header'><span class='response-icon'>📜</span>Response for {state}</div><hr class='divider'>{answer}"
254
  else:
255
+ formatted_response = answer
 
 
 
 
 
 
 
 
256
  return formatted_response
257
 
 
258
  try:
259
  available_states_list = self.get_states()
260
+ dropdown_choices = ["Select a state..."] + (available_states_list if available_states_list and "Error" not in available_states_list[0] else ["Error: States unavailable"])
261
+ initial_value = dropdown_choices[0]
262
+ except Exception:
 
 
 
 
 
 
263
  dropdown_choices = ["Error: Critical failure loading states"]
264
  initial_value = dropdown_choices[0]
265
 
 
266
  example_queries_base = [
267
  ["What are the rules for security deposit returns?", "California"],
268
  ["Can a landlord enter my apartment without notice?", "New York"],
269
  ["My landlord hasn't made necessary repairs. What can I do?", "Texas"],
 
 
 
270
  ]
271
  example_queries = []
272
+ if available_states_list and "Error" not in available_states_list[0] and len(available_states_list) > 0: # Check length too
273
  loaded_states_set = set(available_states_list)
274
  example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
275
+ if not example_queries: # If base examples don't match loaded states, add a generic one
276
+ example_queries.append(["What basic rights do tenants have?", available_states_list[0]])
277
+ elif not example_queries: # Fallback if states list is empty or errored
278
+ example_queries.append(["What basic rights do tenants have?", "California"])
279
 
280
+
281
+ # --- REFINED "Clarity & Counsel" Theme ---
282
  custom_css = """
283
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
284
+
285
+ :root {
286
+ --font-family-main: 'Poppins', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
287
+
288
+ /* Clarity & Counsel - Light Theme */
289
+ --app-bg-light: #F9FAFB;
290
+ --surface-bg-light: #FFFFFF;
291
+ --text-primary-light: #1A202C; /* Tailwind gray-900 - darker for more contrast */
292
+ --text-secondary-light: #718096; /* Tailwind gray-600 */
293
+ --accent-primary-light: #00796B; /* Teal 700 */
294
+ --accent-primary-hover-light: #00695C; /* Teal 800 */
295
+ --interactive-text-light: #00796B;
296
+ --interactive-text-hover-light: #005F52; /* Darker hover for links */
297
+ --border-light: #E2E8F0; /* Tailwind gray-300 */
298
+ --button-secondary-bg-light: #F1F5F9; /* Tailwind slate-100 */
299
+ --button-secondary-text-light: #334155; /* Tailwind slate-700 */
300
+ --button-secondary-hover-bg-light: #E2E8F0; /* Tailwind slate-200 */
301
+ --shadow-light: 0 4px 12px rgba(0,0,0,0.05); /* Single softer, more diffused shadow */
302
+ --focus-ring-light: rgba(0, 121, 107, 0.2);
303
+ --error-bg-light: #FFF1F2; --error-text-light: #C81E1E; --error-border-light: #FFD0D0;
304
+ --success-bg-light: #EFFCF6; --success-text-light: #15803D; --success-border-light: #B3EED1;
305
+
306
+ /* Clarity & Counsel - Dark Theme */
307
+ --app-bg-dark: #0F172A; /* Tailwind slate-900 - deeper dark */
308
+ --surface-bg-dark: #1E293B; /* Tailwind slate-800 */
309
+ --text-primary-dark: #F1F5F9; /* Tailwind slate-100 */
310
+ --text-secondary-dark: #94A3B8; /* Tailwind slate-400 */
311
+ --accent-primary-dark: #2DD4BF; /* Teal 400 */
312
+ --accent-primary-hover-dark: #14B8A6; /* Teal 500 */
313
+ --interactive-text-dark: #5EEAD4; /* Brighter teal for links */
314
+ --interactive-text-hover-dark: #99F6E4; /* Even brighter */
315
+ --border-dark: #334155; /* Tailwind slate-700 */
316
+ --button-secondary-bg-dark: #334155;
317
+ --button-secondary-text-dark: #CBD5E1; /* Tailwind slate-300 */
318
+ --button-secondary-hover-bg-dark: #475569; /* Tailwind slate-600 */
319
+ --shadow-dark: 0 4px 12px rgba(0,0,0,0.2);
320
+ --focus-ring-dark: rgba(45, 212, 191, 0.25);
321
+ --error-bg-dark: #451515; --error-text-dark: #FFD0D0; --error-border-dark: #9E2D2D;
322
+ --success-bg-dark: #073D24; --success-text-dark: #B3EED1; --success-border-dark: #16653D;
323
+
324
+ --radius-sm: 4px; --radius-md: 8px; --radius-lg: 12px; /* More pronounced radius */
325
+ --transition: 0.15s ease-in-out;
326
+ }
327
 
 
328
  body, .gradio-container {
329
+ font-family: var(--font-family-main) !important; background: var(--app-bg-light) !important;
330
+ color: var(--text-primary-light) !important; margin: 0; padding: 0; min-height: 100vh;
331
+ font-size: 16px; line-height: 1.7;
332
+ }
333
+ * { box-sizing: border-box; }
334
+ @media (prefers-color-scheme: dark) {
335
+ body, .gradio-container { background: var(--app-bg-dark) !important; color: var(--text-primary-dark) !important; }
336
+ }
337
+
338
+ .gradio-container > .flex.flex-col {
339
+ max-width: 800px; margin: 0 auto !important; padding: 3rem 1.5rem !important; gap: 3rem !important; /* Increased gap */
340
+ }
341
+
342
+ .content-surface {
343
+ background: var(--surface-bg-light) !important; border-radius: var(--radius-lg) !important;
344
+ padding: 2.75rem !important; /* More padding */
345
+ box-shadow: var(--shadow-light) !important; border: 1px solid var(--border-light) !important;
346
+ }
347
+ @media (prefers-color-scheme: dark) {
348
+ .content-surface {
349
+ background: var(--surface-bg-dark) !important; box-shadow: var(--shadow-dark) !important;
350
+ border: 1px solid var(--border-dark) !important;
351
+ }
352
+ }
353
+
354
+ .app-header {
355
+ background: var(--accent-primary-light) !important; color: #FFFFFF !important;
356
+ padding: 3.5rem 2rem !important; /* More padding */
357
+ text-align: center; margin-bottom: 3rem; /* Increased space */
358
+ border-bottom-left-radius: var(--radius-lg); border-bottom-right-radius: var(--radius-lg);
359
+ box-shadow: 0 6px 15px rgba(0, 121, 107, 0.15); /* Shadow using accent color */
360
+ border-bottom: 4px solid var(--accent-primary-hover-light);
361
+ }
362
+ .app-header-logo { font-size: 3.25rem; margin-bottom: 0.85rem; display: block; }
363
+ .app-header-title { font-size: 2.4rem; font-weight: 600; margin: 0 0 0.6rem 0; }
364
+ .app-header-tagline { font-size: 1.15rem; font-weight: 300; opacity: 0.95; }
365
+ @media (prefers-color-scheme: dark) {
366
+ .app-header {
367
+ background: var(--accent-primary-dark) !important; color: var(--app-bg-dark) !important; /* Dark text on bright teal */
368
+ box-shadow: 0 6px 15px rgba(45, 212, 191, 0.2);
369
+ border-bottom: 4px solid var(--accent-primary-hover-dark);
370
+ }
371
+ }
372
+
373
+ .section-title, .input-form-card h3, .examples-card .gr-examples-header {
374
+ font-size: 1.6rem !important; font-weight: 600 !important; color: var(--text-primary-light) !important;
375
+ margin: 0 auto 2.25rem auto !important; padding-bottom: 1rem !important;
376
+ border-bottom: 1px solid var(--border-light) !important; text-align: center !important; max-width: 100%;
377
+ }
378
+ @media (prefers-color-scheme: dark) {
379
+ .section-title, .input-form-card h3, .examples-card .gr-examples-header {
380
+ color: var(--text-primary-dark) !important; border-bottom-color: var(--border-dark) !important;
381
+ }
382
+ }
383
+
384
+ .content-surface p { font-size: 1.05rem; line-height: 1.75; color: var(--text-secondary-light); margin-bottom: 1.1rem; }
385
+ .content-surface a { color: var(--interactive-text-light); text-decoration: none; font-weight: 500; }
386
+ .content-surface a:hover { color: var(--interactive-text-hover-light); text-decoration: underline; }
387
+ .content-surface strong { font-weight: 600; color: var(--text-primary-light); }
388
+ @media (prefers-color-scheme: dark) {
389
+ .content-surface p { color: var(--text-secondary-dark); }
390
+ .content-surface a { color: var(--interactive-text-dark); }
391
+ .content-surface a:hover { color: var(--interactive-text-hover-dark); }
392
+ .content-surface strong { color: var(--text-primary-dark); }
393
+ }
394
+
395
+ .input-field-group { margin-bottom: 2rem; }
396
+ .input-row { display: flex; gap: 2rem; flex-wrap: wrap; margin-bottom: 2rem; }
397
+ .input-field { flex: 1; min-width: 240px; }
398
+
399
+ .gradio-input-label {
400
+ font-size: 0.95rem !important; font-weight: 500 !important; color: var(--text-primary-light) !important;
401
+ margin-bottom: 0.6rem !important; display: block !important;
402
+ }
403
+ .gradio-input-info { font-size: 0.85rem !important; color: var(--text-secondary-light) !important; margin-top: 0.4rem; }
404
+ @media (prefers-color-scheme: dark) {
405
+ .gradio-input-label { color: var(--text-primary-dark) !important; }
406
+ .gradio-input-info { color: var(--text-secondary-dark) !important; }
407
+ }
408
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
  .gradio-textbox textarea, .gradio-dropdown select, .gradio-textbox input[type=password] {
410
+ border: 1px solid var(--border-light) !important; border-radius: var(--radius-md) !important;
411
+ padding: 0.9rem 1.1rem !important; font-size: 1rem !important;
412
+ background: var(--surface-bg-light) !important; color: var(--text-primary-light) !important;
413
+ width: 100% !important; box-shadow: none !important;
414
+ }
415
+ .gradio-textbox textarea { min-height: 125px; }
416
+ .gradio-textbox textarea::placeholder, .gradio-textbox input[type=password]::placeholder { color: #A0AEC0 !important; } /* Tailwind gray-400 for placeholder */
 
 
 
417
  .gradio-textbox textarea:focus, .gradio-dropdown select:focus, .gradio-textbox input[type=password]:focus {
418
+ border-color: var(--accent-primary-light) !important;
419
+ box-shadow: 0 0 0 3px var(--focus-ring-light) !important; outline: none !important;
420
+ }
421
+ @media (prefers-color-scheme: dark) {
422
+ .gradio-textbox textarea, .gradio-dropdown select, .gradio-textbox input[type=password] {
423
+ border: 1px solid var(--border-dark) !important; background: var(--surface-bg-dark) !important;
424
+ color: var(--text-primary-dark) !important;
425
+ }
426
+ .gradio-textbox textarea::placeholder, .gradio-textbox input[type=password]::placeholder { color: #718096 !important; } /* Tailwind gray-600 dark placeholder */
427
+ .gradio-textbox textarea:focus, .gradio-dropdown select:focus, .gradio-textbox input[type=password]:focus {
428
+ border-color: var(--accent-primary-dark) !important; box-shadow: 0 0 0 3px var(--focus-ring-dark) !important;
429
+ }
430
+ }
431
+ .gradio-dropdown select {
432
+ appearance: none; -webkit-appearance: none; -moz-appearance: none;
433
+ background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22%236B7280%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M5.293%207.293a1%201%200%20011.414%200L10%2010.586l3.293-3.293a1%201%200%20111.414%201.414l-4%204a1%201%200%2001-1.414%200l-4-4a1%201%200%20010-1.414z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
434
+ background-repeat: no-repeat; background-position: right 1.1rem center; background-size: 1em; padding-right: 3.2rem !important;
435
+ }
436
+ @media (prefers-color-scheme: dark) {
437
+ .gradio-dropdown select {
438
+ background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22%239CA3AF%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M5.293%207.293a1%201%200%20011.414%200L10%2010.586l3.293-3.293a1%201%200%20111.414%201.414l-4%204a1%201%200%2001-1.414%200l-4-4a1%201%200%20010-1.414z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
439
+ }
440
+ }
441
+
442
+ .button-row { display: flex; gap: 1.25rem; margin-top: 2.25rem; flex-wrap: wrap; justify-content: flex-end; }
443
  .gradio-button {
444
+ border-radius: var(--radius-md) !important; padding: 0.85rem 2rem !important; /* Generous padding */
445
+ font-size: 1rem !important; font-weight: 500 !important; border: 1px solid transparent !important;
446
+ box-shadow: var(--shadow-light) !important;
447
+ }
448
+ .gradio-button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 5px 10px rgba(0,0,0,0.07) !important; }
449
+ .gradio-button:active:not(:disabled) { transform: translateY(-1px); }
450
+ .gradio-button:disabled {
451
+ background: #E5E7EB !important; color: #9CA3AF !important; box-shadow: none !important; border-color: #D1D5DB !important;
452
+ }
453
+ .gr-button-primary {
454
+ background: var(--accent-primary-light) !important; color: #FFFFFF !important; border-color: var(--accent-primary-light) !important;
455
+ }
456
+ .gr-button-primary:hover:not(:disabled) { background: var(--accent-primary-hover-light) !important; border-color: var(--accent-primary-hover-light) !important;}
457
+ .gr-button-secondary {
458
+ background: var(--button-secondary-bg-light) !important; color: var(--button-secondary-text-light) !important;
459
+ border-color: var(--border-light) !important;
460
+ }
461
+ .gr-button-secondary:hover:not(:disabled) { background: var(--button-secondary-hover-bg-light) !important; border-color: #CBD5E0 !important; }
462
+ @media (prefers-color-scheme: dark) {
463
+ .gradio-button { box-shadow: var(--shadow-dark) !important; }
464
+ .gradio-button:hover:not(:disabled) { box-shadow: 0 5px 10px rgba(0,0,0,0.25) !important; }
465
+ .gradio-button:disabled { background: #334155 !important; color: #6B7280 !important; border-color: #475569 !important;}
466
+ .gr-button-primary {
467
+ background: var(--accent-primary-dark) !important; color: var(--app-bg-dark) !important; /* Dark text on bright teal */
468
+ border-color: var(--accent-primary-dark) !important;
469
+ }
470
+ .gr-button-primary:hover:not(:disabled) { background: var(--accent-primary-hover-dark) !important; border-color: var(--accent-primary-hover-dark) !important; }
471
+ .gr-button-secondary {
472
+ background: var(--button-secondary-bg-dark) !important; color: var(--button-secondary-text-dark) !important;
473
+ border-color: var(--border-dark) !important;
474
+ }
475
+ .gr-button-secondary:hover:not(:disabled) { background: var(--button-secondary-hover-bg-dark) !important; border-color: #475569 !important; }
476
+ }
477
+
478
+ .output-card .response-header { font-size: 1.35rem; font-weight: 600; color: var(--text-primary-light); margin: 0 0 1rem 0; display: flex; align-items: center; gap: 0.65rem; }
479
+ .output-card .response-icon { font-size: 1.5rem; color: var(--text-secondary-light); }
480
+ .output-card .divider { border: none; border-top: 1px solid var(--border-light); margin: 1.5rem 0; }
481
+ .output-card .output-content-wrapper { font-size: 1.05rem; line-height: 1.8; color: var(--text-primary-light); }
482
+ @media (prefers-color-scheme: dark) {
483
+ .output-card .response-header { color: var(--text-primary-dark); }
484
+ .output-card .response-icon { color: var(--text-secondary-dark); }
485
+ .output-card .divider { border-top: 1px solid var(--border-dark); }
486
+ .output-card .output-content-wrapper { color: var(--text-primary-dark); }
487
+ }
488
+
489
+ .output-card .error-message, .output-card .success-message {
490
+ padding: 1.1rem 1.35rem; margin-top: 1.25rem; font-size: 1rem;
491
+ }
492
+ .output-card .error-message .error-icon { font-size: 1.25rem; }
493
+ .output-card .error-details { font-size: 0.9rem; }
494
+ .output-card .placeholder { padding: 3.5rem 1.75rem; font-size: 1.15rem; }
495
+
496
+ .examples-card .gr-examples-table {
497
+ border-radius: var(--radius-lg) !important; border: 1px solid var(--border-light) !important;
498
+ }
499
+ .examples-card .gr-examples-table th, .examples-card .gr-examples-table td {
500
+ padding: 1rem 1.2rem !important; font-size: 1rem !important;
501
+ }
502
+ .examples-card .gr-examples-table th { background: #F9FAFB !important; } /* Slightly off-white header */
503
+ @media (prefers-color-scheme: dark) {
504
+ .examples-card .gr-examples-table { border: 1px solid var(--border-dark) !important;}
505
+ .examples-card .gr-examples-table th { background: #111827 !important; }
506
+ }
507
+
508
+ .app-footer {
509
+ border-top: 1px solid var(--border-light) !important; padding: 3rem 1.5rem !important;
510
+ margin-top: 3rem !important; text-align: center !important;
511
+ font-size: 0.95rem !important; color: var(--text-secondary-light);
512
+ }
513
+ .app-footer p { color: var(--text-secondary-light) !important; margin-bottom: 0.6rem; } /* Explicit color for p */
514
+ .app-footer a { color: var(--interactive-text-light) !important; } /* Explicit color for a */
515
+ .app-footer a:hover { color: var(--interactive-text-hover-light) !important; }
516
+ @media (prefers-color-scheme: dark) {
517
+ .app-footer { border-top: 1px solid var(--border-dark) !important; color: var(--text-secondary-dark); }
518
+ .app-footer p { color: var(--text-secondary-dark) !important; }
519
+ .app-footer a { color: var(--interactive-text-dark) !important; }
520
+ .app-footer a:hover { color: var(--interactive-text-hover-dark) !important; }
521
+ }
522
+
523
+ :focus-visible { outline: 2px solid var(--accent-primary-light) !important; outline-offset: 2px; box-shadow: 0 0 0 3px var(--focus-ring-light) !important; }
524
+ @media (prefers-color-scheme: dark) {
525
+ :focus-visible { outline-color: var(--accent-primary-dark) !important; box-shadow: 0 0 0 3px var(--focus-ring-dark) !important; }
526
+ }
 
 
 
527
  .gradio-button span:focus { outline: none !important; }
528
 
 
529
  @media (max-width: 768px) {
530
+ body { font-size: 15px; }
531
+ .gradio-container > .flex.flex-col { padding: 2rem 1rem !important; gap: 2.5rem !important; }
532
+ .content-surface { padding: 2.25rem !important; }
533
+ .app-header { padding: 3rem 1.25rem !important; }
534
+ .app-header-logo { font-size: 2.8rem; } .app-header-title { font-size: 2rem; } .app-header-tagline { font-size: 1.05rem; }
535
+ .input-row { flex-direction: column; gap: 1.75rem; } .input-field { min-width: 100%; }
536
+ .button-row { justify-content: stretch; } .gradio-button { width: 100%; }
537
+ .section-title, .input-form-card h3, .examples-card .gr-examples-header { font-size: 1.4rem !important; }
538
  }
539
  @media (max-width: 480px) {
540
+ .gradio-container > .flex.flex-col { padding: 1.75rem 0.75rem !important; gap: 2rem !important; }
541
+ .content-surface { padding: 1.75rem !important; border-radius: var(--radius-md) !important; }
542
+ .app-header { padding: 2.5rem 1rem !important; border-bottom-left-radius: var(--radius-md); border-bottom-right-radius: var(--radius-md); }
543
+ .app-header-logo { font-size: 2.4rem; } .app-header-title { font-size: 1.8rem; } .app-header-tagline { font-size: 1rem; }
544
+ .section-title, .input-form-card h3, .examples-card .gr-examples-header { font-size: 1.3rem !important; margin-bottom: 1.75rem !important; padding-bottom: 0.85rem !important; }
545
+ .gradio-textbox textarea, .gradio-dropdown select, .gradio-textbox input[type=password] { font-size: 0.95rem !important; padding: 0.85rem 1rem !important; }
546
+ .gradio-button { padding: 0.85rem 1.5rem !important; font-size: 1rem !important; }
547
+ }
548
+
549
+ .gradio-container > .flex { gap: 3rem !important; }
550
+ .gradio-markdown > *:first-child { margin-top: 0; } .gradio-markdown > *:last-child { margin-bottom: 0; }
551
+ .gradio-dropdown, .gradio-textbox { border: none !important; padding: 0 !important; background: transparent !important; }
 
 
 
 
 
 
 
 
 
 
 
 
552
  """
553
 
554
+ with gr.Blocks(theme=None, css=custom_css, title="Landlord-Tenant Rights Assistant") as demo:
555
+ with gr.Row():
556
+ with gr.Column(scale=12):
557
+ gr.Markdown(
558
+ """
559
+ <div class="app-header">
560
+ <span class="app-header-logo">⚖️</span>
561
+ <h1 class="app-header-title">Landlord-Tenant Rights Assistant</h1>
562
+ <p class="app-header-tagline">Empowering You with State-Specific Legal Insights</p>
563
+ </div>
564
+ """
565
+ )
566
 
567
+ with gr.Group(elem_classes="content-surface"):
568
+ gr.Markdown("<h3 class='section-title'>Know Your Rights</h3>")
569
  gr.Markdown(
570
  """
571
+ <p>Navigate landlord-tenant laws with ease. Enter your <strong>OpenAI API key</strong>, select your state, and ask your question to get detailed, state-specific answers.</p>
572
+ <p>Don't have an API key? <a href='https://platform.openai.com/api-keys' target='_blank'>Get one free from OpenAI</a>.</p>
573
+ <p><strong>Disclaimer:</strong> This tool provides information only, not legal advice. For legal guidance, consult a licensed attorney.</p>
 
 
 
 
 
 
574
  """
 
 
 
 
 
 
 
575
  )
576
 
577
+ with gr.Group(elem_classes="content-surface input-form-card"):
578
+ gr.Markdown("<h3>Ask Your Question</h3>")
 
 
 
 
579
  with gr.Column(elem_classes="input-field-group"):
580
+ api_key_input = gr.Textbox(
581
+ label="OpenAI API Key", type="password", placeholder="Enter your API key (e.g., sk-...)",
582
+ info="Required to process your query. Securely used per request, not stored.", lines=1
583
+ )
 
 
 
584
  with gr.Row(elem_classes="input-row"):
585
+ with gr.Column(elem_classes="input-field", min_width="58%"): # Adjusted min-width
586
+ query_input = gr.Textbox(
587
+ label="Your Question", placeholder="E.g., What are the rules for security deposit returns in my state?",
588
+ lines=5, max_lines=10 # Slightly taller
589
+ )
590
+ with gr.Column(elem_classes="input-field", min_width="38%"): # Adjusted min-width
 
591
  state_input = gr.Dropdown(
592
  label="Select State", choices=dropdown_choices, value=initial_value,
593
+ allow_custom_value=False
594
+ )
 
595
  with gr.Row(elem_classes="button-row"):
596
+ clear_button = gr.Button("Clear", variant="secondary", elem_classes=["gr-button-secondary"])
597
+ submit_button = gr.Button("Submit Query", variant="primary", elem_classes=["gr-button-primary"])
 
 
 
 
 
 
598
 
599
+ with gr.Group(elem_classes="content-surface output-card"): # Added output-card for specific styling if needed
600
+ output = gr.Markdown(
601
+ value="<div class='placeholder'>Your answer will appear here after submitting your query.</div>",
602
+ elem_classes="output-content-wrapper"
603
+ )
 
 
 
 
604
 
 
605
  if example_queries:
606
+ with gr.Group(elem_classes="content-surface examples-card"):
607
+ gr.Examples(
608
+ examples=example_queries, inputs=[query_input, state_input],
609
+ label="Explore Sample Questions", examples_per_page=5
 
 
 
 
 
 
 
610
  )
611
+ else:
612
+ with gr.Group(elem_classes="content-surface"):
613
+ gr.Markdown("<div class='placeholder'>Sample questions could not be loaded.</div>")
614
+
615
+ with gr.Row():
616
+ with gr.Column(scale=12):
617
+ gr.Markdown(
618
+ """
619
+ <div class="app-footer">
620
+ <p>This tool is for informational purposes only and does not constitute legal advice. For legal guidance, always consult with a licensed attorney in your jurisdiction.</p>
621
+ <p>Developed by <span style="color:#2AE7D6; font-weight:bold;">Nischal Subedi</span>.
622
+ Connect on <a href="https://www.linkedin.com/in/nischal1/" target='_blank' style="color:#2AE7D6; text-decoration:underline;">LinkedIn</a>
623
+ or explore insights at <a href="https://datascientistinsights.substack.com/" target='_blank' style="color:#2AE7D6; text-decoration:underline;">Substack</a>.</p>
624
+ </div>
625
+ """
626
+ )
627
 
 
 
 
 
 
 
 
 
 
628
 
 
629
  submit_button.click(
630
+ fn=query_interface_wrapper, inputs=[api_key_input, query_input, state_input], outputs=output, api_name="submit_query"
 
 
 
631
  )
 
632
  clear_button.click(
633
+ fn=lambda: ("", "", initial_value, "<div class='placeholder'>Inputs cleared. Ready for your next question.</div>"),
634
+ inputs=[], outputs=[api_key_input, query_input, state_input, output]
 
 
 
 
635
  )
636
+ logging.info("Refined Clarity & Counsel theme Gradio interface created.")
 
637
  return demo
638
 
639
+ # --- Main Execution Block (remains the same) ---
640
  if __name__ == "__main__":
641
  logging.info("Starting Landlord-Tenant Rights Bot application...")
642
  try:
 
650
  os.makedirs(os.path.dirname(VECTOR_DB_PATH), exist_ok=True)
651
  os.makedirs(os.path.dirname(PDF_PATH), exist_ok=True)
652
 
 
 
 
653
  if not os.path.exists(PDF_PATH):
654
  logging.error(f"FATAL: PDF file not found at the specified path: {PDF_PATH}")
655
+ 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")
 
 
 
 
656
  exit(1)
657
 
 
658
  vector_db_instance = VectorDatabase(persist_directory=VECTOR_DB_PATH)
 
659
  rag = RAGSystem(vector_db=vector_db_instance)
660
+ rag.load_pdf(PDF_PATH)
661
 
 
 
 
 
 
 
 
 
 
 
 
 
 
662
  app_interface = rag.gradio_interface()
 
663
  SERVER_PORT = 7860
664
  logging.info(f"Launching Gradio app on http://0.0.0.0:{SERVER_PORT}")
665
+ print(f"\n--- Gradio App Running ---\nAccess at: http://localhost:{SERVER_PORT}\n--------------------------\n")
666
+ app_interface.launch(server_name="0.0.0.0", server_port=SERVER_PORT, share=True)
 
 
 
 
 
 
667
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
668
  except Exception as e:
669
+ logging.error(f"Application startup failed: {str(e)}", exc_info=True)
670
+ print(f"\n--- FATAL STARTUP ERROR ---\n{str(e)}\nCheck logs for details.\n---------------------------\n")
 
 
 
671
  exit(1)