Nischal Subedi commited on
Commit
6cd408d
·
1 Parent(s): 28bc2fc
Files changed (1) hide show
  1. app.py +228 -628
app.py CHANGED
@@ -244,757 +244,357 @@ Answer:"""
244
 
245
  # --- GRADIO INTERFACE (NEW UI DESIGN) ---
246
  def gradio_interface(self):
247
- def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
248
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
249
- 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'>OpenAI</a>.</div>"
250
  if not state or state is None:
251
- return "<div class='error-message'><span class='error-icon'></span>Please select a valid state from the list.</div>"
252
  if not query or not query.strip():
253
- return "<div class='error-message'><span class='error-icon'></span>Please enter your question in the text box.</div>"
254
  result = self.process_query(query=query, state=state, openai_api_key=api_key)
255
- answer = result.get("answer", "<div class='error-message'><span class='error-icon'>⚠️</span>An unexpected error occurred.</div>")
256
  if "<div class='error-message'>" in answer:
257
  return answer
258
  else:
259
- formatted_response_content = f"<div class='response-header'><span class='response-icon'>📜</span>Response for {state}</div><hr class='divider'>{answer}"
260
- return f"<div class='animated-output-content'>{formatted_response_content}</div>"
 
 
 
261
 
262
  try:
263
- available_states_list = self.get_states()
264
- print(f"DEBUG: States loaded for selection: {available_states_list}")
265
- radio_choices = available_states_list if available_states_list and "Error" not in available_states_list[0] else ["Error: States unavailable"]
266
- initial_value_radio = None
267
  except Exception as e:
268
- print(f"DEBUG: Error loading states for selection: {e}")
269
- radio_choices = ["Error: Critical failure loading states"]
270
- initial_value_radio = None
271
- example_queries_base = [
 
272
  ["What are the rules for security deposit returns?", "California"],
273
- ["Can a landlord enter my apartment without notice?", "New York"],
274
- ["My landlord hasn't made necessary repairs. What can I do?", "Texas"],
275
- ["How much notice must a landlord give to raise rent?", "Florida"],
276
- ["What is an implied warranty of habitability?", "Illinois"]
277
  ]
278
- example_queries = []
279
- if available_states_list and "Error" not in available_states_list[0] and len(available_states_list) > 0:
280
- loaded_states_set = set(available_states_list)
281
- example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
282
- if not example_queries:
283
- example_queries.append(["What basic rights do tenants have?", available_states_list[0] if available_states_list else "California"])
284
- else:
285
- example_queries.append(["What basic rights do tenants have?", "California"])
286
 
287
  custom_css = """
288
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@600;700;800&display=swap');
289
-
290
- :root {
291
- --primary-color: #FF8C00;
292
- --primary-hover: #E07B00;
293
- --background-primary: hsl(30, 100%, 99.9%);
294
- --background-secondary: hsl(30, 100%, 96%);
295
- --text-primary: #4A3C32;
296
- --text-secondary: #8C7B6F;
297
- --border-color: hsl(30, 70%, 85%);
298
- --border-focus: #FF8C00;
299
- --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
300
- --shadow-md: 0 4px 10px rgba(0,0,0,0.1);
301
- --shadow-lg: 0 10px 20px rgba(0,0,0,0.15);
302
- --error-bg: #FFF0E0;
303
- --error-border: #FFD2B2;
304
- --error-text: #E05C00;
305
- }
306
 
307
- @media (prefers-color-scheme: dark) {
308
- body {
309
- --primary-color: #FFA500;
310
- --primary-hover: #CC8400;
311
- --background-primary: #333333;
312
- --background-secondary: #222222;
313
- --text-primary: #E0E0E0;
314
- --text-secondary: #A0A0A0;
315
- --border-color: #444444;
316
- --border-focus: #FFA500;
317
- --shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
318
- --shadow-md: 0 4px 10px rgba(0,0,0,0.4);
319
- --shadow-lg: 0 10px 20px rgba(0,0,0,0.5);
320
- --error-bg: #400000;
321
- --error-border: #800000;
322
- --error-text: #FF6666;
323
- }
324
  }
325
 
326
- body, html {
327
- background-color: var(--background-secondary) !important;
328
- color: var(--text-primary) !important;
329
- transition: background-color 0.3s, color 0.3s;
330
- font-size: 16px !important;
331
  }
332
 
333
- .gradio-container {
334
- max-width: 900px !important;
335
- margin: 0 auto !important;
336
- padding: 1.5rem !important;
337
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
338
- background-color: var(--background-secondary) !important;
339
- box-shadow: none !important;
340
- color: var(--text-primary) !important;
341
  }
342
 
343
- .main-dashboard-container > * {
344
- background-color: var(--background-primary) !important;
345
  }
346
 
347
- .app-header-wrapper {
348
- background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
349
- border: 2px solid var(--border-color) !important;
350
- border-radius: 16px !important;
351
- padding: 2.5rem 1.5rem !important;
352
- margin-bottom: 1.5rem !important;
353
- box-shadow: var(--shadow-md) !important;
354
- position: relative;
355
- overflow: hidden;
356
- text-align: center !important;
357
- color: var(--text-primary) !important;
358
  }
359
 
360
- .app-header-wrapper::before {
361
- content: '';
362
- position: absolute;
363
- top: 0;
364
- left: 0;
365
- width: 100%;
366
- height: 100%;
367
- background: radial-gradient(circle at top left, rgba(255,140,0,0.3) 0%, transparent 60%), radial-gradient(circle at bottom right, rgba(255,140,0,0.3) 0%, transparent 60%);
368
- z-index: 0;
369
- opacity: 0.8;
370
- pointer-events: none;
371
  }
372
 
373
- .app-header-logo {
374
- font-size: 8.5rem !important;
375
- margin-bottom: 0.75rem !important;
376
- display: block !important;
377
- color: var(--primary-color) !important;
378
- position: relative;
379
- z-index: 1;
380
- animation: float-icon 3s ease-in-out infinite alternate;
381
  }
382
 
383
- @keyframes float-icon {
384
- 0% { transform: translateY(0px); }
385
- 50% { transform: translateY(-5px); }
386
- 100% { transform: translateY(0px); }
387
  }
388
 
389
- .app-header-title {
390
- font-family: 'Poppins', sans-serif !important;
391
- font-size: 3rem !important;
392
- font-weight: 800 !important;
393
- color: var(--text-primary) !important;
394
- margin: 0 0 0.75rem 0 !important;
395
- line-height: 1.1 !important;
396
- letter-spacing: -0.03em !important;
397
- position: relative;
398
- z-index: 1;
399
- display: inline-block;
400
- max-width: 100%;
401
  }
402
 
403
- .app-header-tagline {
404
- font-size: 1.25rem !important;
405
- color: var(--text-secondary) !important;
406
- font-weight: 400 !important;
407
- margin: 0 !important;
408
- max-width: 700px;
409
- display: inline-block;
410
- position: relative;
411
- z-index: 1;
412
  }
413
 
414
- .main-dashboard-container {
415
- display: flex !important;
416
- flex-direction: column !important;
417
- gap: 1.25rem !important;
418
  }
419
 
420
- .dashboard-card-section {
421
- background-color: var(--background-primary) !important;
422
- border: 2px solid var(--border-color) !important;
423
- border-radius: 12px !important;
424
- padding: 0 !important;
425
- box-shadow: var(--shadow-sm) !important;
426
- transition: all 0.3s ease-out !important;
427
- cursor: default;
428
- color: var(--text-primary) !important;
429
  }
430
 
431
- .dashboard-card-section:hover {
432
- box-shadow: var(--shadow-md) !important;
433
- transform: translateY(-3px) !important;
434
  }
435
 
436
- .full-width-center {
437
- display: flex !important;
438
- justify-content: center !important;
439
- align-items: center !important;
440
- width: 100% !important;
441
- flex-direction: column !important;
442
- background-color: transparent !important;
443
  }
444
 
445
- .section-title-gradient-bar {
446
- background-color: var(--background-secondary) !important;
447
- padding: 1.25rem 1.75rem !important;
448
- border-top-left-radius: 10px !important;
449
- border-top-right-radius: 10px !important;
450
- margin-bottom: 0 !important;
451
- text-align: center !important;
452
- box-sizing: border-box;
453
- width: 100%;
454
- color: var(--text-primary) !important;
455
  }
456
 
457
- .section-title {
458
- font-family: 'Poppins', sans-serif !important;
459
- font-size: 1.7rem !important;
460
- font-weight: 700 !important;
461
- color: var(--text-primary) !important;
462
- margin: 0 !important;
463
- padding-bottom: 0 !important;
464
- border-bottom: 2px solid var(--border-color) !important;
465
- line-height: 1.1 !important;
466
- display: inline-block !important;
467
- text-align: center !important;
468
- letter-spacing: -0.01em !important;
469
  }
470
 
471
- .dashboard-card-content-area {
472
- padding: 0 1.75rem 1.75rem 1.75rem !important;
473
- background-color: var(--background-primary) !important;
474
- box-sizing: border-box;
475
  width: 100%;
476
- color: var(--text-primary) !important;
477
- }
478
-
479
- .dashboard-card-section p {
480
- line-height: 1.7 !important;
481
- color: var(--text-primary) !important;
482
- font-size: 1rem !important;
483
- text-align: left !important;
484
- background-color: transparent !important;
485
- margin: 0 !important;
486
- padding: 0 !important;
487
- white-space: normal !important;
488
- }
489
-
490
- .dashboard-card-section strong, .dashboard-card-section b {
491
- font-weight: 700 !important;
492
- color: var(--primary-color) !important;
493
- }
494
-
495
- .gr-block, .gr-box, .gr-prose, .gr-form, .gr-panel,
496
- .gr-columns, .gr-column,
497
- .gradio-html, .gradio-markdown, .gradio-textbox, .gradio-radio, .gradio-button {
498
- background-color: transparent !important;
499
- color: var(--text-primary) !important;
500
- white-space: normal !important;
501
- overflow-wrap: break-word;
502
- word-break: break-word;
503
- }
504
-
505
- .gradio-textbox textarea,
506
- .gradio-textbox input,
507
- .gradio-radio label,
508
- .placeholder {
509
- background-color: var(--background-primary) !important;
510
- color: var(--text-primary) !important;
511
- border: 2px solid var(--border-color) !important;
512
- border-radius: 8px !important;
513
- padding: 1rem !important;
514
- font-size: 1rem !important;
515
- font-family: 'Inter', sans-serif !important;
516
- }
517
-
518
- .gradio-textbox {
519
- margin-bottom: 0.5rem !important;
520
- }
521
-
522
- .gradio-textbox textarea,
523
- .gradio-textbox input {
524
- transition: border-color 0.2s ease, box-shadow 0.2s ease !important;
525
- box-shadow: var(--shadow-sm) !important;
526
- }
527
-
528
- .gradio-textbox textarea:focus,
529
- .gradio-textbox input:focus {
530
- outline: none !important;
531
- border-color: var(--border-focus) !important;
532
- box-shadow: 0 0 0 4px rgba(255, 140, 0, 0.2) !important;
533
- }
534
-
535
- .gradio-radio {
536
- padding: 0 !important;
537
- margin-top: 1rem !important;
538
- }
539
-
540
- .gradio-radio input[type="radio"] {
541
- display: none !important;
542
- }
543
-
544
- .gradio-radio label {
545
- display: flex !important;
546
- justify-content: center !important;
547
- align-items: center !important;
548
- padding: 0.75rem 1rem !important;
549
- border-radius: 8px !important;
550
- color: var(--text-primary) !important;
551
- font-weight: 500 !important;
552
- cursor: pointer !important;
553
- transition: all 0.2s ease-out !important;
554
- box-shadow: var(--shadow-sm) !important;
555
- margin: 0.2rem 0 !important;
556
- width: 100% !important;
557
- box-sizing: border-box !important;
558
- }
559
-
560
- .gradio-radio label span.text-lg {
561
- font-weight: 600 !important;
562
- color: var(--text-primary) !important;
563
- font-size: 1rem !important;
564
- }
565
-
566
- .gradio-radio label:hover {
567
- background-color: var(--background-secondary) !important;
568
- border-color: var(--primary-color) !important;
569
- box-shadow: var(--shadow-md) !important;
570
- transform: translateY(-2px) !important;
571
  }
572
 
573
- .gradio-radio input[type="radio"]:checked + label {
574
- background-color: var(--primary-color) !important;
575
- color: #FFFFFF !important;
576
- border-color: var(--primary-hover) !important;
577
- box-shadow: var(--shadow-md) !important;
578
- transform: translateY(-1px) !important;
579
  }
580
 
581
- .gradio-radio input[type="radio"]:checked + label span.text-lg {
582
- color: #FFFFFF !important;
 
 
 
583
  }
584
 
585
- .gradio-radio .gr-form {
586
- padding: 0 !important;
 
 
 
 
 
 
587
  }
588
 
589
- .gradio-textbox label,
590
- .gradio-radio > label {
591
- font-weight: 600 !important;
592
- color: var(--text-primary) !important;
593
- font-size: 1.1rem !important;
594
- margin-bottom: 0.6rem !important;
595
- display: block !important;
596
- text-align: left !important;
597
  }
598
 
599
- .gr-prose {
600
- font-size: 1rem !important;
601
- color: var(--text-secondary) !important;
602
- margin-top: 0.4rem !important;
603
- text-align: left !important;
604
- background-color: transparent !important;
605
  }
606
 
607
- .input-column {
608
- display: flex !important;
609
- flex-direction: column !important;
610
- gap: 1.25rem !important;
611
- margin-bottom: 0.5rem !important;
612
  }
613
 
614
- .input-field {
615
- flex: none !important;
616
- width: 100% !important;
617
  }
618
 
619
- .button-row {
620
- display: flex !important;
621
- gap: 1rem !important;
622
- justify-content: flex-end !important;
623
- margin-top: 1.5rem !important;
 
 
624
  }
625
 
626
- .gradio-button {
627
- padding: 0.85rem 1.8rem !important;
628
- border-radius: 9px !important;
629
- font-weight: 600 !important;
630
- font-size: 1rem !important;
631
- transition: all 0.2s ease-out !important;
632
- cursor: pointer !important;
633
- border: 2px solid transparent !important;
634
- text-align: center !important;
635
- color: var(--text-primary) !important;
636
  }
637
 
638
- .gr-button-primary {
639
- background-color: var(--primary-color) !important;
640
- color: white !important;
641
- box-shadow: var(--shadow-sm) !important;
642
  }
643
 
644
- .gr-button-primary:hover {
645
- background-color: var(--primary-hover) !important;
646
- box-shadow: var(--shadow-md) !important;
647
- transform: translateY(-2px) !important;
648
  }
649
 
650
- .gr-button-primary:active {
651
- transform: translateY(1px) !important;
652
- box-shadow: none !important;
653
  }
654
 
655
- .gr-button-secondary {
656
- background-color: transparent !important;
657
- color: var(--text-primary) !important;
658
- border-color: var(--border-color) !important;
659
  }
660
 
661
- .gr-button-secondary:hover {
662
- background-color: var(--background-secondary) !important;
663
- border-color: var(--primary-color) !important;
664
- transform: translateY(-2px) !important;
665
  }
666
 
667
- .gr-button-secondary:active {
668
- transform: translateY(1px) !important;
669
- box-shadow: none !important;
670
  }
671
 
672
- .output-content-wrapper {
673
- background-color: var(--background-primary) !important;
674
- border: 2px solid var(--border-color) !important;
675
- border-radius: 8px !important;
676
- padding: 1.5rem !important;
677
- min-height: 150px !important;
678
- color: var(--text-primary) !important;
679
- display: flex;
680
- flex-direction: column;
681
- justify-content: center;
682
- align-items: center;
683
  }
684
 
685
- .animated-output-content {
686
- opacity: 0;
687
- animation: fadeInAndSlideUp 0.7s ease-out forwards;
688
- width: 100%;
689
- white-space: pre-wrap;
690
- overflow-wrap: break-word;
691
- word-break: break-word;
692
- text-align: left !important;
693
- color: var(--text-primary) !important;
694
  }
695
 
696
- @keyframes fadeInAndSlideUp {
697
- from { opacity: 0; transform: translateY(15px); }
698
- to { opacity: 1; transform: translateY(0); }
699
  }
700
 
701
- .response-header {
702
- font-size: 1.3rem !important;
703
- font-weight: 700 !important;
704
- color: var(--primary-color) !important;
705
- margin-bottom: 0.75rem !important;
706
- display: flex !important;
707
- align-items: center !important;
708
- gap: 0.6rem !important;
709
- text-align: left !important;
710
- width: 100%;
711
- justify-content: flex-start;
712
  }
713
 
714
- .response-icon {
715
- font-size: 1.5rem !important;
716
- color: var(--primary-color) !important;
717
  }
718
 
719
- .divider {
720
- border: none !important;
721
- border-top: 1px dashed var(--border-color) !important;
722
- margin: 1rem 0 !important;
723
  }
724
 
725
  .error-message {
726
- background-color: var(--error-bg) !important;
727
- border: 2px solid var(--error-border) !important;
728
- color: var(--error-text) !important;
729
- padding: 1.25rem !important;
730
- border-radius: 8px !important;
731
- display: flex !important;
732
- align-items: flex-start !important;
733
- gap: 0.8rem !important;
734
- font-size: 0.95rem !important;
735
- font-weight: 500 !important;
736
- line-height: 1.6 !important;
737
- text-align: left !important;
738
- width: 100%;
739
- box-sizing: border-box;
740
  }
741
 
742
- .error-message a {
743
- color: var(--error-text) !important;
744
- text-decoration: underline !important;
 
745
  }
746
 
747
- .error-icon {
748
- font-size: 1.4rem !important;
749
- line-height: 1 !important;
750
- margin-top: 0.1rem !important;
751
  }
752
 
753
- .error-details {
754
- font-size: 0.85rem !important;
755
- color: var(--error-text) !important;
756
- margin-top: 0.5rem !important;
757
- opacity: 0.8;
758
- }
759
-
760
- .placeholder {
761
- background-color: var(--background-primary) !important;
762
- border: 2px dashed var(--border-color) !important;
763
- border-radius: 8px !important;
764
- padding: 2.5rem 1.5rem !important;
765
- text-align: center !important;
766
- color: var(--text-secondary) !important;
767
- font-style: italic !important;
768
- font-size: 1.1rem !important;
769
- width: 100%;
770
- box-sizing: border-box;
771
  }
772
 
773
- .examples-section .gr-samples-table {
774
- border: 2px solid var(--border-color) !important;
775
- border-radius: 8px !important;
776
- overflow: hidden !important;
777
- margin-top: 1rem !important;
778
  }
779
 
780
- .examples-section .gr-samples-table th,
781
- .examples-section .gr-samples-table td {
782
- padding: 0.9rem !important;
783
- border: none !important;
784
- font-size: 1rem !important;
785
- text-align: left !important;
786
- color: var(--text-primary) !important;
787
  }
788
 
789
- .examples-section .gr-samples-table th {
790
- background-color: var(--background-secondary) !important;
791
- font-weight: 700 !important;
792
- color: var(--text-primary) !important;
793
  }
794
 
795
- .examples-section .gr-samples-table td {
796
- background-color: var(--background-primary) !important;
797
- color: var(--text-primary) !important;
798
- border-top: 1px solid var(--border-color) !important;
799
- cursor: pointer !important;
800
- transition: background 0.2s ease, transform 0.1s ease !important;
801
  }
802
 
803
- .examples-section .gr-samples-table tr:hover td {
804
- background-color: var(--background-secondary) !important;
805
- transform: translateX(5px);
806
  }
 
807
 
808
- .gr-examples .gr-label,
809
- .gr-examples .label-wrap,
810
- .gr-examples .gr-accordion-header {
811
- display: none !important;
812
- }
 
 
813
 
814
- .app-footer-wrapper {
815
- background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
816
- border: 2px solid var(--border-color) !important;
817
- border-radius: 12px !important;
818
- padding: 1.75rem !important;
819
- margin-top: 1.5rem !important;
820
- margin-bottom: 1.5rem !important;
821
- text-align: center !important;
822
- color: var(--text-primary) !important;
823
- }
824
 
825
- .app-footer p {
826
- margin: 0 !important;
827
- max-width: 90% !important;
828
- font-size: 1rem !important;
829
- color: var(--text-secondary) !important;
830
- line-height: 1.6 !important;
831
- background-color: transparent !important;
832
- text-align: center !important;
833
- white-space: normal !important;
834
- }
835
 
836
- .app-footer strong, .app-footer b {
837
- font-weight: 700 !important;
838
- color: var(--primary-color) !important;
839
- }
 
 
840
 
841
- .app-footer a {
842
- color: var(--primary-color) !important;
843
- text-decoration: underline !important;
844
- font-weight: 600 !important;
845
- }
846
 
847
- .app-footer a:hover {
848
- text-decoration: none !important;
849
- }
850
-
851
- @media (max-width: 768px) {
852
- .gradio-container {
853
- padding: 1rem !important;
854
- }
855
- .app-header-title {
856
- font-size: 2.2rem !important;
857
- }
858
- .app-header-tagline {
859
- font-size: 1rem !important;
860
- }
861
- .section-title {
862
- font-size: 1.4rem !important;
863
- }
864
- .input-column {
865
- flex-direction: column !important;
866
- }
867
- .button-row {
868
- flex-direction: column !important;
869
- }
870
- .gradio-button {
871
- width: 100% !important;
872
- }
873
- .dashboard-card-section {
874
- }
875
- .section-title-gradient-bar {
876
- padding: 0.1rem 1rem !important;
877
- }
878
- .dashboard-card-content-area {
879
- padding: 0 1rem 1rem 1rem !important;
880
- }
881
- .output-content-wrapper {
882
- min-height: 120px !important;
883
- }
884
- .placeholder {
885
- padding: 1.5rem 1rem !important;
886
- font-size: 1rem !important;
887
- }
888
- }
889
- """
890
 
891
- with gr.Blocks(css=custom_css, title="Landlord-Tenant Rights Assistant") as demo:
892
- with gr.Group(elem_classes="app-header-wrapper"):
893
- gr.Markdown(
894
- """
895
- <span class='app-header-logo'>⚖️</span>
896
- <h1 class='app-header-title'>Landlord-Tenant Rights Assistant</h1>
897
- <p class='app-header-tagline'>Empowering You with State-Specific Legal Insights</p>
898
- """,
899
- elem_classes="full-width-center"
900
- )
901
-
902
- with gr.Column(elem_classes="main-dashboard-container"):
903
- with gr.Group(elem_classes="dashboard-card-section"):
904
- gr.Markdown("<h3 class='section-title'>How This Assistant Works</h3>", elem_classes="full-width-center section-title-gradient-bar")
905
- with gr.Column(elem_classes="dashboard-card-content-area"):
906
- gr.Markdown(
907
- """
908
- This AI-powered assistant helps navigate complex landlord-tenant laws. Simply ask a question about your state's regulations, and it will provide detailed, legally-grounded insights.
909
- """
910
- )
911
-
912
- with gr.Group(elem_classes="dashboard-card-section"):
913
- gr.Markdown("<h3 class='section-title'>OpenAI API Key</h3>", elem_classes="full-width-center section-title-gradient-bar")
914
- with gr.Column(elem_classes="dashboard-card-content-area"):
915
- api_key_input = gr.Textbox(
916
- label="API Key",
917
- type="password",
918
- placeholder="Enter your OpenAI API key (e.g., sk-...)",
919
- lines=1,
920
- elem_classes=["input-field-group"]
921
- )
922
- gr.Markdown(
923
- "Required to process your query. Get one from OpenAI: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)",
924
- elem_classes="gr-prose"
925
- )
926
-
927
- with gr.Group(elem_classes="dashboard-card-section"):
928
- gr.Markdown("<h3 class='section-title'>Ask Your Question</h3>", elem_classes="full-width-center section-title-gradient-bar")
929
- with gr.Column(elem_classes="dashboard-card-content-area"):
930
- with gr.Column(elem_classes="input-column"):
931
- with gr.Column(elem_classes="input-field", scale=1):
932
- query_input = gr.Textbox(
933
- label="Your Question",
934
- placeholder="E.g., What are the rules for security deposit returns in my state?",
935
- lines=8,
936
- max_lines=15,
937
- elem_classes=["input-field-group"]
938
- )
939
- with gr.Column(elem_classes="input-field", scale=1):
940
- state_input = gr.Radio(
941
- label="Select State",
942
- choices=radio_choices,
943
- value=initial_value_radio,
944
- elem_classes=["input-field-group", "gradio-radio-custom"],
945
- interactive=True
946
- )
947
- with gr.Row(elem_classes="button-row"):
948
- clear_button = gr.Button("Clear", variant="secondary", elem_classes=["gr-button-secondary"])
949
- submit_button = gr.Button("Submit Query", variant="primary", elem_classes=["gr-button-primary"])
950
-
951
- with gr.Group(elem_classes="dashboard-card-section"):
952
- gr.Markdown("<h3 class='section-title'>Legal Assistant's Response</h3>", elem_classes="full-width-center section-title-gradient-bar")
953
- with gr.Column(elem_classes="dashboard-card-content-area"):
954
- output = gr.HTML(
955
- value="<div class='placeholder'>The answer will appear here after submitting your query.</div>",
956
- elem_classes="output-content-wrapper"
957
- )
958
-
959
- with gr.Group(elem_classes="dashboard-card-section examples-section"):
960
- gr.Markdown("<h3 class='section-title'>Example Questions</h3>", elem_classes="full-width-center section-title-gradient-bar")
961
- with gr.Column(elem_classes="dashboard-card-content-area"):
962
- if example_queries:
963
- gr.Examples(
964
- examples=example_queries,
965
- inputs=[query_input, state_input],
966
- examples_per_page=5,
967
- label=""
968
- )
969
- else:
970
- gr.Markdown("<div class='placeholder'>Sample questions could not be loaded. Please ensure the vector database is populated.</div>")
971
-
972
- with gr.Group(elem_classes="app-footer-wrapper"):
973
- gr.Markdown(
974
- f"""
975
- <style>
976
- .custom-link {{
977
- font-weight: bold !important;
978
- color: orange !important;
979
- text-decoration: underline;
980
- }}
981
- </style>
982
- <p><strong>Disclaimer:</strong> This tool is for informational purposes only and does not constitute legal advice. For specific legal guidance, always consult with a licensed attorney in your jurisdiction.</p>
983
- <p>Developed by <strong>Nischal Subedi</strong>. Connect on <a href="https://www.linkedin.com/in/nischal1/" target="_blank" class="custom-link">LinkedIn</a> or explore insights at <a href="https://datascientistinsights.substack.com/" target="_blank" class="custom-link">Substack</a>.</p>
984
- """
985
- )
986
 
987
  submit_button.click(
988
  fn=query_interface_wrapper,
989
  inputs=[api_key_input, query_input, state_input],
990
- outputs=output,
991
- api_name="submit_query"
992
  )
993
-
994
  clear_button.click(
995
- fn=lambda: (
996
- "", "", initial_value_radio, "<div class='placeholder'>Inputs cleared. Ready for your next question.</div>"
997
- ),
998
  inputs=[],
999
  outputs=[api_key_input, query_input, state_input, output]
1000
  )
 
244
 
245
  # --- GRADIO INTERFACE (NEW UI DESIGN) ---
246
  def gradio_interface(self):
247
+ def query_interface_wrapper(api_key: str, query: str, state: str, mode: str) -> str:
248
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
249
+ 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>"
250
  if not state or state is None:
251
+ return "<div class='error-message'>Please select a valid state from the list.</div>"
252
  if not query or not query.strip():
253
+ return "<div class='error-message'>Please enter your question in the text box.</div>"
254
  result = self.process_query(query=query, state=state, openai_api_key=api_key)
255
+ answer = result.get("answer", "<div class='error-message'>An unexpected error occurred.</div>")
256
  if "<div class='error-message'>" in answer:
257
  return answer
258
  else:
259
+ formatted_response = f"<div class='response-title'>Response for {state}</div><div class='response-content'>{answer}</div>"
260
+ return f"<div class='output-box'>{formatted_response}</div>"
261
+
262
+ def toggle_theme(mode: str):
263
+ return gr.update(visible=mode == "dark"), gr.update(visible=mode == "light")
264
 
265
  try:
266
+ available_states = self.get_states()
267
+ print(f"DEBUG: States loaded: {available_states}")
268
+ states = available_states if available_states and "Error" not in available_states[0] else ["Error: States unavailable"]
269
+ initial_state = states[0] if states and "Error" not in states[0] else None
270
  except Exception as e:
271
+ print(f"DEBUG: Error loading states: {e}")
272
+ states = ["Error: Critical failure loading states"]
273
+ initial_state = None
274
+
275
+ example_queries = [
276
  ["What are the rules for security deposit returns?", "California"],
277
+ ["Can a landlord enter without notice?", "New York"],
278
+ ["What to do about unrepaired issues?", "Texas"],
 
 
279
  ]
 
 
 
 
 
 
 
 
280
 
281
  custom_css = """
282
+ @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
 
284
+ body {
285
+ font-family: 'Roboto', sans-serif !important;
286
+ margin: 0;
287
+ padding: 0;
288
+ background-color: #F5F5F5;
289
+ color: #333;
290
+ transition: all 0.3s ease;
 
 
 
 
 
 
 
 
 
 
291
  }
292
 
293
+ .dark-mode {
294
+ background-color: #1E1E1E;
295
+ color: #E0E0E0;
 
 
296
  }
297
 
298
+ .container {
299
+ max-width: 800px;
300
+ margin: 20px auto;
301
+ padding: 20px;
302
+ border-radius: 10px;
303
+ box-shadow: 0 2px 5px rgba(0,0,0,0.1);
 
 
304
  }
305
 
306
+ .dark-mode .container {
307
+ box-shadow: 0 2px 5px rgba(0,0,0,0.3);
308
  }
309
 
310
+ .header {
311
+ text-align: center;
312
+ padding: 20px;
313
+ border-bottom: 2px solid #DDD;
 
 
 
 
 
 
 
314
  }
315
 
316
+ .dark-mode .header {
317
+ border-bottom: 2px solid #444;
 
 
 
 
 
 
 
 
 
318
  }
319
 
320
+ .header h1 {
321
+ margin: 0;
322
+ font-size: 2.5rem;
323
+ color: #FF5722;
 
 
 
 
324
  }
325
 
326
+ .dark-mode .header h1 {
327
+ color: #FF7043;
 
 
328
  }
329
 
330
+ .theme-toggle {
331
+ margin: 10px 0;
332
+ text-align: center;
 
 
 
 
 
 
 
 
 
333
  }
334
 
335
+ .theme-toggle button {
336
+ padding: 10px 20px;
337
+ font-size: 1rem;
338
+ cursor: pointer;
339
+ border: none;
340
+ border-radius: 5px;
341
+ background-color: #FF5722;
342
+ color: white;
343
+ transition: background-color 0.3s;
344
  }
345
 
346
+ .theme-toggle button:hover {
347
+ background-color: #E64A19;
 
 
348
  }
349
 
350
+ .dark-mode .theme-toggle button {
351
+ background-color: #FF7043;
 
 
 
 
 
 
 
352
  }
353
 
354
+ .dark-mode .theme-toggle button:hover {
355
+ background-color: #F4511E;
 
356
  }
357
 
358
+ .section {
359
+ margin: 20px 0;
360
+ padding: 15px;
361
+ background-color: white;
362
+ border-radius: 5px;
 
 
363
  }
364
 
365
+ .dark-mode .section {
366
+ background-color: #2C2C2C;
 
 
 
 
 
 
 
 
367
  }
368
 
369
+ .section label {
370
+ font-size: 1.2rem;
371
+ font-weight: 500;
372
+ margin-bottom: 10px;
373
+ display: block;
 
 
 
 
 
 
 
374
  }
375
 
376
+ .input-area {
 
 
 
377
  width: 100%;
378
+ padding: 12px;
379
+ font-size: 1rem;
380
+ border: 2px solid #CCC;
381
+ border-radius: 5px;
382
+ box-sizing: border-box;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
383
  }
384
 
385
+ .dark-mode .input-area {
386
+ background-color: #333;
387
+ border-color: #555;
388
+ color: #E0E0E0;
 
 
389
  }
390
 
391
+ .radio-group {
392
+ display: grid;
393
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
394
+ gap: 10px;
395
+ margin-top: 10px;
396
  }
397
 
398
+ .radio-group label {
399
+ padding: 10px;
400
+ background-color: #F0F0F0;
401
+ border: 2px solid #DDD;
402
+ border-radius: 5px;
403
+ text-align: center;
404
+ font-size: 1rem;
405
+ cursor: pointer;
406
  }
407
 
408
+ .dark-mode .radio-group label {
409
+ background-color: #3A3A3A;
410
+ border-color: #555;
411
+ color: #E0E0E0;
 
 
 
 
412
  }
413
 
414
+ .radio-group input[type="radio"]:checked + label {
415
+ background-color: #FF5722;
416
+ color: white;
417
+ border-color: #E64A19;
 
 
418
  }
419
 
420
+ .dark-mode .radio-group input[type="radio"]:checked + label {
421
+ background-color: #FF7043;
422
+ border-color: #F4511E;
 
 
423
  }
424
 
425
+ .button-group {
426
+ margin-top: 15px;
427
+ text-align: right;
428
  }
429
 
430
+ .button-group button {
431
+ padding: 10px 20px;
432
+ font-size: 1rem;
433
+ cursor: pointer;
434
+ border: none;
435
+ border-radius: 5px;
436
+ margin-left: 10px;
437
  }
438
 
439
+ .submit-btn {
440
+ background-color: #FF5722;
441
+ color: white;
 
 
 
 
 
 
 
442
  }
443
 
444
+ .submit-btn:hover {
445
+ background-color: #E64A19;
 
 
446
  }
447
 
448
+ .clear-btn {
449
+ background-color: #B0BEC5;
450
+ color: white;
 
451
  }
452
 
453
+ .clear-btn:hover {
454
+ background-color: #90A4AE;
 
455
  }
456
 
457
+ .dark-mode .submit-btn {
458
+ background-color: #FF7043;
 
 
459
  }
460
 
461
+ .dark-mode .submit-btn:hover {
462
+ background-color: #F4511E;
 
 
463
  }
464
 
465
+ .dark-mode .clear-btn {
466
+ background-color: #78909C;
 
467
  }
468
 
469
+ .dark-mode .clear-btn:hover {
470
+ background-color: #607D8B;
 
 
 
 
 
 
 
 
 
471
  }
472
 
473
+ .output-box {
474
+ margin-top: 20px;
475
+ padding: 15px;
476
+ border: 2px solid #DDD;
477
+ border-radius: 5px;
478
+ min-height: 100px;
 
 
 
479
  }
480
 
481
+ .dark-mode .output-box {
482
+ border-color: #555;
483
+ background-color: #2C2C2C;
484
  }
485
 
486
+ .response-title {
487
+ font-size: 1.3rem;
488
+ font-weight: 700;
489
+ margin-bottom: 10px;
490
+ color: #FF5722;
 
 
 
 
 
 
491
  }
492
 
493
+ .dark-mode .response-title {
494
+ color: #FF7043;
 
495
  }
496
 
497
+ .response-content {
498
+ font-size: 1rem;
499
+ line-height: 1.6;
 
500
  }
501
 
502
  .error-message {
503
+ background-color: #FFE0E0;
504
+ border: 2px solid #F44336;
505
+ color: #D32F2F;
506
+ padding: 10px;
507
+ border-radius: 5px;
508
+ margin-top: 10px;
 
 
 
 
 
 
 
 
509
  }
510
 
511
+ .dark-mode .error-message {
512
+ background-color: #400000;
513
+ border-color: #B71C1C;
514
+ color: #EF5350;
515
  }
516
 
517
+ .examples {
518
+ margin-top: 20px;
 
 
519
  }
520
 
521
+ .examples button {
522
+ padding: 8px 15px;
523
+ margin: 5px;
524
+ font-size: 1rem;
525
+ cursor: pointer;
526
+ border: 2px solid #DDD;
527
+ border-radius: 5px;
528
+ background-color: white;
 
 
 
 
 
 
 
 
 
 
529
  }
530
 
531
+ .dark-mode .examples button {
532
+ border-color: #555;
533
+ background-color: #3A3A3A;
534
+ color: #E0E0E0;
 
535
  }
536
 
537
+ .examples button:hover {
538
+ background-color: #F5F5F5;
 
 
 
 
 
539
  }
540
 
541
+ .dark-mode .examples button:hover {
542
+ background-color: #444;
 
 
543
  }
544
 
545
+ .footer {
546
+ text-align: center;
547
+ padding: 20px;
548
+ font-size: 0.9rem;
549
+ color: #666;
550
+ border-top: 2px solid #DDD;
551
  }
552
 
553
+ .dark-mode .footer {
554
+ color: #A0A0A0;
555
+ border-top: 2px solid #444;
556
  }
557
+ """
558
 
559
+ with gr.Blocks(css=custom_css, title="Landlord-Tenant Rights Assistant") as demo:
560
+ with gr.Row():
561
+ with gr.Column(scale=1):
562
+ gr.Markdown("<div class='header'><h1>Landlord-Tenant Rights Assistant</h1></div>")
563
+ theme_light = gr.HTML(visible=True, value="<div class='theme-toggle'><button onclick='document.body.classList.remove(\"dark-mode\");'>Light Mode</button></div>")
564
+ theme_dark = gr.HTML(visible=False, value="<div class='theme-toggle'><button onclick='document.body.classList.add(\"dark-mode\");'>Dark Mode</button></div>")
565
+ gr.Button("Toggle Dark Mode", lambda: [gr.update(visible=True), gr.update(visible=False)], outputs=[theme_dark, theme_light])
566
 
567
+ with gr.Column(elem_classes="container"):
568
+ with gr.Column():
569
+ gr.Markdown("This AI-powered assistant helps navigate complex landlord-tenant laws. Ask a question about your state's regulations for detailed, legally-grounded insights.")
 
 
 
 
 
 
 
570
 
571
+ with gr.Column(elem_classes="section"):
572
+ api_key_input = gr.Textbox(label="API Key", type="password", placeholder="Enter your OpenAI API key (e.g., sk-...)", elem_classes="input-area")
 
 
 
 
 
 
 
 
573
 
574
+ with gr.Column(elem_classes="section"):
575
+ query_input = gr.Textbox(label="Your Question", placeholder="E.g., What are the rules for security deposit returns in my state?", lines=6, elem_classes="input-area")
576
+ state_input = gr.Radio(label="Select State", choices=states, value=initial_state, elem_classes="radio-group")
577
+ with gr.Row(elem_classes="button-group"):
578
+ clear_button = gr.Button("Clear", elem_classes="clear-btn")
579
+ submit_button = gr.Button("Submit Query", elem_classes="submit-btn")
580
 
581
+ with gr.Column(elem_classes="section"):
582
+ output = gr.HTML(value="<div class='output-box'><div class='placeholder'>The answer will appear here after submitting your query.</div></div>")
 
 
 
583
 
584
+ with gr.Column(elem_classes="examples"):
585
+ gr.Examples(examples=example_queries, inputs=[query_input, state_input], label="Example Questions")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
586
 
587
+ with gr.Column():
588
+ gr.Markdown("<div class='footer'><p><strong>Disclaimer:</strong> This tool is for informational purposes only and does not constitute legal advice. Consult a licensed attorney for specific guidance. Developed by Nischal Subedi.</p></div>")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
 
590
  submit_button.click(
591
  fn=query_interface_wrapper,
592
  inputs=[api_key_input, query_input, state_input],
593
+ outputs=output
 
594
  )
595
+
596
  clear_button.click(
597
+ fn=lambda: ["", "", initial_state, "<div class='output-box'><div class='placeholder'>Inputs cleared. Ready for your next question.</div></div>"],
 
 
598
  inputs=[],
599
  outputs=[api_key_input, query_input, state_input, output]
600
  )