Nischal Subedi commited on
Commit
9fea218
·
1 Parent(s): 792a7aa
Files changed (1) hide show
  1. app.py +655 -324
app.py CHANGED
@@ -244,362 +244,693 @@ Answer:"""
244
 
245
  # --- GRADIO INTERFACE (NEW UI DESIGN) ---
246
  def gradio_interface(self):
247
- def query_interface_wrapper(api_key: str, query: str, state: str, mode: str) -> str:
248
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
249
- return "<div class='error-message'>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>"
250
  if not state or state is None:
251
- return "<div class='error-message'>Please select a valid state from the list.</div>"
252
  if not query or not query.strip():
253
- return "<div class='error-message'>Please enter your question in the text box.</div>"
254
  result = self.process_query(query=query, state=state, openai_api_key=api_key)
255
- answer = result.get("answer", "<div class='error-message'>An unexpected error occurred.</div>")
256
  if "<div class='error-message'>" in answer:
257
  return answer
258
  else:
259
- formatted_response = f"<div class='response-title'>Response for {state}</div><div class='response-content'>{answer}</div>"
260
- return f"<div class='output-box'>{formatted_response}</div>"
261
-
262
- def toggle_theme():
263
- is_dark = "dark-mode" in document.body.className
264
- document.body.className = "dark-mode" if not is_dark else ""
265
- return gr.update(visible=not is_dark), gr.update(visible=is_dark)
266
 
267
  try:
268
- available_states = self.get_states()
269
- print(f"DEBUG: States loaded: {available_states}")
270
- states = available_states if available_states and "Error" not in available_states[0] else ["Error: States unavailable"]
271
- initial_state = states[0] if states and "Error" not in states[0] else None
272
  except Exception as e:
273
- print(f"DEBUG: Error loading states: {e}")
274
- states = ["Error: Critical failure loading states"]
275
- initial_state = None
276
-
277
- example_queries = [
278
  ["What are the rules for security deposit returns?", "California"],
279
- ["Can a landlord enter without notice?", "New York"],
280
- ["What to do about unrepaired issues?", "Texas"],
 
 
281
  ]
 
 
 
 
 
 
 
 
282
 
283
  custom_css = """
284
- @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap');
285
-
286
- body {
287
- font-family: 'Roboto', sans-serif !important;
288
- margin: 0;
289
- padding: 0;
290
- background-color: #F5F5F5;
291
- color: #333;
292
- transition: all 0.3s ease;
293
- }
294
-
295
- .dark-mode {
296
- background-color: #1E1E1E;
297
- color: #E0E0E0;
298
- }
299
-
300
- .container {
301
- max-width: 800px;
302
- margin: 20px auto;
303
- padding: 20px;
304
- border-radius: 10px;
305
- box-shadow: 0 2px 5px rgba(0,0,0,0.1);
306
- }
307
-
308
- .dark-mode .container {
309
- box-shadow: 0 2px 5px rgba(0,0,0,0.3);
310
- }
311
-
312
- .header {
313
- text-align: center;
314
- padding: 20px;
315
- border-bottom: 2px solid #DDD;
316
- }
317
-
318
- .dark-mode .header {
319
- border-bottom: 2px solid #444;
320
- }
321
-
322
- .header h1 {
323
- margin: 0;
324
- font-size: 2.5rem;
325
- color: #FF5722;
326
- }
327
-
328
- .dark-mode .header h1 {
329
- color: #FF7043;
330
- }
331
-
332
- .theme-toggle {
333
- margin: 10px 0;
334
- text-align: center;
335
- }
336
-
337
- .theme-toggle button {
338
- padding: 10px 20px;
339
- font-size: 1rem;
340
- cursor: pointer;
341
- border: none;
342
- border-radius: 5px;
343
- background-color: #FF5722;
344
- color: white;
345
- transition: background-color 0.3s;
346
- }
347
-
348
- .theme-toggle button:hover {
349
- background-color: #E64A19;
350
- }
351
-
352
- .dark-mode .theme-toggle button {
353
- background-color: #FF7043;
354
- }
355
-
356
- .dark-mode .theme-toggle button:hover {
357
- background-color: #F4511E;
358
- }
359
-
360
- .section {
361
- margin: 20px 0;
362
- padding: 15px;
363
- background-color: white;
364
- border-radius: 5px;
365
- }
366
-
367
- .dark-mode .section {
368
- background-color: #2C2C2C;
369
- }
370
-
371
- .section label {
372
- font-size: 1.2rem;
373
- font-weight: 500;
374
- margin-bottom: 10px;
375
- display: block;
376
- }
377
-
378
- .input-area {
379
  width: 100%;
380
- padding: 12px;
381
- font-size: 1rem;
382
- border: 2px solid #CCC;
383
- border-radius: 5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
384
  box-sizing: border-box;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
385
  }
386
-
387
- .dark-mode .input-area {
388
- background-color: #333;
389
- border-color: #555;
390
- color: #E0E0E0;
391
- }
392
-
393
- .radio-group {
394
- display: grid;
395
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
396
- gap: 10px;
397
- margin-top: 10px;
398
- }
399
-
400
- .radio-group label {
401
- padding: 10px;
402
- background-color: #F0F0F0;
403
- border: 2px solid #DDD;
404
- border-radius: 5px;
405
- text-align: center;
406
- font-size: 1rem;
407
- cursor: pointer;
408
- }
409
-
410
- .dark-mode .radio-group label {
411
- background-color: #3A3A3A;
412
- border-color: #555;
413
- color: #E0E0E0;
414
- }
415
-
416
- .radio-group input[type="radio"]:checked + label {
417
- background-color: #FF5722;
418
- color: white;
419
- border-color: #E64A19;
420
- }
421
-
422
- .dark-mode .radio-group input[type="radio"]:checked + label {
423
- background-color: #FF7043;
424
- border-color: #F4511E;
425
- }
426
-
427
- .button-group {
428
- margin-top: 15px;
429
- text-align: right;
430
- }
431
-
432
- .button-group button {
433
- padding: 10px 20px;
434
- font-size: 1rem;
435
- cursor: pointer;
436
- border: none;
437
- border-radius: 5px;
438
- margin-left: 10px;
439
- }
440
-
441
- .submit-btn {
442
- background-color: #FF5722;
443
- color: white;
444
- }
445
-
446
- .submit-btn:hover {
447
- background-color: #E64A19;
448
- }
449
-
450
- .clear-btn {
451
- background-color: #B0BEC5;
452
- color: white;
453
- }
454
-
455
- .clear-btn:hover {
456
- background-color: #90A4AE;
457
- }
458
-
459
- .dark-mode .submit-btn {
460
- background-color: #FF7043;
461
- }
462
-
463
- .dark-mode .submit-btn:hover {
464
- background-color: #F4511E;
465
- }
466
-
467
- .dark-mode .clear-btn {
468
- background-color: #78909C;
469
- }
470
-
471
- .dark-mode .clear-btn:hover {
472
- background-color: #607D8B;
473
- }
474
-
475
- .output-box {
476
- margin-top: 20px;
477
- padding: 15px;
478
- border: 2px solid #DDD;
479
- border-radius: 5px;
480
- min-height: 100px;
481
- }
482
-
483
- .dark-mode .output-box {
484
- border-color: #555;
485
- background-color: #2C2C2C;
486
- }
487
-
488
- .response-title {
489
- font-size: 1.3rem;
490
- font-weight: 700;
491
- margin-bottom: 10px;
492
- color: #FF5722;
493
- }
494
-
495
- .dark-mode .response-title {
496
- color: #FF7043;
497
  }
498
-
499
- .response-content {
500
- font-size: 1rem;
501
- line-height: 1.6;
502
  }
503
-
504
  .error-message {
505
- background-color: #FFE0E0;
506
- border: 2px solid #F44336;
507
- color: #D32F2F;
508
- padding: 10px;
509
- border-radius: 5px;
510
- margin-top: 10px;
511
- }
512
-
513
- .dark-mode .error-message {
514
- background-color: #400000;
515
- border-color: #B71C1C;
516
- color: #EF5350;
517
- }
518
-
519
- .examples {
520
- margin-top: 20px;
521
- }
522
-
523
- .examples button {
524
- padding: 8px 15px;
525
- margin: 5px;
526
- font-size: 1rem;
527
- cursor: pointer;
528
- border: 2px solid #DDD;
529
- border-radius: 5px;
530
- background-color: white;
531
- }
532
-
533
- .dark-mode .examples button {
534
- border-color: #555;
535
- background-color: #3A3A3A;
536
- color: #E0E0E0;
537
- }
538
-
539
- .examples button:hover {
540
- background-color: #F5F5F5;
541
- }
542
-
543
- .dark-mode .examples button:hover {
544
- background-color: #444;
545
  }
546
-
547
- .footer {
548
- text-align: center;
549
- padding: 20px;
550
- font-size: 0.9rem;
551
- color: #666;
552
- border-top: 2px solid #DDD;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
553
  }
554
-
555
- .dark-mode .footer {
556
- color: #A0A0A0;
557
- border-top: 2px solid #444;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  }
559
  """
560
 
561
  with gr.Blocks(css=custom_css, title="Landlord-Tenant Rights Assistant") as demo:
562
- with gr.Row():
563
- with gr.Column(scale=1):
564
- gr.Markdown("<div class='header'><h1>Landlord-Tenant Rights Assistant</h1></div>")
565
- theme_toggle_btn = gr.Button("Toggle Dark Mode", elem_classes="theme-toggle")
566
-
567
- with gr.Column(elem_classes="container"):
568
- with gr.Column():
569
- gr.Markdown("This AI-powered assistant helps navigate complex landlord-tenant laws. Ask a question about your state's regulations for detailed, legally-grounded insights.")
570
-
571
- with gr.Column(elem_classes="section"):
572
- api_key_input = gr.Textbox(label="API Key", type="password", placeholder="Enter your OpenAI API key (e.g., sk-...)", elem_classes="input-area")
573
-
574
- with gr.Column(elem_classes="section"):
575
- query_input = gr.Textbox(label="Your Question", placeholder="E.g., What are the rules for security deposit returns in my state?", lines=6, elem_classes="input-area")
576
- state_input = gr.Radio(label="Select State", choices=states, value=initial_state, elem_classes="radio-group")
577
- with gr.Row(elem_classes="button-group"):
578
- clear_button = gr.Button("Clear", elem_classes="clear-btn")
579
- submit_button = gr.Button("Submit Query", elem_classes="submit-btn")
580
-
581
- with gr.Column(elem_classes="section"):
582
- output = gr.HTML(value="<div class='output-box'><div class='placeholder'>The answer will appear here after submitting your query.</div></div>")
583
-
584
- with gr.Column(elem_classes="examples"):
585
- gr.Examples(examples=example_queries, inputs=[query_input, state_input], label="Example Questions")
586
-
587
- with gr.Column():
588
- gr.Markdown("<div class='footer'><p><strong>Disclaimer:</strong> This tool is for informational purposes only and does not constitute legal advice. Consult a licensed attorney for specific guidance. Developed by Nischal Subedi.</p></div>")
589
-
590
- theme_toggle_btn.click(
591
- fn=toggle_theme,
592
- outputs=[]
593
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
594
 
595
  submit_button.click(
596
  fn=query_interface_wrapper,
597
  inputs=[api_key_input, query_input, state_input],
598
- outputs=output
 
599
  )
600
-
601
  clear_button.click(
602
- fn=lambda: ["", "", initial_state, "<div class='output-box'><div class='placeholder'>Inputs cleared. Ready for your next question.</div></div>"],
 
 
603
  inputs=[],
604
  outputs=[api_key_input, query_input, state_input, output]
605
  )
 
244
 
245
  # --- GRADIO INTERFACE (NEW UI DESIGN) ---
246
  def gradio_interface(self):
247
+ def query_interface_wrapper(api_key: str, query: str, state: str) -> str:
248
  if not api_key or not api_key.strip() or not api_key.startswith("sk-"):
249
+ 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>"
250
  if not state or state is None:
251
+ return "<div class='error-message'><span class='error-icon'></span>Please select a valid state from the list.</div>"
252
  if not query or not query.strip():
253
+ return "<div class='error-message'><span class='error-icon'></span>Please enter your question in the text box.</div>"
254
  result = self.process_query(query=query, state=state, openai_api_key=api_key)
255
+ answer = result.get("answer", "<div class='error-message'><span class='error-icon'>⚠️</span>An unexpected error occurred.</div>")
256
  if "<div class='error-message'>" in answer:
257
  return answer
258
  else:
259
+ formatted_response_content = f"<div class='response-header'><span class='response-icon'>📜</span>Response for {state}</div><hr class='divider'>{answer}"
260
+ return f"<div class='animated-output-content'>{formatted_response_content}</div>"
 
 
 
 
 
261
 
262
  try:
263
+ available_states_list = self.get_states()
264
+ print(f"DEBUG: States loaded for selection: {available_states_list}")
265
+ radio_choices = available_states_list if available_states_list and "Error" not in available_states_list[0] else ["Error: States unavailable"]
266
+ initial_value_radio = None
267
  except Exception as e:
268
+ print(f"DEBUG: Error loading states for selection: {e}")
269
+ radio_choices = ["Error: Critical failure loading states"]
270
+ initial_value_radio = None
271
+ example_queries_base = [
 
272
  ["What are the rules for security deposit returns?", "California"],
273
+ ["Can a landlord enter my apartment without notice?", "New York"],
274
+ ["My landlord hasn't made necessary repairs. What can I do?", "Texas"],
275
+ ["How much notice must a landlord give to raise rent?", "Florida"],
276
+ ["What is an implied warranty of habitability?", "Illinois"]
277
  ]
278
+ example_queries = []
279
+ if available_states_list and "Error" not in available_states_list[0] and len(available_states_list) > 0:
280
+ loaded_states_set = set(available_states_list)
281
+ example_queries = [ex for ex in example_queries_base if ex[1] in loaded_states_set]
282
+ if not example_queries:
283
+ example_queries.append(["What basic rights do tenants have?", available_states_list[0] if available_states_list else "California"])
284
+ else:
285
+ example_queries.append(["What basic rights do tenants have?", "California"])
286
 
287
  custom_css = """
288
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Poppins:wght@600;700;800&display=swap');
289
+ :root {
290
+ --primary-color: #FF8C00;
291
+ --primary-hover: #E07B00;
292
+ --background-primary: hsl(30, 100%, 99.9%);
293
+ --background-secondary: hsl(30, 100%, 96%);
294
+ --text-primary: #4A3C32;
295
+ --text-secondary: #8C7B6F;
296
+ --border-color: hsl(30, 70%, 85%);
297
+ --border-focus: #FF8C00;
298
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.08);
299
+ --shadow-md: 0 4px 10px rgba(0,0,0,0.1);
300
+ --shadow-lg: 0 10px 20px rgba(0,0,0,0.15);
301
+ --error-bg: #FFF0E0;
302
+ --error-border: #FFD2B2;
303
+ --error-text: #E05C00;
304
+ }
305
+ @media (prefers-color-scheme: dark) {
306
+ body {
307
+ --primary-color: #FFA500;
308
+ --primary-hover: #CC8400;
309
+ --background-primary: #2C2C2C;
310
+ --background-secondary: #1F1F1F;
311
+ --text-primary: #F0F0F0;
312
+ --text-secondary: #B0B0B0;
313
+ --border-color: #555555;
314
+ --border-focus: #FFA500;
315
+ --shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
316
+ --shadow-md: 0 4px 10px rgba(0,0,0,0.4);
317
+ --shadow-lg: 0 10px 20px rgba(0,0,0,0.5);
318
+ --error-bg: #400000;
319
+ --error-border: #800000;
320
+ --error-text: #FF6666;
321
+ }
322
+ }
323
+ body, html {
324
+ background-color: var(--background-secondary) !important;
325
+ color: var(--text-primary) !important;
326
+ transition: background-color 0.3s, color 0.3s;
327
+ }
328
+ .gradio-container {
329
+ max-width: 900px !important;
330
+ margin: 0 auto !important;
331
+ padding: 1.5rem !important;
332
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif !important;
333
+ background-color: var(--background-secondary) !important;
334
+ box-shadow: none !important;
335
+ color: var(--text-primary) !important;
336
+ }
337
+ .main-dashboard-container > * {
338
+ background-color: var(--background-primary) !important;
339
+ }
340
+ .app-header-wrapper {
341
+ background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
342
+ border: 2px solid var(--border-color) !important;
343
+ border-radius: 16px !important;
344
+ padding: 2.5rem 1.5rem !important;
345
+ margin-bottom: 1.5rem !important;
346
+ box-shadow: var(--shadow-md) !important;
347
+ position: relative;
348
+ overflow: hidden;
349
+ text-align: center !important;
350
+ color: var(--text-primary) !important;
351
+ }
352
+ .app-header-wrapper::before {
353
+ content: '';
354
+ position: absolute;
355
+ top: 0;
356
+ left: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
357
  width: 100%;
358
+ height: 100%;
359
+ 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%);
360
+ z-index: 0;
361
+ opacity: 0.8;
362
+ pointer-events: none;
363
+ }
364
+ .app-header-logo {
365
+ font-size: 8.5rem !important;
366
+ margin-bottom: 0.75rem !important;
367
+ display: block !important;
368
+ color: var(--primary-color) !important;
369
+ position: relative;
370
+ z-index: 1;
371
+ animation: float-icon 3s ease-in-out infinite alternate;
372
+ }
373
+ @keyframes float-icon {
374
+ 0% { transform: translateY(0px); }
375
+ 50% { transform: translateY(-5px); }
376
+ 100% { transform: translateY(0px); }
377
+ }
378
+ .app-header-title {
379
+ font-family: 'Poppins', sans-serif !important;
380
+ font-size: 3rem !important;
381
+ font-weight: 800 !important;
382
+ color: var(--text-primary) !important;
383
+ margin: 0 0 0.75rem 0 !important;
384
+ line-height: 1.1 !important;
385
+ letter-spacing: -0.03em !important;
386
+ position: relative;
387
+ z-index: 1;
388
+ display: inline-block;
389
+ max-width: 100%;
390
+ }
391
+ .app-header-tagline {
392
+ font-size: 1.25rem !important;
393
+ color: var(--text-secondary) !important;
394
+ font-weight: 400 !important;
395
+ margin: 0 !important;
396
+ max-width: 700px;
397
+ display: inline-block;
398
+ position: relative;
399
+ z-index: 1;
400
+ }
401
+ .main-dashboard-container {
402
+ display: flex !important;
403
+ flex-direction: column !important;
404
+ gap: 1.25rem !important;
405
+ }
406
+ .dashboard-card-section {
407
+ background-color: var(--background-primary) !important;
408
+ border: 2px solid var(--border-color) !important;
409
+ border-radius: 12px !important;
410
+ padding: 0 !important;
411
+ box-shadow: var(--shadow-sm) !important;
412
+ transition: all 0.3s ease-out !important;
413
+ cursor: default;
414
+ color: var(--text-primary) !important;
415
+ }
416
+ .dashboard-card-section:hover {
417
+ box-shadow: var(--shadow-md) !important;
418
+ transform: translateY(-3px) !important;
419
+ }
420
+ .full-width-center {
421
+ display: flex !important;
422
+ justify-content: center !important;
423
+ align-items: center !important;
424
+ width: 100% !important;
425
+ flex-direction: column !important;
426
+ background-color: transparent !important;
427
+ }
428
+ .section-title-gradient-bar {
429
+ background-color: var(--background-secondary) !important;
430
+ padding: 1.25rem 1.75rem !important;
431
+ border-top-left-radius: 10px !important;
432
+ border-top-right-radius: 10px !important;
433
+ margin-bottom: 0 !important;
434
+ text-align: center !important;
435
  box-sizing: border-box;
436
+ width: 100%;
437
+ color: var(--text-primary) !important;
438
+ }
439
+ .section-title {
440
+ font-family: 'Poppins', sans-serif !important;
441
+ font-size: 1.7rem !important;
442
+ font-weight: 700 !important;
443
+ color: var(--text-primary) !important;
444
+ margin: 0 !important;
445
+ padding-bottom: 0 !important;
446
+ border-bottom: 2px solid var(--border-color) !important;
447
+ line-height: 1.1 !important;
448
+ display: inline-block !important;
449
+ text-align: center !important;
450
+ letter-spacing: -0.01em !important;
451
+ }
452
+ .dashboard-card-content-area {
453
+ padding: 0 1.75rem 1.75rem 1.75rem !important;
454
+ background-color: var(--background-primary) !important;
455
+ box-sizing: border-box;
456
+ width: 100%;
457
+ color: var(--text-primary) !important;
458
+ }
459
+ .dashboard-card-section p {
460
+ line-height: 1.7 !important;
461
+ color: var(--text-primary) !important;
462
+ font-size: 1rem !important;
463
+ text-align: left !important;
464
+ background-color: transparent !important;
465
+ margin: 0 !important;
466
+ padding: 0 !important;
467
+ white-space: normal !important;
468
+ }
469
+ .dashboard-card-section strong, .dashboard-card-section b {
470
+ font-weight: 700 !important;
471
+ color: var(--primary-color) !important;
472
+ }
473
+ .gr-block, .gr-box, .gr-prose, .gr-form, .gr-panel,
474
+ .gr-columns, .gr-column,
475
+ .gradio-html, .gradio-markdown, .gradio-textbox, .gradio-radio, .gradio-button {
476
+ background-color: transparent !important;
477
+ color: var(--text-primary) !important;
478
+ white-space: normal !important;
479
+ overflow-wrap: break-word;
480
+ word-break: break-word;
481
+ }
482
+ .gradio-textbox textarea,
483
+ .gradio-textbox input,
484
+ .gradio-radio label,
485
+ .placeholder {
486
+ background-color: var(--background-primary) !important;
487
+ color: var(--text-primary) !important;
488
+ }
489
+ .gradio-textbox {
490
+ margin-bottom: 0.5rem !important;
491
+ }
492
+ .gradio-textbox textarea,
493
+ .gradio-textbox input {
494
+ border: 2px solid var(--border-color) !important;
495
+ border-radius: 8px !important;
496
+ padding: 0.85rem 1rem !important;
497
+ font-size: 0.98rem !important;
498
+ font-family: 'Inter', sans-serif !important;
499
+ color: var(--text-primary) !important;
500
+ transition: border-color 0.2s ease, box-shadow 0.2s ease !important;
501
+ box-shadow: var(--shadow-sm) !important;
502
+ }
503
+ .gradio-textbox .scroll-hide {
504
+ background-color: var(--background-primary) !important;
505
+ }
506
+ .gradio-textbox textarea:focus,
507
+ .gradio-textbox input:focus {
508
+ outline: none !important;
509
+ border-color: var(--border-focus) !important;
510
+ box-shadow: 0 0 0 4px rgba(255, 140, 0, 0.2) !important;
511
+ }
512
+ .gradio-radio {
513
+ padding: 0 !important;
514
+ margin-top: 1rem !important;
515
+ }
516
+ .gradio-radio input[type="radio"] {
517
+ display: none !important;
518
+ }
519
+ .gradio-radio label {
520
+ display: flex !important;
521
+ justify-content: center !important;
522
+ align-items: center !important;
523
+ padding: 0.75rem 1rem !important;
524
+ border: 2px solid var(--border-color) !important;
525
+ border-radius: 8px !important;
526
+ color: var(--text-primary) !important;
527
+ font-weight: 500 !important;
528
+ cursor: pointer !important;
529
+ transition: all 0.2s ease-out !important;
530
+ box-shadow: var(--shadow-sm) !important;
531
+ margin: 0.2rem 0 !important;
532
+ width: 100 !important;
533
+ box-sizing: border-box !important;
534
+ }
535
+ .gradio-radio label span.text-lg {
536
+ font-weight: 600 !important;
537
+ color: var(--text-primary) !important;
538
+ font-size: 0.98rem !important;
539
+ }
540
+ .gradio-radio label:hover {
541
+ background-color: var(--background-secondary) !important;
542
+ border-color: var(--primary-color) !important;
543
+ box-shadow: var(--shadow-md) !important;
544
+ transform: translateY(-2px) !important;
545
+ }
546
+ .gradio-radio input[type="radio"]:checked + label {
547
+ background-color: var(--primary-color) !important;
548
+ color: var(--text-primary) !important;
549
+ border-color: var(--primary-hover) !important;
550
+ box-shadow: var(--shadow-md) !important;
551
+ transform: translateY(-1px) !important;
552
+ }
553
+ .gradio-radio input[type="radio"]:checked + label span.text-lg {
554
+ color: var(--text-primary) !important;
555
+ }
556
+ .gradio-radio .gr-form {
557
+ padding: 0 !important;
558
+ }
559
+ .gradio-textbox label,
560
+ .gradio-radio > label {
561
+ font-weight: 600 !important;
562
+ color: var(--text-primary) !important;
563
+ font-size: 1rem !important;
564
+ margin-bottom: 0.6rem !important;
565
+ display: block !important;
566
+ text-align: left !important;
567
+ }
568
+ .gr-prose {
569
+ font-size: 0.9rem !important;
570
+ color: var(--text-secondary) !important;
571
+ margin-top: 0.4rem !important;
572
+ text-align: left !important;
573
+ background-color: transparent !important;
574
+ }
575
+ .input-column {
576
+ display: flex !important;
577
+ flex-direction: column !important;
578
+ gap: 1.25rem !important;
579
+ margin-bottom: 0.5rem !important;
580
+ }
581
+ .input-field {
582
+ flex: none !important;
583
+ width: 100% !important;
584
+ }
585
+ .button-row {
586
+ display: flex !important;
587
+ gap: 1rem !important;
588
+ justify-content: flex-end !important;
589
+ margin-top: 1.5rem !important;
590
+ }
591
+ .gradio-button {
592
+ padding: 0.85rem 1.8rem !important;
593
+ border-radius: 9px !important;
594
+ font-weight: 600 !important;
595
+ font-size: 1rem !important;
596
+ transition: all 0.2s ease-out !important;
597
+ cursor: pointer !important;
598
+ border: 2px solid transparent !important;
599
+ text-align: center !important;
600
+ color: var(--text-primary) !important;
601
+ }
602
+ .gr-button-primary {
603
+ background-color: var(--primary-color) !important;
604
+ color: white !important;
605
+ box-shadow: var(--shadow-sm) !important;
606
+ }
607
+ .gr-button-primary:hover {
608
+ background-color: var(--primary-hover) !important;
609
+ box-shadow: var(--shadow-md) !important;
610
+ transform: translateY(-2px) !important;
611
+ }
612
+ .gr-button-primary:active {
613
+ transform: translateY(1px) !important;
614
+ box-shadow: none !important;
615
+ }
616
+ .gr-button-secondary {
617
+ background-color: transparent !important;
618
+ color: var(--text-primary) !important;
619
+ border-color: var(--border-color) !important;
620
+ }
621
+ .gr-button-secondary:hover {
622
+ background-color: var(--background-secondary) !important;
623
+ border-color: var(--primary-color) !important;
624
+ transform: translateY(-2px) !important;
625
+ }
626
+ .gr-button-secondary:active {
627
+ transform: translateY(1px) !important;
628
+ box-shadow: none !important;
629
+ }
630
+ .output-content-wrapper {
631
+ background-color: var(--background-primary) !important;
632
+ border: 2px solid var(--border-color) !important;
633
+ border-radius: 8px !important;
634
+ padding: 1.5rem !important;
635
+ min-height: 150px !important;
636
+ color: var(--text-primary) !important;
637
+ display: flex;
638
+ flex-direction: column;
639
+ justify-content: center;
640
+ align-items: center;
641
+ }
642
+ .animated-output-content {
643
+ opacity: 0;
644
+ animation: fadeInAndSlideUp 0.7s ease-out forwards;
645
+ width: 100%;
646
+ white-space: pre-wrap;
647
+ overflow-wrap: break-word;
648
+ word-break: break-word;
649
+ text-align: left !important;
650
+ color: var(--text-primary) !important;
651
+ }
652
+ @keyframes fadeInAndSlideUp {
653
+ from { opacity: 0; transform: translateY(15px); }
654
+ to { opacity: 1; transform: translateY(0); }
655
+ }
656
+ .response-header {
657
+ font-size: 1.3rem !important;
658
+ font-weight: 700 !important;
659
+ color: var(--primary-color) !important;
660
+ margin-bottom: 0.75rem !important;
661
+ display: flex !important;
662
+ align-items: center !important;
663
+ gap: 0.6rem !important;
664
+ text-align: left !important;
665
+ width: 100%;
666
+ justify-content: flex-start;
667
  }
668
+ .response-icon {
669
+ font-size: 1.5rem !important;
670
+ color: var(--primary-color) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
671
  }
672
+ .divider {
673
+ border: none !important;
674
+ border-top: 1px dashed var(--border-color) !important;
675
+ margin: 1rem 0 !important;
676
  }
 
677
  .error-message {
678
+ background-color: var(--error-bg) !important;
679
+ border: 2px solid var(--error-border) !important;
680
+ color: var(--error-text) !important;
681
+ padding: 1.25rem !important;
682
+ border-radius: 8px !important;
683
+ display: flex !important;
684
+ align-items: flex-start !important;
685
+ gap: 0.8rem !important;
686
+ font-size: 0.95rem !important;
687
+ font-weight: 500 !important;
688
+ line-height: 1.6 !important;
689
+ text-align: left !important;
690
+ width: 100%;
691
+ box-sizing: border-box;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
692
  }
693
+ .error-message a {
694
+ color: var(--error-text) !important;
695
+ text-decoration: underline !important;
696
+ }
697
+ .error-icon {
698
+ font-size: 1.4rem !important;
699
+ line-height: 1 !important;
700
+ margin-top: 0.1rem !important;
701
+ }
702
+ .error-details {
703
+ font-size: 0.85rem !important;
704
+ color: var(--error-text) !important;
705
+ margin-top: 0.5rem !important;
706
+ opacity: 0.8;
707
+ }
708
+ .placeholder {
709
+ background-color: var(--background-primary) !important;
710
+ border: 2px dashed var(--border-color) !important;
711
+ border-radius: 8px !important;
712
+ padding: 2.5rem 1.5rem !important;
713
+ text-align: center !important;
714
+ color: var(--text-secondary) !important;
715
+ font-style: italic !important;
716
+ font-size: 1.1rem !important;
717
+ width: 100%;
718
+ box-sizing: border-box;
719
  }
720
+ .examples-section .gr-samples-table {
721
+ border: 2px solid var(--border-color) !important;
722
+ border-radius: 8px !important;
723
+ overflow: hidden !important;
724
+ margin-top: 1rem !important;
725
+ }
726
+ .examples-section .gr-samples-table th,
727
+ .examples-section .gr-samples-table td {
728
+ padding: 0.9rem !important;
729
+ border: none !important;
730
+ font-size: 0.95rem !important;
731
+ text-align: left !important;
732
+ color: var(--text-primary) !important;
733
+ }
734
+ .examples-section .gr-samples-table th {
735
+ background-color: var(--background-secondary) !important;
736
+ font-weight: 700 !important;
737
+ color: var(--text-primary) !important;
738
+ }
739
+ .examples-section .gr-samples-table td {
740
+ background-color: var(--background-primary) !important;
741
+ color: var(--text-primary) !important;
742
+ border-top: 1px solid var(--border-color) !important;
743
+ cursor: pointer !important;
744
+ transition: background 0.2s ease, transform 0.1s ease !important;
745
+ }
746
+ .examples-section .gr-samples-table tr:hover td {
747
+ background-color: var(--background-secondary) !important;
748
+ transform: translateX(5px);
749
+ }
750
+ .gr-examples .gr-label,
751
+ .gr-examples .label-wrap,
752
+ .gr-examples .gr-accordion-header {
753
+ display: none !important;
754
+ }
755
+ .app-footer-wrapper {
756
+ background: linear-gradient(145deg, var(--background-primary) 0%, var(--background-secondary) 100%) !important;
757
+ border: 2px solid var(--border-color) !important;
758
+ border-radius: 12px !important;
759
+ padding: 1.75rem !important;
760
+ margin-top: 1.5rem !important;
761
+ margin-bottom: 1.5rem !important;
762
+ text-align: center !important;
763
+ color: var(--text-primary) !important;
764
+ }
765
+ .app-footer p {
766
+ margin: 0 !important;
767
+ max-width: 90% !important;
768
+ font-size: 0.95rem !important;
769
+ color: var(--text-secondary) !important;
770
+ line-height: 1.6 !important;
771
+ background-color: transparent !important;
772
+ text-align: center !important;
773
+ white-space: normal !important;
774
+ }
775
+ .app-footer strong, .app-footer b {
776
+ font-weight: 700 !important;
777
+ color: var(--primary-color) !important;
778
+ }
779
+ .app-footer a {
780
+ color: var(--primary-color) !important;
781
+ text-decoration: underline !important;
782
+ font-weight: 600 !important;
783
+ }
784
+ .app-footer a:hover {
785
+ text-decoration: none !important;
786
+ }
787
+ @media (max-width: 768px) {
788
+ .gradio-container {
789
+ padding: 1rem !important;
790
+ }
791
+ .app-header-title {
792
+ font-size: 2.2rem !important;
793
+ }
794
+ .app-header-tagline {
795
+ font-size: 1rem !important;
796
+ }
797
+ .section-title {
798
+ font-size: 1.4rem !important;
799
+ }
800
+ .input-column {
801
+ flex-direction: column !important;
802
+ }
803
+ .button-row {
804
+ flex-direction: column !important;
805
+ }
806
+ .gradio-button {
807
+ width: 100% !important;
808
+ }
809
+ .dashboard-card-section {
810
+ }
811
+ .section-title-gradient-bar {
812
+ padding: 0.1rem 1rem !important;
813
+ }
814
+ .dashboard-card-content-area {
815
+ padding: 0 1rem 1rem 1rem !important;
816
+ }
817
+ .output-content-wrapper {
818
+ min-height: 120px !important;
819
+ }
820
+ .placeholder {
821
+ padding: 1.5rem 1rem !important;
822
+ font-size: 1rem !important;
823
+ }
824
  }
825
  """
826
 
827
  with gr.Blocks(css=custom_css, title="Landlord-Tenant Rights Assistant") as demo:
828
+ with gr.Group(elem_classes="app-header-wrapper"):
829
+ gr.Markdown(
830
+ """
831
+ <span class='app-header-logo'>⚖️</span>
832
+ <h1 class='app-header-title'>Landlord-Tenant Rights Assistant</h1>
833
+ <p class='app-header-tagline'>Empowering You with State-Specific Legal Insights</p>
834
+ """,
835
+ elem_classes="full-width-center"
836
+ )
837
+
838
+ with gr.Column(elem_classes="main-dashboard-container"):
839
+ with gr.Group(elem_classes="dashboard-card-section"):
840
+ gr.Markdown("<h3 class='section-title'>How This Assistant Works</h3>", elem_classes="full-width-center section-title-gradient-bar")
841
+ with gr.Column(elem_classes="dashboard-card-content-area"):
842
+ gr.Markdown(
843
+ """
844
+ 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.
845
+ """
846
+ )
847
+
848
+ with gr.Group(elem_classes="dashboard-card-section"):
849
+ gr.Markdown("<h3 class='section-title'>OpenAI API Key</h3>", elem_classes="full-width-center section-title-gradient-bar")
850
+ with gr.Column(elem_classes="dashboard-card-content-area"):
851
+ api_key_input = gr.Textbox(
852
+ label="API Key",
853
+ type="password",
854
+ placeholder="Enter your OpenAI API key (e.g., sk-...)",
855
+ lines=1,
856
+ elem_classes=["input-field-group"]
857
+ )
858
+ gr.Markdown(
859
+ "Required to process your query. Get one from OpenAI: [platform.openai.com/api-keys](https://platform.openai.com/api-keys)",
860
+ elem_classes="gr-prose"
861
+ )
862
+
863
+ with gr.Group(elem_classes="dashboard-card-section"):
864
+ gr.Markdown("<h3 class='section-title'>Ask Your Question</h3>", elem_classes="full-width-center section-title-gradient-bar")
865
+ with gr.Column(elem_classes="dashboard-card-content-area"):
866
+ with gr.Column(elem_classes="input-column"):
867
+ with gr.Column(elem_classes="input-field", scale=1):
868
+ query_input = gr.Textbox(
869
+ label="Your Question",
870
+ placeholder="E.g., What are the rules for security deposit returns in my state?",
871
+ lines=8,
872
+ max_lines=15,
873
+ elem_classes=["input-field-group"]
874
+ )
875
+ with gr.Column(elem_classes="input-field", scale=1):
876
+ state_input = gr.Radio(
877
+ label="Select State",
878
+ choices=radio_choices,
879
+ value=initial_value_radio,
880
+ elem_classes=["input-field-group", "gradio-radio-custom"],
881
+ interactive=True
882
+ )
883
+ with gr.Row(elem_classes="button-row"):
884
+ clear_button = gr.Button("Clear", variant="secondary", elem_classes=["gr-button-secondary"])
885
+ submit_button = gr.Button("Submit Query", variant="primary", elem_classes=["gr-button-primary"])
886
+
887
+ with gr.Group(elem_classes="dashboard-card-section"):
888
+ gr.Markdown("<h3 class='section-title'>Legal Assistant's Response</h3>", elem_classes="full-width-center section-title-gradient-bar")
889
+ with gr.Column(elem_classes="dashboard-card-content-area"):
890
+ output = gr.HTML(
891
+ value="<div class='placeholder'>The answer will appear here after submitting your query.</div>",
892
+ elem_classes="output-content-wrapper"
893
+ )
894
+
895
+ with gr.Group(elem_classes="dashboard-card-section examples-section"):
896
+ gr.Markdown("<h3 class='section-title'>Example Questions</h3>", elem_classes="full-width-center section-title-gradient-bar")
897
+ with gr.Column(elem_classes="dashboard-card-content-area"):
898
+ if example_queries:
899
+ gr.Examples(
900
+ examples=example_queries,
901
+ inputs=[query_input, state_input],
902
+ examples_per_page=5,
903
+ label=""
904
+ )
905
+ else:
906
+ gr.Markdown("<div class='placeholder'>Sample questions could not be loaded. Please ensure the vector database is populated.</div>")
907
+
908
+ with gr.Group(elem_classes="app-footer-wrapper"):
909
+ gr.Markdown(
910
+ f"""
911
+ <style>
912
+ .custom-link {{
913
+ font-weight: bold !important;
914
+ color: orange !important;
915
+ text-decoration: underline;
916
+ }}
917
+ </style>
918
+ <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>
919
+ <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>
920
+ """
921
+ )
922
 
923
  submit_button.click(
924
  fn=query_interface_wrapper,
925
  inputs=[api_key_input, query_input, state_input],
926
+ outputs=output,
927
+ api_name="submit_query"
928
  )
929
+
930
  clear_button.click(
931
+ fn=lambda: (
932
+ "", "", initial_value_radio, "<div class='placeholder'>Inputs cleared. Ready for your next question.</div>"
933
+ ),
934
  inputs=[],
935
  outputs=[api_key_input, query_input, state_input, output]
936
  )