Nischal Subedi commited on
Commit
762653c
·
1 Parent(s): 89f2428
Files changed (1) hide show
  1. app.py +114 -620
app.py CHANGED
@@ -250,11 +250,11 @@ Answer:"""
250
  def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
251
  # Basic client-side validation for immediate feedback (redundant but good UX)
252
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
253
- 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>"
254
  if not state or state is None:
255
- return "<div class='error-message'><span class='error-icon'>⚠️</span>Please select a valid state from the list.</div>"
256
  if not query or not query.strip():
257
- return "<div class='error-message'><span class='error-icon'>⚠️</span>Please enter your question in the text box.</div>"
258
 
259
  # Call the core processing logic
260
  result = self.process_query(query=query, state=state, openai_api_key=api_key)
@@ -276,6 +276,7 @@ Answer:"""
276
  print(f"DEBUG: Error loading states for selection: {e}")
277
  radio_choices = ["Error: Critical failure loading states"]
278
  initial_value_radio = None
 
279
  example_queries_base = [
280
  ["What are the rules for security deposit returns?", "California"],
281
  ["Can a landlord enter my apartment without notice?", "New York"],
@@ -292,686 +293,179 @@ Answer:"""
292
  else:
293
  example_queries.append(["What basic rights do tenants have?", "California"])
294
 
 
295
  custom_css = """
296
- /* Import legible fonts from Google Fonts */
297
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@600;700;800&display=swap');
298
-
299
- /* Enforce light theme globally */
300
- :root {
301
- color-scheme: light !important;
302
- --primary-color: #FF8C00 !important;
303
- --primary-hover: #E07B00 !important;
304
- --background-primary: hsl(30, 100%, 99.9%) !important;
305
- --background-secondary: hsl(30, 100%, 96%) !important;
306
- --text-primary: #4A3C32 !important;
307
- --text-secondary: #8C7B6F !important;
308
- --border-color: hsl(30, 70%, 85%) !important;
309
- --border-focus: #FF8C00 !important;
310
- --shadow-sm: 0 1px 3px rgba(0,0,0,0.08) !important;
311
- --shadow-md: 0 4px 10px rgba(0,0,0,0.1) !important;
312
- --shadow-lg: 0 10px 20px rgba(0,0,0,0.15) !important;
313
- --error-bg: #FFF0E0 !important;
314
- --error-border: #FFD2B2 !important;
315
- --error-text: #E05C00 !important;
316
  }
317
-
318
- /* Override browser dark mode completely */
319
  @media (prefers-color-scheme: dark) {
320
- :root, html, body, .gradio-container, .main-dashboard-container > *,
321
- .dashboard-card-section, .section-title-gradient-bar,
322
- .dashboard-card-content-area, .gradio-textbox textarea,
323
- .gradio-textbox input, .gradio-radio label, .placeholder,
324
- .output-content-wrapper, .gr-samples-table, .gr-samples-table th,
325
- .gr-samples-table td, .app-footer-wrapper, .gradio-button,
326
- .gr-button-primary, .gr-button-secondary, .gr-block, .gr-box,
327
- .gr-prose, .gr-form, .gr-panel, .gr-columns, .gr-column,
328
- .gradio-html, .gradio-markdown, .gradio-textbox, .gradio-radio {
329
- color-scheme: light !important;
330
- background-color: var(--background-primary) !important;
331
- color: var(--text-primary) !important;
332
- border-color: var(--border-color) !important;
333
- }
334
- .gr-button-primary {
335
- background-color: var(--primary-color) !important;
336
- color: white !important;
337
- }
338
- .gr-button-secondary {
339
- background-color: transparent !important;
340
- border-color: var(--border-color) !important;
341
- color: var(--text-primary) !important;
342
- }
343
- .error-message, .error-message a {
344
- background-color: var(--error-bg) !important;
345
- color: var(--error-text) !important;
346
- border-color: var(--error-border) !important;
347
- }
348
- .response-header, .response-icon, .dashboard-card-section strong,
349
- .dashboard-card-section b, .app-footer strong, .app-footer b,
350
- .app-footer a, .custom-link {
351
- color: var(--primary-color) !important;
352
  }
353
  }
 
 
354
 
355
- /* Base styles for all elements */
356
- *, *:before, *:after {
357
- box-sizing: border-box !important;
358
- color-scheme: light !important;
359
- }
360
- html, body {
361
- background-color: var(--background-secondary) !important;
362
- color: var(--text-primary) !important;
363
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
364
- margin: 0 !important;
365
- padding: 0 !important;
366
- }
367
- .gradio-container {
368
- max-width: 900px !important;
369
- margin: 0 auto !important;
370
- padding: 1.5rem !important;
371
- background-color: var(--background-secondary) !important;
372
- box-shadow: none !important;
373
- color: var(--text-primary) !important;
374
- }
375
- .main-dashboard-container > * {
376
- background-color: var(--background-primary) !important;
377
- }
378
- .app-header-wrapper {
379
- background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
380
- border: 2px solid var(--border-color) !important;
381
- border-radius: 16px !important;
382
- padding: 2.5rem 1.5rem !important;
383
- margin-bottom: 1.5rem !important;
384
- box-shadow: var(--shadow-md) !important;
385
- position: relative !important;
386
- overflow: hidden !important;
387
- text-align: center !important;
388
  color: var(--text-primary) !important;
 
389
  }
390
- .app-header-wrapper::before {
391
- content: '';
392
- position: absolute;
393
- top: 0;
394
- left: 0;
395
- width: 100%;
396
- height: 100%;
397
- 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%) !important;
398
- z-index: 0 !important;
399
- opacity: 0.8 !important;
400
- pointer-events: none !important;
401
- }
402
- .app-header-logo {
403
- font-size: 8.5rem !important;
404
- margin-bottom: 0.75rem !important;
405
- display: block !important;
406
- color: var(--primary-color) !important;
407
- position: relative !important;
408
- z-index: 1 !important;
409
- animation: float-icon 3s ease-in-out infinite alternate !important;
410
- }
411
- @keyframes float-icon {
412
- 0% { transform: translateY(0px); }
413
- 50% { transform: translateY(-5px); }
414
- 100% { transform: translateY(0px); }
415
- }
416
- .app-header-title {
417
- font-family: 'Poppins', sans-serif !important;
418
- font-size: 3rem !important;
419
- font-weight: 800 !important;
420
  color: var(--text-primary) !important;
421
- margin: 0 0 0.75rem 0 !important;
422
- line-height: 1.1 !important;
423
- letter-spacing: -0.03em !important;
424
- position: relative !important;
425
- z-index: 1 !important;
426
- display: inline-block !important;
427
- max-width: 100% !important;
428
- }
429
- .app-header-tagline {
430
- font-size: 1.25rem !important;
431
- color: var(--text-secondary) !important;
432
- font-weight: 400 !important;
433
- margin: 0 !important;
434
- max-width: 700px !important;
435
- display: inline-block !important;
436
- position: relative !important;
437
- z-index: 1 !important;
438
- }
439
- .main-dashboard-container {
440
- display: flex !important;
441
- flex-direction: column !important;
442
- gap: 1.25rem !important;
443
- }
444
- .dashboard-card-section {
445
- background-color: var(--background-primary) !important;
446
  border: 2px solid var(--border-color) !important;
447
  border-radius: 12px !important;
448
- padding: 0 !important;
449
  box-shadow: var(--shadow-sm) !important;
450
- transition: box-shadow 0.3s ease-out, transform 0.3s ease-out !important;
451
- cursor: default !important;
452
- color: var(--text-primary) !important;
453
- }
454
- .dashboard-card-section:hover {
455
- box-shadow: var(--shadow-md) !important;
456
- transform: translateY(-3px) !important;
457
- }
458
- .full-width-center {
459
- display: flex !important;
460
- justify-content: center !important;
461
- align-items: center !important;
462
- width: 100% !important;
463
- flex-direction: column !important;
464
- background-color: transparent !important;
465
- }
466
- .section-title-gradient-bar {
467
- background-color: var(--background-secondary) !important;
468
- padding: 1.25rem 1.75rem !important;
469
- border-top-left-radius: 10px !important;
470
- border-top-right-radius: 10px !important;
471
- margin-bottom: 0 !important;
472
- text-align: center !important;
473
- box-sizing: border-box !important;
474
- width: 100% !important;
475
- color: var(--text-primary) !important;
476
- }
477
- .section-title {
478
- font-family: 'Poppins', sans-serif !important;
479
- font-size: 1.7rem !important;
480
- font-weight: 700 !important;
481
- color: var(--text-primary) !important;
482
- margin: 0 !important;
483
- padding-bottom: 0 !important;
484
- border-bottom: 2px solid var(--border-color) !important;
485
- line-height: 1.1 !important;
486
- display: inline-block !important;
487
- text-align: center !important;
488
- letter-spacing: -0.01em !important;
489
- }
490
- .dashboard-card-content-area {
491
- padding: 0 1.75rem 1.75rem 1.75rem !important;
492
- background-color: var(--background-primary) !important;
493
- box-sizing: border-box !important;
494
- width: 100% !important;
495
- color: var(--text-primary) !important;
496
- }
497
- .dashboard-card-section p {
498
- line-height: 1.7 !important;
499
- color: var(--text-primary) !important;
500
- font-size: 1rem !important;
501
- text-align: left !important;
502
- background-color: transparent !important;
503
- margin: 0 !important;
504
- padding: 0 !important;
505
- white-space: normal !important;
506
- }
507
- .dashboard-card-section strong, .dashboard-card-section b {
508
- font-weight: 700 !important;
509
- color: var(--primary-color) !important;
510
- }
511
- .gr-block, .gr-box, .gr-prose, .gr-form, .gr-panel,
512
- .gr-columns, .gr-column, .gradio-html, .gradio-markdown,
513
- .gradio-textbox, .gradio-radio, .gradio-button {
514
- background-color: transparent !important;
515
- color: var(--text-primary) !important;
516
- white-space: normal !important;
517
- overflow-wrap: break-word !important;
518
- word-break: break-word !important;
519
- }
520
- .gradio-textbox textarea,
521
- .gradio-textbox input,
522
- .gradio-radio label,
523
- .placeholder {
524
- background-color: var(--background-primary) !important;
525
- color: var(--text-primary) !important;
526
- }
527
- .gradio-textbox {
528
- margin-bottom: 0.5rem !important;
529
  }
530
  .gradio-textbox textarea,
531
  .gradio-textbox input {
 
 
532
  border: 2px solid var(--border-color) !important;
533
  border-radius: 8px !important;
534
- padding: 0.85rem 1rem !important;
535
- font-size: 0.98rem !important;
536
- font-family: 'Inter', sans-serif !important;
537
- color: var(--text-primary) !important;
538
- transition: border-color 0.2s ease, box-shadow 0.2s ease !important;
539
  box-shadow: var(--shadow-sm) !important;
540
  }
541
- .gradio-textbox .scroll-hide {
542
- background-color: var(--background-primary) !important;
543
- }
544
  .gradio-textbox textarea:focus,
545
  .gradio-textbox input:focus {
546
- outline: none !important;
547
  border-color: var(--border-focus) !important;
548
  box-shadow: 0 0 0 4px rgba(255, 140, 0, 0.2) !important;
549
  }
550
- .gradio-radio {
551
- padding: 0 !important;
552
- margin-top: 1rem !important;
553
- }
554
- .gradio-radio input[type="radio"] {
555
- display: none !important;
556
- }
557
  .gradio-radio label {
558
- display: flex !important;
559
- justify-content: center !important;
560
- align-items: center !important;
561
- padding: 0.75rem 1rem !important;
562
  border: 2px solid var(--border-color) !important;
563
  border-radius: 8px !important;
564
- color: var(--text-primary) !important;
565
- font-weight: 500 !important;
566
- cursor: pointer !important;
567
- transition: all 0.2s ease-out !important;
568
  box-shadow: var(--shadow-sm) !important;
569
- margin: 0.2rem 0 !important;
570
- width: 100% !important;
571
- box-sizing: border-box !important;
572
- }
573
- .gradio-radio label span.text-lg {
574
- font-weight: 600 !important;
575
- color: var(--text-primary) !important;
576
- font-size: 0.98rem !important;
577
  }
578
  .gradio-radio label:hover {
579
- background-color: var(--background-secondary) !important;
580
  border-color: var(--primary-color) !important;
581
  box-shadow: var(--shadow-md) !important;
582
- transform: translateY(-2px) !important;
583
  }
584
  .gradio-radio input[type="radio"]:checked + label {
585
- background-color: var(--primary-color) !important;
586
  color: white !important;
587
  border-color: var(--primary-hover) !important;
588
  box-shadow: var(--shadow-md) !important;
589
- transform: translateY(-1px) !important;
590
  }
591
- .gradio-radio input[type="radio"]:checked + label span.text-lg {
 
 
592
  color: white !important;
593
- }
594
- .gradio-radio .gr-form {
595
- padding: 0 !important;
596
- }
597
- .gradio-textbox label,
598
- .gradio-radio > label {
599
- font-weight: 600 !important;
600
- color: var(--text-primary) !important;
601
- font-size: 1rem !important;
602
- margin-bottom: 0.6rem !important;
603
- display: block !important;
604
- text-align: left !important;
605
- }
606
- .gr-prose {
607
- font-size: 0.9rem !important;
608
- color: var(--text-secondary) !important;
609
- margin-top: 0.4rem !important;
610
- text-align: left !important;
611
- background-color: transparent !important;
612
- }
613
- .input-column {
614
- display: flex !important;
615
- flex-direction: column !important;
616
- gap: 1.25rem !important;
617
- margin-bottom: 0.5rem !important;
618
- }
619
- .input-field {
620
- flex: none !important;
621
- width: 100% !important;
622
- }
623
- .button-row {
624
- display: flex !important;
625
- gap: 1rem !important;
626
- justify-content: flex-end !important;
627
- margin-top: 1.5rem !important;
628
- }
629
- .gradio-button {
630
- padding: 0.85rem 1.8rem !important;
631
  border-radius: 9px !important;
632
  font-weight: 600 !important;
633
  font-size: 1rem !important;
634
- transition: all 0.2s ease-out !important;
635
- cursor: pointer !important;
636
  border: 2px solid transparent !important;
637
- text-align: center !important;
638
- color: var(--text-primary) !important;
639
- }
640
- .gr-button-primary {
641
- background-color: var(--primary-color) !important;
642
- color: white !important;
643
  box-shadow: var(--shadow-sm) !important;
644
  }
645
  .gr-button-primary:hover {
646
- background-color: var(--primary-hover) !important;
647
  box-shadow: var(--shadow-md) !important;
648
- transform: translateY(-2px) !important;
649
- }
650
- .gr-button-primary:active {
651
- transform: translateY(1px) !important;
652
- box-shadow: none !important;
653
- }
654
- .gr-button-secondary {
655
- background-color: transparent !important;
656
- color: var(--text-primary) !important;
657
- border: 2px solid var(--border-color) !important;
658
- }
659
- .gr-button-secondary:hover {
660
- background-color: var(--background-secondary) !important;
661
- border-color: var(--primary-color) !important;
662
- transform: translateY(-2px) !important;
663
- }
664
- .gr-button-secondary:active {
665
- transform: translateY(1px) !important;
666
- box-shadow: none !important;
667
- }
668
- .output-content-wrapper {
669
- background-color: var(--background-primary) !important;
670
- border: 2px solid var(--border-color) !important;
671
- border-radius: 8px !important;
672
- padding: 1.5rem !important;
673
- min-height: 150px !important;
674
- color: var(--text-primary) !important;
675
- display: flex !important;
676
- flex-direction: column !important;
677
- justify-content: center !important;
678
- align-items: center !important;
679
- }
680
- .animated-output-content {
681
- opacity: 0 !important;
682
- animation: fadeInAndSlideUp 0.7s ease-out forwards !important;
683
- width: 100% !important;
684
- white-space: pre-wrap !important;
685
- overflow-wrap: break-word !important;
686
- word-break: break-word !important;
687
- text-align: left !important;
688
- color: var(--text-primary) !important;
689
- }
690
- @keyframes fadeInAndSlideUp {
691
- from { opacity: 0; transform: translateY(15px); }
692
- to { opacity: 1; transform: translateY(0); }
693
- }
694
- .response-header {
695
- font-size: 1.3rem !important;
696
- font-weight: 700 !important;
697
- color: var(--primary-color) !important;
698
- margin-bottom: 0.75rem !important;
699
- display: flex !important;
700
- align-items: center !important;
701
- gap: 0.6rem !important;
702
- text-align: left !important;
703
- width: 100% !important;
704
- justify-content: flex-start !important;
705
- }
706
- .response-icon {
707
- font-size: 1.5rem !important;
708
- color: var(--primary-color) !important;
709
- }
710
- .divider {
711
- border: none !important;
712
- border-top: 1px dashed var(--border-color) !important;
713
- margin: 1rem 0 !important;
714
  }
 
715
  .error-message {
716
- background-color: var(--error-bg) !important;
717
- border: 2px solid var(--error-border) !important;
718
  color: var(--error-text) !important;
719
- padding: 1.25rem !important;
720
  border-radius: 8px !important;
721
- display: flex !important;
722
- align-items: flex-start !important;
723
- gap: 0.8rem !important;
724
- font-size: 0.95rem !important;
725
- font-weight: 500 !important;
726
- line-height: 1.6 !important;
727
- text-align: left !important;
728
- width: 100% !important;
729
- box-sizing: border-box !important;
730
- }
731
- .error-message a {
732
- color: var(--error-text) !important;
733
- text-decoration: underline !important;
734
  }
735
  .error-icon {
736
- font-size: 1.4rem !important;
737
- line-height: 1 !important;
738
- margin-top: 0.1rem !important;
739
- }
740
- .error-details {
741
- font-size: 0.85rem !important;
742
- color: var(--error-text) !important;
743
- margin-top: 0.5rem !important;
744
- opacity: 0.8 !important;
745
- }
746
- .placeholder {
747
- background-color: var(--background-primary) !important;
748
- border: 2px dashed var(--border-color) !important;
749
- border-radius: 8px !important;
750
- padding: 2.5rem 1.5rem !important;
751
- text-align: center !important;
752
- color: var(--text-secondary) !important;
753
- font-style: italic !important;
754
- font-size: 1.1rem !important;
755
- width: 100% !important;
756
- box-sizing: border-box !important;
757
- }
758
- .examples-section .gr-samples-table {
759
- border: 2px solid var(--border-color) !important;
760
- border-radius: 8px !important;
761
- overflow: hidden !important;
762
- margin-top: 1rem !important;
763
- }
764
- .examples-section .gr-samples-table th,
765
- .examples-section .gr-samples-table td {
766
- padding: 0.9rem !important;
767
- border: none !important;
768
- font-size: 0.95rem !important;
769
- text-align: left !important;
770
- color: var(--text-primary) !important;
771
  }
772
- .examples-section .gr-samples-table th {
773
- background-color: var(--background-secondary) !important;
774
- font-weight: 700 !important;
775
- color: var(--text-primary) !important;
776
- }
777
- .examples-section .gr-samples-table td {
778
- background-color: var(--background-primary) !important;
779
- color: var(--text-primary) !important;
780
- border-top: 1px solid var(--border-color) !important;
781
- cursor: pointer !important;
782
- transition: background 0.2s ease, transform 0.1s ease !important;
783
- }
784
- .examples-section .gr-samples-table tr:hover td {
785
- background-color: var(--background-secondary) !important;
786
- transform: translateX(5px) !important;
787
- }
788
- .gr-examples .gr-label,
789
- .gr-examples .label-wrap,
790
- .gr-examples .gr-accordion-header {
791
- display: none !important;
792
- }
793
- .app-footer-wrapper {
794
- background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
795
- border: 2px solid var(--border-color) !important;
796
- border-radius: 12px !important;
797
- padding: 1.75rem !important;
798
- margin-top: 1.5rem !important;
799
- margin-bottom: 1.5rem !important;
800
- text-align: center !important;
801
- color: var(--text-primary) !important;
802
- }
803
- .app-footer p {
804
- margin: 0 !important;
805
- max-width: 90% !important;
806
- font-size: 0.95rem !important;
807
- color: var(--text-secondary) !important;
808
- line-height: 1.6 !important;
809
- background-color: transparent !important;
810
- text-align: center !important;
811
- white-space: normal !important;
812
- }
813
- .app-footer strong, .app-footer b {
814
  font-weight: 700 !important;
815
  color: var(--primary-color) !important;
 
 
 
 
816
  }
817
- .app-footer a, .custom-link {
818
- color: var(--primary-color) !important;
819
- text-decoration: underline !important;
820
- font-weight: bold !important;
821
- }
822
- .app-footer a:hover, .custom-link:hover {
823
- text-decoration: none !important;
824
  }
825
- @media (max-width: 768px) {
826
- .gradio-container {
827
- padding: 1rem !important;
828
- }
829
- .app-header-title {
830
- font-size: 2.2rem !important;
831
- }
832
- .app-header-tagline {
833
- font-size: 1rem !important;
834
- }
835
- .section-title {
836
- font-size: 1.4rem !important;
837
- }
838
- .input-column {
839
- flex-direction: column !important;
840
- }
841
- .button-row {
842
- flex-direction: column !important;
843
- }
844
- .gradio-button {
845
- width: 100% !important;
846
- }
847
- .section-title-gradient-bar {
848
- padding: 0.1rem 1rem !important;
849
- }
850
- .dashboard-card-content-area {
851
- padding: 0 1rem 1rem 1rem !important;
852
- }
853
- .output-content-wrapper {
854
- min-height: 120px !important;
855
- }
856
- .placeholder {
857
- padding: 1.5rem 1rem !important;
858
- font-size: 1rem !important;
859
- }
860
  }
861
  """
862
 
863
- with gr.Blocks(css=custom_css, title="Landlord-Tenant Rights Assistant", theme_mode="light") as demo:
864
- with gr.Group(elem_classes="app-header-wrapper"):
865
- gr.Markdown(
866
- """
867
- <span class='app-header-logo'>⚖️</span>
868
- <h1 class='app-header-title'>Landlord-Tenant Rights Assistant</h1>
869
- <p class='app-header-tagline'>Empowering You with State-Specific Legal Insights</p>
870
- """,
871
- elem_classes="full-width-center"
872
- )
873
-
874
- with gr.Column(elem_classes="main-dashboard-container"):
875
- # How This Assistant Works Box
876
- with gr.Group(elem_classes="dashboard-card-section"):
877
- gr.Markdown("<h3 class='section-title'>How This Assistant Works</h3>", elem_classes="full-width-center section-title-gradient-bar")
878
- with gr.Column(elem_classes="dashboard-card-content-area"):
879
- gr.Markdown(
880
- """
881
- 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.
882
- """
883
- )
884
-
885
- # OpenAI API Key Input Card
886
- with gr.Group(elem_classes="dashboard-card-section"):
887
- gr.Markdown("<h3 class='section-title'>OpenAI API Key</h3>", elem_classes="full-width-center section-title-gradient-bar")
888
- with gr.Column(elem_classes="dashboard-card-content-area"):
889
- api_key_input = gr.Textbox(
890
- label="API Key",
891
- type="password",
892
- placeholder="Enter your OpenAI API key (e.g., sk-...)",
893
- lines=1,
894
- elem_classes=["input-field-group"]
895
- )
896
- gr.Markdown(
897
- "Required to process your query. Get one from OpenAI: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)",
898
- elem_classes="gr-prose"
899
- )
900
-
901
- # Query Input and State Selection Card
902
- with gr.Group(elem_classes="dashboard-card-section"):
903
- gr.Markdown("<h3 class='section-title'>Ask Your Question</h3>", elem_classes="full-width-center section-title-gradient-bar")
904
- with gr.Column(elem_classes="dashboard-card-content-area"):
905
- with gr.Column(elem_classes="input-column"):
906
- with gr.Column(elem_classes="input-field", scale=1):
907
- query_input = gr.Textbox(
908
- label="Your Question",
909
- placeholder="E.g., What are the rules for security deposit returns in my state?",
910
- lines=8,
911
- max_lines=15,
912
- elem_classes=["input-field-group"]
913
- )
914
- with gr.Column(elem_classes="input-field", scale=1):
915
- state_input = gr.Radio(
916
- label="Select State",
917
- choices=radio_choices,
918
- value=initial_value_radio,
919
- elem_classes=["input-field-group", "gradio-radio-custom"],
920
- interactive=True
921
- )
922
- with gr.Row(elem_classes="button-row"):
923
- clear_button = gr.Button("Clear", variant="secondary", elem_classes=["gr-button-secondary"])
924
- submit_button = gr.Button("Submit Query", variant="primary", elem_classes=["gr-button-primary"])
925
-
926
- # Output Display Card
927
- with gr.Group(elem_classes="dashboard-card-section"):
928
- gr.Markdown("<h3 class='section-title'>Legal Assistant's Response</h3>", elem_classes="full-width-center section-title-gradient-bar")
929
- with gr.Column(elem_classes="dashboard-card-content-area"):
930
- output = gr.HTML(
931
- value="<div class='placeholder'>The answer will appear here after submitting your query.</div>",
932
- elem_classes="output-content-wrapper"
933
- )
934
-
935
- # Example Questions Section
936
- with gr.Group(elem_classes="dashboard-card-section examples-section"):
937
- gr.Markdown("<h3 class='section-title'>Example Questions</h3>", elem_classes="full-width-center section-title-gradient-bar")
938
- with gr.Column(elem_classes="dashboard-card-content-area"):
939
- if example_queries:
940
- gr.Examples(
941
- examples=example_queries,
942
- inputs=[query_input, state_input],
943
- examples_per_page=5,
944
- label=""
945
- )
946
- else:
947
- gr.Markdown("<div class='placeholder'>Sample questions could not be loaded. Please ensure the vector database is populated.</div>")
948
-
949
- # Footer Section
950
- with gr.Group(elem_classes="app-footer-wrapper"):
951
- gr.Markdown(
952
- """
953
- <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>
954
- <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>
955
- """,
956
- elem_classes="app-footer"
957
- )
958
-
959
- submit_button.click(
960
- fn=query_interface_wrapper,
961
- inputs=[api_key_input, query_input, state_input],
962
- outputs=output,
963
- api_name="submit_query"
964
- )
965
-
966
- clear_button.click(
967
- fn=lambda: (
968
- "", "", initial_value_radio, "<div class='placeholder'>Inputs cleared. Ready for your next question.</div>"
969
- ),
970
- inputs=[],
971
- outputs=[api_key_input, query_input, state_input, output]
972
  )
973
 
974
- return demo
 
975
  # --- Main Execution Block (UNCHANGED from original logic) ---
976
  if __name__ == "__main__":
977
  logging.info("Starting Landlord-Tenant Rights Bot application...")
 
250
  def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
251
  # Basic client-side validation for immediate feedback (redundant but good UX)
252
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
253
+ 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>"
254
  if not state or state is None:
255
+ return "<div class='error-message'><span class='error-icon'></span>Please select a valid state from the list.</div>"
256
  if not query or not query.strip():
257
+ return "<div class='error-message'><span class='error-icon'></span>Please enter your question in the text box.</div>"
258
 
259
  # Call the core processing logic
260
  result = self.process_query(query=query, state=state, openai_api_key=api_key)
 
276
  print(f"DEBUG: Error loading states for selection: {e}")
277
  radio_choices = ["Error: Critical failure loading states"]
278
  initial_value_radio = None
279
+
280
  example_queries_base = [
281
  ["What are the rules for security deposit returns?", "California"],
282
  ["Can a landlord enter my apartment without notice?", "New York"],
 
293
  else:
294
  example_queries.append(["What basic rights do tenants have?", "California"])
295
 
296
+ # --- FORCED LIGHT THEME CSS ---
297
  custom_css = """
298
+ /* Disable browser/OS dark mode adaptation */
299
+ html, body {
300
+ color-scheme: only light !important;
301
+ background: #FAF9F7 !important;
302
+ color: #4A3C32 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
  }
 
 
304
  @media (prefers-color-scheme: dark) {
305
+ html, body {
306
+ background: #FAF9F7 !important;
307
+ color: #4A3C32 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
308
  }
309
  }
310
+ /* Import fonts */
311
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@600;700;800&display=swap');
312
 
313
+ :root {
314
+ --primary-color: #FF8C00;
315
+ --primary-hover: #E07B00;
316
+ --background-primary: #FFFDF9;
317
+ --background-secondary: #FAF9F7;
318
+ --text-primary: #4A3C32;
319
+ --text-secondary: #8C7B6F;
320
+ --border-color: #F3E6D7;
321
+ --border-focus: #FF8C00;
322
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
323
+ --shadow-md: 0 4px 10px rgba(0,0,0,0.1);
324
+ --shadow-lg: 0 10px 20px rgba(0,0,0,0.15);
325
+ --error-bg: #FFF0E0;
326
+ --error-border: #FFD2B2;
327
+ --error-text: #E05C00;
328
+ }
329
+
330
+ /* Gradio and container overrides */
331
+ body, html, .gradio-container, .main-dashboard-container, .dashboard-card-section, .dashboard-card-content-area,
332
+ .gr-block, .gr-box, .gr-form, .gr-panel, .gradio-html, .gradio-markdown, .gradio-textbox, .gradio-radio, .gradio-button {
333
+ background: var(--background-secondary) !important;
 
 
 
 
 
 
 
 
 
 
 
 
334
  color: var(--text-primary) !important;
335
+ font-family: 'Inter', sans-serif !important;
336
  }
337
+ .dashboard-card-section,
338
+ .dashboard-card-content-area {
339
+ background: var(--background-primary) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  color: var(--text-primary) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
341
  border: 2px solid var(--border-color) !important;
342
  border-radius: 12px !important;
 
343
  box-shadow: var(--shadow-sm) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  }
345
  .gradio-textbox textarea,
346
  .gradio-textbox input {
347
+ background: var(--background-primary) !important;
348
+ color: var(--text-primary) !important;
349
  border: 2px solid var(--border-color) !important;
350
  border-radius: 8px !important;
 
 
 
 
 
351
  box-shadow: var(--shadow-sm) !important;
352
  }
 
 
 
353
  .gradio-textbox textarea:focus,
354
  .gradio-textbox input:focus {
 
355
  border-color: var(--border-focus) !important;
356
  box-shadow: 0 0 0 4px rgba(255, 140, 0, 0.2) !important;
357
  }
 
 
 
 
 
 
 
358
  .gradio-radio label {
359
+ background: var(--background-primary) !important;
360
+ color: var(--text-primary) !important;
 
 
361
  border: 2px solid var(--border-color) !important;
362
  border-radius: 8px !important;
 
 
 
 
363
  box-shadow: var(--shadow-sm) !important;
 
 
 
 
 
 
 
 
364
  }
365
  .gradio-radio label:hover {
366
+ background: var(--background-secondary) !important;
367
  border-color: var(--primary-color) !important;
368
  box-shadow: var(--shadow-md) !important;
 
369
  }
370
  .gradio-radio input[type="radio"]:checked + label {
371
+ background: var(--primary-color) !important;
372
  color: white !important;
373
  border-color: var(--primary-hover) !important;
374
  box-shadow: var(--shadow-md) !important;
 
375
  }
376
+ .gradio-button,
377
+ .gr-button-primary {
378
+ background: var(--primary-color) !important;
379
  color: white !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  border-radius: 9px !important;
381
  font-weight: 600 !important;
382
  font-size: 1rem !important;
 
 
383
  border: 2px solid transparent !important;
 
 
 
 
 
 
384
  box-shadow: var(--shadow-sm) !important;
385
  }
386
  .gr-button-primary:hover {
387
+ background: var(--primary-hover) !important;
388
  box-shadow: var(--shadow-md) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
  }
390
+ /* Error message styling */
391
  .error-message {
392
+ background: var(--error-bg) !important;
393
+ border: 1.5px solid var(--error-border) !important;
394
  color: var(--error-text) !important;
 
395
  border-radius: 8px !important;
396
+ padding: 0.75rem 1rem !important;
397
+ margin-bottom: 1rem !important;
398
+ font-weight: 600 !important;
399
+ display: flex;
400
+ align-items: center;
401
+ gap: 0.7em;
 
 
 
 
 
 
 
402
  }
403
  .error-icon {
404
+ font-size: 1.3em;
405
+ color: var(--error-text);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
406
  }
407
+ /* Response header styling */
408
+ .response-header {
409
+ font-family: 'Poppins', sans-serif !important;
410
+ font-size: 1.2rem !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
411
  font-weight: 700 !important;
412
  color: var(--primary-color) !important;
413
+ margin-bottom: 0.5rem !important;
414
+ display: flex;
415
+ align-items: center;
416
+ gap: 0.5em;
417
  }
418
+ .divider {
419
+ border: none;
420
+ border-top: 2px solid var(--border-color) !important;
421
+ margin: 0.5em 0 1em 0 !important;
 
 
 
422
  }
423
+ /* Remove any dark mode overrides from Gradio or browser */
424
+ [data-theme="dark"], .dark, html[data-theme="dark"] {
425
+ background: var(--background-secondary) !important;
426
+ color: var(--text-primary) !important;
427
+ --gradio-background-primary: var(--background-primary) !important;
428
+ --gradio-background-secondary: var(--background-secondary) !important;
429
+ --gradio-text-primary: var(--text-primary) !important;
430
+ --gradio-text-secondary: var(--text-secondary) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
431
  }
432
  """
433
 
434
+ import gradio as gr
435
+
436
+ with gr.Blocks(css=custom_css) as demo:
437
+ gr.HTML("""
438
+ <div class="app-header-wrapper">
439
+ <span class="app-header-logo">🏠</span>
440
+ <span class="app-header-title">Tenant Rights Q&A</span>
441
+ <div class="app-header-tagline">Get answers about your rights as a tenant, for any US state.</div>
442
+ </div>
443
+ """)
444
+
445
+ with gr.Row(elem_classes="main-dashboard-container"):
446
+ with gr.Column():
447
+ with gr.Box(elem_classes="dashboard-card-section"):
448
+ gr.Markdown("#### Enter your OpenAI API key, your question, and select your state:", elem_classes="section-title-gradient-bar")
449
+ api_key = gr.Textbox(label="OpenAI API Key", placeholder="sk-...", type="password", elem_classes="input-field")
450
+ query = gr.Textbox(label="Your Question", placeholder="Type your question here...", lines=3, elem_classes="input-field")
451
+ state = gr.Radio(choices=radio_choices, label="State", value=initial_value_radio, elem_classes="input-field")
452
+ submit_btn = gr.Button("Submit", elem_classes="gr-button-primary")
453
+ output = gr.HTML(elem_id="output")
454
+
455
+ with gr.Column():
456
+ with gr.Box(elem_classes="dashboard-card-section"):
457
+ gr.Markdown("#### Example Questions", elem_classes="section-title-gradient-bar")
458
+ for q, s in example_queries:
459
+ gr.Markdown(f"**Q:** {q}<br>**State:** {s}")
460
+
461
+ submit_btn.click(
462
+ query_interface_wrapper,
463
+ inputs=[api_key, query, state],
464
+ outputs=output
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
465
  )
466
 
467
+ return demo
468
+
469
  # --- Main Execution Block (UNCHANGED from original logic) ---
470
  if __name__ == "__main__":
471
  logging.info("Starting Landlord-Tenant Rights Bot application...")