Nischal Subedi commited on
Commit
d25b399
·
1 Parent(s): 1763275
Files changed (1) hide show
  1. app.py +237 -304
app.py CHANGED
@@ -248,14 +248,14 @@ Answer:"""
248
  def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
249
  # Basic client-side validation for immediate feedback (redundant but good UX)
250
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
251
- 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>"
252
- if not state or state is None:
253
- return "<div class='error-message'><span class='error-icon'>⚠️</span>Please select a valid state from the list.</div>"
254
  if not query or not query.strip():
255
- return "<div class='error-message'><span class='error-icon'>⚠️</span>Please enter your question in the text box.</div>"
256
 
257
  # Call the core processing logic
258
- result = self.process_query(query=query, state=state, openai_api_key=api_key)
259
  answer = result.get("answer", "<div class='error-message'><span class='error-icon'>⚠️</span>An unexpected error occurred.</div>")
260
 
261
  # Check if the answer already contains an error message
@@ -266,14 +266,16 @@ Answer:"""
266
  return f"<div class='animated-output-content'>{formatted_response_content}</div>"
267
 
268
  try:
269
- available_states_list = self.get_states()
270
  print(f"DEBUG: States loaded for selection: {available_states_list}")
271
- radio_choices = available_states_list if available_states_list and "Error" not in available_states_list[0] else ["Error: States unavailable"]
272
- initial_value_radio = None
 
 
273
  except Exception as e:
274
  print(f"DEBUG: Error loading states for selection: {e}")
275
- radio_choices = ["Error: Critical failure loading states"]
276
- initial_value_radio = None
277
 
278
  example_queries_base = [
279
  ["What are the rules for security deposit returns?", "California"],
@@ -283,30 +285,21 @@ Answer:"""
283
  ["What is an implied warranty of habitability?", "Illinois"]
284
  ]
285
  example_queries = []
286
- if available_states_list and "Error" not in available_states_list[0] and len(available_states_list) > 0:
287
- loaded_states_set = set(available_states_list)
288
  example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
289
- if not example_queries:
290
- example_queries.append(["What basic rights do tenants have?", available_states_list[0] if available_states_list else "California"])
291
- else:
292
  example_queries.append(["What basic rights do tenants have?", "California"])
293
 
294
- custom_css = """
295
- /* Browser-agnostic reset */
296
- *, *::before, *::after {
297
- margin: 0;
298
- padding: 0;
299
- box-sizing: border-box;
300
- font-family: inherit;
301
- font-size: inherit;
302
- color: inherit;
303
- -webkit-font-smoothing: antialiased;
304
- -moz-osx-font-smoothing: grayscale;
305
- }
306
 
307
- /* Explicitly set color scheme to light to prevent browser dark mode interference */
308
- :root {
309
- color-scheme: light;
 
 
 
310
  --primary-color: #FF8C00;
311
  --primary-hover: #E07B00;
312
  --background-primary: hsl(30, 100%, 99.9%);
@@ -321,37 +314,62 @@ Answer:"""
321
  --error-bg: #FFF0E0;
322
  --error-border: #FFD2B2;
323
  --error-text: #E05C00;
324
- }
325
-
326
- /* Import legible fonts */
327
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@600;700;800&display=swap');
328
 
329
- /* Ensure body and html have consistent styling */
330
- body, html {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  background-color: var(--background-secondary) !important;
332
  color: var(--text-primary) !important;
333
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
334
- line-height: 1.5;
335
- -webkit-text-size-adjust: 100%;
336
- -webkit-tap-highlight-color: transparent;
337
- }
338
-
339
- /* Gradio container */
340
- .gradio-container {
341
  max-width: 900px !important;
342
  margin: 0 auto !important;
343
  padding: 1.5rem !important;
344
- background-color: var(--background-secondary) !important;
 
345
  box-shadow: none !important;
346
  color: var(--text-primary) !important;
347
- }
348
-
349
- .main-dashboard-container > * {
350
  background-color: var(--background-primary) !important;
351
- }
352
-
353
- /* Header styling */
354
- .app-header-wrapper {
355
  background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
356
  border: 2px solid var(--border-color) !important;
357
  border-radius: 16px !important;
@@ -362,9 +380,8 @@ Answer:"""
362
  overflow: hidden;
363
  text-align: center !important;
364
  color: var(--text-primary) !important;
365
- }
366
-
367
- .app-header-wrapper::before {
368
  content: '';
369
  position: absolute;
370
  top: 0;
@@ -375,9 +392,8 @@ Answer:"""
375
  z-index: 0;
376
  opacity: 0.8;
377
  pointer-events: none;
378
- }
379
-
380
- .app-header-logo {
381
  font-size: 8.5rem !important;
382
  margin-bottom: 0.75rem !important;
383
  display: block !important;
@@ -385,15 +401,13 @@ Answer:"""
385
  position: relative;
386
  z-index: 1;
387
  animation: float-icon 3s ease-in-out infinite alternate;
388
- }
389
-
390
- @keyframes float-icon {
391
- 0% { transform: translateY(0px); }
392
- 50% { transform: translateY(-5px); }
393
- 100% { transform: translateY(0px); }
394
- }
395
-
396
- .app-header-title {
397
  font-family: 'Poppins', sans-serif !important;
398
  font-size: 3rem !important;
399
  font-weight: 800 !important;
@@ -405,9 +419,8 @@ Answer:"""
405
  z-index: 1;
406
  display: inline-block;
407
  max-width: 100%;
408
- }
409
-
410
- .app-header-tagline {
411
  font-size: 1.25rem !important;
412
  color: var(--text-secondary) !important;
413
  font-weight: 400 !important;
@@ -416,40 +429,35 @@ Answer:"""
416
  display: inline-block;
417
  position: relative;
418
  z-index: 1;
419
- }
420
-
421
- /* Dashboard layout */
422
- .main-dashboard-container {
423
  display: flex !important;
424
  flex-direction: column !important;
425
  gap: 1.25rem !important;
426
- }
427
-
428
- .dashboard-card-section {
429
  background-color: var(--background-primary) !important;
430
  border: 2px solid var(--border-color) !important;
431
  border-radius: 12px !important;
432
  padding: 0 !important;
433
  box-shadow: var(--shadow-sm) !important;
434
  transition: all 0.3s ease-out !important;
 
435
  color: var(--text-primary) !important;
436
- }
437
-
438
- .dashboard-card-section:hover {
439
  box-shadow: var(--shadow-md) !important;
440
  transform: translateY(-3px) !important;
441
- }
442
-
443
- .full-width-center {
444
  display: flex !important;
445
  justify-content: center !important;
446
  align-items: center !important;
447
  width: 100% !important;
448
  flex-direction: column !important;
449
  background-color: transparent !important;
450
- }
451
-
452
- .section-title-gradient-bar {
453
  background-color: var(--background-secondary) !important;
454
  padding: 1.25rem 1.75rem !important;
455
  border-top-left-radius: 10px !important;
@@ -459,9 +467,8 @@ Answer:"""
459
  box-sizing: border-box;
460
  width: 100%;
461
  color: var(--text-primary) !important;
462
- }
463
-
464
- .section-title {
465
  font-family: 'Poppins', sans-serif !important;
466
  font-size: 1.7rem !important;
467
  font-weight: 700 !important;
@@ -473,17 +480,15 @@ Answer:"""
473
  display: inline-block !important;
474
  text-align: center !important;
475
  letter-spacing: -0.01em !important;
476
- }
477
-
478
- .dashboard-card-content-area {
479
  padding: 0 1.75rem 1.75rem 1.75rem !important;
480
  background-color: var(--background-primary) !important;
481
  box-sizing: border-box;
482
  width: 100%;
483
  color: var(--text-primary) !important;
484
- }
485
-
486
- .dashboard-card-section p {
487
  line-height: 1.7 !important;
488
  color: var(--text-primary) !important;
489
  font-size: 1rem !important;
@@ -492,38 +497,32 @@ Answer:"""
492
  margin: 0 !important;
493
  padding: 0 !important;
494
  white-space: normal !important;
495
- }
496
-
497
- .dashboard-card-section strong, .dashboard-card-section b {
498
  font-weight: 700 !important;
499
  color: var(--primary-color) !important;
500
- }
501
-
502
- /* Gradio component overrides */
503
  .gr-block, .gr-box, .gr-prose, .gr-form, .gr-panel,
504
  .gr-columns, .gr-column,
505
- .gradio-html, .gradio-markdown, .gradio-textbox, .gradio-radio, .gradio-button {
506
  background-color: transparent !important;
507
  color: var(--text-primary) !important;
508
  white-space: normal !important;
509
  overflow-wrap: break-word;
510
  word-break: break-word;
511
- }
512
-
513
  .gradio-textbox textarea,
514
  .gradio-textbox input,
515
  .gradio-radio label,
516
- .placeholder {
517
  background-color: var(--background-primary) !important;
518
  color: var(--text-primary) !important;
519
- }
520
-
521
- .gradio-textbox {
522
  margin-bottom: 0.5rem !important;
523
- }
524
-
525
  .gradio-textbox textarea,
526
- .gradio-textbox input {
527
  border: 2px solid var(--border-color) !important;
528
  border-radius: 8px !important;
529
  padding: 0.85rem 1rem !important;
@@ -532,32 +531,24 @@ Answer:"""
532
  color: var(--text-primary) !important;
533
  transition: border-color 0.2s ease, box-shadow 0.2s ease !important;
534
  box-shadow: var(--shadow-sm) !important;
535
- -webkit-appearance: none !important;
536
- -moz-appearance: none !important;
537
- appearance: none !important;
538
- }
539
-
540
- .gradio-textbox .scroll-hide {
541
  background-color: var(--background-primary) !important;
542
- }
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
-
551
- .gradio-radio {
552
  padding: 0 !important;
553
  margin-top: 1rem !important;
554
- }
555
-
556
- .gradio-radio input[type="radio"] {
557
  display: none !important;
558
- }
559
-
560
- .gradio-radio label {
561
  display: flex !important;
562
  justify-content: center !important;
563
  align-items: center !important;
@@ -570,77 +561,66 @@ Answer:"""
570
  transition: all 0.2s ease-out !important;
571
  box-shadow: var(--shadow-sm) !important;
572
  margin: 0.2rem 0 !important;
573
- width: 100% !important;
574
  box-sizing: border-box !important;
575
- }
576
-
577
- .gradio-radio label span.text-lg {
578
  font-weight: 600 !important;
579
  color: var(--text-primary) !important;
580
  font-size: 0.98rem !important;
581
- }
582
-
583
- .gradio-radio label:hover {
584
  background-color: var(--background-secondary) !important;
585
  border-color: var(--primary-color) !important;
586
  box-shadow: var(--shadow-md) !important;
587
  transform: translateY(-2px) !important;
588
- }
589
-
590
- .gradio-radio input[type="radio"]:checked + label {
591
  background-color: var(--primary-color) !important;
592
- color: white !important;
593
  border-color: var(--primary-hover) !important;
594
  box-shadow: var(--shadow-md) !important;
595
  transform: translateY(-1px) !important;
596
- }
597
-
598
- .gradio-radio input[type="radio"]:checked + label span.text-lg {
599
- color: white !important;
600
- }
601
-
602
- .gradio-radio .gr-form {
603
  padding: 0 !important;
604
- }
605
-
606
  .gradio-textbox label,
607
- .gradio-radio > label {
608
  font-weight: 600 !important;
609
  color: var(--text-primary) !important;
610
  font-size: 1rem !important;
611
  margin-bottom: 0.6rem !important;
612
  display: block !important;
613
  text-align: left !important;
614
- }
615
-
616
- .gr-prose {
617
  font-size: 0.9rem !important;
618
  color: var(--text-secondary) !important;
619
  margin-top: 0.4rem !important;
620
  text-align: left !important;
621
  background-color: transparent !important;
622
- }
623
-
624
- .input-column {
625
  display: flex !important;
626
  flex-direction: column !important;
627
  gap: 1.25rem !important;
628
  margin-bottom: 0.5rem !important;
629
- }
630
-
631
- .input-field {
632
  flex: none !important;
633
  width: 100% !important;
634
- }
635
-
636
- .button-row {
637
  display: flex !important;
638
  gap: 1rem !important;
639
  justify-content: flex-end !important;
640
  margin-top: 1.5rem !important;
641
- }
642
-
643
- .gradio-button {
644
  padding: 0.85rem 1.8rem !important;
645
  border-radius: 9px !important;
646
  font-weight: 600 !important;
@@ -650,46 +630,36 @@ Answer:"""
650
  border: 2px solid transparent !important;
651
  text-align: center !important;
652
  color: var(--text-primary) !important;
653
- -webkit-appearance: none !important;
654
- -moz-appearance: none !important;
655
- appearance: none !important;
656
- }
657
-
658
- .gr-button-primary {
659
  background-color: var(--primary-color) !important;
660
- color: white !important;
661
  box-shadow: var(--shadow-sm) !important;
662
- }
663
-
664
- .gr-button-primary:hover {
665
  background-color: var(--primary-hover) !important;
666
  box-shadow: var(--shadow-md) !important;
667
  transform: translateY(-2px) !important;
668
- }
669
-
670
- .gr-button-primary:active {
671
  transform: translateY(1px) !important;
672
  box-shadow: none !important;
673
- }
674
-
675
- .gr-button-secondary {
676
  background-color: transparent !important;
677
  color: var(--text-primary) !important;
678
  border-color: var(--border-color) !important;
679
- }
680
-
681
- .gr-button-secondary:hover {
682
  background-color: var(--background-secondary) !important;
683
  border-color: var(--primary-color) !important;
684
  transform: translateY(-2px) !important;
685
- }
686
-
687
- .gr-button-secondary:active {
688
  transform: translateY(1px) !important;
689
  box-shadow: none !important;
690
- }
691
-
692
- .output-content-wrapper {
693
  background-color: var(--background-primary) !important;
694
  border: 2px solid var(--border-color) !important;
695
  border-radius: 8px !important;
@@ -700,9 +670,8 @@ Answer:"""
700
  flex-direction: column;
701
  justify-content: center;
702
  align-items: center;
703
- }
704
-
705
- .animated-output-content {
706
  opacity: 0;
707
  animation: fadeInAndSlideUp 0.7s ease-out forwards;
708
  width: 100%;
@@ -711,14 +680,12 @@ Answer:"""
711
  word-break: break-word;
712
  text-align: left !important;
713
  color: var(--text-primary) !important;
714
- }
715
-
716
- @keyframes fadeInAndSlideUp {
717
- from { opacity: 0; transform: translateY(15px); }
718
- to { opacity: 1; transform: translateY(0); }
719
- }
720
-
721
- .response-header {
722
  font-size: 1.3rem !important;
723
  font-weight: 700 !important;
724
  color: var(--primary-color) !important;
@@ -729,20 +696,17 @@ Answer:"""
729
  text-align: left !important;
730
  width: 100%;
731
  justify-content: flex-start;
732
- }
733
-
734
- .response-icon {
735
  font-size: 1.5rem !important;
736
  color: var(--primary-color) !important;
737
- }
738
-
739
- .divider {
740
  border: none !important;
741
  border-top: 1px dashed var(--border-color) !important;
742
  margin: 1rem 0 !important;
743
- }
744
-
745
- .error-message {
746
  background-color: var(--error-bg) !important;
747
  border: 2px solid var(--error-border) !important;
748
  color: var(--error-text) !important;
@@ -757,27 +721,23 @@ Answer:"""
757
  text-align: left !important;
758
  width: 100%;
759
  box-sizing: border-box;
760
- }
761
-
762
- .error-message a {
763
  color: var(--error-text) !important;
764
  text-decoration: underline !important;
765
- }
766
-
767
- .error-icon {
768
  font-size: 1.4rem !important;
769
  line-height: 1 !important;
770
  margin-top: 0.1rem !important;
771
- }
772
-
773
- .error-details {
774
  font-size: 0.85rem !important;
775
  color: var(--error-text) !important;
776
  margin-top: 0.5rem !important;
777
  opacity: 0.8;
778
- }
779
-
780
- .placeholder {
781
  background-color: var(--background-primary) !important;
782
  border: 2px dashed var(--border-color) !important;
783
  border-radius: 8px !important;
@@ -788,50 +748,43 @@ Answer:"""
788
  font-size: 1.1rem !important;
789
  width: 100%;
790
  box-sizing: border-box;
791
- }
792
-
793
- .examples-section .gr-samples-table {
794
  border: 2px solid var(--border-color) !important;
795
  border-radius: 8px !important;
796
  overflow: hidden !important;
797
  margin-top: 1rem !important;
798
- }
799
-
800
  .examples-section .gr-samples-table th,
801
- .examples-section .gr-samples-table td {
802
  padding: 0.9rem !important;
803
  border: none !important;
804
  font-size: 0.95rem !important;
805
  text-align: left !important;
806
  color: var(--text-primary) !important;
807
- }
808
-
809
- .examples-section .gr-samples-table th {
810
  background-color: var(--background-secondary) !important;
811
  font-weight: 700 !important;
812
  color: var(--text-primary) !important;
813
- }
814
-
815
- .examples-section .gr-samples-table td {
816
  background-color: var(--background-primary) !important;
817
  color: var(--text-primary) !important;
818
  border-top: 1px solid var(--border-color) !important;
819
  cursor: pointer !important;
820
  transition: background 0.2s ease, transform 0.1s ease !important;
821
- }
822
-
823
- .examples-section .gr-samples-table tr:hover td {
824
  background-color: var(--background-secondary) !important;
825
  transform: translateX(5px);
826
- }
827
-
828
  .gr-examples .gr-label,
829
  .gr-examples .label-wrap,
830
- .gr-examples .gr-accordion-header {
831
  display: none !important;
832
- }
833
-
834
- .app-footer-wrapper {
835
  background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
836
  border: 2px solid var(--border-color) !important;
837
  border-radius: 12px !important;
@@ -840,9 +793,8 @@ Answer:"""
840
  margin-bottom: 1.5rem !important;
841
  text-align: center !important;
842
  color: var(--text-primary) !important;
843
- }
844
-
845
- .app-footer p {
846
  margin: 0 !important;
847
  max-width: 90% !important;
848
  font-size: 0.95rem !important;
@@ -851,80 +803,60 @@ Answer:"""
851
  background-color: transparent !important;
852
  text-align: center !important;
853
  white-space: normal !important;
854
- }
855
-
856
- .app-footer strong, .app-footer b {
857
  font-weight: 700 !important;
858
  color: var(--primary-color) !important;
859
- }
860
-
861
- .app-footer a {
862
  color: var(--primary-color) !important;
863
  text-decoration: underline !important;
864
  font-weight: 600 !important;
865
- }
866
-
867
- .app-footer a:hover {
868
  text-decoration: none !important;
869
- }
870
-
871
- /* Responsive design */
872
- @media (max-width: 768px) {
873
- .gradio-container {
874
  padding: 1rem !important;
875
- }
876
- .app-header-title {
877
  font-size: 2.2rem !important;
878
- }
879
- .app-header-tagline {
880
  font-size: 1rem !important;
881
- }
882
- .section-title {
883
  font-size: 1.4rem !important;
884
- }
885
- .input-column {
886
  flex-direction: column !important;
887
- }
888
- .button-row {
889
  flex-direction: column !important;
890
- }
891
- .gradio-button {
892
  width: 100% !important;
893
- }
894
- .dashboard-card-section {
895
- transform: none !important;
896
- }
897
- .section-title-gradient-bar {
898
  padding: 0.1rem 1rem !important;
899
- }
900
- .dashboard-card-content-area {
901
  padding: 0 1rem 1rem 1rem !important;
902
- }
903
- .output-content-wrapper {
904
  min-height: 120px !important;
905
- }
906
- .placeholder {
907
  padding: 1.5rem 1rem !important;
908
  font-size: 1rem !important;
909
- }
910
- }
911
-
912
- /* Ensure no browser-specific dark mode interference */
913
- @media (prefers-color-scheme: dark) {
914
- :root {
915
- color-scheme: light !important;
916
- }
917
- body, html, .gradio-container, .main-dashboard-container > *,
918
- .dashboard-card-section, .section-title-gradient-bar,
919
- .dashboard-card-content-area, .gradio-textbox textarea,
920
- .gradio-textbox input, .gradio-radio label,
921
- .placeholder, .output-content-wrapper {
922
- background-color: inherit !important;
923
- color: inherit !important;
924
- }
925
- }
926
  """
927
 
 
928
  with gr.Blocks(css=custom_css, title="Landlord-Tenant Rights Assistant", theme_mode="light") as demo:
929
  with gr.Group(elem_classes="app-header-wrapper"):
930
  gr.Markdown(
@@ -979,8 +911,8 @@ Answer:"""
979
  with gr.Column(elem_classes="input-field", scale=1):
980
  state_input = gr.Radio(
981
  label="Select State",
982
- choices=radio_choices,
983
- value=initial_value_radio,
984
  elem_classes=["input-field-group", "gradio-radio-custom"],
985
  interactive=True
986
  )
@@ -1033,10 +965,10 @@ Answer:"""
1033
  outputs=output,
1034
  api_name="submit_query"
1035
  )
1036
-
1037
  clear_button.click(
1038
  fn=lambda: (
1039
- "", "", initial_value_radio, "<div class='placeholder'>Inputs cleared. Ready for your next question.</div>"
1040
  ),
1041
  inputs=[],
1042
  outputs=[api_key_input, query_input, state_input, output]
@@ -1044,6 +976,7 @@ Answer:"""
1044
 
1045
  return demo
1046
 
 
1047
  # --- Main Execution Block (UNCHANGED from original logic) ---
1048
  if __name__ == "__main__":
1049
  logging.info("Starting Landlord-Tenant Rights Bot application...")
 
248
  def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
249
  # Basic client-side validation for immediate feedback (redundant but good UX)
250
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
251
+ 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>"
252
+ if not state or state is None or state == "Error: Critical failure loading states": # Added check for error state
253
+ return "<div class='error-message'><span class='error-icon'></span>Please select a valid state from the list.</div>"
254
  if not query or not query.strip():
255
+ return "<div class='error-message'><span class='error-icon'></span>Please enter your question in the text box.</div>"
256
 
257
  # Call the core processing logic
258
+ result = self.assistant.process_query(query=query, state=state, openai_api_key=api_key)
259
  answer = result.get("answer", "<div class='error-message'><span class='error-icon'>⚠️</span>An unexpected error occurred.</div>")
260
 
261
  # Check if the answer already contains an error message
 
266
  return f"<div class='animated-output-content'>{formatted_response_content}</div>"
267
 
268
  try:
269
+ available_states_list = self.assistant.get_states()
270
  print(f"DEBUG: States loaded for selection: {available_states_list}")
271
+ # Ensure radio_choices is not empty if there's an error and handling the error message
272
+ self.state_choices = available_states_list if available_states_list and "Error" not in available_states_list[0] else ["Error: States unavailable"]
273
+ # Set initial value only if there are valid choices
274
+ self.initial_state_value = self.state_choices[0] if self.state_choices and "Error" not in self.state_choices[0] else None
275
  except Exception as e:
276
  print(f"DEBUG: Error loading states for selection: {e}")
277
+ self.state_choices = ["Error: Critical failure loading states"]
278
+ self.initial_state_value = None
279
 
280
  example_queries_base = [
281
  ["What are the rules for security deposit returns?", "California"],
 
285
  ["What is an implied warranty of habitability?", "Illinois"]
286
  ]
287
  example_queries = []
288
+ if self.state_choices and "Error" not in self.state_choices[0] and len(self.state_choices) > 0:
289
+ loaded_states_set = set(self.state_choices)
290
  example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
291
+ if not example_queries: # Fallback if no matching examples found
292
+ example_queries.append(["What basic rights do tenants have?", self.state_choices[0]])
293
+ else: # If states failed to load, provide a generic example
294
  example_queries.append(["What basic rights do tenants have?", "California"])
295
 
 
 
 
 
 
 
 
 
 
 
 
 
296
 
297
+ custom_css = f"""
298
+ /* Import legible fonts from Google Fonts */
299
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@600;700;800&display=swap');
300
+
301
+ /* Root variables for consistent light theme, apply to html.dark as well to override */
302
+ :root, html.dark {{
303
  --primary-color: #FF8C00;
304
  --primary-hover: #E07B00;
305
  --background-primary: hsl(30, 100%, 99.9%);
 
314
  --error-bg: #FFF0E0;
315
  --error-border: #FFD2B2;
316
  --error-text: #E05C00;
 
 
 
 
317
 
318
+ /* **IMPORTANT**: Override Gradio's internal CSS variables to force light theme values */
319
+ --background-fill-primary: var(--background-primary) !important;
320
+ --background-fill-secondary: var(--background-secondary) !important;
321
+ --background-fill-tertiary: var(--background-secondary) !important; /* For less prominent backgrounds */
322
+ --text-color-subdued: var(--text-secondary) !important;
323
+ --text-color-highlight: var(--primary-color) !important;
324
+ --text-color-body: var(--text-primary) !important;
325
+ --text-color-placeholder: var(--text-secondary) !important;
326
+ --border-color-primary: var(--border-color) !important;
327
+ --border-color-accent: var(--border-focus) !important;
328
+ --shadow-s: var(--shadow-sm) !important;
329
+ --shadow-m: var(--shadow-md) !important;
330
+ --shadow-l: var(--shadow-lg) !important;
331
+ --button-primary-background: var(--primary-color) !important;
332
+ --button-primary-background-hover: var(--primary-hover) !important;
333
+ --button-secondary-background: transparent !important;
334
+ --button-secondary-background-hover: var(--background-secondary) !important;
335
+ --button-secondary-border-color: var(--border-color) !important;
336
+ --button-secondary-border-color-hover: var(--primary-color) !important;
337
+ --input-background-fill: var(--background-primary) !important;
338
+ --input-border-color: var(--border-color) !important;
339
+ --input-border-color-focus: var(--border-focus) !important;
340
+ --block-background: var(--background-primary) !important;
341
+ --block-border-color: var(--border-color) !important;
342
+ --panel-background: var(--background-primary) !important;
343
+ --color-text-primary: var(--text-primary) !important;
344
+ --color-text-secondary: var(--text-secondary) !important;
345
+ --color-border-primary: var(--border-color) !important;
346
+ --color-border-secondary: var(--border-color) !important;
347
+ --color-button-primary-background: var(--primary-color) !important;
348
+ --color-button-primary-background-hover: var(--primary-hover) !important;
349
+ --color-button-secondary-background: transparent !important;
350
+ --color-button-secondary-background-hover: var(--background-secondary) !important;
351
+ --color-accent-soft: rgba(255, 140, 0, 0.2) !important; /* Used for focus shadows */
352
+ }}
353
+
354
+ body, html {{
355
  background-color: var(--background-secondary) !important;
356
  color: var(--text-primary) !important;
357
+ transition: none !important; /* Prevent theme change transitions */
358
+ }}
359
+ .gradio-container {{
 
 
 
 
 
360
  max-width: 900px !important;
361
  margin: 0 auto !important;
362
  padding: 1.5rem !important;
363
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
364
+ background-color: var(--background-secondary) !important; /* Ensure main container background */
365
  box-shadow: none !important;
366
  color: var(--text-primary) !important;
367
+ }}
368
+ .main-dashboard-container > * {{
 
369
  background-color: var(--background-primary) !important;
370
+ border-color: var(--border-color) !important; /* Ensure card borders are consistent */
371
+ }}
372
+ .app-header-wrapper {{
 
373
  background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
374
  border: 2px solid var(--border-color) !important;
375
  border-radius: 16px !important;
 
380
  overflow: hidden;
381
  text-align: center !important;
382
  color: var(--text-primary) !important;
383
+ }}
384
+ .app-header-wrapper::before {{
 
385
  content: '';
386
  position: absolute;
387
  top: 0;
 
392
  z-index: 0;
393
  opacity: 0.8;
394
  pointer-events: none;
395
+ }}
396
+ .app-header-logo {{
 
397
  font-size: 8.5rem !important;
398
  margin-bottom: 0.75rem !important;
399
  display: block !important;
 
401
  position: relative;
402
  z-index: 1;
403
  animation: float-icon 3s ease-in-out infinite alternate;
404
+ }}
405
+ @keyframes float-icon {{
406
+ 0% {{ transform: translateY(0px); }}
407
+ 50% {{ transform: translateY(-5px); }}
408
+ 100% {{ transform: translateY(0px); }}
409
+ }}
410
+ .app-header-title {{
 
 
411
  font-family: 'Poppins', sans-serif !important;
412
  font-size: 3rem !important;
413
  font-weight: 800 !important;
 
419
  z-index: 1;
420
  display: inline-block;
421
  max-width: 100%;
422
+ }}
423
+ .app-header-tagline {{
 
424
  font-size: 1.25rem !important;
425
  color: var(--text-secondary) !important;
426
  font-weight: 400 !important;
 
429
  display: inline-block;
430
  position: relative;
431
  z-index: 1;
432
+ }}
433
+ .main-dashboard-container {{
 
 
434
  display: flex !important;
435
  flex-direction: column !important;
436
  gap: 1.25rem !important;
437
+ }}
438
+ .dashboard-card-section {{
 
439
  background-color: var(--background-primary) !important;
440
  border: 2px solid var(--border-color) !important;
441
  border-radius: 12px !important;
442
  padding: 0 !important;
443
  box-shadow: var(--shadow-sm) !important;
444
  transition: all 0.3s ease-out !important;
445
+ cursor: default;
446
  color: var(--text-primary) !important;
447
+ }}
448
+ .dashboard-card-section:hover {{
 
449
  box-shadow: var(--shadow-md) !important;
450
  transform: translateY(-3px) !important;
451
+ }}
452
+ .full-width-center {{
 
453
  display: flex !important;
454
  justify-content: center !important;
455
  align-items: center !important;
456
  width: 100% !important;
457
  flex-direction: column !important;
458
  background-color: transparent !important;
459
+ }}
460
+ .section-title-gradient-bar {{
 
461
  background-color: var(--background-secondary) !important;
462
  padding: 1.25rem 1.75rem !important;
463
  border-top-left-radius: 10px !important;
 
467
  box-sizing: border-box;
468
  width: 100%;
469
  color: var(--text-primary) !important;
470
+ }}
471
+ .section-title {{
 
472
  font-family: 'Poppins', sans-serif !important;
473
  font-size: 1.7rem !important;
474
  font-weight: 700 !important;
 
480
  display: inline-block !important;
481
  text-align: center !important;
482
  letter-spacing: -0.01em !important;
483
+ }}
484
+ .dashboard-card-content-area {{
 
485
  padding: 0 1.75rem 1.75rem 1.75rem !important;
486
  background-color: var(--background-primary) !important;
487
  box-sizing: border-box;
488
  width: 100%;
489
  color: var(--text-primary) !important;
490
+ }}
491
+ .dashboard-card-section p {{
 
492
  line-height: 1.7 !important;
493
  color: var(--text-primary) !important;
494
  font-size: 1rem !important;
 
497
  margin: 0 !important;
498
  padding: 0 !important;
499
  white-space: normal !important;
500
+ }}
501
+ .dashboard-card-section strong, .dashboard-card-section b {{
 
502
  font-weight: 700 !important;
503
  color: var(--primary-color) !important;
504
+ }}
 
 
505
  .gr-block, .gr-box, .gr-prose, .gr-form, .gr-panel,
506
  .gr-columns, .gr-column,
507
+ .gradio-html, .gradio-markdown, .gradio-textbox, .gradio-radio, .gradio-button {{
508
  background-color: transparent !important;
509
  color: var(--text-primary) !important;
510
  white-space: normal !important;
511
  overflow-wrap: break-word;
512
  word-break: break-word;
513
+ }}
 
514
  .gradio-textbox textarea,
515
  .gradio-textbox input,
516
  .gradio-radio label,
517
+ .placeholder {{
518
  background-color: var(--background-primary) !important;
519
  color: var(--text-primary) !important;
520
+ }}
521
+ .gradio-textbox {{
 
522
  margin-bottom: 0.5rem !important;
523
+ }}
 
524
  .gradio-textbox textarea,
525
+ .gradio-textbox input {{
526
  border: 2px solid var(--border-color) !important;
527
  border-radius: 8px !important;
528
  padding: 0.85rem 1rem !important;
 
531
  color: var(--text-primary) !important;
532
  transition: border-color 0.2s ease, box-shadow 0.2s ease !important;
533
  box-shadow: var(--shadow-sm) !important;
534
+ }}
535
+ .gradio-textbox .scroll-hide {{
 
 
 
 
536
  background-color: var(--background-primary) !important;
537
+ }}
 
538
  .gradio-textbox textarea:focus,
539
+ .gradio-textbox input:focus {{
540
  outline: none !important;
541
  border-color: var(--border-focus) !important;
542
  box-shadow: 0 0 0 4px rgba(255, 140, 0, 0.2) !important;
543
+ }}
544
+ .gradio-radio {{
 
545
  padding: 0 !important;
546
  margin-top: 1rem !important;
547
+ }}
548
+ .gradio-radio input[type="radio"] {{
 
549
  display: none !important;
550
+ }}
551
+ .gradio-radio label {{
 
552
  display: flex !important;
553
  justify-content: center !important;
554
  align-items: center !important;
 
561
  transition: all 0.2s ease-out !important;
562
  box-shadow: var(--shadow-sm) !important;
563
  margin: 0.2rem 0 !important;
564
+ width: 100 !important;
565
  box-sizing: border-box !important;
566
+ }}
567
+ .gradio-radio label span.text-lg {{
 
568
  font-weight: 600 !important;
569
  color: var(--text-primary) !important;
570
  font-size: 0.98rem !important;
571
+ }}
572
+ .gradio-radio label:hover {{
 
573
  background-color: var(--background-secondary) !important;
574
  border-color: var(--primary-color) !important;
575
  box-shadow: var(--shadow-md) !important;
576
  transform: translateY(-2px) !important;
577
+ }}
578
+ .gradio-radio input[type="radio"]:checked + label {{
 
579
  background-color: var(--primary-color) !important;
580
+ color: white !important; /* Ensure text is white when selected */
581
  border-color: var(--primary-hover) !important;
582
  box-shadow: var(--shadow-md) !important;
583
  transform: translateY(-1px) !important;
584
+ }}
585
+ .gradio-radio input[type="radio"]:checked + label span.text-lg {{
586
+ color: white !important; /* Ensure text is white when selected */
587
+ }}
588
+ .gradio-radio .gr-form {{
 
 
589
  padding: 0 !important;
590
+ }}
 
591
  .gradio-textbox label,
592
+ .gradio-radio > label {{
593
  font-weight: 600 !important;
594
  color: var(--text-primary) !important;
595
  font-size: 1rem !important;
596
  margin-bottom: 0.6rem !important;
597
  display: block !important;
598
  text-align: left !important;
599
+ }}
600
+ .gr-prose {{
 
601
  font-size: 0.9rem !important;
602
  color: var(--text-secondary) !important;
603
  margin-top: 0.4rem !important;
604
  text-align: left !important;
605
  background-color: transparent !important;
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
+ .input-field {{
 
614
  flex: none !important;
615
  width: 100% !important;
616
+ }}
617
+ .button-row {{
 
618
  display: flex !important;
619
  gap: 1rem !important;
620
  justify-content: flex-end !important;
621
  margin-top: 1.5rem !important;
622
+ }}
623
+ .gradio-button {{
 
624
  padding: 0.85rem 1.8rem !important;
625
  border-radius: 9px !important;
626
  font-weight: 600 !important;
 
630
  border: 2px solid transparent !important;
631
  text-align: center !important;
632
  color: var(--text-primary) !important;
633
+ }}
634
+ .gr-button-primary {{
 
 
 
 
635
  background-color: var(--primary-color) !important;
636
+ color: white !important; /* Ensure text is white for primary buttons */
637
  box-shadow: var(--shadow-sm) !important;
638
+ }}
639
+ .gr-button-primary:hover {{
 
640
  background-color: var(--primary-hover) !important;
641
  box-shadow: var(--shadow-md) !important;
642
  transform: translateY(-2px) !important;
643
+ }}
644
+ .gr-button-primary:active {{
 
645
  transform: translateY(1px) !important;
646
  box-shadow: none !important;
647
+ }}
648
+ .gr-button-secondary {{
 
649
  background-color: transparent !important;
650
  color: var(--text-primary) !important;
651
  border-color: var(--border-color) !important;
652
+ }}
653
+ .gr-button-secondary:hover {{
 
654
  background-color: var(--background-secondary) !important;
655
  border-color: var(--primary-color) !important;
656
  transform: translateY(-2px) !important;
657
+ }}
658
+ .gr-button-secondary:active {{
 
659
  transform: translateY(1px) !important;
660
  box-shadow: none !important;
661
+ }}
662
+ .output-content-wrapper {{
 
663
  background-color: var(--background-primary) !important;
664
  border: 2px solid var(--border-color) !important;
665
  border-radius: 8px !important;
 
670
  flex-direction: column;
671
  justify-content: center;
672
  align-items: center;
673
+ }}
674
+ .animated-output-content {{
 
675
  opacity: 0;
676
  animation: fadeInAndSlideUp 0.7s ease-out forwards;
677
  width: 100%;
 
680
  word-break: break-word;
681
  text-align: left !important;
682
  color: var(--text-primary) !important;
683
+ }}
684
+ @keyframes fadeInAndSlideUp {{
685
+ from {{ opacity: 0; transform: translateY(15px); }}
686
+ to {{ opacity: 1; transform: translateY(0); }}
687
+ }}
688
+ .response-header {{
 
 
689
  font-size: 1.3rem !important;
690
  font-weight: 700 !important;
691
  color: var(--primary-color) !important;
 
696
  text-align: left !important;
697
  width: 100%;
698
  justify-content: flex-start;
699
+ }}
700
+ .response-icon {{
 
701
  font-size: 1.5rem !important;
702
  color: var(--primary-color) !important;
703
+ }}
704
+ .divider {{
 
705
  border: none !important;
706
  border-top: 1px dashed var(--border-color) !important;
707
  margin: 1rem 0 !important;
708
+ }}
709
+ .error-message {{
 
710
  background-color: var(--error-bg) !important;
711
  border: 2px solid var(--error-border) !important;
712
  color: var(--error-text) !important;
 
721
  text-align: left !important;
722
  width: 100%;
723
  box-sizing: border-box;
724
+ }}
725
+ .error-message a {{
 
726
  color: var(--error-text) !important;
727
  text-decoration: underline !important;
728
+ }}
729
+ .error-icon {{
 
730
  font-size: 1.4rem !important;
731
  line-height: 1 !important;
732
  margin-top: 0.1rem !important;
733
+ }}
734
+ .error-details {{
 
735
  font-size: 0.85rem !important;
736
  color: var(--error-text) !important;
737
  margin-top: 0.5rem !important;
738
  opacity: 0.8;
739
+ }}
740
+ .placeholder {{
 
741
  background-color: var(--background-primary) !important;
742
  border: 2px dashed var(--border-color) !important;
743
  border-radius: 8px !important;
 
748
  font-size: 1.1rem !important;
749
  width: 100%;
750
  box-sizing: border-box;
751
+ }}
752
+ .examples-section .gr-samples-table {{
 
753
  border: 2px solid var(--border-color) !important;
754
  border-radius: 8px !important;
755
  overflow: hidden !important;
756
  margin-top: 1rem !important;
757
+ }}
 
758
  .examples-section .gr-samples-table th,
759
+ .examples-section .gr-samples-table td {{
760
  padding: 0.9rem !important;
761
  border: none !important;
762
  font-size: 0.95rem !important;
763
  text-align: left !important;
764
  color: var(--text-primary) !important;
765
+ }}
766
+ .examples-section .gr-samples-table th {{
 
767
  background-color: var(--background-secondary) !important;
768
  font-weight: 700 !important;
769
  color: var(--text-primary) !important;
770
+ }}
771
+ .examples-section .gr-samples-table td {{
 
772
  background-color: var(--background-primary) !important;
773
  color: var(--text-primary) !important;
774
  border-top: 1px solid var(--border-color) !important;
775
  cursor: pointer !important;
776
  transition: background 0.2s ease, transform 0.1s ease !important;
777
+ }}
778
+ .examples-section .gr-samples-table tr:hover td {{
 
779
  background-color: var(--background-secondary) !important;
780
  transform: translateX(5px);
781
+ }}
 
782
  .gr-examples .gr-label,
783
  .gr-examples .label-wrap,
784
+ .gr-examples .gr-accordion-header {{
785
  display: none !important;
786
+ }}
787
+ .app-footer-wrapper {{
 
788
  background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
789
  border: 2px solid var(--border-color) !important;
790
  border-radius: 12px !important;
 
793
  margin-bottom: 1.5rem !important;
794
  text-align: center !important;
795
  color: var(--text-primary) !important;
796
+ }}
797
+ .app-footer p {{
 
798
  margin: 0 !important;
799
  max-width: 90% !important;
800
  font-size: 0.95rem !important;
 
803
  background-color: transparent !important;
804
  text-align: center !important;
805
  white-space: normal !important;
806
+ }}
807
+ .app-footer strong, .app-footer b {{
 
808
  font-weight: 700 !important;
809
  color: var(--primary-color) !important;
810
+ }}
811
+ .app-footer a {{
 
812
  color: var(--primary-color) !important;
813
  text-decoration: underline !important;
814
  font-weight: 600 !important;
815
+ }}
816
+ .app-footer a:hover {{
 
817
  text-decoration: none !important;
818
+ }}
819
+ @media (max-width: 768px) {{
820
+ .gradio-container {{
 
 
821
  padding: 1rem !important;
822
+ }}
823
+ .app-header-title {{
824
  font-size: 2.2rem !important;
825
+ }}
826
+ .app-header-tagline {{
827
  font-size: 1rem !important;
828
+ }}
829
+ .section-title {{
830
  font-size: 1.4rem !important;
831
+ }}
832
+ .input-column {{
833
  flex-direction: column !important;
834
+ }}
835
+ .button-row {{
836
  flex-direction: column !important;
837
+ }}
838
+ .gradio-button {{
839
  width: 100% !important;
840
+ }}
841
+ .dashboard-card-section {{
842
+ }}
843
+ .section-title-gradient-bar {{
 
844
  padding: 0.1rem 1rem !important;
845
+ }}
846
+ .dashboard-card-content-area {{
847
  padding: 0 1rem 1rem 1rem !important;
848
+ }}
849
+ .output-content-wrapper {{
850
  min-height: 120px !important;
851
+ }}
852
+ .placeholder {{
853
  padding: 1.5rem 1rem !important;
854
  font-size: 1rem !important;
855
+ }}
856
+ }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
857
  """
858
 
859
+ # Ensure theme_mode="light" is explicitly set
860
  with gr.Blocks(css=custom_css, title="Landlord-Tenant Rights Assistant", theme_mode="light") as demo:
861
  with gr.Group(elem_classes="app-header-wrapper"):
862
  gr.Markdown(
 
911
  with gr.Column(elem_classes="input-field", scale=1):
912
  state_input = gr.Radio(
913
  label="Select State",
914
+ choices=self.state_choices, # Use self.state_choices
915
+ value=self.initial_state_value, # Use self.initial_state_value
916
  elem_classes=["input-field-group", "gradio-radio-custom"],
917
  interactive=True
918
  )
 
965
  outputs=output,
966
  api_name="submit_query"
967
  )
968
+
969
  clear_button.click(
970
  fn=lambda: (
971
+ "", "", self.initial_state_value, "<div class='placeholder'>Inputs cleared. Ready for your next question.</div>"
972
  ),
973
  inputs=[],
974
  outputs=[api_key_input, query_input, state_input, output]
 
976
 
977
  return demo
978
 
979
+
980
  # --- Main Execution Block (UNCHANGED from original logic) ---
981
  if __name__ == "__main__":
982
  logging.info("Starting Landlord-Tenant Rights Bot application...")