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