Nischal Subedi commited on
Commit
ae4713f
·
1 Parent(s): 919beb0

updated UI_v35

Browse files
Files changed (1) hide show
  1. app.py +256 -568
app.py CHANGED
@@ -6,7 +6,7 @@ import re
6
 
7
  import gradio as gr
8
  try:
9
- from vector_db import VectorDatabase
10
  except ImportError:
11
  print("Error: Could not import VectorDatabase from vector_db.py.")
12
  print("Please ensure vector_db.py exists in the same directory and is correctly defined.")
@@ -72,7 +72,7 @@ Answer:"""
72
  for statute in statutes:
73
  statute = statute.strip()
74
  if '§' in statute and any(char.isdigit() for char in statute):
75
- if not re.match(r'^\([\w\.]+\)$', statute) and 'http' not in statute:
76
  if len(statute) > 5:
77
  valid_statutes.append(statute)
78
 
@@ -189,8 +189,8 @@ Answer:"""
189
  error_message = "Error: The request was too long for the AI model. This can happen with very complex questions or extensive retrieved context."
190
  details = "Try simplifying your question or asking about a more specific aspect."
191
  elif "timeout" in str(e).lower():
192
- error_message = "Error: The request to the AI model timed out. The service might be busy."
193
- details = "Please try again in a few moments."
194
 
195
  formatted_error = f"<div class='error-message'><span class='error-icon'>❌</span>{error_message}</div>"
196
  if details:
@@ -220,6 +220,7 @@ Answer:"""
220
  raise FileNotFoundError(f"PDF file not found: {pdf_path}")
221
  try:
222
  logging.info(f"Attempting to load/verify data from PDF: {pdf_path}")
 
223
  num_states_processed = self.vector_db.process_and_load_pdf(pdf_path)
224
  doc_count = self.vector_db.document_collection.count()
225
  state_count = self.vector_db.state_collection.count()
@@ -242,7 +243,7 @@ Answer:"""
242
  def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
243
  # Basic client-client-side validation for immediate feedback
244
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
245
- 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'>Get one here</a>.</div>"
246
  if not state or state == "Select a state..." or "Error" in state:
247
  return "<div class='error-message'><span class='error-icon'>⚠️</span>Please select a valid state from the dropdown.</div>"
248
  if not query or not query.strip():
@@ -254,11 +255,11 @@ Answer:"""
254
 
255
  # Check if the answer already contains an error message (from deeper within process_query)
256
  if "<div class='error-message'>" in answer:
257
- return answer # Return the pre-formatted error message directly
258
  else:
259
- # Format the successful response with the new UI structure
260
- formatted_response = f"<div class='response-header'><span class='response-icon'>📜</span>Response for {state}</div><hr class='divider'>{answer}"
261
- return formatted_response
262
 
263
  try:
264
  available_states_list = self.get_states()
@@ -281,7 +282,7 @@ Answer:"""
281
  example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
282
  # Add a generic example if no specific state examples match or if list is empty
283
  if not example_queries:
284
- example_queries.append(["What basic rights do tenants have?", available_states_list[0] if available_states_list else "California"])
285
  else: # Fallback if states list is problematic
286
  example_queries.append(["What basic rights do tenants have?", "California"])
287
 
@@ -291,338 +292,203 @@ Answer:"""
291
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:wght@700;800;900&display=swap');
292
  :root {
293
  /* Dark Theme Colors (Default: Legal Noir) */
294
- --app-bg-dark: #0F0F1A; /* Very dark blue-purple for the absolute background */
295
- --header-bg-dark: #1A1A2B; /* Darker, slightly more saturated for header/footer elements */
296
- --main-card-bg-dark: #1E1E30; /* Main content "console" background */
297
- --card-section-bg-dark: #2A2A40; /* Background for individual cards within the console */
298
- --text-primary-dark: #EAEAF0; /* Crisp off-white for main text */
299
- --text-secondary-dark: #A0A0B0; /* Muted light gray for secondary text */
300
- --accent-main-dark: #FFC107; /* Vibrant Gold/Amber for primary accents */
301
- --accent-hover-dark: #E0A800; /* Darker gold on hover */
302
- --border-dark: #3F3F5A; /* Subtle dark border */
303
- --shadow-dark: 0 1.2rem 3.5rem rgba(0,0,0,0.6); /* Deep, soft shadow for rich contrast */
304
- --focus-ring-dark: rgba(255, 193, 7, 0.4); /* Gold focus ring */
305
- --error-bg-dark: #4D001A; /* Deep red for errors */
306
- --error-text-dark: #FFB3C2; /* Light pink for error text */
307
  --error-border-dark: #990026;
308
  /* Light Theme Colors (Alternative: Legal Lumen) */
309
- --app-bg-light: #F0F2F5; /* Soft light gray background */
310
- --header-bg-light: #E0E4EB; /* Lighter header/footer elements */
311
- --main-card-bg-light: #FFFFFF; /* Pure white for main content "console" */
312
- --card-section-bg-light: #F8F9FA; /* Background for individual cards within the console */
313
- --text-primary-light: #212529; /* Dark charcoal */
314
- --text-secondary-light: #6C757D; /* Muted gray */
315
- --accent-main-light: #007bff; /* Professional blue */
316
- --accent-hover-light: #0056b3; /* Darker blue on hover */
317
- --border-light: #DDE2E8; /* Light border */
318
- --shadow-light: 0 0.8rem 2.5rem rgba(0,0,0,0.15); /* Soft shadow */
319
  --focus-ring-light: rgba(0, 123, 255, 0.3);
320
  --error-bg-light: #F8D7DA;
321
  --error-text-light: #721C24;
322
  --error-border-light: #F5C6CB;
323
  /* General Styling Variables */
324
  --font-family-main: 'Inter', sans-serif;
325
- --font-family-header: 'Playfair Display', serif; /* Elegant serif for titles */
326
  --radius-xs: 4px;
327
  --radius-sm: 8px;
328
  --radius-md: 12px;
329
- --radius-lg: 24px; /* Larger radius for a premium, soft feel */
330
- --transition-speed: 0.4s ease-in-out; /* Slightly faster, smoother transitions */
331
- --padding-xl: 4.5rem; /* Extra large padding for spaciousness */
332
  --padding-lg: 3.5rem;
333
  --padding-md: 2.5rem;
334
  --padding-sm: 1.8rem;
335
  }
336
- /* Apply theme based on system preference */
337
  body, .gradio-container {
338
  font-family: var(--font-family-main) !important;
339
- background: var(--app-bg-dark) !important; /* Default to dark theme */
340
  color: var(--text-primary-dark) !important;
341
- margin: 0;
342
- padding: 0;
343
- min-height: 100vh;
344
- font-size: 16px;
345
- line-height: 1.75; /* Enhanced line spacing for legibility */
346
- -webkit-font-smoothing: antialiased;
347
- -moz-osx-font-smoothing: grayscale;
348
  transition: background var(--transition-speed), color var(--transition-speed);
349
  }
350
  * { box-sizing: border-box; }
351
  @media (prefers-color-scheme: light) {
352
- body, .gradio-container {
353
- background: var(--app-bg-light) !important;
354
- color: var(--text-primary-light) !important;
355
- }
356
  }
357
- /* Global container for content alignment and padding */
358
  .gradio-container > .flex.flex-col {
359
- max-width: 1120px; /* Even wider for a more expansive dashboard feel */
360
- margin: 0 auto !important;
361
- padding: 0 !important; /* Managed by custom classes */
362
- gap: 0 !important;
363
- transition: padding var(--transition-speed);
364
  }
365
- /* Header styling: dynamic, centralized, and visually connecting */
366
  .app-header-wrapper {
367
- background: var(--header-bg-dark);
368
- color: var(--text-primary-dark) !important;
369
- padding: var(--padding-xl) var(--padding-lg) !important;
370
- text-align: center !important;
371
- border-bottom-left-radius: var(--radius-lg);
372
- border-bottom-right-radius: var(--radius-lg);
373
- box-shadow: var(--shadow-dark);
374
- position: relative;
375
- overflow: hidden;
376
- z-index: 10;
377
  transition: background var(--transition-speed), box-shadow var(--transition-speed);
378
- margin-bottom: 2.5rem; /* Space between header and main console */
379
- border: 1px solid var(--border-dark); /* Subtle frame */
380
- border-top: none; /* No top border for flush with screen top */
381
- /* ADDED FOR ROBUST CENTERING */
382
- max-width: 1120px; /* Match the main content width */
383
- margin-left: auto;
384
- margin-right: auto;
385
- width: 100%; /* Ensure it fills parent width up to max-width */
386
  }
387
  @media (prefers-color-scheme: light) {
388
- .app-header-wrapper {
389
- background: var(--header-bg-light);
390
- color: var(--text-primary-light) !important;
391
- box-shadow: var(--shadow-light);
392
- border: 1px solid var(--border-light);
393
- border-top: none;
394
- }
395
- }
396
- .app-header {
397
- display: flex;
398
- flex-direction: column;
399
- align-items: center;
400
- position: relative;
401
- z-index: 1;
402
  }
 
403
  .app-header-logo {
404
- font-size: 5.5rem; /* Even larger icon for maximum impact */
405
- margin-bottom: 0.8rem; /* Further reduced margin */
406
- line-height: 1;
407
- filter: drop-shadow(0 0 15px var(--accent-main-dark)); /* Stronger glow effect for icon */
408
- transform: translateY(-40px); opacity: 0; /* Initial state for animation */
409
  animation: fadeInSlideDown 1.5s ease-out forwards; animation-delay: 0.3s;
410
  transition: filter var(--transition-speed);
411
  }
412
  @media (prefers-color-scheme: light) { .app-header-logo { filter: drop-shadow(0 0 10px var(--accent-main-light)); } }
413
  .app-header-title {
414
- font-family: var(--font-family-header) !important;
415
- font-size: 4.2rem; /* The main title: massive, bold, and dynamic */
416
- font-weight: 900; /* Extra, extra bold */
417
- margin: 0 0 0.8rem 0; /* Further reduced margin */
418
- letter-spacing: -0.07em; /* Tighter for a strong presence */
419
- text-shadow: 0 8px 16px rgba(0,0,0,0.5); /* Deepest shadow for depth */
420
  transform: translateY(-40px); opacity: 0;
421
  animation: fadeInSlideDown 1.5s ease-out forwards; animation-delay: 0.6s;
422
  transition: text-shadow var(--transition-speed), color var(--transition-speed);
423
  }
424
  @media (prefers-color-scheme: light) { .app-header-title { text-shadow: 0 6px 12px rgba(0,0,0,0.25); } }
425
  .app-header-tagline {
426
- font-family: var(--font-family-main) !important;
427
- font-size: 1.6rem; /* Larger, more inviting tagline */
428
- font-weight: 300;
429
- opacity: 0.9;
430
- max-width: 900px;
431
  transform: translateY(-40px); opacity: 0;
432
  animation: fadeInSlideDown 1.5s ease-out forwards; animation-delay: 0.9s;
433
  transition: opacity var(--transition-speed);
434
  }
435
- /* Keyframe animations for header elements */
436
- @keyframes fadeInSlideDown {
437
- from { opacity: 0; transform: translateY(-40px); }
438
- to { opacity: 1; transform: translateY(0); }
439
- }
440
- /* --- NEW: Main Dashboard Console Container --- */
441
  .main-dashboard-container {
442
- background: var(--main-card-bg-dark) !important;
443
- border-radius: var(--radius-lg);
444
- box-shadow: var(--shadow-dark);
445
- border: 1px solid var(--border-dark) !important;
446
- padding: var(--padding-lg) !important; /* Overall padding for the console */
447
- margin: 0 auto 0.8rem auto; /* Adjusted for tighter gap */
448
- z-index: 1; /* Ensure it's behind header, but visually seamless */
449
- position: relative;
450
  transition: background var(--transition-speed), border-color var(--transition-speed), box-shadow var(--transition-speed);
451
- display: flex;
452
- flex-direction: column;
453
- gap: 2.5rem; /* Space between the main "cards" inside the console */
454
- /* ADDED FOR ROBUST WIDTH CONSTRAINT */
455
- max-width: 1120px; /* Ensure it matches the header and footer width */
456
  }
457
  @media (prefers-color-scheme: light) {
458
- .main-dashboard-container {
459
- background: var(--main-card-bg-light) !important;
460
- box-shadow: var(--shadow-light);
461
- border: 1px solid var(--border-light) !important;
462
- }
463
  }
464
- /* --- NEW: Card Sections within the Main Console --- */
465
  .dashboard-card-section {
466
- background: var(--card-section-bg-dark) !important;
467
- border-radius: var(--radius-md);
468
- border: 1px solid var(--border-dark);
469
- padding: var(--padding-md); /* Padding inside each card section */
470
- box-shadow: inset 0 0 10px rgba(0,0,0,0.2); /* Subtle inner shadow for depth */
471
  transition: background var(--transition-speed), border-color var(--transition-speed), box-shadow var(--transition-speed);
472
- display: flex;
473
- flex-direction: column;
474
- gap: 1.5rem; /* Space between elements within a card section */
475
  }
476
  @media (prefers-color-scheme: light) {
477
- .dashboard-card-section {
478
- background: var(--card-section-bg-light) !important;
479
- border: 1px solid var(--border-light);
480
- box-shadow: inset 0 0 8px rgba(0,0,0,0.05);
481
- }
482
  }
483
- /* Section titles within the unified main content area */
484
- .section-title { /* For 'Know Your Rights' */
485
- font-family: var(--font-family-header) !important;
486
- font-size: 2.8rem !important; /* Larger section titles */
487
- font-weight: 800 !important; /* Extra bold */
488
- color: var(--text-primary-dark) !important;
489
- text-align: center !important;
490
- margin: 0 auto 1.8rem auto !important; /* Reduced margin from 2.5rem */
491
- padding-bottom: 0.8rem !important; /* Reduced padding from 1rem */
492
- border-bottom: 2px solid var(--border-dark) !important;
493
- width: 100%;
494
  transition: color var(--transition-speed), border-color var(--transition-speed);
495
  }
496
- .sub-section-title { /* For 'Ask Your Question', 'Example Questions to Ask' */
497
  font-family: var(--font-family-header) !important;
498
- font-size: 2.5rem !important; /* INCREASED for more prominence, from 2.2rem */
499
  font-weight: 700 !important;
500
  color: var(--text-primary-dark) !important;
501
- text-align: center !important; /* Ensures centering for all sub-section titles */
502
- margin-top: 1.5rem !important; /* Reduced from 2rem */
503
- margin-bottom: 0.8rem !important; /* Reduced from 1rem */
504
  transition: color var(--transition-speed);
 
 
505
  }
506
  @media (prefers-color-scheme: light) {
507
- .section-title, .sub-section-title {
508
- color: var(--text-primary-light) !important;
509
- border-bottom-color: var(--border-light) !important;
510
- }
511
  }
512
- /* General text styling within main content: highly legible */
513
  .dashboard-card-section p, .output-content-wrapper p {
514
- font-size: 1.15rem; /* Larger, more readable body text */
515
- line-height: 1.8; /* Generous line height for comfort */
516
- color: var(--text-secondary-dark);
517
- margin-bottom: 1.2rem; /* Reduced from 1.5rem */
518
- transition: color var(--transition-speed);
519
  }
520
  .dashboard-card-section a, .output-content-wrapper a {
521
- color: var(--accent-main-dark);
522
- text-decoration: none;
523
- font-weight: 500;
524
  transition: color var(--transition-speed), text-decoration var(--transition-speed);
525
  }
526
- .dashboard-card-section a:hover, .output-content-wrapper a:hover {
527
- color: var(--accent-hover-dark);
528
- text-decoration: underline;
529
- }
530
- .dashboard-card-section strong, .output-content-wrapper strong {
531
- font-weight: 700;
532
- color: var(--text-primary-dark);
533
- transition: color var(--transition-speed);
534
- }
535
  @media (prefers-color-scheme: light) {
536
  .dashboard-card-section p, .output-content-wrapper p { color: var(--text-secondary-light); }
537
  .dashboard-card-section a, .output-content-wrapper a { color: var(--accent-main-light); }
538
  .dashboard-card-section a:hover, .output-content-wrapper a:hover { color: var(--accent-hover-light); }
539
  .dashboard-card-section strong, .output-content-wrapper strong { color: var(--text-primary-light); }
540
  }
541
- /* Horizontal rule for visual separation within the main block */
542
- .section-divider {
543
- border: none;
544
- border-top: 1px solid var(--border-dark); /* Subtle, but present for structure */
545
- margin: 2rem 0 !important; /* Reduced from 2.5rem */
546
- transition: border-color var(--transition-speed);
547
- }
548
  @media (prefers-color-scheme: light) { .section-divider { border-top: 1px solid var(--border-light); } }
549
- /* Input field groups and layout: spacious and clear */
550
- .input-field-group { margin-bottom: 1rem; } /* Reduced from 1.5rem */
551
- .input-row {
552
- display: flex;
553
- gap: 1.8rem; /* Reduced from 2rem */
554
- flex-wrap: wrap;
555
- margin-bottom: 1rem; /* Reduced from 1.5rem */
556
- }
557
- .input-field {
558
- flex: 1; /* Allows flexible sizing */
559
- }
560
- /* Input labels and info text: prominent and clear */
561
  .gradio-input-label {
562
- font-size: 1.2rem !important; /* Larger, more prominent labels */
563
- font-weight: 500 !important;
564
- color: var(--text-primary-dark) !important;
565
- margin-bottom: 0.8rem !important; /* Reduced from 1rem */
566
- display: block !important;
567
- transition: color var(--transition-speed);
568
- }
569
- .gradio-input-info {
570
- font-size: 1.0rem !important;
571
- color: var(--text-secondary-dark) !important;
572
- margin-top: 0.6rem;
573
- transition: color var(--transition-speed);
574
  }
 
575
  @media (prefers-color-scheme: light) {
576
  .gradio-input-label { color: var(--text-primary-light) !important; }
577
  .gradio-input-info { color: var(--text-secondary-light) !important; }
578
  }
579
- /* Textbox, Dropdown, Password input styling: larger, more refined */
580
- .gradio-textbox textarea,
581
- .gradio-dropdown select,
582
- .gradio-textbox input[type=password] {
583
- border: 2px solid var(--border-dark) !important; /* Thicker border for visibility */
584
- border-radius: var(--radius-md) !important;
585
- padding: 1.3rem 1.6rem !important; /* Even more padding */
586
- font-size: 1.15rem !important; /* Larger font size */
587
- background: var(--main-card-bg-dark) !important; /* Input background matches main console */
588
- color: var(--text-primary-dark) !important;
589
- width: 100% !important;
590
- box-shadow: inset 0 1px 3px rgba(0,0,0,0.3); /* Inner shadow for depth */
591
  transition: border-color var(--transition-speed), box-shadow var(--transition-speed), background var(--transition-speed), color var(--transition-speed);
592
  }
593
- .gradio-textbox textarea { min-height: 180px; } /* Taller textarea */
594
- .gradio-textbox textarea::placeholder,
595
- .gradio-textbox input[type=password]::placeholder { color: #808090 !important; } /* Improved placeholder color */
596
- .gradio-textbox textarea:focus,
597
- .gradio-dropdown select:focus,
598
- .gradio-textbox input[type=password]:focus {
599
  border-color: var(--accent-main-dark) !important;
600
- box-shadow: 0 0 0 6px var(--focus-ring-dark) !important, inset 0 1px 3px rgba(0,0,0,0.4); /* Stronger focus ring and inner shadow */
601
  outline: none !important;
602
  }
603
  @media (prefers-color-scheme: light) {
604
- .gradio-textbox textarea,
605
- .gradio-dropdown select,
606
- .gradio-textbox input[type=password] {
607
- border: 2px solid var(--border-light) !important;
608
- background: var(--main-card-bg-light) !important;
609
- color: var(--text-primary-light) !important;
610
- box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
611
  }
612
- .gradio-textbox textarea:focus,
613
- .gradio-dropdown select:focus,
614
- .gradio-textbox input[type=password]:focus {
615
  border-color: var(--accent-main-light) !important;
616
  box-shadow: 0 0 0 6px var(--focus-ring-light) !important, inset 0 1px 3px rgba(0,0,0,0.2);
617
  }
618
  }
619
- /* Custom dropdown arrow */
620
  .gradio-dropdown select {
621
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
622
  background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22%23A0A0B0%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M5.293%207.293a1%201%200%20011.414%200L10%2010.586l3.293-3.293a1%201%200%20111.414%201.414l-4%204a1%201%200%2001-1.414%200l-4-4a1%201%200%20010-1.414z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
623
- background-repeat: no-repeat;
624
- background-position: right 1.8rem center;
625
- background-size: 1.4em; /* Larger arrow */
626
  padding-right: 5rem !important;
627
  }
628
  @media (prefers-color-scheme: light) {
@@ -630,136 +496,48 @@ Answer:"""
630
  background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22%236C757D%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M5.293%207.293a1%201%200%20011.414%200L10%2010.586l3.293-3.293a1%201%200%20111.414%201.414l-4%204a1%201%200%2001-1.414%200l-4-4a1%201%200%20010-1.414z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
631
  }
632
  }
633
- /* Button group and styling: dynamic and clear actions */
634
- .button-row {
635
- display: flex;
636
- gap: 2rem; /* Reduced from 2.5rem */
637
- margin-top: 2rem; /* Reduced from 2.5rem */
638
- flex-wrap: wrap;
639
- justify-content: flex-end;
640
- }
641
  .gradio-button {
642
- border-radius: var(--radius-md) !important;
643
- padding: 1.2rem 2.8rem !important; /* More padding */
644
- font-size: 1.15rem !important; /* Larger font */
645
- font-weight: 600 !important; /* Bolder text */
646
- border: 1px solid transparent !important;
647
- box-shadow: 0 6px 20px rgba(0,0,0,0.35); /* Deeper shadow */
648
- transition: all var(--transition-speed) cubic-bezier(0.0, 0.0, 0.2, 1); /* Enhanced cubic-bezier for springy feel */
649
- }
650
- .gradio-button:hover:not(:disabled) {
651
- transform: translateY(-6px); /* More pronounced lift */
652
- box-shadow: 0 12px 28px rgba(0,0,0,0.45) !important;
653
  }
 
654
  .gradio-button:active:not(:disabled) { transform: translateY(-3px); }
655
  .gradio-button:disabled {
656
- background: #3A3A50 !important;
657
- color: #6A6A80 !important;
658
- box-shadow: none !important;
659
- border-color: #4A4A60 !important;
660
- cursor: not-allowed;
661
- }
662
- /* Primary button */
663
- .gr-button-primary {
664
- background: var(--accent-main-dark) !important;
665
- color: var(--header-bg-dark) !important; /* Dark text on gold */
666
- border-color: var(--accent-main-dark) !important;
667
- }
668
- .gr-button-primary:hover:not(:disabled) {
669
- background: var(--accent-hover-dark) !important;
670
- border-color: var(--accent-hover-dark) !important;
671
- }
672
- /* Secondary button */
673
- .gr-button-secondary {
674
- background: transparent !important;
675
- color: var(--text-secondary-dark) !important;
676
- border: 2px solid var(--border-dark) !important; /* More prominent border */
677
- box-shadow: none !important;
678
- }
679
- .gr-button-secondary:hover:not(:disabled) {
680
- background: rgba(255, 193, 7, 0.15) !important; /* Gold tint on hover */
681
- color: var(--accent-main-dark) !important;
682
- border-color: var(--accent-main-dark) !important;
683
  }
 
 
 
 
684
  @media (prefers-color-scheme: light) {
685
  .gradio-button { box-shadow: 0 6px 20px rgba(0,0,0,0.1); }
686
  .gradio-button:hover:not(:disabled) { box-shadow: 0 12px 28px rgba(0,0,0,0.2) !important; }
687
- .gradio-button:disabled {
688
- background: #E9ECEF !important;
689
- color: #ADB5BD !important;
690
- border-color: #DEE2E6 !important;
691
- }
692
- .gr-button-primary {
693
- background: var(--accent-main-light) !important;
694
- color: #FFFFFF !important;
695
- border-color: var(--accent-main-light) !important;
696
- }
697
- .gr-button-primary:hover:not(:disabled) {
698
- background: var(--accent-hover-light) !important;
699
- border-color: var(--accent-hover-light) !important;
700
- }
701
- .gr-button-secondary {
702
- background: transparent !important;
703
- color: var(--text-secondary-light) !important;
704
- border: 2px solid var(--border-light) !important;
705
- }
706
- .gr-button-secondary:hover:not(:disabled) {
707
- background: rgba(0, 123, 255, 0.1) !important;
708
- color: var(--accent-main-light) !important;
709
- border-color: var(--accent-main-light) !important;
710
- }
711
- }
712
- /* Output Styling within the main content area */
713
- .output-card { /* Class for the output content area markdown component */
714
- padding: 0 !important; /* Markdown itself might have padding, control it within */
715
- margin-top: 0 !important; /* Ensure no extra margin */
716
- margin-bottom: 0 !important; /* Ensure no extra margin */
717
- }
718
- .output-card .response-header { /* For actual answers */
719
- font-size: 1.8rem; /* Larger and clearer */
720
- font-weight: 700;
721
- color: var(--text-primary-dark);
722
- margin: 0 0 1rem 0; /* Reduced margin */
723
- display: flex;
724
- align-items: center;
725
- gap: 1.2rem;
726
- transition: color var(--transition-speed);
727
- }
728
- .output-card .response-icon {
729
- font-size: 2rem; /* Larger icon */
730
- color: var(--accent-main-dark);
731
- transition: color var(--transition-speed);
732
- }
733
- .output-card .divider {
734
- border: none;
735
- border-top: 1px solid var(--border-dark);
736
- margin: 1.5rem 0 1.8rem 0; /* Adjusted for tighter spacing */
737
- transition: border-color var(--transition-speed);
738
- }
739
- .output-card .output-content-wrapper { /* Applies to the entire markdown content block */
740
- font-size: 1.15rem; /* Highly legible output text */
741
- line-height: 1.8;
742
- color: var(--text-primary-dark);
743
  transition: color var(--transition-speed);
744
  }
 
 
 
745
  .output-card .output-content-wrapper p { margin-bottom: 1rem; }
746
- .output-card .output-content-wrapper ul,
747
- .output-card .output-content-wrapper ol {
748
- margin-left: 2.2rem; /* More indentation */
749
- margin-bottom: 1.2rem;
750
- padding-left: 0;
751
- list-style-type: disc;
752
- }
753
  .output-card .output-content-wrapper ol { list-style-type: decimal; }
754
  .output-card .output-content-wrapper li { margin-bottom: 0.8rem; }
755
  .output-card .output-content-wrapper strong { font-weight: 700; }
756
- .output-card .output-content-wrapper a {
757
- color: var(--accent-main-dark);
758
- text-decoration: underline;
759
- }
760
- .output-card .output-content-wrapper a:hover {
761
- color: var(--accent-hover-dark);
762
- }
763
  @media (prefers-color-scheme: light) {
764
  .output-card .response-header { color: var(--text-primary-light); }
765
  .output-card .response-icon { color: var(--accent-main-light); }
@@ -768,95 +546,48 @@ Answer:"""
768
  .output-card .output-content-wrapper a { color: var(--accent-main-light); }
769
  .output-card .output-content-wrapper a:hover { color: var(--accent-hover-light); }
770
  }
771
- /* Error messages: clear, prominent, and legible */
772
- .output-card .error-message { /* This is also an inner div within the output markdown */
773
- padding: 1.5rem 2rem; /* Reduced padding */
774
- margin-top: 1.5rem; /* Keeping this margin to separate from prev section */
775
- font-size: 1.1rem;
776
- border-radius: var(--radius-md);
777
- background: var(--error-bg-dark);
778
- color: var(--error-text-dark);
779
- border: 2px solid var(--error-border-dark);
780
- display: flex;
781
- align-items: flex-start;
782
- gap: 1.5em;
783
  transition: background var(--transition-speed), color var(--transition-speed), border-color var(--transition-speed);
784
  }
785
- .output-card .error-message .error-icon {
786
- font-size: 1.8rem;
787
- line-height: 1;
788
- padding-top: 0.1em;
789
- }
790
- .output-card .error-details {
791
- font-size: 0.95rem;
792
- margin-top: 0.8rem;
793
- opacity: 0.9;
794
- word-break: break-word;
795
- }
796
  @media (prefers-color-scheme: light) {
797
- .output-card .error-message {
798
- background: var(--error-bg-light);
799
- color: var(--error-text-light);
800
- border: 2px solid var(--error-border-light);
801
- }
802
  }
803
- /* Output placeholder styling: inviting and distinct */
804
- .output-card .placeholder { /* Also an inner div within the output markdown */
805
- padding: 2.5rem 2rem; /* Reduced padding */
806
- font-size: 1.2rem; /* Adjusted font size */
807
- border-radius: var(--radius-md);
808
- border: 3px dashed var(--border-dark); /* Thicker dashed border */
809
- color: var(--text-secondary-dark);
810
- text-align: center;
811
- opacity: 0.8;
812
  transition: border-color var(--transition-speed), color var(--transition-speed);
813
  }
814
  @media (prefers-color-scheme: light) {
815
- .output-card .placeholder {
816
- border-color: var(--border-light);
817
- color: var(--text-secondary-light);
818
- }
819
  }
820
- /* Examples table styling: integrated within the main content block, but with clear visual identity */
821
- /* Applied to the gr.Column that wraps the examples */
822
  .examples-section .gr-examples-table {
823
- border-radius: var(--radius-md) !important;
824
- border: 1px solid var(--border-dark) !important;
825
- overflow: hidden;
826
- background: var(--card-section-bg-dark) !important; /* Ensure table matches main content background */
827
- box-shadow: inset 0 0 10px rgba(0,0,0,0.2); /* Subtle inner shadow */
828
  transition: border-color var(--transition-speed), background var(--transition-speed), box-shadow var(--transition-speed);
829
  }
830
  @media (prefers-color-scheme: light) {
831
- .examples-section .gr-examples-table {
832
- border: 1px solid var(--border-light) !important;
833
- background: var(--card-section-bg-light) !important;
834
- box-shadow: inset 0 0 8px rgba(0,0,0,0.05);
835
- }
836
- }
837
- .examples-section .gr-examples-table th,
838
- .examples-section .gr-examples-table td {
839
- padding: 1rem 1.2rem !important; /* Slightly reduced padding for tighter coupling */
840
- font-size: 1.05rem !important;
841
- border: none !important;
842
  }
 
843
  .examples-section .gr-examples-table th {
844
- background: var(--header-bg-dark) !important; /* Match header background for table header */
845
- color: var(--text-primary-dark) !important;
846
- font-weight: 600 !important;
847
- text-align: left;
848
  transition: background var(--transition-speed), color var(--transition-speed);
849
  }
850
  .examples-section .gr-examples-table td {
851
- background: var(--card-section-bg-dark) !important;
852
- color: var(--text-primary-dark) !important;
853
- border-top: 1px solid var(--border-dark) !important;
854
- cursor: pointer;
855
  transition: background var(--transition-speed), color var(--transition-speed), border-color var(--transition-speed);
856
  }
857
- .examples-section .gr-examples-table tr:hover td {
858
- background: rgba(255, 193, 7, 0.1) !important; /* Gold tint on hover */
859
- }
860
  .examples-section .gr-examples-table tr:first-child td { border-top: none !important; }
861
  @media (prefers-color-scheme: light) {
862
  .examples-section .gr-examples-table { border: 1px solid var(--border-light) !important; background: var(--card-section-bg-light) !important; }
@@ -864,160 +595,104 @@ Answer:"""
864
  .examples-section .gr-examples-table td { background: var(--card-section-bg-light) !important; color: var(--text-primary-light) !important; border-top: 1px solid var(--border-light) !important; }
865
  .examples-section .gr-examples-table tr:hover td { background: rgba(0, 123, 255, 0.08) !important; }
866
  }
867
- /* Footer styling: clean and informative, integrated at the very bottom */
868
  .app-footer-wrapper {
869
- background: var(--header-bg-dark); /* Match header background for consistency */
870
- border-top: 1px solid var(--border-dark) !important;
871
- margin-top: 0.5rem; /* Adjusted for tighter gap, assuming minimum needed */
872
- padding-top: 2.5rem; /* Reduced from 3rem */
873
- padding-bottom: 2.5rem; /* Reduced from 3rem */
874
- border-top-left-radius: var(--radius-lg);
875
- border-top-right-radius: var(--radius-lg);
876
- box-shadow: inset 0 8px 15px rgba(0,0,0,0.2); /* Inset shadow for depth */
877
  transition: background var(--transition-speed), border-color var(--transition-speed), box-shadow var(--transition-speed);
878
- /* ADDED FOR ROBUST CENTERING */
879
- max-width: 1120px; /* Ensure it matches the main content width */
880
- margin-left: auto;
881
- margin-right: auto;
882
- width: 100%; /* Ensure it fills parent width up to max-width */
883
  }
884
  @media (prefers-color-scheme: light) {
885
- .app-footer-wrapper {
886
- background: var(--header-bg-light);
887
- border-top: 1px solid var(--border-light) !important;
888
- box-shadow: inset 0 6px 12px rgba(0,0,0,0.1);
889
- }
890
  }
891
- .app-footer {
892
  padding: 0 var(--padding-lg) !important;
893
- /* Changed from align-items: center */
894
  display: flex;
895
  flex-direction: column;
896
- align-items: flex-start; /* Align content to the start (left) */
897
- max-width: 1120px; /* Match main content width */
898
- margin: 0 auto; /* Center the footer content block */
899
  }
900
- .app-footer p {
901
  font-size: 1.05rem !important;
902
  color: var(--text-secondary-dark) !important;
903
  margin-bottom: 1rem;
904
- max-width: 900px; /* Keep max-width for readability */
905
- text-align: left !important; /* Ensure text aligns left within its container */
906
  transition: color var(--transition-speed);
 
907
  }
908
- .app-footer a {
909
- color: var(--accent-main-dark) !important;
910
- font-weight: 500;
911
- transition: color var(--transition-speed), text-decoration var(--transition-speed);
912
- }
913
- .app-footer a:hover {
914
- color: var(--accent-hover-dark) !important;
915
- text-decoration: underline;
916
- }
917
  @media (prefers-color-scheme: light) {
918
  .app-footer-wrapper { border-top-color: var(--border-light) !important; }
919
  .app-footer p { color: var(--text-secondary-light) !important; }
920
  .app-footer a { color: var(--accent-main-light) !important; }
921
  .app-footer a:hover { color: var(--accent-hover-light) !important; }
922
  }
923
- /* Accessibility: Focus styles */
924
  :focus-visible {
925
- outline: 5px solid var(--accent-main-dark) !important; /* Even thicker for accessibility */
926
- outline-offset: 5px;
927
  box-shadow: 0 0 0 8px var(--focus-ring-dark) !important;
928
- border-radius: var(--radius-md) !important; /* Ensure border-radius for focus */
929
  }
930
  @media (prefers-color-scheme: light) {
931
- :focus-visible {
932
- outline-color: var(--accent-main-light) !important;
933
- box_shadow: 0 0 0 8px var(--focus-ring-light) !important;
934
- }
935
  }
936
  .gradio-button span:focus { outline: none !important; }
937
- /* MOST AGGRESSIVE "FALSE" & FILTER ICON REMOVAL - THIS SHOULD FINALLY KILL IT */
938
- /* Targets ALL known problematic elements that might contain "false", the filter menu icon, or accordion toggle */
939
- .gr-examples .gr-label,
940
- .gr-examples button.gr-button-filter, /* The "☰" icon button */
941
- .gr-examples .label-wrap, /* Common wrapper for labels */
942
- .gr-examples div[data-testid*="label-text"], /* Div containing "false" text by data-testid */
943
- .gr-examples span[data-testid*="label-text"], /* Span containing "false" text by data-testid */
944
- .gr-examples div[class*="label"], /* Any div with "label" in its class name */
945
- .gr-examples .gr-example-label, /* Specific class for example labels */
946
- .gr-examples .gr-box.gr-component.gradio-example > div:first-child:has(> span[data-testid]), /* Target parent div of specific internal spans with data-testid */
947
- .gr-examples .gr-box.gr-component.gradio-example > div:first-child > span, /* Direct span within example items, often containing "false" */
948
- /* NEW: Targeting Accordion-related elements if Examples acts as an Accordion internally */
949
- .gr-examples .gr-accordion-header,
950
- .gr-examples .gr-accordion-title,
951
- .gr-examples .gr-accordion-toggle-icon,
952
- .gr-examples .gr-accordion-header button,
953
- .gr-examples .gr-button.gr-button-filter, /* More general button target for filter */
954
- .gr-examples .gr-button.gr-button-primary.gr-button-filter, /* Specific primary filter button target */
955
- /* NEW AND MORE AGGRESSIVE: Target the entire header of gr.Examples and its direct children */
956
- .gr-examples .gr-examples-header, /* The main header container for examples */
957
- .gr-examples .gr-examples-header > * /* ALL direct children within that header */
958
- {
959
- display: none !important;
960
- visibility: hidden !important; /* Double down on hiding */
961
- width: 0 !important;
962
- height: 0 !important;
963
- overflow: hidden !important;
964
- margin: 0 !important;
965
- padding: 0 !important;
966
- border: 0 !important;
967
- font-size: 0 !important;
968
- line-height: 0 !important;
969
- position: absolute !important; /* Take it out of normal flow */
970
- pointer-events: none !important; /* Ensure it's not interactive */
971
- }
972
- /* Responsive Adjustments (meticulously refined) */
973
  @media (max-width: 1024px) {
974
  .gradio-container > .flex.flex-col { max-width: 960px; padding: 0 1.5rem !important; }
975
- .app-header-title { font-size: 3.8rem; }
976
- .app-header-tagline { font-size: 1.5rem; }
977
  .app-header-wrapper { padding: var(--padding-md) var(--padding-lg) !important; margin-bottom: 2rem; border-bottom-left-radius: var(--radius-md); border-bottom-right-radius: var(--radius-md); }
978
  .main-dashboard-container { padding: var(--padding-md) !important; margin-bottom: 0.6rem; border-radius: var(--radius-md); gap: 2rem; }
979
  .dashboard-card-section { padding: var(--padding-sm); border-radius: var(--radius-sm); }
980
  .section-title { font-size: 2.2rem !important; margin-bottom: 1.5rem !important; }
981
- .sub-section-title { font-size: 1.8rem !important; margin-bottom: 0.7rem !important; } /* Adjusted for 1024px */
982
- .section-divider { margin: 1.8rem 0; }
983
- .input-row { gap: 1.5rem; }
984
- .input-field { min-width: 280px; }
985
- .gradio-textbox textarea { min-height: 160px; }
986
- .output-card .response-header { font-size: 1.7rem; }
987
  .examples-section { padding-top: 1.2rem; }
988
  .examples-section .gr-examples-table th, .examples-section .gr-examples-table td { padding: 0.9rem 1.1rem !important; }
989
  .app-footer-wrapper { margin-top: 0.6rem; border-top-left-radius: var(--radius-md); border-top-right-radius: var(--radius-md); }
990
- .app-footer { padding: 0 1.5rem !important; } /* Match main content padding */
991
  }
992
  @media (max-width: 768px) {
993
  .gradio-container > .flex.flex-col { padding: 0 1rem !important; }
994
  .app-header-wrapper { padding: var(--padding-sm) var(--padding-md) !important; margin-bottom: 1.8rem; border-bottom-left-radius: var(--radius-md); border-bottom-right-radius: var(--radius-md); }
995
- .app-header-logo { font-size: 4.5rem; margin-bottom: 0.6rem; }
996
- .app-header-title { font-size: 3.2rem; letter-spacing: -0.06em; }
997
  .app-header-tagline { font-size: 1.3rem; }
998
  .main-dashboard-container { padding: var(--padding-sm) !important; margin-bottom: 0.5rem; border-radius: var(--radius-md); gap: 1.8rem; }
999
  .dashboard-card-section { padding: 1.5rem; border-radius: var(--radius-sm); }
1000
  .section-title { font-size: 2rem !important; margin-bottom: 1.2rem !important; }
1001
  .sub-section-title { font-size: 1.6rem !important; margin-top: 1rem !important; margin-bottom: 0.6rem !important; }
1002
- .section-divider { margin: 1.5rem 0; }
1003
- .input-row { flex-direction: column; gap: 1rem; }
1004
- .input-field { min-width: 100%; }
1005
- .gradio-textbox textarea { min-height: 140px; }
1006
- .button-row { justify-content: stretch; gap: 1rem; }
1007
  .gradio-button { width: 100%; padding: 1.1rem 2rem !important; font-size: 1.1rem !important; }
1008
- .output-card .response-header { font-size: 1.5rem; }
1009
- .output-card .response-icon { font-size: 1.7rem; }
1010
  .output-card .placeholder { padding: 2.5rem 1.5rem; font-size: 1.1rem; }
1011
  .examples-section { padding-top: 1.2rem; }
1012
  .examples-section .gr-examples-table th, .examples-section .gr-examples-table td { padding: 0.9rem 1.1rem !important; font-size: 1.0rem !important; }
1013
  .app-footer-wrapper { margin-top: 0.5rem; border-top-left-radius: var(--radius-md); border-top-right-radius: var(--radius-md); padding-top: 2rem; padding-bottom: 2rem; }
1014
- .app-footer { padding: 0 1rem !important; } /* Match main content padding */
1015
  }
1016
  @media (max-width: 480px) {
1017
  .gradio-container > .flex.flex-col { padding: 0 0.8rem !important; }
1018
  .app-header-wrapper { padding: 1.2rem 1rem !important; margin-bottom: 1.5rem; border-bottom-left-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
1019
- .app-header-logo { font-size: 3.8rem; margin-bottom: 0.5rem; }
1020
- .app-header-title { font-size: 2.8rem; }
1021
  .app-header-tagline { font-size: 1.1rem; }
1022
  .main-dashboard-container { padding: 1.2rem !important; margin-bottom: 0.4rem; border-radius: var(--radius-sm); gap: 1.5rem; }
1023
  .dashboard-card-section { padding: 1rem; border-radius: var(--radius-xs); }
@@ -1027,13 +702,12 @@ Answer:"""
1027
  .gradio-textbox textarea, .gradio-dropdown select, .gradio-textbox input[type=password] { font-size: 1.05rem !important; padding: 1rem 1.2rem !important; }
1028
  .gradio-textbox textarea { min-height: 120px; }
1029
  .gradio-button { padding: 1rem 1.5rem !important; font-size: 1rem !important; }
1030
- .output-card .response-header { font-size: 1.4rem; }
1031
- .output-card .response-icon { font-size: 1.5rem; }
1032
  .output-card .placeholder { padding: 2rem 1rem; font-size: 1.05rem; }
1033
  .examples-section { padding-top: 0.8rem; }
1034
  .examples-section .gr-examples-table th, .examples-section .gr-examples-table td { padding: 0.6rem 0.8rem !important; font-size: 0.95rem !important; }
1035
  .app-footer-wrapper { margin-top: 0.4rem; border-top-left-radius: var(--radius-sm); border-top-right-radius: var(--radius-sm); padding-top: 1.5rem; padding-bottom: 1.5rem; }
1036
- .app-footer { padding: 0 0.8rem !important; } /* Match main content padding */
1037
  }
1038
  """
1039
 
@@ -1055,7 +729,7 @@ Answer:"""
1055
 
1056
  # --- Section 1: Introduction and Disclaimer Card ---
1057
  with gr.Group(elem_classes="dashboard-card-section"):
1058
- gr.Markdown("<h3 class='sub-section-title'>Welcome & Disclaimer</h3>")
1059
  gr.Markdown(
1060
  """
1061
  <p>Navigate landlord-tenant laws with ease. This assistant provides detailed, state-specific answers grounded in legal authority.</p>
@@ -1065,23 +739,23 @@ Answer:"""
1065
 
1066
  # --- Section 2: OpenAI API Key Input Card ---
1067
  with gr.Group(elem_classes="dashboard-card-section"):
1068
- gr.Markdown("<h3 class='sub-section-title'>OpenAI API Key</h3>")
1069
  api_key_input = gr.Textbox(
1070
- label="", # Removed redundant label here
1071
  type="password", placeholder="Enter your API key (e.g., sk-...)",
1072
  info="Required to process your query. Securely used per request, not stored. <a href='https://platform.openai.com/api-keys' target='_blank'>Get one free from OpenAI</a>.", lines=1
1073
  )
1074
-
1075
  # --- Section 3: Query Input and State Selection Card ---
1076
  with gr.Group(elem_classes="dashboard-card-section"):
1077
- gr.Markdown("<h3 class='sub-section-title'>Ask Your Question</h3>")
1078
- with gr.Row(elem_classes="input-row"): # Query and State in one row
1079
- with gr.Column(elem_classes="input-field", scale=3): # Query box takes more space
1080
  query_input = gr.Textbox(
1081
  label="Question", placeholder="E.g., What are the rules for security deposit returns in my state?",
1082
  lines=5, max_lines=10
1083
  )
1084
- with gr.Column(elem_classes="input-field", scale=1): # State dropdown takes less space
1085
  state_input = gr.Dropdown(
1086
  label="Select State", choices=dropdown_choices, value=initial_value,
1087
  allow_custom_value=False
@@ -1092,27 +766,27 @@ Answer:"""
1092
 
1093
  # --- Section 4: Output Display Card ---
1094
  with gr.Group(elem_classes="dashboard-card-section"):
1095
- gr.Markdown("<h3 class='sub-section-title'>Legal Assistant's Response</h3>")
1096
  output = gr.Markdown(
1097
  value="<div class='placeholder output-card'>The answer will appear here after submitting your query.</div>",
1098
- elem_classes="output-content-wrapper output-card" # Apply these classes for styling
1099
  )
1100
-
1101
  # --- Section 5: Example Questions Section (NO ACCORDION, direct display) ---
1102
- with gr.Group(elem_classes="dashboard-card-section examples-section"):
1103
- gr.Markdown("<h3 class='sub-section-title'>Example Questions to Ask</h3>")
1104
  if example_queries:
1105
  gr.Examples(
1106
  examples=example_queries, inputs=[query_input, state_input],
1107
  examples_per_page=5,
1108
- label="", # This is crucial, but CSS handles the rest
1109
  )
1110
  else:
1111
  gr.Markdown("<div class='placeholder'>Sample questions could not be loaded.</div>")
1112
 
1113
  # --- Footer Section ---
1114
  with gr.Group(elem_classes="app-footer-wrapper"):
1115
- gr.Markdown(
1116
  """
1117
  <div class="app-footer">
1118
  <p>This tool is for informational purposes only and does not constitute legal advice. For legal guidance, always consult with a licensed attorney in your jurisdiction.</p>
@@ -1129,31 +803,29 @@ Answer:"""
1129
  )
1130
  clear_button.click(
1131
  fn=lambda: (
1132
- "", # Clear API key input
1133
- "", # Clear query input
1134
- initial_value, # Reset state dropdown to initial value
1135
- "<div class='placeholder output-card'>Inputs cleared. Ready for your next question.</div>" # Reset output to placeholder
1136
  ),
1137
  inputs=[], outputs=[api_key_input, query_input, state_input, output]
1138
  )
1139
- logging.info("Completely new, cohesive, dynamic, and legible Gradio interface created with Legal Console theme.")
1140
- return demo
 
1141
  # --- Main Execution Block (remains untouched from original logic) ---
1142
  if __name__ == "__main__":
1143
  logging.info("Starting Landlord-Tenant Rights Bot application...")
1144
  try:
1145
  SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
1146
- DEFAULT_PDF_PATH = os.path.join(SCRIPT_DIR, "tenant-landlord.pdf")
1147
  DEFAULT_DB_PATH = os.path.join(SCRIPT_DIR, "chroma_db")
1148
 
1149
- # Use environment variables if set, otherwise default paths
1150
  PDF_PATH = os.getenv("PDF_PATH", DEFAULT_PDF_PATH)
1151
  VECTOR_DB_PATH = os.getenv("VECTOR_DB_PATH", DEFAULT_DB_PATH)
1152
 
1153
- # Ensure directories exist
1154
  os.makedirs(os.path.dirname(VECTOR_DB_PATH), exist_ok=True)
1155
 
1156
- # Validate PDF file existence and readability before proceeding
1157
  logging.info(f"Attempting to load PDF from: {PDF_PATH}")
1158
  if not os.path.exists(PDF_PATH):
1159
  logging.error(f"FATAL: PDF file not found at the specified path: {PDF_PATH}")
@@ -1164,21 +836,37 @@ if __name__ == "__main__":
1164
  logging.error(f"FATAL: PDF file at '{PDF_PATH}' exists but is not readable. Check file permissions.")
1165
  print(f"\n--- PERMISSION ERROR ---\nPDF file ('{os.path.basename(PDF_PATH)}') found but not readable at: {PDF_PATH}\nPlease check file permissions (e.g., using 'chmod +r' in terminal).\n---------------------------\n")
1166
  exit(1)
1167
-
1168
  logging.info(f"PDF file '{os.path.basename(PDF_PATH)}' found and is readable.")
1169
 
1170
- # Initialize Vector Database and RAG System
1171
  vector_db_instance = VectorDatabase(persist_directory=VECTOR_DB_PATH)
1172
  rag = RAGSystem(vector_db=vector_db_instance)
1173
- rag.load_pdf(PDF_PATH) # Load/process PDF data into the vector DB
 
 
 
 
 
1174
 
1175
- # Create and launch the Gradio interface
1176
  app_interface = rag.gradio_interface()
1177
- SERVER_PORT = 7860
 
1178
  logging.info(f"Launching Gradio app on http://0.0.0.0:{SERVER_PORT}")
1179
- print(f"\n--- Gradio App Running ---\nAccess at: http://localhost:{SERVER_PORT}\n(Share link will be available if you use share=True)\n--------------------------\n")
1180
- app_interface.launch(server_name="0.0.0.0", server_port=SERVER_PORT, share=True)
1181
-
 
 
 
 
 
 
 
 
 
 
 
 
1182
  except Exception as e:
1183
  logging.error(f"Application startup failed: {str(e)}", exc_info=True)
1184
  print(f"\n--- FATAL STARTUP ERROR ---\n{str(e)}\nCheck logs for more details.\n---------------------------\n")
 
6
 
7
  import gradio as gr
8
  try:
9
+ from vector_db import VectorDatabase # Assuming this is in the same directory or installed
10
  except ImportError:
11
  print("Error: Could not import VectorDatabase from vector_db.py.")
12
  print("Please ensure vector_db.py exists in the same directory and is correctly defined.")
 
72
  for statute in statutes:
73
  statute = statute.strip()
74
  if '§' in statute and any(char.isdigit() for char in statute):
75
+ if not re.match(r'^\([\w\.]+\)$', statute) and 'http' not in statute:
76
  if len(statute) > 5:
77
  valid_statutes.append(statute)
78
 
 
189
  error_message = "Error: The request was too long for the AI model. This can happen with very complex questions or extensive retrieved context."
190
  details = "Try simplifying your question or asking about a more specific aspect."
191
  elif "timeout" in str(e).lower():
192
+ error_message = "Error: The request to the AI model timed out. The service might be busy."
193
+ details = "Please try again in a few moments."
194
 
195
  formatted_error = f"<div class='error-message'><span class='error-icon'>❌</span>{error_message}</div>"
196
  if details:
 
220
  raise FileNotFoundError(f"PDF file not found: {pdf_path}")
221
  try:
222
  logging.info(f"Attempting to load/verify data from PDF: {pdf_path}")
223
+ # Assuming process_and_load_pdf is part of VectorDatabase and correctly implemented
224
  num_states_processed = self.vector_db.process_and_load_pdf(pdf_path)
225
  doc_count = self.vector_db.document_collection.count()
226
  state_count = self.vector_db.state_collection.count()
 
243
  def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
244
  # Basic client-client-side validation for immediate feedback
245
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
246
+ 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'>Get one here</a>.</div>"
247
  if not state or state == "Select a state..." or "Error" in state:
248
  return "<div class='error-message'><span class='error-icon'>⚠️</span>Please select a valid state from the dropdown.</div>"
249
  if not query or not query.strip():
 
255
 
256
  # Check if the answer already contains an error message (from deeper within process_query)
257
  if "<div class='error-message'>" in answer:
258
+ return answer # Return the pre-formatted error message directly
259
  else:
260
+ # Format the successful response with the new UI structure
261
+ formatted_response = f"<div class='response-header'><span class='response-icon'>📜</span>Response for {state}</div><hr class='divider'>{answer}"
262
+ return formatted_response
263
 
264
  try:
265
  available_states_list = self.get_states()
 
282
  example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
283
  # Add a generic example if no specific state examples match or if list is empty
284
  if not example_queries:
285
+ example_queries.append(["What basic rights do tenants have?", available_states_list[0] if available_states_list else "California"])
286
  else: # Fallback if states list is problematic
287
  example_queries.append(["What basic rights do tenants have?", "California"])
288
 
 
292
  @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Playfair+Display:wght@700;800;900&display=swap');
293
  :root {
294
  /* Dark Theme Colors (Default: Legal Noir) */
295
+ --app-bg-dark: #0F0F1A;
296
+ --header-bg-dark: #1A1A2B;
297
+ --main-card-bg-dark: #1E1E30;
298
+ --card-section-bg-dark: #2A2A40;
299
+ --text-primary-dark: #EAEAF0;
300
+ --text-secondary-dark: #A0A0B0;
301
+ --accent-main-dark: #FFC107;
302
+ --accent-hover-dark: #E0A800;
303
+ --border-dark: #3F3F5A;
304
+ --shadow-dark: 0 1.2rem 3.5rem rgba(0,0,0,0.6);
305
+ --focus-ring-dark: rgba(255, 193, 7, 0.4);
306
+ --error-bg-dark: #4D001A;
307
+ --error-text-dark: #FFB3C2;
308
  --error-border-dark: #990026;
309
  /* Light Theme Colors (Alternative: Legal Lumen) */
310
+ --app-bg-light: #F0F2F5;
311
+ --header-bg-light: #E0E4EB;
312
+ --main-card-bg-light: #FFFFFF;
313
+ --card-section-bg-light: #F8F9FA;
314
+ --text-primary-light: #212529;
315
+ --text-secondary-light: #6C757D;
316
+ --accent-main-light: #007bff;
317
+ --accent-hover-light: #0056b3;
318
+ --border-light: #DDE2E8;
319
+ --shadow-light: 0 0.8rem 2.5rem rgba(0,0,0,0.15);
320
  --focus-ring-light: rgba(0, 123, 255, 0.3);
321
  --error-bg-light: #F8D7DA;
322
  --error-text-light: #721C24;
323
  --error-border-light: #F5C6CB;
324
  /* General Styling Variables */
325
  --font-family-main: 'Inter', sans-serif;
326
+ --font-family-header: 'Playfair Display', serif;
327
  --radius-xs: 4px;
328
  --radius-sm: 8px;
329
  --radius-md: 12px;
330
+ --radius-lg: 24px;
331
+ --transition-speed: 0.4s ease-in-out;
332
+ --padding-xl: 4.5rem;
333
  --padding-lg: 3.5rem;
334
  --padding-md: 2.5rem;
335
  --padding-sm: 1.8rem;
336
  }
 
337
  body, .gradio-container {
338
  font-family: var(--font-family-main) !important;
339
+ background: var(--app-bg-dark) !important;
340
  color: var(--text-primary-dark) !important;
341
+ margin: 0; padding: 0; min-height: 100vh; font-size: 16px;
342
+ line-height: 1.75; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale;
 
 
 
 
 
343
  transition: background var(--transition-speed), color var(--transition-speed);
344
  }
345
  * { box-sizing: border-box; }
346
  @media (prefers-color-scheme: light) {
347
+ body, .gradio-container { background: var(--app-bg-light) !important; color: var(--text-primary-light) !important; }
 
 
 
348
  }
 
349
  .gradio-container > .flex.flex-col {
350
+ max-width: 1120px; margin: 0 auto !important; padding: 0 !important;
351
+ gap: 0 !important; transition: padding var(--transition-speed);
 
 
 
352
  }
 
353
  .app-header-wrapper {
354
+ background: var(--header-bg-dark); color: var(--text-primary-dark) !important;
355
+ padding: var(--padding-xl) var(--padding-lg) !important; text-align: center !important;
356
+ border-bottom-left-radius: var(--radius-lg); border-bottom-right-radius: var(--radius-lg);
357
+ box-shadow: var(--shadow-dark); position: relative; overflow: hidden; z-index: 10;
 
 
 
 
 
 
358
  transition: background var(--transition-speed), box-shadow var(--transition-speed);
359
+ margin-bottom: 2.5rem; border: 1px solid var(--border-dark); border-top: none;
360
+ max-width: 1120px; margin-left: auto; margin-right: auto; width: 100%;
 
 
 
 
 
 
361
  }
362
  @media (prefers-color-scheme: light) {
363
+ .app-header-wrapper { background: var(--header-bg-light); color: var(--text-primary-light) !important; box-shadow: var(--shadow-light); border: 1px solid var(--border-light); border-top: none; }
 
 
 
 
 
 
 
 
 
 
 
 
 
364
  }
365
+ .app-header { display: flex; flex-direction: column; align-items: center; position: relative; z-index: 1; }
366
  .app-header-logo {
367
+ font-size: 5.5rem; margin-bottom: 0.8rem; line-height: 1;
368
+ filter: drop-shadow(0 0 15px var(--accent-main-dark));
369
+ transform: translateY(-40px); opacity: 0;
 
 
370
  animation: fadeInSlideDown 1.5s ease-out forwards; animation-delay: 0.3s;
371
  transition: filter var(--transition-speed);
372
  }
373
  @media (prefers-color-scheme: light) { .app-header-logo { filter: drop-shadow(0 0 10px var(--accent-main-light)); } }
374
  .app-header-title {
375
+ font-family: var(--font-family-header) !important; font-size: 4.2rem; font-weight: 900;
376
+ margin: 0 0 0.8rem 0; letter-spacing: -0.07em;
377
+ text-shadow: 0 8px 16px rgba(0,0,0,0.5);
 
 
 
378
  transform: translateY(-40px); opacity: 0;
379
  animation: fadeInSlideDown 1.5s ease-out forwards; animation-delay: 0.6s;
380
  transition: text-shadow var(--transition-speed), color var(--transition-speed);
381
  }
382
  @media (prefers-color-scheme: light) { .app-header-title { text-shadow: 0 6px 12px rgba(0,0,0,0.25); } }
383
  .app-header-tagline {
384
+ font-family: var(--font-family-main) !important; font-size: 1.6rem; font-weight: 300;
385
+ opacity: 0.9; max-width: 900px;
 
 
 
386
  transform: translateY(-40px); opacity: 0;
387
  animation: fadeInSlideDown 1.5s ease-out forwards; animation-delay: 0.9s;
388
  transition: opacity var(--transition-speed);
389
  }
390
+ @keyframes fadeInSlideDown { from { opacity: 0; transform: translateY(-40px); } to { opacity: 1; transform: translateY(0); } }
 
 
 
 
 
391
  .main-dashboard-container {
392
+ background: var(--main-card-bg-dark) !important; border-radius: var(--radius-lg);
393
+ box-shadow: var(--shadow-dark); border: 1px solid var(--border-dark) !important;
394
+ padding: var(--padding-lg) !important; margin: 0 auto 0.8rem auto;
395
+ z-index: 1; position: relative;
 
 
 
 
396
  transition: background var(--transition-speed), border-color var(--transition-speed), box-shadow var(--transition-speed);
397
+ display: flex; flex-direction: column; gap: 2.5rem; max-width: 1120px;
 
 
 
 
398
  }
399
  @media (prefers-color-scheme: light) {
400
+ .main-dashboard-container { background: var(--main-card-bg-light) !important; box-shadow: var(--shadow-light); border: 1px solid var(--border-light) !important; }
 
 
 
 
401
  }
 
402
  .dashboard-card-section {
403
+ background: var(--card-section-bg-dark) !important; border-radius: var(--radius-md);
404
+ border: 1px solid var(--border-dark); padding: var(--padding-md);
405
+ box-shadow: inset 0 0 10px rgba(0,0,0,0.2);
 
 
406
  transition: background var(--transition-speed), border-color var(--transition-speed), box-shadow var(--transition-speed);
407
+ display: flex; flex-direction: column; gap: 1.5rem;
 
 
408
  }
409
  @media (prefers-color-scheme: light) {
410
+ .dashboard-card-section { background: var(--card-section-bg-light) !important; border: 1px solid var(--border-light); box-shadow: inset 0 0 8px rgba(0,0,0,0.05); }
 
 
 
 
411
  }
412
+ .section-title {
413
+ font-family: var(--font-family-header) !important; font-size: 2.8rem !important; font-weight: 800 !important;
414
+ color: var(--text-primary-dark) !important; text-align: center !important;
415
+ margin: 0 auto 1.8rem auto !important; padding-bottom: 0.8rem !important;
416
+ border-bottom: 2px solid var(--border-dark) !important; width: 100%;
 
 
 
 
 
 
417
  transition: color var(--transition-speed), border-color var(--transition-speed);
418
  }
419
+ .sub-section-title { /* MODIFIED FOR CENTERING */
420
  font-family: var(--font-family-header) !important;
421
+ font-size: 2.5rem !important;
422
  font-weight: 700 !important;
423
  color: var(--text-primary-dark) !important;
424
+ text-align: center !important;
425
+ margin-top: 1.5rem !important;
426
+ margin-bottom: 0.8rem !important;
427
  transition: color var(--transition-speed);
428
+ display: block !important; /* ADDED: Ensure it's a block element */
429
+ width: 100% !important; /* ADDED: Ensure it takes full width for centering */
430
  }
431
  @media (prefers-color-scheme: light) {
432
+ .section-title, .sub-section-title { color: var(--text-primary-light) !important; border-bottom-color: var(--border-light) !important; }
 
 
 
433
  }
 
434
  .dashboard-card-section p, .output-content-wrapper p {
435
+ font-size: 1.15rem; line-height: 1.8; color: var(--text-secondary-dark);
436
+ margin-bottom: 1.2rem; transition: color var(--transition-speed);
 
 
 
437
  }
438
  .dashboard-card-section a, .output-content-wrapper a {
439
+ color: var(--accent-main-dark); text-decoration: none; font-weight: 500;
 
 
440
  transition: color var(--transition-speed), text-decoration var(--transition-speed);
441
  }
442
+ .dashboard-card-section a:hover, .output-content-wrapper a:hover { color: var(--accent-hover-dark); text-decoration: underline; }
443
+ .dashboard-card-section strong, .output-content-wrapper strong { font-weight: 700; color: var(--text-primary-dark); transition: color var(--transition-speed); }
 
 
 
 
 
 
 
444
  @media (prefers-color-scheme: light) {
445
  .dashboard-card-section p, .output-content-wrapper p { color: var(--text-secondary-light); }
446
  .dashboard-card-section a, .output-content-wrapper a { color: var(--accent-main-light); }
447
  .dashboard-card-section a:hover, .output-content-wrapper a:hover { color: var(--accent-hover-light); }
448
  .dashboard-card-section strong, .output-content-wrapper strong { color: var(--text-primary-light); }
449
  }
450
+ .section-divider { border: none; border-top: 1px solid var(--border-dark); margin: 2rem 0 !important; transition: border-color var(--transition-speed); }
 
 
 
 
 
 
451
  @media (prefers-color-scheme: light) { .section-divider { border-top: 1px solid var(--border-light); } }
452
+ .input-field-group { margin-bottom: 1rem; }
453
+ .input-row { display: flex; gap: 1.8rem; flex-wrap: wrap; margin-bottom: 1rem; }
454
+ .input-field { flex: 1; }
 
 
 
 
 
 
 
 
 
455
  .gradio-input-label {
456
+ font-size: 1.2rem !important; font-weight: 500 !important; color: var(--text-primary-dark) !important;
457
+ margin-bottom: 0.8rem !important; display: block !important; transition: color var(--transition-speed);
 
 
 
 
 
 
 
 
 
 
458
  }
459
+ .gradio-input-info { font-size: 1.0rem !important; color: var(--text-secondary-dark) !important; margin-top: 0.6rem; transition: color var(--transition-speed); }
460
  @media (prefers-color-scheme: light) {
461
  .gradio-input-label { color: var(--text-primary-light) !important; }
462
  .gradio-input-info { color: var(--text-secondary-light) !important; }
463
  }
464
+ .gradio-textbox textarea, .gradio-dropdown select, .gradio-textbox input[type=password] {
465
+ border: 2px solid var(--border-dark) !important; border-radius: var(--radius-md) !important;
466
+ padding: 1.3rem 1.6rem !important; font-size: 1.15rem !important;
467
+ background: var(--main-card-bg-dark) !important; color: var(--text-primary-dark) !important;
468
+ width: 100% !important; box-shadow: inset 0 1px 3px rgba(0,0,0,0.3);
 
 
 
 
 
 
 
469
  transition: border-color var(--transition-speed), box-shadow var(--transition-speed), background var(--transition-speed), color var(--transition-speed);
470
  }
471
+ .gradio-textbox textarea { min-height: 180px; }
472
+ .gradio-textbox textarea::placeholder, .gradio-textbox input[type=password]::placeholder { color: #808090 !important; }
473
+ .gradio-textbox textarea:focus, .gradio-dropdown select:focus, .gradio-textbox input[type=password]:focus {
 
 
 
474
  border-color: var(--accent-main-dark) !important;
475
+ box-shadow: 0 0 0 6px var(--focus-ring-dark) !important, inset 0 1px 3px rgba(0,0,0,0.4);
476
  outline: none !important;
477
  }
478
  @media (prefers-color-scheme: light) {
479
+ .gradio-textbox textarea, .gradio-dropdown select, .gradio-textbox input[type=password] {
480
+ border: 2px solid var(--border-light) !important; background: var(--main-card-bg-light) !important;
481
+ color: var(--text-primary-light) !important; box-shadow: inset 0 1px 3px rgba(0,0,0,0.1);
 
 
 
 
482
  }
483
+ .gradio-textbox textarea:focus, .gradio-dropdown select:focus, .gradio-textbox input[type=password]:focus {
 
 
484
  border-color: var(--accent-main-light) !important;
485
  box-shadow: 0 0 0 6px var(--focus-ring-light) !important, inset 0 1px 3px rgba(0,0,0,0.2);
486
  }
487
  }
 
488
  .gradio-dropdown select {
489
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
490
  background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22%23A0A0B0%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M5.293%207.293a1%201%200%20011.414%200L10%2010.586l3.293-3.293a1%201%200%20111.414%201.414l-4%204a1%201%200%2001-1.414%200l-4-4a1%201%200%20010-1.414z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
491
+ background-repeat: no-repeat; background-position: right 1.8rem center; background-size: 1.4em;
 
 
492
  padding-right: 5rem !important;
493
  }
494
  @media (prefers-color-scheme: light) {
 
496
  background-image: url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2020%2020%22%20fill%3D%22%236C757D%22%3E%3Cpath%20fill-rule%3D%22evenodd%22%20d%3D%22M5.293%207.293a1%201%200%20011.414%200L10%2010.586l3.293-3.293a1%201%200%20111.414%201.414l-4%204a1%201%200%2001-1.414%200l-4-4a1%201%200%20010-1.414z%22%20clip-rule%3D%22evenodd%22%2F%3E%3C%2Fsvg%3E');
497
  }
498
  }
499
+ .button-row { display: flex; gap: 2rem; margin-top: 2rem; flex-wrap: wrap; justify-content: flex-end; }
 
 
 
 
 
 
 
500
  .gradio-button {
501
+ border-radius: var(--radius-md) !important; padding: 1.2rem 2.8rem !important;
502
+ font-size: 1.15rem !important; font-weight: 600 !important;
503
+ border: 1px solid transparent !important; box-shadow: 0 6px 20px rgba(0,0,0,0.35);
504
+ transition: all var(--transition-speed) cubic-bezier(0.0, 0.0, 0.2, 1);
 
 
 
 
 
 
 
505
  }
506
+ .gradio-button:hover:not(:disabled) { transform: translateY(-6px); box-shadow: 0 12px 28px rgba(0,0,0,0.45) !important; }
507
  .gradio-button:active:not(:disabled) { transform: translateY(-3px); }
508
  .gradio-button:disabled {
509
+ background: #3A3A50 !important; color: #6A6A80 !important;
510
+ box-shadow: none !important; border-color: #4A4A60 !important; cursor: not-allowed;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
511
  }
512
+ .gr-button-primary { background: var(--accent-main-dark) !important; color: var(--header-bg-dark) !important; border-color: var(--accent-main-dark) !important; }
513
+ .gr-button-primary:hover:not(:disabled) { background: var(--accent-hover-dark) !important; border-color: var(--accent-hover-dark) !important; }
514
+ .gr-button-secondary { background: transparent !important; color: var(--text-secondary-dark) !important; border: 2px solid var(--border-dark) !important; box-shadow: none !important; }
515
+ .gr-button-secondary:hover:not(:disabled) { background: rgba(255, 193, 7, 0.15) !important; color: var(--accent-main-dark) !important; border-color: var(--accent-main-dark) !important; }
516
  @media (prefers-color-scheme: light) {
517
  .gradio-button { box-shadow: 0 6px 20px rgba(0,0,0,0.1); }
518
  .gradio-button:hover:not(:disabled) { box-shadow: 0 12px 28px rgba(0,0,0,0.2) !important; }
519
+ .gradio-button:disabled { background: #E9ECEF !important; color: #ADB5BD !important; border-color: #DEE2E6 !important; }
520
+ .gr-button-primary { background: var(--accent-main-light) !important; color: #FFFFFF !important; border-color: var(--accent-main-light) !important; }
521
+ .gr-button-primary:hover:not(:disabled) { background: var(--accent-hover-light) !important; border-color: var(--accent-hover-light) !important; }
522
+ .gr-button-secondary { background: transparent !important; color: var(--text-secondary-light) !important; border: 2px solid var(--border-light) !important; }
523
+ .gr-button-secondary:hover:not(:disabled) { background: rgba(0, 123, 255, 0.1) !important; color: var(--accent-main-light) !important; border-color: var(--accent-main-light) !important; }
524
+ }
525
+ .output-card { padding: 0 !important; margin-top: 0 !important; margin-bottom: 0 !important; }
526
+ .output-card .response-header {
527
+ font-size: 1.8rem; font-weight: 700; color: var(--text-primary-dark);
528
+ margin: 0 0 1rem 0; display: flex; align-items: center; gap: 1.2rem;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
529
  transition: color var(--transition-speed);
530
  }
531
+ .output-card .response-icon { font-size: 2rem; color: var(--accent-main-dark); transition: color var(--transition-speed); }
532
+ .output-card .divider { border: none; border-top: 1px solid var(--border-dark); margin: 1.5rem 0 1.8rem 0; transition: border-color var(--transition-speed); }
533
+ .output-card .output-content-wrapper { font-size: 1.15rem; line-height: 1.8; color: var(--text-primary-dark); transition: color var(--transition-speed); }
534
  .output-card .output-content-wrapper p { margin-bottom: 1rem; }
535
+ .output-card .output-content-wrapper ul, .output-card .output-content-wrapper ol { margin-left: 2.2rem; margin-bottom: 1.2rem; padding-left: 0; list-style-type: disc; }
 
 
 
 
 
 
536
  .output-card .output-content-wrapper ol { list-style-type: decimal; }
537
  .output-card .output-content-wrapper li { margin-bottom: 0.8rem; }
538
  .output-card .output-content-wrapper strong { font-weight: 700; }
539
+ .output-card .output-content-wrapper a { color: var(--accent-main-dark); text-decoration: underline; }
540
+ .output-card .output-content-wrapper a:hover { color: var(--accent-hover-dark); }
 
 
 
 
 
541
  @media (prefers-color-scheme: light) {
542
  .output-card .response-header { color: var(--text-primary-light); }
543
  .output-card .response-icon { color: var(--accent-main-light); }
 
546
  .output-card .output-content-wrapper a { color: var(--accent-main-light); }
547
  .output-card .output-content-wrapper a:hover { color: var(--accent-hover-light); }
548
  }
549
+ .output-card .error-message {
550
+ padding: 1.5rem 2rem; margin-top: 1.5rem; font-size: 1.1rem;
551
+ border-radius: var(--radius-md); background: var(--error-bg-dark);
552
+ color: var(--error-text-dark); border: 2px solid var(--error-border-dark);
553
+ display: flex; align-items: flex-start; gap: 1.5em;
 
 
 
 
 
 
 
554
  transition: background var(--transition-speed), color var(--transition-speed), border-color var(--transition-speed);
555
  }
556
+ .output-card .error-message .error-icon { font-size: 1.8rem; line-height: 1; padding-top: 0.1em; }
557
+ .output-card .error-details { font-size: 0.95rem; margin-top: 0.8rem; opacity: 0.9; word-break: break-word; }
 
 
 
 
 
 
 
 
 
558
  @media (prefers-color-scheme: light) {
559
+ .output-card .error-message { background: var(--error-bg-light); color: var(--error-text-light); border: 2px solid var(--error-border-light); }
 
 
 
 
560
  }
561
+ .output-card .placeholder {
562
+ padding: 2.5rem 2rem; font-size: 1.2rem; border-radius: var(--radius-md);
563
+ border: 3px dashed var(--border-dark); color: var(--text-secondary-dark);
564
+ text-align: center; opacity: 0.8;
 
 
 
 
 
565
  transition: border-color var(--transition-speed), color var(--transition-speed);
566
  }
567
  @media (prefers-color-scheme: light) {
568
+ .output-card .placeholder { border-color: var(--border-light); color: var(--text-secondary-light); }
 
 
 
569
  }
 
 
570
  .examples-section .gr-examples-table {
571
+ border-radius: var(--radius-md) !important; border: 1px solid var(--border-dark) !important;
572
+ overflow: hidden; background: var(--card-section-bg-dark) !important;
573
+ box-shadow: inset 0 0 10px rgba(0,0,0,0.2);
 
 
574
  transition: border-color var(--transition-speed), background var(--transition-speed), box-shadow var(--transition-speed);
575
  }
576
  @media (prefers-color-scheme: light) {
577
+ .examples-section .gr-examples-table { border: 1px solid var(--border-light) !important; background: var(--card-section-bg-light) !important; box-shadow: inset 0 0 8px rgba(0,0,0,0.05); }
 
 
 
 
 
 
 
 
 
 
578
  }
579
+ .examples-section .gr-examples-table th, .examples-section .gr-examples-table td { padding: 1rem 1.2rem !important; font-size: 1.05rem !important; border: none !important; }
580
  .examples-section .gr-examples-table th {
581
+ background: var(--header-bg-dark) !important; color: var(--text-primary-dark) !important;
582
+ font-weight: 600 !important; text-align: left;
 
 
583
  transition: background var(--transition-speed), color var(--transition-speed);
584
  }
585
  .examples-section .gr-examples-table td {
586
+ background: var(--card-section-bg-dark) !important; color: var(--text-primary-dark) !important;
587
+ border-top: 1px solid var(--border-dark) !important; cursor: pointer;
 
 
588
  transition: background var(--transition-speed), color var(--transition-speed), border-color var(--transition-speed);
589
  }
590
+ .examples-section .gr-examples-table tr:hover td { background: rgba(255, 193, 7, 0.1) !important; }
 
 
591
  .examples-section .gr-examples-table tr:first-child td { border-top: none !important; }
592
  @media (prefers-color-scheme: light) {
593
  .examples-section .gr-examples-table { border: 1px solid var(--border-light) !important; background: var(--card-section-bg-light) !important; }
 
595
  .examples-section .gr-examples-table td { background: var(--card-section-bg-light) !important; color: var(--text-primary-light) !important; border-top: 1px solid var(--border-light) !important; }
596
  .examples-section .gr-examples-table tr:hover td { background: rgba(0, 123, 255, 0.08) !important; }
597
  }
 
598
  .app-footer-wrapper {
599
+ background: var(--header-bg-dark); border-top: 1px solid var(--border-dark) !important;
600
+ margin-top: 0.5rem; padding-top: 2.5rem; padding-bottom: 2.5rem;
601
+ border-top-left-radius: var(--radius-lg); border-top-right-radius: var(--radius-lg);
602
+ box-shadow: inset 0 8px 15px rgba(0,0,0,0.2);
 
 
 
 
603
  transition: background var(--transition-speed), border-color var(--transition-speed), box-shadow var(--transition-speed);
604
+ max-width: 1120px; margin-left: auto; margin-right: auto; width: 100%;
 
 
 
 
605
  }
606
  @media (prefers-color-scheme: light) {
607
+ .app-footer-wrapper { background: var(--header-bg-light); border-top: 1px solid var(--border-light) !important; box-shadow: inset 0 6px 12px rgba(0,0,0,0.1); }
 
 
 
 
608
  }
609
+ .app-footer { /* MODIFIED FOR RIGHT ALIGNMENT OF TEXT */
610
  padding: 0 var(--padding-lg) !important;
 
611
  display: flex;
612
  flex-direction: column;
613
+ align-items: stretch; /* MODIFIED: Make child <p> tags take full width */
614
+ max-width: 1120px;
615
+ margin: 0 auto;
616
  }
617
+ .app-footer p { /* MODIFIED FOR RIGHT ALIGNMENT */
618
  font-size: 1.05rem !important;
619
  color: var(--text-secondary-dark) !important;
620
  margin-bottom: 1rem;
621
+ /* max-width: 900px; /* REMOVED/COMMENTED: To allow full width alignment */
622
+ text-align: right !important; /* MODIFIED: Align text to the right */
623
  transition: color var(--transition-speed);
624
+ width: 100%; /* ADDED: Ensure <p> takes full width of stretched item */
625
  }
626
+ .app-footer a { color: var(--accent-main-dark) !important; font-weight: 500; transition: color var(--transition-speed), text-decoration var(--transition-speed); }
627
+ .app-footer a:hover { color: var(--accent-hover-dark) !important; text-decoration: underline; }
 
 
 
 
 
 
 
628
  @media (prefers-color-scheme: light) {
629
  .app-footer-wrapper { border-top-color: var(--border-light) !important; }
630
  .app-footer p { color: var(--text-secondary-light) !important; }
631
  .app-footer a { color: var(--accent-main-light) !important; }
632
  .app-footer a:hover { color: var(--accent-hover-light) !important; }
633
  }
 
634
  :focus-visible {
635
+ outline: 5px solid var(--accent-main-dark) !important; outline-offset: 5px;
 
636
  box-shadow: 0 0 0 8px var(--focus-ring-dark) !important;
637
+ border-radius: var(--radius-md) !important;
638
  }
639
  @media (prefers-color-scheme: light) {
640
+ :focus-visible { outline-color: var(--accent-main-light) !important; box_shadow: 0 0 0 8px var(--focus-ring-light) !important; }
 
 
 
641
  }
642
  .gradio-button span:focus { outline: none !important; }
643
+ .gr-examples .gr-label, .gr-examples button.gr-button-filter, .gr-examples .label-wrap,
644
+ .gr-examples div[data-testid*="label-text"], .gr-examples span[data-testid*="label-text"],
645
+ .gr-examples div[class*="label"], .gr-examples .gr-example-label,
646
+ .gr-examples .gr-box.gr-component.gradio-example > div:first-child:has(> span[data-testid]),
647
+ .gr-examples .gr-box.gr-component.gradio-example > div:first-child > span,
648
+ .gr-examples .gr-accordion-header, .gr-examples .gr-accordion-title, .gr-examples .gr-accordion-toggle-icon,
649
+ .gr-examples .gr-accordion-header button, .gr-examples .gr-button.gr-button-filter,
650
+ .gr-examples .gr-button.gr-button-primary.gr-button-filter,
651
+ .gr-examples .gr-examples-header, .gr-examples .gr-examples-header > * {
652
+ display: none !important; visibility: hidden !important; width: 0 !important; height: 0 !important;
653
+ overflow: hidden !important; margin: 0 !important; padding: 0 !important; border: 0 !important;
654
+ font-size: 0 !important; line-height: 0 !important; position: absolute !important;
655
+ pointer-events: none !important;
656
+ }
657
+ /* Responsive Adjustments (meticulously refined) - KEPT AS IS */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
658
  @media (max-width: 1024px) {
659
  .gradio-container > .flex.flex-col { max-width: 960px; padding: 0 1.5rem !important; }
660
+ .app-header-title { font-size: 3.8rem; } .app-header-tagline { font-size: 1.5rem; }
 
661
  .app-header-wrapper { padding: var(--padding-md) var(--padding-lg) !important; margin-bottom: 2rem; border-bottom-left-radius: var(--radius-md); border-bottom-right-radius: var(--radius-md); }
662
  .main-dashboard-container { padding: var(--padding-md) !important; margin-bottom: 0.6rem; border-radius: var(--radius-md); gap: 2rem; }
663
  .dashboard-card-section { padding: var(--padding-sm); border-radius: var(--radius-sm); }
664
  .section-title { font-size: 2.2rem !important; margin-bottom: 1.5rem !important; }
665
+ .sub-section-title { font-size: 1.8rem !important; margin-bottom: 0.7rem !important; }
666
+ .section-divider { margin: 1.8rem 0; } .input-row { gap: 1.5rem; } .input-field { min-width: 280px; }
667
+ .gradio-textbox textarea { min-height: 160px; } .output-card .response-header { font-size: 1.7rem; }
 
 
 
668
  .examples-section { padding-top: 1.2rem; }
669
  .examples-section .gr-examples-table th, .examples-section .gr-examples-table td { padding: 0.9rem 1.1rem !important; }
670
  .app-footer-wrapper { margin-top: 0.6rem; border-top-left-radius: var(--radius-md); border-top-right-radius: var(--radius-md); }
671
+ .app-footer { padding: 0 1.5rem !important; }
672
  }
673
  @media (max-width: 768px) {
674
  .gradio-container > .flex.flex-col { padding: 0 1rem !important; }
675
  .app-header-wrapper { padding: var(--padding-sm) var(--padding-md) !important; margin-bottom: 1.8rem; border-bottom-left-radius: var(--radius-md); border-bottom-right-radius: var(--radius-md); }
676
+ .app-header-logo { font-size: 4.5rem; margin-bottom: 0.6rem; } .app-header-title { font-size: 3.2rem; letter-spacing: -0.06em; }
 
677
  .app-header-tagline { font-size: 1.3rem; }
678
  .main-dashboard-container { padding: var(--padding-sm) !important; margin-bottom: 0.5rem; border-radius: var(--radius-md); gap: 1.8rem; }
679
  .dashboard-card-section { padding: 1.5rem; border-radius: var(--radius-sm); }
680
  .section-title { font-size: 2rem !important; margin-bottom: 1.2rem !important; }
681
  .sub-section-title { font-size: 1.6rem !important; margin-top: 1rem !important; margin-bottom: 0.6rem !important; }
682
+ .section-divider { margin: 1.5rem 0; } .input-row { flex-direction: column; gap: 1rem; } .input-field { min-width: 100%; }
683
+ .gradio-textbox textarea { min-height: 140px; } .button-row { justify-content: stretch; gap: 1rem; }
 
 
 
684
  .gradio-button { width: 100%; padding: 1.1rem 2rem !important; font-size: 1.1rem !important; }
685
+ .output-card .response-header { font-size: 1.5rem; } .output-card .response-icon { font-size: 1.7rem; }
 
686
  .output-card .placeholder { padding: 2.5rem 1.5rem; font-size: 1.1rem; }
687
  .examples-section { padding-top: 1.2rem; }
688
  .examples-section .gr-examples-table th, .examples-section .gr-examples-table td { padding: 0.9rem 1.1rem !important; font-size: 1.0rem !important; }
689
  .app-footer-wrapper { margin-top: 0.5rem; border-top-left-radius: var(--radius-md); border-top-right-radius: var(--radius-md); padding-top: 2rem; padding-bottom: 2rem; }
690
+ .app-footer { padding: 0 1rem !important; }
691
  }
692
  @media (max-width: 480px) {
693
  .gradio-container > .flex.flex-col { padding: 0 0.8rem !important; }
694
  .app-header-wrapper { padding: 1.2rem 1rem !important; margin-bottom: 1.5rem; border-bottom-left-radius: var(--radius-sm); border-bottom-right-radius: var(--radius-sm); }
695
+ .app-header-logo { font-size: 3.8rem; margin-bottom: 0.5rem; } .app-header-title { font-size: 2.8rem; }
 
696
  .app-header-tagline { font-size: 1.1rem; }
697
  .main-dashboard-container { padding: 1.2rem !important; margin-bottom: 0.4rem; border-radius: var(--radius-sm); gap: 1.5rem; }
698
  .dashboard-card-section { padding: 1rem; border-radius: var(--radius-xs); }
 
702
  .gradio-textbox textarea, .gradio-dropdown select, .gradio-textbox input[type=password] { font-size: 1.05rem !important; padding: 1rem 1.2rem !important; }
703
  .gradio-textbox textarea { min-height: 120px; }
704
  .gradio-button { padding: 1rem 1.5rem !important; font-size: 1rem !important; }
705
+ .output-card .response-header { font-size: 1.4rem; } .output-card .response-icon { font-size: 1.5rem; }
 
706
  .output-card .placeholder { padding: 2rem 1rem; font-size: 1.05rem; }
707
  .examples-section { padding-top: 0.8rem; }
708
  .examples-section .gr-examples-table th, .examples-section .gr-examples-table td { padding: 0.6rem 0.8rem !important; font-size: 0.95rem !important; }
709
  .app-footer-wrapper { margin-top: 0.4rem; border-top-left-radius: var(--radius-sm); border-top-right-radius: var(--radius-sm); padding-top: 1.5rem; padding-bottom: 1.5rem; }
710
+ .app-footer { padding: 0 0.8rem !important; }
711
  }
712
  """
713
 
 
729
 
730
  # --- Section 1: Introduction and Disclaimer Card ---
731
  with gr.Group(elem_classes="dashboard-card-section"):
732
+ gr.Markdown("<h3 class='sub-section-title'>Welcome & Disclaimer</h3>") # Should be centered by CSS
733
  gr.Markdown(
734
  """
735
  <p>Navigate landlord-tenant laws with ease. This assistant provides detailed, state-specific answers grounded in legal authority.</p>
 
739
 
740
  # --- Section 2: OpenAI API Key Input Card ---
741
  with gr.Group(elem_classes="dashboard-card-section"):
742
+ gr.Markdown("<h3 class='sub-section-title'>OpenAI API Key</h3>") # Should be centered by CSS
743
  api_key_input = gr.Textbox(
744
+ label="",
745
  type="password", placeholder="Enter your API key (e.g., sk-...)",
746
  info="Required to process your query. Securely used per request, not stored. <a href='https://platform.openai.com/api-keys' target='_blank'>Get one free from OpenAI</a>.", lines=1
747
  )
748
+
749
  # --- Section 3: Query Input and State Selection Card ---
750
  with gr.Group(elem_classes="dashboard-card-section"):
751
+ gr.Markdown("<h3 class='sub-section-title'>Ask Your Question</h3>") # Should be centered by CSS
752
+ with gr.Row(elem_classes="input-row"):
753
+ with gr.Column(elem_classes="input-field", scale=3):
754
  query_input = gr.Textbox(
755
  label="Question", placeholder="E.g., What are the rules for security deposit returns in my state?",
756
  lines=5, max_lines=10
757
  )
758
+ with gr.Column(elem_classes="input-field", scale=1):
759
  state_input = gr.Dropdown(
760
  label="Select State", choices=dropdown_choices, value=initial_value,
761
  allow_custom_value=False
 
766
 
767
  # --- Section 4: Output Display Card ---
768
  with gr.Group(elem_classes="dashboard-card-section"):
769
+ gr.Markdown("<h3 class='sub-section-title'>Legal Assistant's Response</h3>") # Should be centered by CSS
770
  output = gr.Markdown(
771
  value="<div class='placeholder output-card'>The answer will appear here after submitting your query.</div>",
772
+ elem_classes="output-content-wrapper output-card"
773
  )
774
+
775
  # --- Section 5: Example Questions Section (NO ACCORDION, direct display) ---
776
+ with gr.Group(elem_classes="dashboard-card-section examples-section"):
777
+ gr.Markdown("<h3 class='sub-section-title'>Example Questions to Ask</h3>") # Should be centered by CSS
778
  if example_queries:
779
  gr.Examples(
780
  examples=example_queries, inputs=[query_input, state_input],
781
  examples_per_page=5,
782
+ label="",
783
  )
784
  else:
785
  gr.Markdown("<div class='placeholder'>Sample questions could not be loaded.</div>")
786
 
787
  # --- Footer Section ---
788
  with gr.Group(elem_classes="app-footer-wrapper"):
789
+ gr.Markdown( # Text inside these <p> tags will be right-aligned by CSS
790
  """
791
  <div class="app-footer">
792
  <p>This tool is for informational purposes only and does not constitute legal advice. For legal guidance, always consult with a licensed attorney in your jurisdiction.</p>
 
803
  )
804
  clear_button.click(
805
  fn=lambda: (
806
+ "",
807
+ "",
808
+ initial_value,
809
+ "<div class='placeholder output-card'>Inputs cleared. Ready for your next question.</div>"
810
  ),
811
  inputs=[], outputs=[api_key_input, query_input, state_input, output]
812
  )
813
+ logging.info("Completely new, cohesive, dynamic, and legible Gradio interface created with Legal Console theme and UI fixes.")
814
+ return demo
815
+
816
  # --- Main Execution Block (remains untouched from original logic) ---
817
  if __name__ == "__main__":
818
  logging.info("Starting Landlord-Tenant Rights Bot application...")
819
  try:
820
  SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
821
+ DEFAULT_PDF_PATH = os.path.join(SCRIPT_DIR, "tenant-landlord.pdf")
822
  DEFAULT_DB_PATH = os.path.join(SCRIPT_DIR, "chroma_db")
823
 
 
824
  PDF_PATH = os.getenv("PDF_PATH", DEFAULT_PDF_PATH)
825
  VECTOR_DB_PATH = os.getenv("VECTOR_DB_PATH", DEFAULT_DB_PATH)
826
 
 
827
  os.makedirs(os.path.dirname(VECTOR_DB_PATH), exist_ok=True)
828
 
 
829
  logging.info(f"Attempting to load PDF from: {PDF_PATH}")
830
  if not os.path.exists(PDF_PATH):
831
  logging.error(f"FATAL: PDF file not found at the specified path: {PDF_PATH}")
 
836
  logging.error(f"FATAL: PDF file at '{PDF_PATH}' exists but is not readable. Check file permissions.")
837
  print(f"\n--- PERMISSION ERROR ---\nPDF file ('{os.path.basename(PDF_PATH)}') found but not readable at: {PDF_PATH}\nPlease check file permissions (e.g., using 'chmod +r' in terminal).\n---------------------------\n")
838
  exit(1)
839
+
840
  logging.info(f"PDF file '{os.path.basename(PDF_PATH)}' found and is readable.")
841
 
 
842
  vector_db_instance = VectorDatabase(persist_directory=VECTOR_DB_PATH)
843
  rag = RAGSystem(vector_db=vector_db_instance)
844
+
845
+ # Ensure vector_db.py has process_and_load_pdf and it's called appropriately
846
+ # Example: if rag.load_pdf handles it internally based on db content.
847
+ # If not, you might need to call vector_db_instance.process_and_load_pdf(PDF_PATH) here
848
+ # For now, assuming rag.load_pdf is sufficient as per original structure.
849
+ rag.load_pdf(PDF_PATH)
850
 
 
851
  app_interface = rag.gradio_interface()
852
+ SERVER_PORT = int(os.getenv("PORT", 7860)) # Use PORT env var if on Spaces/Cloud, else 7860
853
+
854
  logging.info(f"Launching Gradio app on http://0.0.0.0:{SERVER_PORT}")
855
+ print(f"\n--- Gradio App Running ---\nAccess at: http://localhost:{SERVER_PORT} or your public Spaces URL\n--------------------------\n")
856
+ app_interface.launch(server_name="0.0.0.0", server_port=SERVER_PORT, share=False) # share=False is typical for Spaces
857
+
858
+ except ModuleNotFoundError as e:
859
+ if "vector_db" in str(e):
860
+ logging.error(f"FATAL: Could not import VectorDatabase. Ensure 'vector_db.py' is in the same directory and 'chromadb', 'langchain', 'pypdf', 'sentence-transformers' are installed.", exc_info=True)
861
+ print(f"\n--- MISSING DEPENDENCY OR FILE ---\nCould not find/import 'vector_db.py' or one of its dependencies.\nError: {e}\nPlease ensure 'vector_db.py' is present and all required packages (chromadb, langchain, pypdf, sentence-transformers, etc.) are in your requirements.txt and installed.\n---------------------------\n")
862
+ else:
863
+ logging.error(f"Application startup failed due to a missing module: {str(e)}", exc_info=True)
864
+ print(f"\n--- FATAL STARTUP ERROR - MISSING MODULE ---\n{str(e)}\nPlease ensure all dependencies are installed.\nCheck logs for more details.\n---------------------------\n")
865
+ exit(1)
866
+ except FileNotFoundError as e:
867
+ logging.error(f"Application startup failed due to a missing file: {str(e)}", exc_info=True)
868
+ print(f"\n--- FATAL STARTUP ERROR - FILE NOT FOUND ---\n{str(e)}\nPlease ensure the file exists at the specified path.\nCheck logs for more details.\n---------------------------\n")
869
+ exit(1)
870
  except Exception as e:
871
  logging.error(f"Application startup failed: {str(e)}", exc_info=True)
872
  print(f"\n--- FATAL STARTUP ERROR ---\n{str(e)}\nCheck logs for more details.\n---------------------------\n")