Nischal Subedi commited on
Commit
8336e55
·
1 Parent(s): b9a4862
Files changed (1) hide show
  1. app.py +746 -746
app.py CHANGED
@@ -244,781 +244,781 @@ 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
- # Basic client-side validation for immediate feedback (redundant but good UX)
249
- if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
250
- 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>"
251
- if not state or state is None:
252
- return "<div class='error-message'><span class='error-icon'></span>Please select a valid state from the list.</div>"
253
- if not query or not query.strip():
254
- return "<div class='error-message'><span class='error-icon'></span>Please enter your question in the text box.</div>"
255
-
256
- # Call the core processing logic
257
- result = self.process_query(query=query, state=state, openai_api_key=api_key)
258
- answer = result.get("answer", "<div class='error-message'><span class='error-icon'>⚠️</span>An unexpected error occurred.</div>")
 
 
 
 
 
 
 
259
 
260
- # Check if the answer already contains an error message
261
- if "<div class='error-message'>" in answer:
262
- return answer
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
  else:
264
- formatted_response_content = f"<div class='response-header'><span class='response-icon'>📜</span>Response for {state}</div><hr class='divider'>{answer}"
265
- return f"<div class='animated-output-content'>{formatted_response_content}</div>"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
 
267
- try:
268
- available_states_list = self.get_states()
269
- print(f"DEBUG: States loaded for selection: {available_states_list}")
270
- radio_choices = available_states_list if available_states_list and "Error" not in available_states_list[0] else ["Error: States unavailable"]
271
- initial_value_radio = None
272
- except Exception as e:
273
- print(f"DEBUG: Error loading states for selection: {e}")
274
- radio_choices = ["Error: Critical failure loading states"]
275
- initial_value_radio = None
276
- example_queries_base = [
277
- ["What are the rules for security deposit returns?", "California"],
278
- ["Can a landlord enter my apartment without notice?", "New York"],
279
- ["My landlord hasn't made necessary repairs. What can I do?", "Texas"],
280
- ["How much notice must a landlord give to raise rent?", "Florida"],
281
- ["What is an implied warranty of habitability?", "Illinois"]
282
- ]
283
- example_queries = []
284
- if available_states_list and "Error" not in available_states_list[0] and len(available_states_list) > 0:
285
- loaded_states_set = set(available_states_list)
286
- example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
287
- if not example_queries:
288
- example_queries.append(["What basic rights do tenants have?", available_states_list[0] if available_states_list else "California"])
289
- else:
290
- example_queries.append(["What basic rights do tenants have?", "California"])
291
-
292
- custom_css = """
293
- /* Import legible fonts from Google Fonts */
294
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@600;700;800&display=swap');
295
-
296
- /* Root variables for consistent theming (light mode) */
297
- :root {
298
- --primary-color: #FF8C00;
299
- --primary-hover: #E07B00;
300
- --background-primary: hsl(30, 100%, 99.9%);
301
- --background-secondary: hsl(30, 100%, 96%);
302
- --text-primary: #4A3C32;
303
- --text-secondary: #8C7B6F;
304
- --border-color: hsl(30, 70%, 85%);
305
- --border-focus: #FF8C00;
306
- --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
307
- --shadow-md: 0 4px 10px rgba(0,0,0,0.1);
308
- --shadow-lg: 0 10px 20px rgba(0,0,0,0.15);
309
- --error-bg: #FFF0E0;
310
- --error-border: #FFD2B2;
311
- --error-text: #E05C00;
312
- }
313
-
314
- /* Dark mode variables */
315
- @media (prefers-color-scheme: dark) {
316
- body {
317
- --primary-color: #FFA500;
318
- --primary-hover: #CC8400;
319
- --background-primary: #2C2C2C;
320
- --background-secondary: #1F1F1F;
321
- --text-primary: #F0F0F0;
322
- --text-secondary: #B0B0B0;
323
- --border-color: #555555;
324
- --border-focus: #FFA500;
325
- --shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
326
- --shadow-md: 0 4px 10px rgba(0,0,0,0.4);
327
- --shadow-lg: 0 10px 20px rgba(0,0,0,0.5);
328
- --error-bg: #400000;
329
- --error-border: #800000;
330
- --error-text: #FF6666;
331
- }
332
- }
333
-
334
- body, html {
335
- background-color: var(--background-secondary) !important;
336
- color: var(--text-primary) !important;
337
- transition: background-color 0.3s, color 0.3s;
338
- }
339
-
340
- .gradio-container {
341
- max-width: 900px !important;
342
- margin: 0 auto !important;
343
- padding: 1.5rem !important;
344
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
345
- background-color: var(--background-secondary) !important;
346
- box-shadow: none !important;
347
- color: var(--text-primary) !important;
348
- }
349
-
350
- .main-dashboard-container > * {
351
- background-color: var(--background-primary) !important;
352
- }
353
-
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;
358
- padding: 2.5rem 1.5rem !important;
359
- margin-bottom: 1.5rem !important;
360
- box-shadow: var(--shadow-md) !important;
361
- position: relative;
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;
371
- left: 0;
372
- width: 100%;
373
- height: 100%;
374
- 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%);
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;
384
- color: var(--primary-color) !important;
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;
400
- color: var(--text-primary) !important;
401
- margin: 0 0 0.75rem 0 !important;
402
- line-height: 1.1 !important;
403
- letter-spacing: -0.03em !important;
404
- position: relative;
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;
414
- margin: 0 !important;
415
- max-width: 700px;
416
- display: inline-block;
417
- position: relative;
418
- z-index: 1;
419
- }
420
-
421
- .main-dashboard-container {
422
- display: flex !important;
423
- flex-direction: column !important;
424
- gap: 1.25rem !important;
425
- }
426
-
427
- .dashboard-card-section {
428
- background-color: var(--background-primary) !important;
429
- border: 2px solid var(--border-color) !important;
430
- border-radius: 12px !important;
431
- padding: 0 !important;
432
- box-shadow: var(--shadow-sm) !important;
433
- transition: all 0.3s ease-out !important;
434
- cursor: default;
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;
456
- border-top-right-radius: 10px !important;
457
- margin-bottom: 0 !important;
458
- text-align: center !important;
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;
468
- color: var(--text-primary) !important;
469
- margin: 0 !important;
470
- padding-bottom: 0 !important;
471
- border-bottom: 2px solid var(--border-color) !important;
472
- line-height: 1.1 !important;
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;
490
- text-align: left !important;
491
- background-color: transparent !important;
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
- .gr-block, .gr-box, .gr-prose, .gr-form, .gr-panel,
503
- .gr-columns, .gr-column,
504
- .gradio-html, .gradio-markdown, .gradio-textbox, .gradio-radio, .gradio-button {
505
- background-color: transparent !important;
506
- color: var(--text-primary) !important;
507
- white-space: normal !important;
508
- overflow-wrap: break-word;
509
- word-break: break-word;
510
- }
511
-
512
- .gradio-textbox textarea,
513
- .gradio-textbox input,
514
- .gradio-radio label,
515
- .placeholder {
516
- background-color: var(--background-primary) !important;
517
- color: var(--text-primary) !important;
518
- }
519
-
520
- .gradio-textbox {
521
- margin-bottom: 0.5rem !important;
522
- }
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;
529
- font-size: 0.98rem !important;
530
- font-family: 'Inter', sans-serif !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
-
536
- .gradio-textbox .scroll-hide {
537
- background-color: var(--background-primary) !important;
538
- }
539
-
540
- .gradio-textbox textarea:focus,
541
- .gradio-textbox input:focus {
542
- outline: none !important;
543
- border-color: var(--border-focus) !important;
544
- box-shadow: 0 0 0 4px rgba(255, 140, 0, 0.2) !important;
545
- }
546
-
547
- .gradio-radio {
548
- padding: 0 !important;
549
- margin-top: 1rem !important;
550
- }
551
-
552
- .gradio-radio input[type="radio"] {
553
- display: none !important;
554
- }
555
-
556
- .gradio-radio label {
557
- display: flex !important;
558
- justify-content: center !important;
559
- align-items: center !important;
560
- padding: 0.75rem 1rem !important;
561
- border: 2px solid var(--border-color) !important;
562
- border-radius: 8px !important;
563
- color: var(--text-primary) !important;
564
- font-weight: 500 !important;
565
- cursor: pointer !important;
566
- transition: all 0.2s ease-out !important;
567
- box-shadow: var(--shadow-sm) !important;
568
- margin: 0.2rem 0 !important;
569
- width: 100 !important;
570
- box-sizing: border-box !important;
571
- }
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
-
579
- .gradio-radio label:hover {
580
- background-color: var(--background-secondary) !important;
581
- border-color: var(--primary-color) !important;
582
- box-shadow: var(--shadow-md) !important;
583
- transform: translateY(-2px) !important;
584
- }
585
-
586
- .gradio-radio input[type="radio"]:checked + label {
587
- background-color: var(--primary-color) !important;
588
- color: var(--text-primary) !important;
589
- border-color: var(--primary-hover) !important;
590
- box-shadow: var(--shadow-md) !important;
591
- transform: translateY(-1px) !important;
592
- }
593
-
594
- .gradio-radio input[type="radio"]:checked + label span.text-lg {
595
- color: var(--text-primary) !important;
596
- }
597
-
598
- .gradio-radio .gr-form {
599
- padding: 0 !important;
600
- }
601
-
602
- .gradio-textbox label,
603
- .gradio-radio > label {
604
- font-weight: 600 !important;
605
- color: var(--text-primary) !important;
606
- font-size: 1rem !important;
607
- margin-bottom: 0.6rem !important;
608
- display: block !important;
609
- text-align: left !important;
610
- }
611
-
612
- .gr-prose {
613
- font-size: 0.9rem !important;
614
- color: var(--text-secondary) !important;
615
- margin-top: 0.4rem !important;
616
- text-align: left !important;
617
- background-color: transparent !important;
618
- }
619
-
620
- .input-column {
621
- display: flex !important;
622
- flex-direction: column !important;
623
- gap: 1.25rem !important;
624
- margin-bottom: 0.5rem !important;
625
- }
626
-
627
- .input-field {
628
- flex: none !important;
629
- width: 100% !important;
630
- }
631
-
632
- .button-row {
633
- display: flex !important;
634
- gap: 1rem !important;
635
- justify-content: flex-end !important;
636
- margin-top: 1.5rem !important;
637
- }
638
-
639
- .gradio-button {
640
- padding: 0.85rem 1.8rem !important;
641
- border-radius: 9px !important;
642
- font-weight: 600 !important;
643
- font-size: 1rem !important;
644
- transition: all 0.2s ease-out !important;
645
- cursor: pointer !important;
646
- border: 2px solid transparent !important;
647
- text-align: center !important;
648
- color: var(--text-primary) !important;
649
- }
650
-
651
- .gr-button-primary {
652
- background-color: var(--primary-color) !important;
653
- color: white !important;
654
- box-shadow: var(--shadow-sm) !important;
655
- }
656
-
657
- .gr-button-primary:hover {
658
- background-color: var(--primary-hover) !important;
659
- box-shadow: var(--shadow-md) !important;
660
- transform: translateY(-2px) !important;
661
- }
662
-
663
- .gr-button-primary:active {
664
- transform: translateY(1px) !important;
665
- box-shadow: none !important;
666
- }
667
-
668
- .gr-button-secondary {
669
- background-color: transparent !important;
670
- color: var(--text-primary) !important;
671
- border-color: var(--border-color) !important;
672
- }
673
-
674
- .gr-button-secondary:hover {
675
- background-color: var(--background-secondary) !important;
676
- border-color: var(--primary-color) !important;
677
- transform: translateY(-2px) !important;
678
- }
679
-
680
- .gr-button-secondary:active {
681
- transform: translateY(1px) !important;
682
- box-shadow: none !important;
683
- }
684
-
685
- .output-content-wrapper {
686
- background-color: var(--background-primary) !important;
687
- border: 2px solid var(--border-color) !important;
688
- border-radius: 8px !important;
689
- padding: 1.5rem !important;
690
- min-height: 150px !important;
691
- color: var(--text-primary) !important;
692
- display: flex;
693
- flex-direction: column;
694
- justify-content: center;
695
- align-items: center;
696
- }
697
-
698
- .animated-output-content {
699
- opacity: 0;
700
- animation: fadeInAndSlideUp 0.7s ease-out forwards;
701
- width: 100%;
702
- white-space: pre-wrap;
703
- overflow-wrap: break-word;
704
- word-break: break-word;
705
- text-align: left !important;
706
- color: var(--text-primary) !important;
707
- }
708
-
709
- @keyframes fadeInAndSlideUp {
710
- from { opacity: 0; transform: translateY(15px); }
711
- to { opacity: 1; transform: translateY(0); }
712
- }
713
-
714
- .response-header {
715
- font-size: 1.3rem !important;
716
- font-weight: 700 !important;
717
- color: var(--primary-color) !important;
718
- margin-bottom: 0.75rem !important;
719
- display: flex !important;
720
- align-items: center !important;
721
- gap: 0.6rem !important;
722
- text-align: left !important;
723
- width: 100%;
724
- justify-content: flex-start;
725
- }
726
-
727
- .response-icon {
728
- font-size: 1.5rem !important;
729
- color: var(--primary-color) !important;
730
- }
731
-
732
- .divider {
733
- border: none !important;
734
- border-top: 1px dashed var(--border-color) !important;
735
- margin: 1rem 0 !important;
736
- }
737
-
738
- .error-message {
739
- background-color: var(--error-bg) !important;
740
- border: 2px solid var(--error-border) !important;
741
- color: var(--error-text) !important;
742
- padding: 1.25rem !important;
743
- border-radius: 8px !important;
744
- display: flex !important;
745
- align-items: flex-start !important;
746
- gap: 0.8rem !important;
747
- font-size: 0.95rem !important;
748
- font-weight: 500 !important;
749
- line-height: 1.6 !important;
750
- text-align: left !important;
751
- width: 100%;
752
- box-sizing: border-box;
753
- }
754
-
755
- .error-message a {
756
- color: var(--error-text) !important;
757
- text-decoration: underline !important;
758
- }
759
-
760
- .error-icon {
761
- font-size: 1.4rem !important;
762
- line-height: 1 !important;
763
- margin-top: 0.1rem !important;
764
- }
765
-
766
- .error-details {
767
- font-size: 0.85rem !important;
768
- color: var(--error-text) !important;
769
- margin-top: 0.5rem !important;
770
- opacity: 0.8;
771
- }
772
-
773
- .placeholder {
774
- background-color: var(--background-primary) !important;
775
- border: 2px dashed var(--border-color) !important;
776
- border-radius: 8px !important;
777
- padding: 2.5rem 1.5rem !important;
778
- text-align: center !important;
779
- color: var(--text-secondary) !important;
780
- font-style: italic !important;
781
- font-size: 1.1rem !important;
782
- width: 100%;
783
- box-sizing: border-box;
784
- }
785
-
786
- .examples-section .gr-samples-table {
787
- border: 2px solid var(--border-color) !important;
788
- border-radius: 8px !important;
789
- overflow: hidden !important;
790
- margin-top: 1rem !important;
791
- }
792
-
793
- .examples-section .gr-samples-table th,
794
- .examples-section .gr-samples-table td {
795
- padding: 0.9rem !important;
796
- border: none !important;
797
- font-size: 0.95rem !important;
798
- text-align: left !important;
799
- color: var(--text-primary) !important;
800
- }
801
-
802
- .examples-section .gr-samples-table th {
803
- background-color: var(--background-secondary) !important;
804
- font-weight: 700 !important;
805
- color: var(--text-primary) !important;
806
- }
807
-
808
- .examples-section .gr-samples-table td {
809
- background-color: var(--background-primary) !important;
810
- color: var(--text-primary) !important;
811
- border-top: 1px solid var(--border-color) !important;
812
- cursor: pointer !important;
813
- transition: background 0.2s ease, transform 0.1s ease !important;
814
- }
815
-
816
- .examples-section .gr-samples-table tr:hover td {
817
- background-color: var(--background-secondary) !important;
818
- transform: translateX(5px);
819
- }
820
-
821
- .gr-examples .gr-label,
822
- .gr-examples .label-wrap,
823
- .gr-examples .gr-accordion-header {
824
- display: none !important;
825
- }
826
-
827
- .app-footer-wrapper {
828
- background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
829
- border: 2px solid var(--border-color) !important;
830
- border-radius: 12px !important;
831
- padding: 1.75rem !important;
832
- margin-top: 1.5rem !important;
833
- margin-bottom: 1.5rem !important;
834
- text-align: center !important;
835
- color: var(--text-primary) !important;
836
- }
837
-
838
- .app-footer p {
839
- margin: 0 !important;
840
- max-width: 90% !important;
841
- font-size: 0.95rem !important;
842
- color: var(--text-secondary) !important;
843
- line-height: 1.6 !important;
844
- background-color: transparent !important;
845
- text-align: center !important;
846
- white-space: normal !important;
847
- }
848
-
849
- .app-footer strong, .app-footer b {
850
- font-weight: 700 !important;
851
- color: var(--primary-color) !important;
852
- }
853
-
854
- .app-footer a {
855
- color: var(--primary-color) !important;
856
- text-decoration: underline !important;
857
- font-weight: 600 !important;
858
- }
859
-
860
- .app-footer a:hover {
861
- text-decoration: none !important;
862
- }
863
-
864
- @media (max-width: 768px) {
865
  .gradio-container {
866
- padding: 1rem !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
867
  }
 
868
  .app-header-title {
869
- font-size: 2.2rem !important;
 
 
 
 
 
 
 
 
 
 
870
  }
 
871
  .app-header-tagline {
872
- font-size: 1rem !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
873
  }
 
874
  .section-title {
875
- font-size: 1.4rem !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
876
  }
 
 
 
 
 
 
 
 
 
877
  .input-column {
 
878
  flex-direction: column !important;
 
 
 
 
 
 
 
879
  }
 
880
  .button-row {
881
- flex-direction: column !important;
 
 
 
882
  }
 
883
  .gradio-button {
884
- width: 100% !important;
 
 
 
 
 
 
 
 
885
  }
886
- .dashboard-card-section {
 
 
 
 
887
  }
888
- .section-title-gradient-bar {
889
- padding: 0.1rem 1rem !important;
 
 
 
890
  }
891
- .dashboard-card-content-area {
892
- padding: 0 1rem 1rem 1rem !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
893
  }
 
 
 
 
 
 
894
  .output-content-wrapper {
895
- min-height: 120px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
896
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
897
  .placeholder {
898
- padding: 1.5rem 1rem !important;
899
- font-size: 1rem !important;
 
 
 
 
 
 
 
 
900
  }
901
- }
902
- """
903
-
904
- with gr.Blocks(css=custom_css, title="Landlord-Tenant Rights Assistant") as demo:
905
- with gr.Group(elem_classes="app-header-wrapper"):
906
- gr.Markdown(
907
- """
908
- <span class='app-header-logo'>⚖️</span>
909
- <h1 class='app-header-title'>Landlord-Tenant Rights Assistant</h1>
910
- <p class='app-header-tagline'>Empowering You with State-Specific Legal Insights</p>
911
- """,
912
- elem_classes="full-width-center"
913
- )
914
 
915
- with gr.Column(elem_classes="main-dashboard-container"):
916
- # How This Assistant Works Box
917
- with gr.Group(elem_classes="dashboard-card-section"):
918
- gr.Markdown("<h3 class='section-title'>How This Assistant Works</h3>", elem_classes="full-width-center section-title-gradient-bar")
919
- with gr.Column(elem_classes="dashboard-card-content-area"):
920
- gr.Markdown(
921
- """
922
- 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.
923
- """
924
- )
925
-
926
- # OpenAI API Key Input Card
927
- with gr.Group(elem_classes="dashboard-card-section"):
928
- gr.Markdown("<h3 class='section-title'>OpenAI API Key</h3>", elem_classes="full-width-center section-title-gradient-bar")
929
- with gr.Column(elem_classes="dashboard-card-content-area"):
930
- api_key_input = gr.Textbox(
931
- label="API Key",
932
- type="password",
933
- placeholder="Enter your OpenAI API key (e.g., sk-...)",
934
- lines=1,
935
- elem_classes=["input-field-group"]
936
- )
937
- gr.Markdown(
938
- "Required to process your query. Get one from OpenAI: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)",
939
- elem_classes="gr-prose"
940
- )
941
-
942
- # Query Input and State Selection Card
943
- with gr.Group(elem_classes="dashboard-card-section"):
944
- gr.Markdown("<h3 class='section-title'>Ask Your Question</h3>", elem_classes="full-width-center section-title-gradient-bar")
945
- with gr.Column(elem_classes="dashboard-card-content-area"):
946
- with gr.Column(elem_classes="input-column"):
947
- with gr.Column(elem_classes="input-field", scale=1):
948
- query_input = gr.Textbox(
949
- label="Your Question",
950
- placeholder="E.g., What are the rules for security deposit returns in my state?",
951
- lines=8,
952
- max_lines=15,
953
- elem_classes=["input-field-group"]
954
- )
955
- with gr.Column(elem_classes="input-field", scale=1):
956
- state_input = gr.Radio(
957
- label="Select State",
958
- choices=radio_choices,
959
- value=initial_value_radio,
960
- elem_classes=["input-field-group", "gradio-radio-custom"],
961
- interactive=True
962
- )
963
- with gr.Row(elem_classes="button-row"):
964
- clear_button = gr.Button("Clear", variant="secondary", elem_classes=["gr-button-secondary"])
965
- submit_button = gr.Button("Submit Query", variant="primary", elem_classes=["gr-button-primary"])
966
-
967
- # Output Display Card
968
- with gr.Group(elem_classes="dashboard-card-section"):
969
- gr.Markdown("<h3 class='section-title'>Legal Assistant's Response</h3>", elem_classes="full-width-center section-title-gradient-bar")
970
- with gr.Column(elem_classes="dashboard-card-content-area"):
971
- output = gr.HTML(
972
- value="<div class='placeholder'>The answer will appear here after submitting your query.</div>",
973
- elem_classes="output-content-wrapper"
974
- )
975
-
976
- # Example Questions Section
977
- with gr.Group(elem_classes="dashboard-card-section examples-section"):
978
- gr.Markdown("<h3 class='section-title'>Example Questions</h3>", elem_classes="full-width-center section-title-gradient-bar")
979
- with gr.Column(elem_classes="dashboard-card-content-area"):
980
- if example_queries:
981
- gr.Examples(
982
- examples=example_queries,
983
- inputs=[query_input, state_input],
984
- examples_per_page=5,
985
- label=""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
986
  )
987
- else:
988
- gr.Markdown("<div class='placeholder'>Sample questions could not be loaded. Please ensure the vector database is populated.</div>")
989
-
990
- # Footer Section
991
- with gr.Group(elem_classes="app-footer-wrapper"):
992
- gr.Markdown(
993
- f"""
994
- <style>
995
- .custom-link {{
996
- font-weight: bold !important;
997
- color: orange !important;
998
- text-decoration: underline;
999
- }}
1000
- </style>
1001
- <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>
1002
- <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>
1003
- """
1004
- )
1005
 
1006
- submit_button.click(
1007
- fn=query_interface_wrapper,
1008
- inputs=[api_key_input, query_input, state_input],
1009
- outputs=output,
1010
- api_name="submit_query"
1011
- )
1012
-
1013
- clear_button.click(
1014
- fn=lambda: (
1015
- "", "", initial_value_radio, "<div class='placeholder'>Inputs cleared. Ready for your next question.</div>"
1016
- ),
1017
- inputs=[],
1018
- outputs=[api_key_input, query_input, state_input, output]
1019
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1020
 
1021
- return demo
1022
 
1023
  # --- Main Execution Block (UNCHANGED from original logic) ---
1024
  if __name__ == "__main__":
 
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
+ # Basic client-side validation for immediate feedback (redundant but good UX)
249
+ if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
250
+ 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>"
251
+ if not state or state is None:
252
+ return "<div class='error-message'><span class='error-icon'></span>Please select a valid state from the list.</div>"
253
+ if not query or not query.strip():
254
+ return "<div class='error-message'><span class='error-icon'></span>Please enter your question in the text box.</div>"
255
+
256
+ # Call the core processing logic
257
+ result = self.process_query(query=query, state=state, openai_api_key=api_key)
258
+ answer = result.get("answer", "<div class='error-message'><span class='error-icon'>⚠️</span>An unexpected error occurred.</div>")
259
+
260
+ # Check if the answer already contains an error message
261
+ if "<div class='error-message'>" in answer:
262
+ return answer
263
+ else:
264
+ formatted_response_content = f"<div class='response-header'><span class='response-icon'>📜</span>Response for {state}</div><hr class='divider'>{answer}"
265
+ return f"<div class='animated-output-content'>{formatted_response_content}</div>"
266
 
267
+ try:
268
+ available_states_list = self.get_states()
269
+ print(f"DEBUG: States loaded for selection: {available_states_list}")
270
+ radio_choices = available_states_list if available_states_list and "Error" not in available_states_list[0] else ["Error: States unavailable"]
271
+ initial_value_radio = None
272
+ except Exception as e:
273
+ print(f"DEBUG: Error loading states for selection: {e}")
274
+ radio_choices = ["Error: Critical failure loading states"]
275
+ initial_value_radio = None
276
+ example_queries_base = [
277
+ ["What are the rules for security deposit returns?", "California"],
278
+ ["Can a landlord enter my apartment without notice?", "New York"],
279
+ ["My landlord hasn't made necessary repairs. What can I do?", "Texas"],
280
+ ["How much notice must a landlord give to raise rent?", "Florida"],
281
+ ["What is an implied warranty of habitability?", "Illinois"]
282
+ ]
283
+ example_queries = []
284
+ if available_states_list and "Error" not in available_states_list[0] and len(available_states_list) > 0:
285
+ loaded_states_set = set(available_states_list)
286
+ example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
287
+ if not example_queries:
288
+ example_queries.append(["What basic rights do tenants have?", available_states_list[0] if available_states_list else "California"])
289
  else:
290
+ example_queries.append(["What basic rights do tenants have?", "California"])
291
+
292
+ custom_css = """
293
+ /* Import legible fonts from Google Fonts */
294
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@600;700;800&display=swap');
295
+
296
+ /* Root variables for consistent theming (light mode) */
297
+ :root {
298
+ --primary-color: #FF8C00;
299
+ --primary-hover: #E07B00;
300
+ --background-primary: hsl(30, 100%, 99.9%);
301
+ --background-secondary: hsl(30, 100%, 96%);
302
+ --text-primary: #4A3C32;
303
+ --text-secondary: #8C7B6F;
304
+ --border-color: hsl(30, 70%, 85%);
305
+ --border-focus: #FF8C00;
306
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
307
+ --shadow-md: 0 4px 10px rgba(0,0,0,0.1);
308
+ --shadow-lg: 0 10px 20px rgba(0,0,0,0.15);
309
+ --error-bg: #FFF0E0;
310
+ --error-border: #FFD2B2;
311
+ --error-text: #E05C00;
312
+ }
313
+
314
+ /* Dark mode variables */
315
+ @media (prefers-color-scheme: dark) {
316
+ body {
317
+ --primary-color: #FFA500;
318
+ --primary-hover: #CC8400;
319
+ --background-primary: #2C2C2C;
320
+ --background-secondary: #1F1F1F;
321
+ --text-primary: #F0F0F0;
322
+ --text-secondary: #B0B0B0;
323
+ --border-color: #555555;
324
+ --border-focus: #FFA500;
325
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
326
+ --shadow-md: 0 4px 10px rgba(0,0,0,0.4);
327
+ --shadow-lg: 0 10px 20px rgba(0,0,0,0.5);
328
+ --error-bg: #400000;
329
+ --error-border: #800000;
330
+ --error-text: #FF6666;
331
+ }
332
+ }
333
+
334
+ body, html {
335
+ background-color: var(--background-secondary) !important;
336
+ color: var(--text-primary) !important;
337
+ transition: background-color 0.3s, color 0.3s;
338
+ }
339
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
340
  .gradio-container {
341
+ max-width: 900px !important;
342
+ margin: 0 auto !important;
343
+ padding: 1.5rem !important;
344
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
345
+ background-color: var(--background-secondary) !important;
346
+ box-shadow: none !important;
347
+ color: var(--text-primary) !important;
348
+ }
349
+
350
+ .main-dashboard-container > * {
351
+ background-color: var(--background-primary) !important;
352
+ }
353
+
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;
358
+ padding: 2.5rem 1.5rem !important;
359
+ margin-bottom: 1.5rem !important;
360
+ box-shadow: var(--shadow-md) !important;
361
+ position: relative;
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;
371
+ left: 0;
372
+ width: 100%;
373
+ height: 100%;
374
+ 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%);
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;
384
+ color: var(--primary-color) !important;
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;
400
+ color: var(--text-primary) !important;
401
+ margin: 0 0 0.75rem 0 !important;
402
+ line-height: 1.1 !important;
403
+ letter-spacing: -0.03em !important;
404
+ position: relative;
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;
414
+ margin: 0 !important;
415
+ max-width: 700px;
416
+ display: inline-block;
417
+ position: relative;
418
+ z-index: 1;
419
+ }
420
+
421
+ .main-dashboard-container {
422
+ display: flex !important;
423
+ flex-direction: column !important;
424
+ gap: 1.25rem !important;
425
+ }
426
+
427
+ .dashboard-card-section {
428
+ background-color: var(--background-primary) !important;
429
+ border: 2px solid var(--border-color) !important;
430
+ border-radius: 12px !important;
431
+ padding: 0 !important;
432
+ box-shadow: var(--shadow-sm) !important;
433
+ transition: all 0.3s ease-out !important;
434
+ cursor: default;
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;
456
+ border-top-right-radius: 10px !important;
457
+ margin-bottom: 0 !important;
458
+ text-align: center !important;
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;
468
+ color: var(--text-primary) !important;
469
+ margin: 0 !important;
470
+ padding-bottom: 0 !important;
471
+ border-bottom: 2px solid var(--border-color) !important;
472
+ line-height: 1.1 !important;
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;
490
+ text-align: left !important;
491
+ background-color: transparent !important;
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
+ .gr-block, .gr-box, .gr-prose, .gr-form, .gr-panel,
503
+ .gr-columns, .gr-column,
504
+ .gradio-html, .gradio-markdown, .gradio-textbox, .gradio-radio, .gradio-button {
505
+ background-color: transparent !important;
506
+ color: var(--text-primary) !important;
507
+ white-space: normal !important;
508
+ overflow-wrap: break-word;
509
+ word-break: break-word;
510
+ }
511
+
512
+ .gradio-textbox textarea,
513
+ .gradio-textbox input,
514
+ .gradio-radio label,
515
+ .placeholder {
516
+ background-color: var(--background-primary) !important;
517
+ color: var(--text-primary) !important;
518
+ }
519
+
520
+ .gradio-textbox {
521
+ margin-bottom: 0.5rem !important;
522
+ }
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;
529
+ font-size: 0.98rem !important;
530
+ font-family: 'Inter', sans-serif !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
+
536
+ .gradio-textbox .scroll-hide {
537
+ background-color: var(--background-primary) !important;
538
+ }
539
+
540
+ .gradio-textbox textarea:focus,
541
+ .gradio-textbox input:focus {
542
+ outline: none !important;
543
+ border-color: var(--border-focus) !important;
544
+ box-shadow: 0 0 0 4px rgba(255, 140, 0, 0.2) !important;
545
+ }
546
+
547
+ .gradio-radio {
548
+ padding: 0 !important;
549
+ margin-top: 1rem !important;
550
+ }
551
+
552
+ .gradio-radio input[type="radio"] {
553
+ display: none !important;
554
+ }
555
+
556
+ .gradio-radio label {
557
+ display: flex !important;
558
+ justify-content: center !important;
559
+ align-items: center !important;
560
+ padding: 0.75rem 1rem !important;
561
+ border: 2px solid var(--border-color) !important;
562
+ border-radius: 8px !important;
563
+ color: var(--text-primary) !important;
564
+ font-weight: 500 !important;
565
+ cursor: pointer !important;
566
+ transition: all 0.2s ease-out !important;
567
+ box-shadow: var(--shadow-sm) !important;
568
+ margin: 0.2rem 0 !important;
569
+ width: 100 !important;
570
+ box-sizing: border-box !important;
571
+ }
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
+
579
+ .gradio-radio label:hover {
580
+ background-color: var(--background-secondary) !important;
581
+ border-color: var(--primary-color) !important;
582
+ box-shadow: var(--shadow-md) !important;
583
+ transform: translateY(-2px) !important;
584
+ }
585
+
586
+ .gradio-radio input[type="radio"]:checked + label {
587
+ background-color: var(--primary-color) !important;
588
+ color: var(--text-primary) !important;
589
+ border-color: var(--primary-hover) !important;
590
+ box-shadow: var(--shadow-md) !important;
591
+ transform: translateY(-1px) !important;
592
+ }
593
+
594
+ .gradio-radio input[type="radio"]:checked + label span.text-lg {
595
+ color: var(--text-primary) !important;
596
+ }
597
+
598
+ .gradio-radio .gr-form {
599
+ padding: 0 !important;
600
+ }
601
+
602
+ .gradio-textbox label,
603
+ .gradio-radio > label {
604
+ font-weight: 600 !important;
605
+ color: var(--text-primary) !important;
606
+ font-size: 1rem !important;
607
+ margin-bottom: 0.6rem !important;
608
+ display: block !important;
609
+ text-align: left !important;
610
  }
611
+
612
+ .gr-prose {
613
+ font-size: 0.9rem !important;
614
+ color: var(--text-secondary) !important;
615
+ margin-top: 0.4rem !important;
616
+ text-align: left !important;
617
+ background-color: transparent !important;
618
+ }
619
+
620
  .input-column {
621
+ display: flex !important;
622
  flex-direction: column !important;
623
+ gap: 1.25rem !important;
624
+ margin-bottom: 0.5rem !important;
625
+ }
626
+
627
+ .input-field {
628
+ flex: none !important;
629
+ width: 100% !important;
630
  }
631
+
632
  .button-row {
633
+ display: flex !important;
634
+ gap: 1rem !important;
635
+ justify-content: flex-end !important;
636
+ margin-top: 1.5rem !important;
637
  }
638
+
639
  .gradio-button {
640
+ padding: 0.85rem 1.8rem !important;
641
+ border-radius: 9px !important;
642
+ font-weight: 600 !important;
643
+ font-size: 1rem !important;
644
+ transition: all 0.2s ease-out !important;
645
+ cursor: pointer !important;
646
+ border: 2px solid transparent !important;
647
+ text-align: center !important;
648
+ color: var(--text-primary) !important;
649
  }
650
+
651
+ .gr-button-primary {
652
+ background-color: var(--primary-color) !important;
653
+ color: white !important;
654
+ box-shadow: var(--shadow-sm) !important;
655
  }
656
+
657
+ .gr-button-primary:hover {
658
+ background-color: var(--primary-hover) !important;
659
+ box-shadow: var(--shadow-md) !important;
660
+ transform: translateY(-2px) !important;
661
  }
662
+
663
+ .gr-button-primary:active {
664
+ transform: translateY(1px) !important;
665
+ box-shadow: none !important;
666
+ }
667
+
668
+ .gr-button-secondary {
669
+ background-color: transparent !important;
670
+ color: var(--text-primary) !important;
671
+ border-color: var(--border-color) !important;
672
+ }
673
+
674
+ .gr-button-secondary:hover {
675
+ background-color: var(--background-secondary) !important;
676
+ border-color: var(--primary-color) !important;
677
+ transform: translateY(-2px) !important;
678
  }
679
+
680
+ .gr-button-secondary:active {
681
+ transform: translateY(1px) !important;
682
+ box-shadow: none !important;
683
+ }
684
+
685
  .output-content-wrapper {
686
+ background-color: var(--background-primary) !important;
687
+ border: 2px solid var(--border-color) !important;
688
+ border-radius: 8px !important;
689
+ padding: 1.5rem !important;
690
+ min-height: 150px !important;
691
+ color: var(--text-primary) !important;
692
+ display: flex;
693
+ flex-direction: column;
694
+ justify-content: center;
695
+ align-items: center;
696
+ }
697
+
698
+ .animated-output-content {
699
+ opacity: 0;
700
+ animation: fadeInAndSlideUp 0.7s ease-out forwards;
701
+ width: 100%;
702
+ white-space: pre-wrap;
703
+ overflow-wrap: break-word;
704
+ word-break: break-word;
705
+ text-align: left !important;
706
+ color: var(--text-primary) !important;
707
+ }
708
+
709
+ @keyframes fadeInAndSlideUp {
710
+ from { opacity: 0; transform: translateY(15px); }
711
+ to { opacity: 1; transform: translateY(0); }
712
+ }
713
+
714
+ .response-header {
715
+ font-size: 1.3rem !important;
716
+ font-weight: 700 !important;
717
+ color: var(--primary-color) !important;
718
+ margin-bottom: 0.75rem !important;
719
+ display: flex !important;
720
+ align-items: center !important;
721
+ gap: 0.6rem !important;
722
+ text-align: left !important;
723
+ width: 100%;
724
+ justify-content: flex-start;
725
+ }
726
+
727
+ .response-icon {
728
+ font-size: 1.5rem !important;
729
+ color: var(--primary-color) !important;
730
  }
731
+
732
+ .divider {
733
+ border: none !important;
734
+ border-top: 1px dashed var(--border-color) !important;
735
+ margin: 1rem 0 !important;
736
+ }
737
+
738
+ .error-message {
739
+ background-color: var(--error-bg) !important;
740
+ border: 2px solid var(--error-border) !important;
741
+ color: var(--error-text) !important;
742
+ padding: 1.25rem !important;
743
+ border-radius: 8px !important;
744
+ display: flex !important;
745
+ align-items: flex-start !important;
746
+ gap: 0.8rem !important;
747
+ font-size: 0.95rem !important;
748
+ font-weight: 500 !important;
749
+ line-height: 1.6 !important;
750
+ text-align: left !important;
751
+ width: 100%;
752
+ box-sizing: border-box;
753
+ }
754
+
755
+ .error-message a {
756
+ color: var(--error-text) !important;
757
+ text-decoration: underline !important;
758
+ }
759
+
760
+ .error-icon {
761
+ font-size: 1.4rem !important;
762
+ line-height: 1 !important;
763
+ margin-top: 0.1rem !important;
764
+ }
765
+
766
+ .error-details {
767
+ font-size: 0.85rem !important;
768
+ color: var(--error-text) !important;
769
+ margin-top: 0.5rem !important;
770
+ opacity: 0.8;
771
+ }
772
+
773
  .placeholder {
774
+ background-color: var(--background-primary) !important;
775
+ border: 2px dashed var(--border-color) !important;
776
+ border-radius: 8px !important;
777
+ padding: 2.5rem 1.5rem !important;
778
+ text-align: center !important;
779
+ color: var(--text-secondary) !important;
780
+ font-style: italic !important;
781
+ font-size: 1.1rem !important;
782
+ width: 100%;
783
+ box-sizing: border-box;
784
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
785
 
786
+ .examples-section .gr-samples-table {
787
+ border: 2px solid var(--border-color) !important;
788
+ border-radius: 8px !important;
789
+ overflow: hidden !important;
790
+ margin-top: 1rem !important;
791
+ }
792
+
793
+ .examples-section .gr-samples-table th,
794
+ .examples-section .gr-samples-table td {
795
+ padding: 0.9rem !important;
796
+ border: none !important;
797
+ font-size: 0.95rem !important;
798
+ text-align: left !important;
799
+ color: var(--text-primary) !important;
800
+ }
801
+
802
+ .examples-section .gr-samples-table th {
803
+ background-color: var(--background-secondary) !important;
804
+ font-weight: 700 !important;
805
+ color: var(--text-primary) !important;
806
+ }
807
+
808
+ .examples-section .gr-samples-table td {
809
+ background-color: var(--background-primary) !important;
810
+ color: var(--text-primary) !important;
811
+ border-top: 1px solid var(--border-color) !important;
812
+ cursor: pointer !important;
813
+ transition: background 0.2s ease, transform 0.1s ease !important;
814
+ }
815
+
816
+ .examples-section .gr-samples-table tr:hover td {
817
+ background-color: var(--background-secondary) !important;
818
+ transform: translateX(5px);
819
+ }
820
+
821
+ .gr-examples .gr-label,
822
+ .gr-examples .label-wrap,
823
+ .gr-examples .gr-accordion-header {
824
+ display: none !important;
825
+ }
826
+
827
+ .app-footer-wrapper {
828
+ background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
829
+ border: 2px solid var(--border-color) !important;
830
+ border-radius: 12px !important;
831
+ padding: 1.75rem !important;
832
+ margin-top: 1.5rem !important;
833
+ margin-bottom: 1.5rem !important;
834
+ text-align: center !important;
835
+ color: var(--text-primary) !important;
836
+ }
837
+
838
+ .app-footer p {
839
+ margin: 0 !important;
840
+ max-width: 90% !important;
841
+ font-size: 0.95rem !important;
842
+ color: var(--text-secondary) !important;
843
+ line-height: 1.6 !important;
844
+ background-color: transparent !important;
845
+ text-align: center !important;
846
+ white-space: normal !important;
847
+ }
848
+
849
+ .app-footer strong, .app-footer b {
850
+ font-weight: 700 !important;
851
+ color: var(--primary-color) !important;
852
+ }
853
+
854
+ .app-footer a {
855
+ color: var(--primary-color) !important;
856
+ text-decoration: underline !important;
857
+ font-weight: 600 !important;
858
+ }
859
+
860
+ .app-footer a:hover {
861
+ text-decoration: none !important;
862
+ }
863
+
864
+ @media (max-width: 768px) {
865
+ .gradio-container {
866
+ padding: 1rem !important;
867
+ }
868
+ .app-header-title {
869
+ font-size: 2.2rem !important;
870
+ }
871
+ .app-header-tagline {
872
+ font-size: 1rem !important;
873
+ }
874
+ .section-title {
875
+ font-size: 1.4rem !important;
876
+ }
877
+ .input-column {
878
+ flex-direction: column !important;
879
+ }
880
+ .button-row {
881
+ flex-direction: column !important;
882
+ }
883
+ .gradio-button {
884
+ width: 100% !important;
885
+ }
886
+ .dashboard-card-section {
887
+ }
888
+ .section-title-gradient-bar {
889
+ padding: 0.1rem 1rem !important;
890
+ }
891
+ .dashboard-card-content-area {
892
+ padding: 0 1rem 1rem 1rem !important;
893
+ }
894
+ .output-content-wrapper {
895
+ min-height: 120px !important;
896
+ }
897
+ .placeholder {
898
+ padding: 1.5rem 1rem !important;
899
+ font-size: 1rem !important;
900
+ }
901
+ }
902
+ """
903
+
904
+ with gr.Blocks(css=custom_css, title="Landlord-Tenant Rights Assistant") as demo:
905
+ with gr.Group(elem_classes="app-header-wrapper"):
906
+ gr.Markdown(
907
+ """
908
+ <span class='app-header-logo'>⚖️</span>
909
+ <h1 class='app-header-title'>Landlord-Tenant Rights Assistant</h1>
910
+ <p class='app-header-tagline'>Empowering You with State-Specific Legal Insights</p>
911
+ """,
912
+ elem_classes="full-width-center"
913
+ )
914
+
915
+ with gr.Column(elem_classes="main-dashboard-container"):
916
+ # How This Assistant Works Box
917
+ with gr.Group(elem_classes="dashboard-card-section"):
918
+ gr.Markdown("<h3 class='section-title'>How This Assistant Works</h3>", elem_classes="full-width-center section-title-gradient-bar")
919
+ with gr.Column(elem_classes="dashboard-card-content-area"):
920
+ gr.Markdown(
921
+ """
922
+ 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.
923
+ """
924
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
925
 
926
+ # OpenAI API Key Input Card
927
+ with gr.Group(elem_classes="dashboard-card-section"):
928
+ gr.Markdown("<h3 class='section-title'>OpenAI API Key</h3>", elem_classes="full-width-center section-title-gradient-bar")
929
+ with gr.Column(elem_classes="dashboard-card-content-area"):
930
+ api_key_input = gr.Textbox(
931
+ label="API Key",
932
+ type="password",
933
+ placeholder="Enter your OpenAI API key (e.g., sk-...)",
934
+ lines=1,
935
+ elem_classes=["input-field-group"]
936
+ )
937
+ gr.Markdown(
938
+ "Required to process your query. Get one from OpenAI: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)",
939
+ elem_classes="gr-prose"
940
+ )
941
+
942
+ # Query Input and State Selection Card
943
+ with gr.Group(elem_classes="dashboard-card-section"):
944
+ gr.Markdown("<h3 class='section-title'>Ask Your Question</h3>", elem_classes="full-width-center section-title-gradient-bar")
945
+ with gr.Column(elem_classes="dashboard-card-content-area"):
946
+ with gr.Column(elem_classes="input-column"):
947
+ with gr.Column(elem_classes="input-field", scale=1):
948
+ query_input = gr.Textbox(
949
+ label="Your Question",
950
+ placeholder="E.g., What are the rules for security deposit returns in my state?",
951
+ lines=8,
952
+ max_lines=15,
953
+ elem_classes=["input-field-group"]
954
+ )
955
+ with gr.Column(elem_classes="input-field", scale=1):
956
+ state_input = gr.Radio(
957
+ label="Select State",
958
+ choices=radio_choices,
959
+ value=initial_value_radio,
960
+ elem_classes=["input-field-group", "gradio-radio-custom"],
961
+ interactive=True
962
+ )
963
+ with gr.Row(elem_classes="button-row"):
964
+ clear_button = gr.Button("Clear", variant="secondary", elem_classes=["gr-button-secondary"])
965
+ submit_button = gr.Button("Submit Query", variant="primary", elem_classes=["gr-button-primary"])
966
+
967
+ # Output Display Card
968
+ with gr.Group(elem_classes="dashboard-card-section"):
969
+ gr.Markdown("<h3 class='section-title'>Legal Assistant's Response</h3>", elem_classes="full-width-center section-title-gradient-bar")
970
+ with gr.Column(elem_classes="dashboard-card-content-area"):
971
+ output = gr.HTML(
972
+ value="<div class='placeholder'>The answer will appear here after submitting your query.</div>",
973
+ elem_classes="output-content-wrapper"
974
+ )
975
+
976
+ # Example Questions Section
977
+ with gr.Group(elem_classes="dashboard-card-section examples-section"):
978
+ gr.Markdown("<h3 class='section-title'>Example Questions</h3>", elem_classes="full-width-center section-title-gradient-bar")
979
+ with gr.Column(elem_classes="dashboard-card-content-area"):
980
+ if example_queries:
981
+ gr.Examples(
982
+ examples=example_queries,
983
+ inputs=[query_input, state_input],
984
+ examples_per_page=5,
985
+ label=""
986
+ )
987
+ else:
988
+ gr.Markdown("<div class='placeholder'>Sample questions could not be loaded. Please ensure the vector database is populated.</div>")
989
+
990
+ # Footer Section
991
+ with gr.Group(elem_classes="app-footer-wrapper"):
992
+ gr.Markdown(
993
+ f"""
994
+ <style>
995
+ .custom-link {{
996
+ font-weight: bold !important;
997
+ color: orange !important;
998
+ text-decoration: underline;
999
+ }}
1000
+ </style>
1001
+ <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>
1002
+ <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>
1003
+ """
1004
+ )
1005
+
1006
+ submit_button.click(
1007
+ fn=query_interface_wrapper,
1008
+ inputs=[api_key_input, query_input, state_input],
1009
+ outputs=output,
1010
+ api_name="submit_query"
1011
+ )
1012
+
1013
+ clear_button.click(
1014
+ fn=lambda: (
1015
+ "", "", initial_value_radio, "<div class='placeholder'>Inputs cleared. Ready for your next question.</div>"
1016
+ ),
1017
+ inputs=[],
1018
+ outputs=[api_key_input, query_input, state_input, output]
1019
+ )
1020
 
1021
+ return demo
1022
 
1023
  # --- Main Execution Block (UNCHANGED from original logic) ---
1024
  if __name__ == "__main__":