Nischal Subedi commited on
Commit
ba5ba29
·
1 Parent(s): 331fa8e
Files changed (1) hide show
  1. app.py +249 -307
app.py CHANGED
@@ -247,160 +247,122 @@ Answer:"""
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: # Removed "Select a state..." from error check as it's not a choice
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 (from deeper within process_query)
261
  if "<div class='error-message'>" in answer:
262
- # Error messages are returned directly as they contain their own styling
263
  return answer
264
  else:
265
- # Wrap successful response in a div with an animation class
266
  formatted_response_content = f"<div class='response-header'><span class='response-icon'>📜</span>Response for {state}</div><hr class='divider'>{answer}"
267
  return f"<div class='animated-output-content'>{formatted_response_content}</div>"
268
 
269
  try:
270
  available_states_list = self.get_states()
271
- # DEBUG: Print states to console to verify if data is loaded
272
  print(f"DEBUG: States loaded for selection: {available_states_list}")
273
- # For Radio, remove "Select a state..." from choices, initial value will be None
274
  radio_choices = available_states_list if available_states_list and "Error" not in available_states_list[0] else ["Error: States unavailable"]
275
- initial_value_radio = None # Set initial value to None, forcing user to pick
276
- except Exception as e: # Catch-all for safety
277
  print(f"DEBUG: Error loading states for selection: {e}")
278
  radio_choices = ["Error: Critical failure loading states"]
279
  initial_value_radio = None
280
-
281
-
282
- # Define example queries, filtering based on available states
283
  example_queries_base = [
284
  ["What are the rules for security deposit returns?", "California"],
285
  ["Can a landlord enter my apartment without notice?", "New York"],
286
  ["My landlord hasn't made necessary repairs. What can I do?", "Texas"],
287
  ["How much notice must a landlord give to raise rent?", "Florida"],
288
- ["What is an and implied warranty of habitability?", "Illinois"]
289
  ]
290
  example_queries = []
291
  if available_states_list and "Error" not in available_states_list[0] and len(available_states_list) > 0:
292
  loaded_states_set = set(available_states_list)
293
- # Filter for examples whose state is in the loaded states
294
  example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
295
- # Add a generic example if no specific state examples match or if list is empty
296
  if not example_queries:
297
- # Add one example using the first available state, or a common one if no states
298
  example_queries.append(["What basic rights do tenants have?", available_states_list[0] if available_states_list else "California"])
299
- else: # Fallback if states list is problematic (e.g., empty or error)
300
  example_queries.append(["What basic rights do tenants have?", "California"])
301
 
302
-
303
- # Custom CSS for better UI design, clear boundaries, and text alignment for HuggingFace
304
  custom_css = """
305
  /* Import legible fonts from Google Fonts */
306
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@600;700;800&display=swap');
307
 
308
- /* Root variables for consistent theming - adjusted for a very warm, clean palette */
309
  :root {
310
- --primary-color: #FF8C00; /* Darker Orange for buttons/accents */
311
- --primary-hover: #E07B00; /* Slightly darker orange for hover */
312
- --background-primary: hsl(30, 100%, 99.9%); /* Almost pure white, with a very subtle warm tint */
313
- --background-secondary: hsl(30, 100%, 96%); /* Clear, light, warm peach/cream */
314
- --text-primary: #4A3C32; /* Dark warm brown/charcoal for main text */
315
- --text-secondary: #8C7B6F; /* Muted warm gray/brown for secondary text */
316
- --border-color: hsl(30, 70%, 85%); /* Light, warm orange-brown for borders */
317
- --border-focus: #FF8C00; /* Focus color matches primary */
318
  --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
319
  --shadow-md: 0 4px 10px rgba(0,0,0,0.1);
320
  --shadow-lg: 0 10px 20px rgba(0,0,0,0.15);
321
- --error-bg: #FFF0E0; /* Light orange-pink for errors */
322
- --error-border: #FFD2B2; /* Medium orange-pink for error borders */
323
- --error-text: #E05C00; /* Darker, strong orange-red for error text */
324
  }
325
 
326
- /* Dark mode variables - for consistency if a dark mode toggle were present */
327
- body.dark {
328
- --primary-color: #FFA500; /* Bright orange for dark mode */
329
- --primary-hover: #CC8400;
330
- --background-primary: #2C2C2C; /* Dark charcoal */
331
- --background-secondary: #1F1F1F; /* Even darker charcoal */
332
- --text-primary: #F0F0F0;
333
- --text-secondary: #B0B0B0;
334
- --border-color: #555555;
335
- --border-focus: #FFA500;
336
- --error-bg: #400000;
337
- --error-border: #800000;
338
- --error-text: #FF6666;
339
- }
340
-
341
- /* Universal reset for backgrounds to ensure no default grey */
342
- *, *::before, *::after {
343
- /* This is a soft reset. Specific overrides below will take precedence. */
344
- /* background-color: initial; /* Resets to default, which for many is transparent */
345
- /* box-sizing: border-box; */
346
- }
347
-
348
- /* Ensure the very outer body background is also set, overriding any Gradio defaults */
349
  body, html {
350
  background-color: var(--background-secondary) !important;
351
  }
352
 
353
- /* Base container improvements */
354
  .gradio-container {
355
- max-width: 900px !important; /* Slightly smaller for focused content */
356
- margin: 0 auto !important; /* Center the whole app */
357
  padding: 1.5rem !important;
358
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
359
- background-color: var(--background-secondary) !important; /* Overall background of the container */
360
- box-shadow: none !important; /* Remove default gradio container shadow */
361
  }
362
- /* All main content sections (cards) will have the primary background, not a gradient */
363
  .main-dashboard-container > * {
364
  background-color: var(--background-primary) !important;
365
  }
366
 
367
- /* Header styling - centered and prominent */
368
  .app-header-wrapper {
369
- background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important; /* Gradient background */
370
  border: 2px solid var(--border-color) !important;
371
  border-radius: 16px !important;
372
- padding: 2.5rem 1.5rem !important; /* More vertical padding */
373
  margin-bottom: 1.5rem !important;
374
  box-shadow: var(--shadow-md) !important;
375
- position: relative; /* For potential pseudo-element effects */
376
- overflow: hidden; /* For any overflow animations */
377
- text-align: center !important; /* Centers block content within the wrapper */
378
  }
379
 
380
- .app-header-wrapper::before { /* Subtle background pattern for dynamism */
381
  content: '';
382
  position: absolute;
383
  top: 0;
384
  left: 0;
385
  width: 100%;
386
  height: 100%;
387
- background: radial-gradient(circle at top left, rgba(255,140,0,0.3) 0%, transparent 60%), /* More vibrant orange tint */
388
- radial-gradient(circle at bottom right, rgba(255,140,0,0.3) 0%, transparent 60%);
389
  z-index: 0;
390
  opacity: 0.8;
391
  pointer-events: none;
392
  }
393
 
394
  .app-header-logo {
395
- font-size: 8.5rem !important; /* Significantly larger icon */
396
  margin-bottom: 0.75rem !important;
397
- display: block !important;
398
- color: var(--primary-color) !important; /* Theme color */
399
  position: relative;
400
- z-index: 1; /* Bring icon to front of pseudo-element */
401
  animation: float-icon 3s ease-in-out infinite alternate;
402
  }
403
- /* Keyframes for floating icon */
404
  @keyframes float-icon {
405
  0% { transform: translateY(0px); }
406
  50% { transform: translateY(-5px); }
@@ -409,319 +371,311 @@ Answer:"""
409
 
410
  .app-header-title {
411
  font-family: 'Poppins', sans-serif !important;
412
- font-size: 3rem !important; /* Even larger title */
413
- font-weight: 800 !important; /* Bolder */
414
  color: var(--text-primary) !important;
415
  margin: 0 0 0.75rem 0 !important;
416
  line-height: 1.1 !important;
417
- letter-spacing: -0.03em !important; /* Tighter spacing */
418
  position: relative;
419
  z-index: 1;
420
- display: inline-block; /* Essential for text-align: center on parent to work for this block */
421
- max-width: 100%; /* Prevent overflow on smaller screens */
422
  }
 
423
  .app-header-tagline {
424
- font-size: 1.25rem !important; /* Slightly larger tagline */
425
  color: var(--text-secondary) !important;
426
  font-weight: 400 !important;
427
  margin: 0 !important;
428
- max-width: 700px; /* Constrain tagline width */
429
- display: inline-block; /* Essential for text-align: center on parent to work for this block */
430
  position: relative;
431
  z-index: 1;
432
  }
433
 
434
- /* Main container with consistent spacing */
435
  .main-dashboard-container {
436
  display: flex !important;
437
  flex-direction: column !important;
438
- gap: 1.25rem !important; /* Consistent spacing between cards */
439
  }
440
- /* Card sections with clear boundaries and subtle dynamic effects */
441
  .dashboard-card-section {
442
- background-color: var(--background-primary) !important; /* Solid primary background for card body */
443
- border: 2px solid var(--border-color) !important; /* Distinct border */
444
  border-radius: 12px !important;
445
- padding: 0 !important; /* Removed padding to allow gradient bar and content area to control it */
446
- box-shadow: var(--shadow-sm) !important; /* Subtle shadow */
447
- transition: all 0.3s ease-out !important; /* Smoother transition */
448
- cursor: default; /* Indicate not directly clickable (unless examples) */
449
  }
 
450
  .dashboard-card-section:hover {
451
  box-shadow: var(--shadow-md) !important;
452
- transform: translateY(-3px) !important; /* More pronounced lift */
453
  }
454
 
455
- /* Class for Markdown blocks to center their content */
456
  .full-width-center {
457
  display: flex !important;
458
  justify-content: center !important;
459
  align-items: center !important;
460
  width: 100% !important;
461
- flex-direction: column !important; /* Ensure content stacks vertically if needed */
462
- background-color: transparent !important; /* Ensure this wrapper is transparent */
463
  }
464
 
465
- /* NEW: Class for the solid color title bar within each card */
466
- .section-title-gradient-bar {
467
- background-color: var(--background-secondary) !important; /* Solid warm peach/cream */
468
- padding: 1.25rem 1.75rem !important; /* Inner padding for the title bar */
469
- border-top-left-radius: 10px !important; /* Match parent's border radius */
470
  border-top-right-radius: 10px !important;
471
- margin-bottom: 0.3rem !important; /* Adjusted from 1rem to reduce space below title bar */
472
- text-align: center !important; /* Ensure content inside this bar is centered */
473
- box-sizing: border-box; /* Include padding in width */
474
- width: 100%; /* Ensure it spans full width */
475
  }
476
 
477
- /* Section titles (h3 inside markdown) */
478
  .section-title {
479
  font-family: 'Poppins', sans-serif !important;
480
- font-size: 1.7rem !important; /* Slightly larger */
481
- font-weight: 700 !important; /* Bolder */
482
  color: var(--text-primary) !important;
483
- margin: 0 !important; /* No margin on h3 itself as parent handles spacing */
484
- padding-bottom: 0.1rem !important; /* REDUCED further to bring text closer to border */
485
- border-bottom: 2px solid var(--border-color) !important; /* Underline effect */
486
- line-height: 1.1 !important; /* Ensure line height does not add extra space */
487
- display: inline-block !important; /* Allow centering within text-align: center of parent */
488
- text-align: center !important; /* Fallback centering */
489
  letter-spacing: -0.01em !important;
490
  }
491
-
492
- /* General content area padding within dashboard sections (below the title bar) */
493
  .dashboard-card-content-area {
494
- padding: 0 1.75rem 1.75rem 1.75rem !important; /* Match overall padding, 0 top because title bar handles it */
495
- background-color: var(--background-primary) !important; /* Pure white background */
496
- box-sizing: border-box; /* Include padding in width */
497
- width: 100%; /* Ensure it spans full width */
498
  }
 
499
  .dashboard-card-section p {
500
  line-height: 1.7 !important;
501
  color: var(--text-primary) !important;
502
  font-size: 1rem !important;
503
- text-align: left !important; /* Ensure content text is left-aligned */
504
- background-color: transparent !important; /* Ensure p tags don't have unexpected backgrounds */
505
- margin: 0 !important; /* Remove default paragraph margins */
506
- padding: 0 !important; /* Remove default paragraph padding */
507
- white-space: normal !important; /* Ensure text wraps */
508
  }
 
509
  .dashboard-card-section strong, .dashboard-card-section b {
510
- font-weight: 700 !important; /* Ensure bold is actually bold */
511
- color: var(--primary-color) !important; /* Highlight strong text with primary color */
512
  }
513
 
514
- /* Aggressive Overrides for all Gradio internal containers and text blocks */
515
- /* These ensure transparency or explicit primary background for common Gradio elements */
516
  .gr-block, .gr-box, .gr-prose, .gr-form, .gr-panel,
517
- .gr-columns, .gr-column, /* Ensure columns themselves are transparent */
518
  .gradio-html, .gradio-markdown, .gradio-textbox, .gradio-radio, .gradio-button {
519
- background-color: transparent !important; /* Force transparent or specific primary background */
520
- color: var(--text-primary) !important; /* Ensure text color is consistent */
521
- white-space: normal !important; /* Allow text to wrap */
522
  overflow-wrap: break-word;
523
  word-break: break-word;
524
  }
525
- /* Specific elements that MUST have background-primary to look white/clean */
526
  .gradio-textbox textarea,
527
  .gradio-textbox input,
528
  .gradio-radio label,
529
  .placeholder {
530
- background-color: var(--background-primary) !important; /* These are input/output content areas */
531
  }
532
 
533
-
534
- /* Improved input styling with clear boundaries and focus */
535
  .gradio-textbox {
536
  margin-bottom: 0.5rem !important;
537
  }
538
- /* Target the actual input elements for background color */
539
  .gradio-textbox textarea,
540
  .gradio-textbox input {
541
- border: 2px solid var(--border-color) !important; /* Clear border */
542
  border-radius: 8px !important;
543
- padding: 0.85rem 1rem !important; /* Slightly more padding */
544
  font-size: 0.98rem !important;
545
  font-family: 'Inter', sans-serif !important;
546
- color: var(--text-primary) !important; /* Text color inside inputs */
547
- transition: border-color 0.2s ease, box-shadow 0.2s ease !important; /* Smooth transitions */
548
  box-shadow: var(--shadow-sm) !important;
549
  }
550
- /* Target the internal scrollable div within gradio-textbox */
551
  .gradio-textbox .scroll-hide {
552
- background-color: var(--background-primary) !important; /* Ensure scrollable area is white */
553
  }
554
- /* Focus styles for textboxes */
555
  .gradio-textbox textarea:focus,
556
  .gradio-textbox input:focus {
557
  outline: none !important;
558
- border-color: var(--border-focus) !important; /* Distinct border on focus */
559
- box-shadow: 0 0 0 4px rgba(255, 140, 0, 0.2) !important; /* Broader, softer glow on focus */
560
  }
561
 
562
- /* Styling for the radio button group (state selection) */
563
  .gradio-radio {
564
- padding: 0 !important; /* Remove any default padding */
565
- margin-top: 1rem !important; /* Add a little space above */
566
  }
567
- /* Hide default radio circle/dot */
568
  .gradio-radio input[type="radio"] {
569
  display: none !important;
570
  }
571
 
572
  .gradio-radio label {
573
- /* Style the clickable area for each radio option */
574
- display: flex !important; /* Use flexbox for internal alignment */
575
- justify-content: center !important; /* Center content horizontally */
576
  align-items: center !important;
577
  padding: 0.75rem 1rem !important;
578
  border: 2px solid var(--border-color) !important;
579
  border-radius: 8px !important;
580
- /* background-color: var(--background-primary) !important; Set in general rule for input types */
581
  color: var(--text-primary) !important;
582
  font-weight: 500 !important;
583
  cursor: pointer !important;
584
  transition: all 0.2s ease-out !important;
585
  box-shadow: var(--shadow-sm) !important;
586
- margin: 0.2rem 0 !important; /* Increased vertical margin between options */
587
- width: 100% !important; /* Ensure options take full width of their column */
588
- box-sizing: border-box !important; /* Include padding/border in width */
589
  }
590
 
591
- /* Style the text/content within the radio label */
592
- .gradio-radio label span.text-lg { /* Gradio uses text-lg for the label text by default */
593
- font-weight: 600 !important; /* Make text bold */
594
  color: var(--text-primary) !important;
595
- font-size: 0.98rem !important; /* Match input text size */
596
  }
597
 
598
- /* Hover effect for radio options */
599
  .gradio-radio label:hover {
600
- background-color: var(--background-secondary) !important; /* Slightly darker cream on hover */
601
- border-color: var(--primary-color) !important; /* Highlight border with primary color */
602
  box-shadow: var(--shadow-md) !important;
603
  transform: translateY(-2px) !important;
604
  }
605
 
606
- /* Selected state for radio options */
607
- .gradio-radio input[type="radio"]:checked + label { /* Target label when its radio input is checked */
608
- background-color: var(--primary-color) !important; /* Primary color for selected item */
609
- color: white !important; /* White text on selected */
610
  border-color: var(--primary-hover) !important;
611
  box-shadow: var(--shadow-md) !important;
612
  transform: translateY(-1px) !important;
613
  }
 
614
  .gradio-radio input[type="radio"]:checked + label span.text-lg {
615
- color: white !important; /* Ensure text is white when selected */
616
  }
617
- /* Gradio's internal wrapper for radio buttons, ensure it doesn't add unwanted padding */
618
  .gradio-radio .gr-form {
619
  padding: 0 !important;
620
  }
621
 
622
-
623
- /* Label styling for better readability (for Query, State labels) */
624
  .gradio-textbox label,
625
- .gradio-radio > label { /* Target the main label for the radio group */
626
- font-weight: 600 !important; /* Bolder labels */
627
  color: var(--text-primary) !important;
628
  font-size: 1rem !important;
629
  margin-bottom: 0.6rem !important;
630
  display: block !important;
631
- text-align: left !important; /* Ensure these labels are left-aligned */
632
  }
633
- /* Info text styling below inputs (e.g., for API Key) */
634
- /* Specifically target gr-prose if it is the direct child of a gr.Block or gr.Group */
635
- .gr-prose {
636
  font-size: 0.9rem !important;
637
  color: var(--text-secondary) !important;
638
- margin-top: 0.4rem !important; /* More space for info text */
639
- text-align: left !important; /* Ensure info text is left aligned */
640
- background-color: transparent !important; /* Ensure no unwanted background */
641
  }
642
- /* Input column layout improvements */
643
- .input-column { /* Renamed from .input-row */
644
  display: flex !important;
645
- flex-direction: column !important; /* Stack items vertically */
646
- gap: 1.25rem !important; /* Consistent gap between query and state input */
647
  margin-bottom: 0.5rem !important;
648
  }
 
649
  .input-field {
650
- flex: none !important; /* Remove flex sizing as items are stacked */
651
- width: 100% !important; /* Ensure each field takes full width */
652
  }
653
 
654
- /* Button styling improvements with active state for dynamism */
655
  .button-row {
656
  display: flex !important;
657
  gap: 1rem !important;
658
- justify-content: flex-end !important; /* Align buttons to the right */
659
- margin-top: 1.5rem !important; /* More space above buttons */
660
  }
 
661
  .gradio-button {
662
- padding: 0.85rem 1.8rem !important; /* More padding for bigger buttons */
663
- border-radius: 9px !important; /* Slightly more rounded */
664
- font-weight: 600 !important; /* Bolder text */
665
  font-size: 1rem !important;
666
- transition: all 0.2s ease-out !important; /* Smooth transition for hover/active */
667
  cursor: pointer !important;
668
  border: 2px solid transparent !important;
669
- text-align: center !important; /* Ensure button text is centered */
670
  }
 
671
  .gr-button-primary {
672
- background-color: var(--primary-color) !important; /* Explicitly set background */
673
  color: white !important;
674
  box-shadow: var(--shadow-sm) !important;
675
  }
 
676
  .gr-button-primary:hover {
677
- background-color: var(--primary-hover) !important; /* Explicitly set background */
678
  box-shadow: var(--shadow-md) !important;
679
- transform: translateY(-2px) !important; /* Subtle lift effect on hover */
680
  }
681
- .gr-button-primary:active { /* Press down effect on click */
 
682
  transform: translateY(1px) !important;
683
  box-shadow: none !important;
684
  }
 
685
  .gr-button-secondary {
686
- background-color: transparent !important; /* Explicitly set background */
687
  color: var(--text-primary) !important;
688
  border-color: var(--border-color) !important;
689
  }
 
690
  .gr-button-secondary:hover {
691
- background-color: var(--background-secondary) !important; /* Explicitly set background */
692
  border-color: var(--primary-color) !important;
693
  transform: translateY(-2px) !important;
694
  }
695
- .gr-button-secondary:active { /* Press down effect on click */
 
696
  transform: translateY(1px) !important;
697
  box-shadow: none !important;
698
  }
699
 
700
- /* Output styling with clear boundaries and dynamic fade-in */
701
  .output-content-wrapper {
702
- background-color: var(--background-primary) !important; /* Explicitly set background */
703
- border: 2px solid var(--border-color) !important; /* Clear border */
704
  border-radius: 8px !important;
705
  padding: 1.5rem !important;
706
- min-height: 150px !important; /* More space for output */
707
  color: var(--text-primary) !important;
708
- /* Ensure the inner animated content fits well */
709
  display: flex;
710
  flex-direction: column;
711
- justify-content: center; /* Center content vertically if small */
712
- align-items: center; /* Center content horizontally if small */
713
  }
714
- /* The div holding the actual response content, enabling fade-in animation */
715
  .animated-output-content {
716
  opacity: 0;
717
- animation: fadeInAndSlideUp 0.7s ease-out forwards; /* More pronounced animation */
718
- width: 100%; /* Take full width of parent */
719
- /* Preserve formatting within the animated content */
720
  white-space: pre-wrap;
721
  overflow-wrap: break-word;
722
  word-break: break-word;
723
- text-align: left !important; /* Ensure text is left-aligned within this div */
724
  }
 
725
  @keyframes fadeInAndSlideUp {
726
  from { opacity: 0; transform: translateY(15px); }
727
  to { opacity: 1; transform: translateY(0); }
@@ -730,27 +684,29 @@ Answer:"""
730
  .response-header {
731
  font-size: 1.3rem !important;
732
  font-weight: 700 !important;
733
- color: var(--primary-color) !important; /* Matches primary color */
734
  margin-bottom: 0.75rem !important;
735
  display: flex !important;
736
  align-items: center !important;
737
  gap: 0.6rem !important;
738
- text-align: left !important; /* Ensure header itself is not affected by parent centering */
739
- width: 100%; /* Take full width */
740
- justify-content: flex-start; /* Align content to the start */
741
  }
 
742
  .response-icon {
743
  font-size: 1.5rem !important;
744
  color: var(--primary-color) !important;
745
  }
 
746
  .divider {
747
  border: none !important;
748
- border-top: 1px dashed var(--border-color) !important; /* Dashed divider for visual separation */
749
  margin: 1rem 0 !important;
750
  }
751
- /* Error message styling */
752
  .error-message {
753
- background-color: var(--error-bg) !important; /* Explicitly set background */
754
  border: 2px solid var(--error-border) !important;
755
  color: var(--error-text) !important;
756
  padding: 1.25rem !important;
@@ -761,110 +717,118 @@ Answer:"""
761
  font-size: 0.95rem !important;
762
  font-weight: 500 !important;
763
  line-height: 1.6 !important;
764
- text-align: left !important; /* Ensure error message text is left aligned */
765
- width: 100%; /* Take full width of parent */
766
- box-sizing: border-box; /* Include padding/border in width */
767
  }
 
768
  .error-message a {
769
  color: var(--error-text) !important;
770
  text-decoration: underline !important;
771
  }
 
772
  .error-icon {
773
  font-size: 1.4rem !important;
774
  line-height: 1 !important;
775
  margin-top: 0.1rem !important;
776
  }
 
777
  .error-details {
778
  font-size: 0.85rem !important;
779
  color: var(--error-text) !important;
780
  margin-top: 0.5rem !important;
781
  opacity: 0.8;
782
  }
783
- /* Placeholder styling for empty output */
784
  .placeholder {
785
- background-color: var(--background-primary) !important; /* Explicitly set background to primary white */
786
  border: 2px dashed var(--border-color) !important;
787
  border-radius: 8px !important;
788
  padding: 2.5rem 1.5rem !important;
789
- text-align: center !important; /* Ensure placeholder text is centered */
790
  color: var(--text-secondary) !important;
791
  font-style: italic !important;
792
  font-size: 1.1rem !important;
793
- width: 100%; /* Ensure it takes full width of parent */
794
- box-sizing: border-box; /* Include padding/border in width */
795
  }
796
 
797
- /* Examples table styling with dynamic hover */
798
  .examples-section .gr-samples-table {
799
  border: 2px solid var(--border-color) !important;
800
  border-radius: 8px !important;
801
  overflow: hidden !important;
802
  margin-top: 1rem !important;
803
  }
 
804
  .examples-section .gr-samples-table th,
805
  .examples-section .gr-samples-table td {
806
  padding: 0.9rem !important;
807
  border: none !important;
808
  font-size: 0.95rem !important;
809
- text-align: left !important; /* Ensure example text is left-aligned */
810
  }
 
811
  .examples-section .gr-samples-table th {
812
- background-color: var(--background-secondary) !important; /* Explicitly set background */
813
  font-weight: 700 !important;
814
  color: var(--text-primary) !important;
815
  }
 
816
  .examples-section .gr-samples-table td {
817
- background-color: var(--background-primary) !important; /* Explicitly set background */
818
  color: var(--text-primary) !important;
819
  border-top: 1px solid var(--border-color) !important;
820
  cursor: pointer !important;
821
- transition: background 0.2s ease, transform 0.1s ease !important; /* Smooth transitions */
822
  }
 
823
  .examples-section .gr-samples-table tr:hover td {
824
- background-color: var(--background-secondary) !important; /* Explicitly set background */
825
- transform: translateX(5px); /* Subtle slide on hover */
826
  }
827
- /* Hide Gradio default elements for examples for cleaner look */
828
  .gr-examples .gr-label,
829
  .gr-examples .label-wrap,
830
  .gr-examples .gr-accordion-header {
831
  display: none !important;
832
  }
833
-
834
- /* Footer styling - centered text */
835
  .app-footer-wrapper {
836
- background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important; /* Gradient background */
837
  border: 2px solid var(--border-color) !important;
838
  border-radius: 12px !important;
839
  padding: 1.75rem !important;
840
  margin-top: 1.5rem !important;
841
- margin-bottom: 1.5rem !important; /* Ensures space at the very bottom before the container padding */
842
- text-align: center !important; /* Centered footer text */
843
  }
 
844
  .app-footer p {
845
- margin: 0.6rem auto !important; /* Auto margins to center block */
846
- max-width: 90% !important; /* Constrain width for wrapping */
847
  font-size: 0.95rem !important;
848
  color: var(--text-secondary) !important;
849
  line-height: 1.6 !important;
850
- background-color: transparent !important; /* Ensure paragraph in footer does not get unexpected background */
851
- text-align: center !important; /* Ensure footer text is centered */
852
- white-space: normal !important; /* Allow text to wrap */
853
  }
 
854
  .app-footer strong, .app-footer b {
855
- font-weight: 700 !important; /* Ensure bold is actually bold */
856
- color: var(--primary-color) !important; /* Highlight strong text with primary color */
857
  }
 
858
  .app-footer a {
859
  color: var(--primary-color) !important;
860
- text-decoration: underline !important; /* Ensure links are underlined */
861
  font-weight: 600 !important;
862
  }
 
863
  .app-footer a:hover {
864
- text-decoration: none !important; /* Remove underline on hover for subtle effect */
865
  }
866
 
867
- /* Responsive design for smaller screens */
868
  @media (max-width: 768px) {
869
  .gradio-container {
870
  padding: 1rem !important;
@@ -878,17 +842,16 @@ Answer:"""
878
  .section-title {
879
  font-size: 1.4rem !important;
880
  }
881
- .input-column { /* Apply vertical stacking for columns */
882
- flex-direction: column !important;
883
  }
884
  .button-row {
885
- flex-direction: column !important; /* Stack buttons vertically */
886
  }
887
  .gradio-button {
888
- width: 100% !important; /* Full width buttons */
889
  }
890
  .dashboard-card-section {
891
- /* padding handled by section-title-gradient-bar and dashboard-card-content-area */
892
  }
893
  .section-title-gradient-bar {
894
  padding: 0.1rem 1rem !important;
@@ -906,9 +869,7 @@ Answer:"""
906
  }
907
  """
908
 
909
- # Using gr.Blocks with custom CSS only to ensure no undesired colors are inherited from a theme.
910
  with gr.Blocks(css=custom_css, title="Landlord-Tenant Rights Assistant") as demo:
911
- # Header Section - uses gr.Group for distinct card-like styling
912
  with gr.Group(elem_classes="app-header-wrapper"):
913
  gr.Markdown(
914
  """
@@ -919,15 +880,11 @@ Answer:"""
919
  elem_classes="full-width-center"
920
  )
921
 
922
- # Main Dashboard Container - acts as a column to stack various sections
923
  with gr.Column(elem_classes="main-dashboard-container"):
924
-
925
- # How This Assistant Works Box (formerly Welcome & Disclaimer)
926
  with gr.Group(elem_classes="dashboard-card-section"):
927
- # Title bar with solid warm peach/cream background
928
  gr.Markdown("<h3 class='section-title'>How This Assistant Works</h3>", elem_classes="full-width-center section-title-gradient-bar")
929
- # Content area with pure primary background
930
- with gr.Column(elem_classes="dashboard-card-content-area"): # New wrapper for content area
931
  gr.Markdown(
932
  """
933
  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.
@@ -936,79 +893,69 @@ Answer:"""
936
 
937
  # OpenAI API Key Input Card
938
  with gr.Group(elem_classes="dashboard-card-section"):
939
- # Title bar with solid warm peach/cream background
940
  gr.Markdown("<h3 class='section-title'>OpenAI API Key</h3>", elem_classes="full-width-center section-title-gradient-bar")
941
- # Content area with pure primary background
942
- with gr.Column(elem_classes="dashboard-card-content-area"): # New wrapper for content area
943
  api_key_input = gr.Textbox(
944
  label="API Key",
945
- type="password", # Hides the input for security
946
  placeholder="Enter your OpenAI API key (e.g., sk-...)",
947
  lines=1,
948
- elem_classes=["input-field-group"] # Custom class for input styling
949
  )
950
- # Separate Markdown for the clickable info link, using standard Markdown link syntax
951
  gr.Markdown(
952
  "Required to process your query. Get one from OpenAI: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)",
953
- elem_classes="gr-prose" # Reuse Gradio's info text class for styling
954
  )
955
 
956
  # Query Input and State Selection Card
957
  with gr.Group(elem_classes="dashboard-card-section"):
958
- # Title bar with solid warm peach/cream background
959
  gr.Markdown("<h3 class='section-title'>Ask Your Question</h3>", elem_classes="full-width-center section-title-gradient-bar")
960
- # Content area with pure primary background
961
- with gr.Column(elem_classes="dashboard-card-content-area"): # New wrapper for content area
962
- with gr.Column(elem_classes="input-column"): # Column to stack inputs
963
- with gr.Column(elem_classes="input-field", scale=1): # Query box takes full width
964
  query_input = gr.Textbox(
965
  label="Your Question",
966
  placeholder="E.g., What are the rules for security deposit returns in my state?",
967
- lines=8, # Increased default height
968
- max_lines=15, # Increased max height
969
  elem_classes=["input-field-group"]
970
  )
971
- with gr.Column(elem_classes="input-field", scale=1): # State radio buttons take full width below
972
  state_input = gr.Radio(
973
  label="Select State",
974
  choices=radio_choices,
975
- value=initial_value_radio, # Set initial value to None
976
- elem_classes=["input-field-group", "gradio-radio-custom"], # Added custom class for specific styling
977
- interactive=True # Ensure it's interactive
978
  )
979
-
980
- with gr.Row(elem_classes="button-row"): # Row for action buttons
981
  clear_button = gr.Button("Clear", variant="secondary", elem_classes=["gr-button-secondary"])
982
  submit_button = gr.Button("Submit Query", variant="primary", elem_classes=["gr-button-primary"])
983
 
984
- # Output Display Card - Using gr.HTML for better animation control
985
  with gr.Group(elem_classes="dashboard-card-section"):
986
- # Title bar with solid warm peach/cream background
987
  gr.Markdown("<h3 class='section-title'>Legal Assistant's Response</h3>", elem_classes="full-width-center section-title-gradient-bar")
988
- # Content area with pure primary background
989
- with gr.Column(elem_classes="dashboard-card-content-area"): # New wrapper for content area
990
- output = gr.HTML( # Changed to gr.HTML to wrap content with animation class
991
  value="<div class='placeholder'>The answer will appear here after submitting your query.</div>",
992
- elem_classes="output-content-wrapper" # Custom class for output styling
993
  )
994
 
995
  # Example Questions Section
996
  with gr.Group(elem_classes="dashboard-card-section examples-section"):
997
- # Title bar with solid warm peach/cream background
998
  gr.Markdown("<h3 class='section-title'>Example Questions</h3>", elem_classes="full-width-center section-title-gradient-bar")
999
- # Content area with pure primary background
1000
- with gr.Column(elem_classes="dashboard-card-content-area"): # New wrapper for content area
1001
  if example_queries:
1002
  gr.Examples(
1003
  examples=example_queries,
1004
  inputs=[query_input, state_input],
1005
  examples_per_page=5,
1006
- label="" # Hide default Gradio label for examples to use our custom title
1007
  )
1008
  else:
1009
  gr.Markdown("<div class='placeholder'>Sample questions could not be loaded. Please ensure the vector database is populated.</div>")
1010
 
1011
- # Footer Section - contains disclaimer and developer info (now including the full disclaimer)
1012
  with gr.Group(elem_classes="app-footer-wrapper"):
1013
  gr.Markdown(
1014
  f"""
@@ -1019,26 +966,21 @@ Answer:"""
1019
  text-decoration: underline;
1020
  }}
1021
  </style>
1022
- <p>**Disclaimer:** 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>
1023
- <p>Developed by **Nischal Subedi**. Connect on <a href="https://www.linkedin.com/in/nischal1/" target="_blank" class="custom-link" style="font-weight: bold !important; color: orange !important;">LinkedIn</a> or explore insights at <a href="https://datascientistinsights.substack.com/" target="_blank" class="custom-link" style="font-weight: bold !important; color: orange !important;">Substack</a>.</p>
1024
  """
1025
  )
1026
 
1027
-
1028
- # Event Listeners for buttons
1029
  submit_button.click(
1030
  fn=query_interface_wrapper,
1031
  inputs=[api_key_input, query_input, state_input],
1032
  outputs=output,
1033
- api_name="submit_query" # Useful for debugging / external calls
1034
  )
1035
 
1036
  clear_button.click(
1037
  fn=lambda: (
1038
- "", # Clear API key input
1039
- "", # Clear query input
1040
- initial_value_radio, # Reset state radio to default (None)
1041
- "<div class='placeholder'>Inputs cleared. Ready for your next question.</div>" # Reset output message
1042
  ),
1043
  inputs=[],
1044
  outputs=[api_key_input, query_input, state_input, output]
 
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); }
 
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); }
 
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;
 
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;
 
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;
 
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
  """
 
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.
 
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"""
 
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]