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