ElMETRICO commited on
Commit
77997d0
·
verified ·
1 Parent(s): 95fb395

Polish CrossTalk AI UI: standardized spacing, contrast, and table styling

Browse files
Files changed (1) hide show
  1. app.py +274 -116
app.py CHANGED
@@ -261,182 +261,331 @@ def safe_search(query):
261
 
262
 
263
  custom_css = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
  .gradio-container {
265
- background: #0B0F19 !important;
266
- color: #F8FAFC !important;
267
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
268
  }
269
 
270
  #shell {
271
- max-width: 1180px;
272
  margin: 0 auto;
273
- padding: 26px 18px 36px 18px;
274
  }
275
 
 
 
276
  .hero {
277
- background: #111827;
278
- border: 1px solid #243044;
279
- border-radius: 18px;
280
- padding: 26px 30px;
281
- margin-bottom: 22px;
282
- box-shadow: 0 18px 45px rgba(0,0,0,0.24);
283
  }
284
 
285
  .badge {
286
- display: inline-block;
287
- padding: 6px 11px;
 
 
288
  border-radius: 999px;
289
- background: #172033;
290
- border: 1px solid #2B3A55;
291
- color: #CBD5E1;
292
- font-weight: 700;
293
  font-size: 12px;
294
- margin-bottom: 14px;
 
 
295
  }
296
 
297
  .title {
298
- font-size: 34px;
299
- line-height: 1.12;
300
- font-weight: 850;
301
- letter-spacing: -0.035em;
302
- margin: 0 0 10px 0;
303
  color: #FFFFFF;
304
  }
305
 
306
  .subtitle {
307
- color: #CBD5E1;
308
  font-size: 15px;
309
  line-height: 1.65;
310
- max-width: 980px;
 
311
  }
312
 
313
  .info-strip {
314
  display: flex;
315
  flex-wrap: wrap;
316
- gap: 10px;
317
- margin-top: 18px;
318
  }
319
 
320
  .info-pill {
321
- padding: 9px 12px;
322
- border-radius: 10px;
323
- background: #0F172A;
324
- border: 1px solid #26344D;
325
- color: #E2E8F0;
326
- font-size: 13px;
327
- font-weight: 650;
328
  }
329
 
 
 
330
  .main-card {
331
- background: #111827 !important;
332
- border: 1px solid #243044 !important;
333
- border-radius: 18px !important;
334
- padding: 22px !important;
335
- box-shadow: 0 16px 38px rgba(0,0,0,0.22);
336
- margin-bottom: 18px !important;
 
 
337
  }
338
 
339
- .search-card {
340
- border-top: 4px solid #3B82F6 !important;
 
 
 
 
 
341
  }
342
 
343
- .output-card {
344
- border-top: 4px solid #22C55E !important;
 
 
 
 
 
 
 
 
 
 
 
345
  }
346
 
347
  .section-title {
348
- color: #F8FAFC;
349
- font-size: 22px;
350
- font-weight: 800;
351
- margin-bottom: 6px;
352
  }
353
 
354
  .section-subtitle {
355
- color: #94A3B8;
356
- font-size: 14px;
357
- line-height: 1.55;
358
- margin-bottom: 18px;
 
 
 
 
 
 
 
 
 
 
359
  }
360
 
361
- textarea, input {
362
- background: #0B1220 !important;
363
  color: #FFFFFF !important;
364
- border: 1px solid #334155 !important;
365
- border-radius: 12px !important;
366
  font-size: 15px !important;
 
 
367
  }
368
 
369
- textarea:focus, input:focus {
370
- border-color: #60A5FA !important;
371
- box-shadow: 0 0 0 3px rgba(59,130,246,0.18) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
372
  }
373
 
374
  button.search-button {
375
- background: #2563EB !important;
376
- border: 1px solid #2563EB !important;
377
- border-radius: 12px !important;
378
  color: #FFFFFF !important;
379
- font-weight: 800 !important;
380
- min-height: 48px !important;
 
 
 
381
  }
382
 
383
  button.search-button:hover {
384
- background: #1D4ED8 !important;
 
 
 
 
385
  }
386
 
387
  button.clear-button {
388
- background: #1F2937 !important;
389
- border: 1px solid #374151 !important;
390
- border-radius: 12px !important;
391
- color: #E5E7EB !important;
392
- font-weight: 750 !important;
393
- min-height: 48px !important;
 
 
 
394
  }
395
 
396
- .status-panel textarea {
397
- background: #07111F !important;
398
- border: 1px solid #334155 !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
399
  color: #FFFFFF !important;
400
- font-weight: 750 !important;
 
 
 
 
 
 
 
 
 
 
 
401
  }
402
 
403
  .result-note {
404
- background: #0F172A;
405
- border: 1px solid #243044;
406
- border-radius: 12px;
407
- padding: 12px 14px;
408
- color: #CBD5E1;
409
- font-size: 13px;
410
- line-height: 1.55;
411
  }
412
 
413
  .dataframe {
414
- border-radius: 12px !important;
415
  overflow: hidden !important;
416
- border: 1px solid #243044 !important;
 
417
  }
418
 
419
- .footer {
420
- text-align: center;
421
- color: #94A3B8;
422
- font-size: 13px;
423
- line-height: 1.6;
424
- margin-top: 18px;
 
425
  }
426
 
427
- #examples-block {
428
- margin-top: 14px;
429
  }
430
 
431
- .input-wide {
432
- margin-top: 8px;
433
- margin-bottom: 14px;
 
 
 
 
 
 
 
434
  }
435
 
436
- @media (max-width: 900px) {
437
  .title {
438
  font-size: 28px;
439
  }
 
 
 
 
 
 
 
 
 
440
  }
441
  """
442
 
@@ -463,19 +612,21 @@ with gr.Blocks(
463
  primary_hue="blue",
464
  secondary_hue="slate",
465
  neutral_hue="slate"
466
- )
 
467
  ) as demo:
468
 
469
  with gr.Column(elem_id="shell"):
470
  gr.HTML(
471
  """
472
  <div class="hero">
473
- <div class="badge">Full trained hybrid retrieval system</div>
474
  <h1 class="title">CrossTalk AI</h1>
475
- <div class="subtitle">
476
- A retrieval-based language identification and translation support system for low-resource ethnic languages of Bangladesh.
477
- It combines verified dictionary lookup with fine-tuned E5 semantic retrieval and FAISS vector search.
478
- </div>
 
479
  <div class="info-strip">
480
  <div class="info-pill">12 language groups</div>
481
  <div class="info-pill">70K+ indexed entries</div>
@@ -487,27 +638,30 @@ with gr.Blocks(
487
  """
488
  )
489
 
490
- # SEARCH SECTION - FULL WIDTH
491
  with gr.Column(elem_classes=["main-card", "search-card"]):
492
  gr.HTML(
493
  """
494
- <div class="section-title">Search input</div>
 
 
495
  <div class="section-subtitle">
496
- Enter an ethnic source word. Exact and base-form matches are treated as verified dictionary outputs.
 
497
  </div>
498
  """
499
  )
500
 
501
  query = gr.Textbox(
502
  label="Source word",
503
- placeholder="Example: kəkhyáŋ, Hula, Aina, aam, bajaoo",
504
  lines=1,
505
  elem_classes=["input-wide"]
506
  )
507
 
508
- with gr.Row():
509
- clear_btn = gr.Button("Clear", elem_classes=["clear-button"])
510
  submit_btn = gr.Button("Search", elem_classes=["search-button"])
 
511
 
512
  gr.Examples(
513
  examples=[
@@ -519,23 +673,26 @@ with gr.Blocks(
519
  ["unknown tribal word"]
520
  ],
521
  inputs=query,
522
- label="Examples",
523
  elem_id="examples-block"
524
  )
525
 
526
- # OUTPUT SECTION - BELOW SEARCH
527
  with gr.Column(elem_classes=["main-card", "output-card"]):
528
  gr.HTML(
529
  """
530
- <div class="section-title">Retrieval output</div>
 
 
531
  <div class="section-subtitle">
532
- The system returns verified dictionary matches first. Semantic fallback appears only when no verified match is found.
 
533
  </div>
534
  """
535
  )
536
 
537
  status = gr.Textbox(
538
- label="System status",
539
  lines=1,
540
  elem_classes=["status-panel"],
541
  value="Ready."
@@ -544,7 +701,7 @@ with gr.Blocks(
544
  summary = gr.Markdown(
545
  value="""
546
  <div class="result-note">
547
- Enter a source word and click Search. Results will appear below.
548
  </div>
549
  """
550
  )
@@ -560,7 +717,8 @@ with gr.Blocks(
560
  gr.HTML(
561
  """
562
  <div class="footer">
563
- Exact/base-form matches are verified dictionary outputs. Fine-tuned semantic fallback results are candidate suggestions only, not confirmed translations.
 
564
  </div>
565
  """
566
  )
@@ -581,7 +739,7 @@ with gr.Blocks(
581
  fn=lambda: (
582
  "",
583
  "Ready.",
584
- '<div class="result-note">Enter a source word and click Search. Results will appear below.</div>',
585
  EMPTY_RESULTS
586
  ),
587
  inputs=None,
 
261
 
262
 
263
  custom_css = """
264
+ :root {
265
+ --bg: #0A0E17;
266
+ --panel: #10151F;
267
+ --panel-border: #1E2635;
268
+ --panel-border-strong: #2A3549;
269
+ --text-primary: #F8FAFC;
270
+ --text-secondary: #94A3B8;
271
+ --text-tertiary: #64748B;
272
+ --accent-blue: #3B82F6;
273
+ --accent-blue-hover: #2563EB;
274
+ --accent-green: #22C55E;
275
+ --input-bg: #0B1120;
276
+ --radius-lg: 16px;
277
+ --radius-md: 12px;
278
+ --radius-sm: 8px;
279
+ --space-1: 8px;
280
+ --space-2: 12px;
281
+ --space-3: 16px;
282
+ --space-4: 24px;
283
+ --space-5: 32px;
284
+ }
285
+
286
+ * {
287
+ box-sizing: border-box;
288
+ }
289
+
290
  .gradio-container {
291
+ background: var(--bg) !important;
292
+ color: var(--text-primary) !important;
293
+ font-family: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif !important;
294
  }
295
 
296
  #shell {
297
+ max-width: 880px;
298
  margin: 0 auto;
299
+ padding: var(--space-5) var(--space-3) 48px var(--space-3);
300
  }
301
 
302
+ /* ---------- HERO ---------- */
303
+
304
  .hero {
305
+ background: linear-gradient(180deg, #131A29 0%, #10151F 100%);
306
+ border: 1px solid var(--panel-border);
307
+ border-radius: var(--radius-lg);
308
+ padding: 32px 34px;
309
+ margin-bottom: var(--space-4);
310
+ box-shadow: 0 20px 50px rgba(0,0,0,0.30);
311
  }
312
 
313
  .badge {
314
+ display: inline-flex;
315
+ align-items: center;
316
+ gap: 6px;
317
+ padding: 6px 12px;
318
  border-radius: 999px;
319
+ background: rgba(59,130,246,0.10);
320
+ border: 1px solid rgba(59,130,246,0.35);
321
+ color: #93C5FD;
322
+ font-weight: 650;
323
  font-size: 12px;
324
+ letter-spacing: 0.02em;
325
+ text-transform: uppercase;
326
+ margin-bottom: var(--space-3);
327
  }
328
 
329
  .title {
330
+ font-size: 36px;
331
+ line-height: 1.15;
332
+ font-weight: 800;
333
+ letter-spacing: -0.03em;
334
+ margin: 0 0 12px 0;
335
  color: #FFFFFF;
336
  }
337
 
338
  .subtitle {
339
+ color: var(--text-secondary);
340
  font-size: 15px;
341
  line-height: 1.65;
342
+ max-width: 640px;
343
+ margin: 0 0 var(--space-4) 0;
344
  }
345
 
346
  .info-strip {
347
  display: flex;
348
  flex-wrap: wrap;
349
+ gap: 8px;
 
350
  }
351
 
352
  .info-pill {
353
+ padding: 8px 13px;
354
+ border-radius: var(--radius-sm);
355
+ background: var(--input-bg);
356
+ border: 1px solid var(--panel-border);
357
+ color: #CBD5E1;
358
+ font-size: 12.5px;
359
+ font-weight: 600;
360
  }
361
 
362
+ /* ---------- CARDS ---------- */
363
+
364
  .main-card {
365
+ background: var(--panel) !important;
366
+ border: 1px solid var(--panel-border) !important;
367
+ border-radius: var(--radius-lg) !important;
368
+ padding: 28px !important;
369
+ box-shadow: 0 16px 40px rgba(0,0,0,0.24);
370
+ margin-bottom: var(--space-3) !important;
371
+ position: relative;
372
+ overflow: hidden;
373
  }
374
 
375
+ .main-card::before {
376
+ content: "";
377
+ position: absolute;
378
+ top: 0;
379
+ left: 0;
380
+ right: 0;
381
+ height: 3px;
382
  }
383
 
384
+ .search-card::before {
385
+ background: var(--accent-blue);
386
+ }
387
+
388
+ .output-card::before {
389
+ background: var(--accent-green);
390
+ }
391
+
392
+ .section-header {
393
+ display: flex;
394
+ align-items: baseline;
395
+ justify-content: space-between;
396
+ margin-bottom: 4px;
397
  }
398
 
399
  .section-title {
400
+ color: var(--text-primary);
401
+ font-size: 19px;
402
+ font-weight: 750;
403
+ letter-spacing: -0.01em;
404
  }
405
 
406
  .section-subtitle {
407
+ color: var(--text-secondary);
408
+ font-size: 13.5px;
409
+ line-height: 1.6;
410
+ margin-bottom: var(--space-3);
411
+ }
412
+
413
+ /* ---------- FORM CONTROLS ---------- */
414
+
415
+ label span {
416
+ color: var(--text-secondary) !important;
417
+ font-size: 12.5px !important;
418
+ font-weight: 650 !important;
419
+ text-transform: uppercase;
420
+ letter-spacing: 0.03em;
421
  }
422
 
423
+ textarea, input[type="text"] {
424
+ background: var(--input-bg) !important;
425
  color: #FFFFFF !important;
426
+ border: 1px solid var(--panel-border-strong) !important;
427
+ border-radius: var(--radius-md) !important;
428
  font-size: 15px !important;
429
+ padding: 12px 14px !important;
430
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
431
  }
432
 
433
+ textarea:focus, input[type="text"]:focus {
434
+ border-color: var(--accent-blue) !important;
435
+ box-shadow: 0 0 0 3px rgba(59,130,246,0.16) !important;
436
+ outline: none !important;
437
+ }
438
+
439
+ textarea::placeholder, input::placeholder {
440
+ color: var(--text-tertiary) !important;
441
+ }
442
+
443
+ .input-wide {
444
+ margin: var(--space-1) 0 var(--space-3) 0;
445
+ }
446
+
447
+ /* ---------- BUTTONS ---------- */
448
+
449
+ .button-row {
450
+ display: flex;
451
+ gap: 10px;
452
+ margin-bottom: var(--space-1);
453
  }
454
 
455
  button.search-button {
456
+ background: var(--accent-blue) !important;
457
+ border: 1px solid var(--accent-blue) !important;
458
+ border-radius: var(--radius-md) !important;
459
  color: #FFFFFF !important;
460
+ font-weight: 700 !important;
461
+ font-size: 14.5px !important;
462
+ min-height: 46px !important;
463
+ flex: 2;
464
+ transition: background 0.15s ease, transform 0.05s ease;
465
  }
466
 
467
  button.search-button:hover {
468
+ background: var(--accent-blue-hover) !important;
469
+ }
470
+
471
+ button.search-button:active {
472
+ transform: translateY(1px);
473
  }
474
 
475
  button.clear-button {
476
+ background: transparent !important;
477
+ border: 1px solid var(--panel-border-strong) !important;
478
+ border-radius: var(--radius-md) !important;
479
+ color: var(--text-secondary) !important;
480
+ font-weight: 650 !important;
481
+ font-size: 14.5px !important;
482
+ min-height: 46px !important;
483
+ flex: 1;
484
+ transition: border-color 0.15s ease, color 0.15s ease;
485
  }
486
 
487
+ button.clear-button:hover {
488
+ border-color: var(--text-secondary) !important;
489
+ color: #FFFFFF !important;
490
+ }
491
+
492
+ /* ---------- EXAMPLES ---------- */
493
+
494
+ #examples-block {
495
+ margin-top: var(--space-3);
496
+ padding-top: var(--space-3);
497
+ border-top: 1px solid var(--panel-border);
498
+ }
499
+
500
+ #examples-block .label-wrap span {
501
+ color: var(--text-tertiary) !important;
502
+ font-size: 12px !important;
503
+ text-transform: uppercase;
504
+ letter-spacing: 0.03em;
505
+ }
506
+
507
+ #examples-block button {
508
+ background: var(--input-bg) !important;
509
+ border: 1px solid var(--panel-border) !important;
510
+ border-radius: 999px !important;
511
+ color: #CBD5E1 !important;
512
+ font-size: 13px !important;
513
+ font-weight: 550 !important;
514
+ padding: 6px 14px !important;
515
+ }
516
+
517
+ #examples-block button:hover {
518
+ border-color: var(--accent-blue) !important;
519
  color: #FFFFFF !important;
520
+ }
521
+
522
+ /* ---------- STATUS + OUTPUT ---------- */
523
+
524
+ .status-panel textarea {
525
+ background: #070C16 !important;
526
+ border: 1px solid var(--panel-border) !important;
527
+ color: #E2E8F0 !important;
528
+ font-weight: 650 !important;
529
+ font-size: 13.5px !important;
530
+ min-height: 40px !important;
531
+ padding: 10px 14px !important;
532
  }
533
 
534
  .result-note {
535
+ background: var(--input-bg);
536
+ border: 1px solid var(--panel-border);
537
+ border-radius: var(--radius-md);
538
+ padding: 14px 16px;
539
+ color: var(--text-secondary);
540
+ font-size: 13.5px;
541
+ line-height: 1.6;
542
  }
543
 
544
  .dataframe {
545
+ border-radius: var(--radius-md) !important;
546
  overflow: hidden !important;
547
+ border: 1px solid var(--panel-border) !important;
548
+ font-size: 13.5px !important;
549
  }
550
 
551
+ .dataframe th {
552
+ background: #0D1420 !important;
553
+ color: var(--text-secondary) !important;
554
+ font-size: 11.5px !important;
555
+ text-transform: uppercase;
556
+ letter-spacing: 0.03em;
557
+ font-weight: 700 !important;
558
  }
559
 
560
+ .dataframe td {
561
+ color: #E2E8F0 !important;
562
  }
563
 
564
+ /* ---------- FOOTER ---------- */
565
+
566
+ .footer {
567
+ text-align: center;
568
+ color: var(--text-tertiary);
569
+ font-size: 12.5px;
570
+ line-height: 1.7;
571
+ margin-top: var(--space-4);
572
+ padding-top: var(--space-3);
573
+ border-top: 1px solid var(--panel-border);
574
  }
575
 
576
+ @media (max-width: 640px) {
577
  .title {
578
  font-size: 28px;
579
  }
580
+ .main-card {
581
+ padding: 20px !important;
582
+ }
583
+ .hero {
584
+ padding: 24px 22px;
585
+ }
586
+ .button-row {
587
+ flex-direction: column;
588
+ }
589
  }
590
  """
591
 
 
612
  primary_hue="blue",
613
  secondary_hue="slate",
614
  neutral_hue="slate"
615
+ ),
616
+ title="CrossTalk AI"
617
  ) as demo:
618
 
619
  with gr.Column(elem_id="shell"):
620
  gr.HTML(
621
  """
622
  <div class="hero">
623
+ <div class="badge">Hybrid retrieval system</div>
624
  <h1 class="title">CrossTalk AI</h1>
625
+ <p class="subtitle">
626
+ A retrieval-based language identification and translation support system for
627
+ low-resource ethnic languages of Bangladesh combining verified dictionary lookup
628
+ with fine-tuned E5 semantic retrieval and FAISS vector search.
629
+ </p>
630
  <div class="info-strip">
631
  <div class="info-pill">12 language groups</div>
632
  <div class="info-pill">70K+ indexed entries</div>
 
638
  """
639
  )
640
 
641
+ # SEARCH SECTION
642
  with gr.Column(elem_classes=["main-card", "search-card"]):
643
  gr.HTML(
644
  """
645
+ <div class="section-header">
646
+ <div class="section-title">Search</div>
647
+ </div>
648
  <div class="section-subtitle">
649
+ Enter a source word. Exact and base-form matches are treated as verified
650
+ dictionary outputs; everything else is a semantic suggestion.
651
  </div>
652
  """
653
  )
654
 
655
  query = gr.Textbox(
656
  label="Source word",
657
+ placeholder="e.g. kəkhyáŋ, Hula, Aina, aam, bajaoo",
658
  lines=1,
659
  elem_classes=["input-wide"]
660
  )
661
 
662
+ with gr.Row(elem_classes=["button-row"]):
 
663
  submit_btn = gr.Button("Search", elem_classes=["search-button"])
664
+ clear_btn = gr.Button("Clear", elem_classes=["clear-button"])
665
 
666
  gr.Examples(
667
  examples=[
 
673
  ["unknown tribal word"]
674
  ],
675
  inputs=query,
676
+ label="Try an example",
677
  elem_id="examples-block"
678
  )
679
 
680
+ # OUTPUT SECTION
681
  with gr.Column(elem_classes=["main-card", "output-card"]):
682
  gr.HTML(
683
  """
684
+ <div class="section-header">
685
+ <div class="section-title">Results</div>
686
+ </div>
687
  <div class="section-subtitle">
688
+ Verified dictionary matches appear first. Semantic fallback results are shown
689
+ only when no verified match exists.
690
  </div>
691
  """
692
  )
693
 
694
  status = gr.Textbox(
695
+ label="Status",
696
  lines=1,
697
  elem_classes=["status-panel"],
698
  value="Ready."
 
701
  summary = gr.Markdown(
702
  value="""
703
  <div class="result-note">
704
+ Enter a source word above and click Search results will appear here.
705
  </div>
706
  """
707
  )
 
717
  gr.HTML(
718
  """
719
  <div class="footer">
720
+ Exact / base-form matches are verified dictionary outputs.
721
+ Fine-tuned semantic fallback results are candidate suggestions only, not confirmed translations.
722
  </div>
723
  """
724
  )
 
739
  fn=lambda: (
740
  "",
741
  "Ready.",
742
+ '<div class="result-note">Enter a source word above and click Search results will appear here.</div>',
743
  EMPTY_RESULTS
744
  ),
745
  inputs=None,