ElMETRICO commited on
Commit
044718d
·
verified ·
1 Parent(s): 0bc11b7

Update CrossTalk AI with ChatGPT-style UX

Browse files
Files changed (1) hide show
  1. app.py +207 -358
app.py CHANGED
@@ -260,359 +260,206 @@ def safe_search(query):
260
 
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
- /* Gradio wraps every component in its own bordered/background "block".
297
- We already draw our own card + input borders, so strip the default
298
- wrapper chrome everywhere to avoid double-boxing labels/inputs. */
299
- .gradio-container .block,
300
- .gradio-container .form {
301
- background: transparent !important;
302
- border: none !important;
303
- box-shadow: none !important;
304
- padding: 0 !important;
305
  }
306
 
307
  #shell {
308
- max-width: 880px;
309
  margin: 0 auto;
310
- padding: var(--space-5) var(--space-3) 48px var(--space-3);
311
  }
312
 
313
- /* ---------- HERO ---------- */
 
 
 
 
 
 
 
314
 
315
- .hero {
316
- background: linear-gradient(180deg, #131A29 0%, #10151F 100%);
317
- border: 1px solid var(--panel-border);
318
- border-radius: var(--radius-lg);
319
- padding: 32px 34px;
320
- margin-bottom: var(--space-4);
321
- box-shadow: 0 20px 50px rgba(0,0,0,0.30);
322
  }
323
 
324
- .badge {
325
- display: inline-flex;
326
- align-items: center;
327
- gap: 6px;
328
- padding: 6px 12px;
329
  border-radius: 999px;
330
- background: rgba(59,130,246,0.10);
331
- border: 1px solid rgba(59,130,246,0.35);
332
- color: #93C5FD;
333
- font-weight: 650;
334
  font-size: 12px;
335
- letter-spacing: 0.02em;
336
- text-transform: uppercase;
337
- margin-bottom: var(--space-3);
 
 
 
338
  }
339
 
340
  .title {
341
- font-size: 36px;
342
- line-height: 1.15;
343
- font-weight: 800;
344
- letter-spacing: -0.03em;
345
- margin: 0 0 12px 0;
346
- color: #FFFFFF;
347
  }
348
 
349
  .subtitle {
350
- color: var(--text-secondary);
 
 
351
  font-size: 15px;
352
  line-height: 1.65;
353
- max-width: 640px;
354
- margin: 0 0 var(--space-4) 0;
355
- }
356
-
357
- .info-strip {
358
- display: flex;
359
- flex-wrap: wrap;
360
- gap: 8px;
361
- }
362
-
363
- .info-pill {
364
- padding: 8px 13px;
365
- border-radius: var(--radius-sm);
366
- background: var(--input-bg);
367
- border: 1px solid var(--panel-border);
368
- color: #CBD5E1;
369
- font-size: 12.5px;
370
- font-weight: 600;
371
- }
372
-
373
- /* ---------- CARDS ---------- */
374
-
375
- .main-card {
376
- background: var(--panel) !important;
377
- border: 1px solid var(--panel-border) !important;
378
- border-radius: var(--radius-lg) !important;
379
- padding: 28px !important;
380
- box-shadow: 0 16px 40px rgba(0,0,0,0.24);
381
- margin-bottom: var(--space-3) !important;
382
- position: relative;
383
- overflow: hidden;
384
- }
385
-
386
- .main-card::before {
387
- content: "";
388
- position: absolute;
389
- top: 0;
390
- left: 0;
391
- right: 0;
392
- height: 3px;
393
  }
394
 
395
- .search-card::before {
396
- background: var(--accent-blue);
 
 
 
397
  }
398
 
399
- .output-card::before {
400
- background: var(--accent-green);
 
 
 
 
 
 
401
  }
402
 
403
- .section-header {
404
- display: flex;
405
- align-items: baseline;
406
- justify-content: space-between;
407
  margin-bottom: 4px;
408
  }
409
 
410
- .section-title {
411
- color: var(--text-primary);
412
- font-size: 19px;
413
- font-weight: 750;
414
- letter-spacing: -0.01em;
 
 
415
  }
416
 
417
- .section-subtitle {
418
- color: var(--text-secondary);
419
- font-size: 13.5px;
420
- line-height: 1.6;
421
- margin-bottom: var(--space-3);
422
- }
423
-
424
- /* ---------- FORM CONTROLS ---------- */
425
-
426
- label span {
427
- color: var(--text-secondary) !important;
428
- font-size: 12.5px !important;
429
- font-weight: 650 !important;
430
- text-transform: uppercase;
431
- letter-spacing: 0.03em;
432
- display: inline-block;
433
- margin-bottom: 6px;
434
- }
435
-
436
- textarea, input[type="text"] {
437
- background: var(--input-bg) !important;
438
  color: #FFFFFF !important;
439
- border: 1px solid var(--panel-border-strong) !important;
440
- border-radius: var(--radius-md) !important;
441
- font-size: 15px !important;
442
- padding: 12px 14px !important;
443
- transition: border-color 0.15s ease, box-shadow 0.15s ease;
444
- }
445
-
446
- textarea:focus, input[type="text"]:focus {
447
- border-color: var(--accent-blue) !important;
448
- box-shadow: 0 0 0 3px rgba(59,130,246,0.16) !important;
449
- outline: none !important;
450
  }
451
 
452
- textarea::placeholder, input::placeholder {
453
- color: var(--text-tertiary) !important;
 
454
  }
455
 
456
- .input-wide {
457
- margin: var(--space-1) 0 var(--space-3) 0;
 
 
 
 
 
458
  }
459
 
460
- /* ---------- BUTTONS ---------- */
461
-
462
- .button-row {
463
- display: flex;
464
- gap: 10px;
465
- margin-bottom: var(--space-1);
466
  }
467
 
468
- button.search-button {
469
- background: var(--accent-blue) !important;
470
- border: 1px solid var(--accent-blue) !important;
471
- border-radius: var(--radius-md) !important;
472
- color: #FFFFFF !important;
473
  font-weight: 700 !important;
474
- font-size: 14.5px !important;
475
- min-height: 46px !important;
476
- flex: 2;
477
- transition: background 0.15s ease, transform 0.05s ease;
478
- }
479
-
480
- button.search-button:hover {
481
- background: var(--accent-blue-hover) !important;
482
- }
483
-
484
- button.search-button:active {
485
- transform: translateY(1px);
486
  }
487
 
488
- button.clear-button {
489
  background: transparent !important;
490
- border: 1px solid var(--panel-border-strong) !important;
491
- border-radius: var(--radius-md) !important;
492
- color: var(--text-secondary) !important;
493
- font-weight: 650 !important;
494
- font-size: 14.5px !important;
495
- min-height: 46px !important;
496
- flex: 1;
497
- transition: border-color 0.15s ease, color 0.15s ease;
498
- }
499
-
500
- button.clear-button:hover {
501
- border-color: var(--text-secondary) !important;
502
- color: #FFFFFF !important;
503
- }
504
-
505
- /* ---------- EXAMPLES ---------- */
506
-
507
- #examples-block {
508
- margin-top: var(--space-3);
509
- padding-top: var(--space-3);
510
- border-top: 1px solid var(--panel-border);
511
  }
512
 
513
- #examples-block .label-wrap span {
514
- color: var(--text-tertiary) !important;
515
- font-size: 12px !important;
516
- text-transform: uppercase;
517
- letter-spacing: 0.03em;
 
 
 
518
  }
519
 
520
- #examples-block button {
521
- background: var(--input-bg) !important;
522
- border: 1px solid var(--panel-border) !important;
523
- border-radius: 999px !important;
524
- color: #CBD5E1 !important;
525
- font-size: 13px !important;
526
- font-weight: 550 !important;
527
- padding: 6px 14px !important;
528
  }
529
 
530
- #examples-block button:hover {
531
- border-color: var(--accent-blue) !important;
532
- color: #FFFFFF !important;
533
  }
534
 
535
- /* ---------- STATUS + OUTPUT ---------- */
536
-
537
- .status-panel textarea {
538
- background: #070C16 !important;
539
- border: 1px solid var(--panel-border) !important;
540
- color: #E2E8F0 !important;
541
- font-weight: 650 !important;
542
- font-size: 13.5px !important;
543
- min-height: 40px !important;
544
- padding: 10px 14px !important;
545
  }
546
 
547
- .result-note {
548
- background: var(--input-bg);
549
- border: 1px solid var(--panel-border);
550
- border-radius: var(--radius-md);
551
- padding: 14px 16px;
552
- color: var(--text-secondary);
553
- font-size: 13.5px;
554
  line-height: 1.6;
 
555
  }
556
 
557
- /* The table has 10 columns — it must scroll horizontally rather than
558
- shrink, or headers wrap into unreadable fragments. */
559
- .dataframe {
560
- border-radius: var(--radius-md) !important;
561
- border: 1px solid var(--panel-border) !important;
562
- font-size: 13.5px !important;
563
- }
564
-
565
- .dataframe .table-wrap,
566
- .dataframe [class*="scroll"] {
567
- overflow-x: auto !important;
568
  }
569
 
570
- .dataframe table {
571
- min-width: 1100px !important;
572
- border-collapse: collapse !important;
 
 
573
  }
574
 
575
- .dataframe th {
576
- background: #0D1420 !important;
577
- color: var(--text-secondary) !important;
578
- font-size: 11.5px !important;
579
- text-transform: uppercase;
580
- letter-spacing: 0.03em;
581
- font-weight: 700 !important;
582
- white-space: nowrap !important;
583
- padding: 10px 14px !important;
584
  }
585
 
586
- .dataframe td {
587
- color: #E2E8F0 !important;
588
- padding: 10px 14px !important;
589
- max-width: 220px;
590
  }
591
 
592
- /* ---------- FOOTER ---------- */
593
-
594
- .footer {
595
- text-align: center;
596
- color: var(--text-tertiary);
597
- font-size: 12.5px;
598
- line-height: 1.7;
599
- margin-top: var(--space-4);
600
- padding-top: var(--space-3);
601
- border-top: 1px solid var(--panel-border);
602
- }
603
 
604
- @media (max-width: 640px) {
605
  .title {
606
- font-size: 28px;
607
- }
608
- .main-card {
609
- padding: 20px !important;
610
- }
611
- .hero {
612
- padding: 24px 22px;
613
  }
614
- .button-row {
615
- flex-direction: column;
 
616
  }
617
  }
618
  """
@@ -634,62 +481,89 @@ EMPTY_RESULTS = pd.DataFrame(
634
  )
635
 
636
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
637
  with gr.Blocks(
638
  css=custom_css,
639
  theme=gr.themes.Base(
640
- primary_hue="blue",
641
  secondary_hue="slate",
642
- neutral_hue="slate"
643
- ),
644
- title="CrossTalk AI"
645
  ) as demo:
646
 
647
  with gr.Column(elem_id="shell"):
648
  gr.HTML(
649
  """
 
 
 
 
 
650
  <div class="hero">
651
- <div class="badge">Hybrid retrieval system</div>
652
- <h1 class="title">CrossTalk AI</h1>
653
- <p class="subtitle">
654
- A retrieval-based language identification and translation support system for
655
- low-resource ethnic languages of Bangladesh — combining verified dictionary lookup
656
- with fine-tuned E5 semantic retrieval and FAISS vector search.
657
- </p>
658
- <div class="info-strip">
659
- <div class="info-pill">12 language groups</div>
660
- <div class="info-pill">70K+ indexed entries</div>
661
- <div class="info-pill">Fine-tuned E5 retriever</div>
662
- <div class="info-pill">FAISS vector search</div>
663
- <div class="info-pill">Confidence-aware output</div>
664
  </div>
665
  </div>
666
- """
667
- )
668
 
669
- # SEARCH SECTION
670
- with gr.Column(elem_classes=["main-card", "search-card"]):
671
- gr.HTML(
672
- """
673
- <div class="section-header">
674
- <div class="section-title">Search</div>
675
  </div>
676
- <div class="section-subtitle">
677
- Enter a source word. Exact and base-form matches are treated as verified
678
- dictionary outputs; everything else is a semantic suggestion.
679
  </div>
680
- """
681
- )
 
 
 
 
 
682
 
 
683
  query = gr.Textbox(
684
  label="Source word",
685
- placeholder="e.g. kəkhyáŋ, Hula, Aina, aam, bajaoo",
686
  lines=1,
687
- elem_classes=["input-wide"]
688
  )
689
 
690
- with gr.Row(elem_classes=["button-row"]):
691
- submit_btn = gr.Button("Search", elem_classes=["search-button"])
692
  clear_btn = gr.Button("Clear", elem_classes=["clear-button"])
 
693
 
694
  gr.Examples(
695
  examples=[
@@ -701,77 +575,52 @@ with gr.Blocks(
701
  ["unknown tribal word"]
702
  ],
703
  inputs=query,
704
- label="Try an example",
705
  elem_id="examples-block"
706
  )
707
 
708
- # OUTPUT SECTION
709
- with gr.Column(elem_classes=["main-card", "output-card"]):
710
- gr.HTML(
711
- """
712
- <div class="section-header">
713
- <div class="section-title">Results</div>
714
- </div>
715
- <div class="section-subtitle">
716
- Verified dictionary matches appear first. Semantic fallback results are shown
717
- only when no verified match exists.
718
- </div>
719
  """
720
- )
721
-
722
- status = gr.Textbox(
723
- label="Status",
724
- lines=1,
725
- elem_classes=["status-panel"],
726
- value="Ready."
727
- )
728
-
729
- summary = gr.HTML(
730
- value="""
731
- <div class="result-note">
732
- Enter a source word above and click Search — results will appear here.
733
  </div>
734
  """
735
  )
736
 
737
  results = gr.Dataframe(
738
- label="Results table",
739
  value=EMPTY_RESULTS,
740
  interactive=False,
741
  wrap=True,
742
- max_height=460
743
  )
744
 
745
  gr.HTML(
746
  """
747
  <div class="footer">
748
- Exact / base-form matches are verified dictionary outputs.
749
- Fine-tuned semantic fallback results are candidate suggestions only, not confirmed translations.
750
  </div>
751
  """
752
  )
753
 
754
  submit_btn.click(
755
- fn=safe_search,
756
  inputs=query,
757
- outputs=[status, summary, results]
758
  )
759
 
760
  query.submit(
761
- fn=safe_search,
762
  inputs=query,
763
- outputs=[status, summary, results]
764
  )
765
 
766
  clear_btn.click(
767
- fn=lambda: (
768
- "",
769
- "Ready.",
770
- '<div class="result-note">Enter a source word above and click Search — results will appear here.</div>',
771
- EMPTY_RESULTS
772
- ),
773
  inputs=None,
774
- outputs=[query, status, summary, results]
775
  )
776
 
777
 
 
260
 
261
 
262
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
263
 
264
+ custom_css = """
265
  .gradio-container {
266
+ background: #212121 !important;
267
+ color: #ECECEC !important;
268
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
 
 
 
 
 
 
 
 
 
 
 
269
  }
270
 
271
  #shell {
272
+ max-width: 980px;
273
  margin: 0 auto;
274
+ padding: 34px 18px 42px 18px;
275
  }
276
 
277
+ .topbar {
278
+ display: flex;
279
+ justify-content: space-between;
280
+ align-items: center;
281
+ margin-bottom: 26px;
282
+ color: #B4B4B4;
283
+ font-size: 14px;
284
+ }
285
 
286
+ .brand {
287
+ font-weight: 700;
288
+ color: #ECECEC;
 
 
 
 
289
  }
290
 
291
+ .model-badge {
292
+ background: #2F2F2F;
293
+ border: 1px solid #3A3A3A;
294
+ color: #CFCFCF;
295
+ padding: 7px 11px;
296
  border-radius: 999px;
 
 
 
 
297
  font-size: 12px;
298
+ }
299
+
300
+ .hero {
301
+ text-align: center;
302
+ padding: 22px 10px 20px 10px;
303
+ margin-bottom: 18px;
304
  }
305
 
306
  .title {
307
+ font-size: 38px;
308
+ line-height: 1.12;
309
+ font-weight: 750;
310
+ letter-spacing: -0.035em;
311
+ margin: 0;
312
+ color: #F5F5F5;
313
  }
314
 
315
  .subtitle {
316
+ max-width: 760px;
317
+ margin: 14px auto 0 auto;
318
+ color: #B4B4B4;
319
  font-size: 15px;
320
  line-height: 1.65;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  }
322
 
323
+ .capabilities {
324
+ display: grid;
325
+ grid-template-columns: repeat(3, 1fr);
326
+ gap: 12px;
327
+ margin: 26px 0 22px 0;
328
  }
329
 
330
+ .cap-card {
331
+ background: #2A2A2A;
332
+ border: 1px solid #3A3A3A;
333
+ border-radius: 16px;
334
+ padding: 14px 15px;
335
+ color: #D7D7D7;
336
+ font-size: 13px;
337
+ line-height: 1.45;
338
  }
339
 
340
+ .cap-card strong {
341
+ display: block;
342
+ color: #FFFFFF;
343
+ font-size: 14px;
344
  margin-bottom: 4px;
345
  }
346
 
347
+ .search-panel {
348
+ background: #2F2F2F !important;
349
+ border: 1px solid #454545 !important;
350
+ border-radius: 24px !important;
351
+ padding: 14px 16px 16px 16px !important;
352
+ box-shadow: 0 14px 38px rgba(0,0,0,0.20);
353
+ margin-bottom: 18px !important;
354
  }
355
 
356
+ textarea, input {
357
+ background: #2F2F2F !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
358
  color: #FFFFFF !important;
359
+ border: 1px solid #4A4A4A !important;
360
+ border-radius: 18px !important;
361
+ font-size: 16px !important;
 
 
 
 
 
 
 
 
362
  }
363
 
364
+ textarea:focus, input:focus {
365
+ border-color: #6B7280 !important;
366
+ box-shadow: 0 0 0 2px rgba(255,255,255,0.08) !important;
367
  }
368
 
369
+ button.send-button {
370
+ background: #ECECEC !important;
371
+ color: #111111 !important;
372
+ border: 1px solid #ECECEC !important;
373
+ border-radius: 14px !important;
374
+ font-weight: 800 !important;
375
+ min-height: 44px !important;
376
  }
377
 
378
+ button.send-button:hover {
379
+ background: #FFFFFF !important;
 
 
 
 
380
  }
381
 
382
+ button.clear-button {
383
+ background: #262626 !important;
384
+ color: #D1D5DB !important;
385
+ border: 1px solid #414141 !important;
386
+ border-radius: 14px !important;
387
  font-weight: 700 !important;
388
+ min-height: 44px !important;
 
 
 
 
 
 
 
 
 
 
 
389
  }
390
 
391
+ .output-panel {
392
  background: transparent !important;
393
+ border: none !important;
394
+ padding: 0 !important;
395
+ margin-top: 8px !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
396
  }
397
 
398
+ .answer-card {
399
+ background: #2A2A2A;
400
+ border: 1px solid #3D3D3D;
401
+ border-radius: 18px;
402
+ padding: 18px 20px;
403
+ margin-bottom: 14px;
404
+ color: #ECECEC;
405
+ line-height: 1.65;
406
  }
407
 
408
+ .answer-title {
409
+ font-weight: 800;
410
+ color: #FFFFFF;
411
+ margin-bottom: 8px;
 
 
 
 
412
  }
413
 
414
+ .answer-note {
415
+ color: #C7C7C7;
416
+ font-size: 14px;
417
  }
418
 
419
+ .dataframe {
420
+ border-radius: 16px !important;
421
+ overflow: hidden !important;
422
+ border: 1px solid #3D3D3D !important;
 
 
 
 
 
 
423
  }
424
 
425
+ .footer {
426
+ text-align: center;
427
+ color: #9CA3AF;
428
+ font-size: 12.5px;
 
 
 
429
  line-height: 1.6;
430
+ margin-top: 24px;
431
  }
432
 
433
+ #examples-block {
434
+ margin-top: 10px;
 
 
 
 
 
 
 
 
 
435
  }
436
 
437
+ #examples-block button {
438
+ border-radius: 999px !important;
439
+ background: #2A2A2A !important;
440
+ border: 1px solid #3D3D3D !important;
441
+ color: #D1D5DB !important;
442
  }
443
 
444
+ .block {
445
+ background: transparent !important;
 
 
 
 
 
 
 
446
  }
447
 
448
+ label {
449
+ color: #CFCFCF !important;
 
 
450
  }
451
 
452
+ @media (max-width: 900px) {
453
+ #shell {
454
+ padding: 24px 14px 34px 14px;
455
+ }
 
 
 
 
 
 
 
456
 
 
457
  .title {
458
+ font-size: 30px;
 
 
 
 
 
 
459
  }
460
+
461
+ .capabilities {
462
+ grid-template-columns: 1fr;
463
  }
464
  }
465
  """
 
481
  )
482
 
483
 
484
+ def clean_markdown_summary(x):
485
+ x = "" if x is None else str(x)
486
+ x = x.replace('<div class="result-note">', "")
487
+ x = x.replace("</div>", "")
488
+ return x.strip()
489
+
490
+
491
+ def chat_style_search(query):
492
+ status, summary, result = safe_search(query)
493
+ summary = clean_markdown_summary(summary)
494
+
495
+ answer_html = f"""
496
+ <div class="answer-card">
497
+ <div class="answer-title">{status}</div>
498
+ <div class="answer-note">{summary}</div>
499
+ </div>
500
+ """
501
+
502
+ return answer_html, result
503
+
504
+
505
+ def clear_interface():
506
+ return "", """
507
+ <div class="answer-card">
508
+ <div class="answer-title">Ready.</div>
509
+ <div class="answer-note">Enter a source word to retrieve verified dictionary matches or semantic candidates.</div>
510
+ </div>
511
+ """, EMPTY_RESULTS
512
+
513
+
514
  with gr.Blocks(
515
  css=custom_css,
516
  theme=gr.themes.Base(
517
+ primary_hue="slate",
518
  secondary_hue="slate",
519
+ neutral_hue="zinc"
520
+ )
 
521
  ) as demo:
522
 
523
  with gr.Column(elem_id="shell"):
524
  gr.HTML(
525
  """
526
+ <div class="topbar">
527
+ <div class="brand">CrossTalk AI</div>
528
+ <div class="model-badge">Full trained retrieval system</div>
529
+ </div>
530
+
531
  <div class="hero">
532
+ <h1 class="title">What ethnic word would you like to search?</h1>
533
+ <div class="subtitle">
534
+ CrossTalk AI identifies and retrieves meanings from low-resource ethnic language dictionaries using verified lexical matching,
535
+ fine-tuned E5 semantic retrieval, FAISS vector search, and confidence-aware output control.
 
 
 
 
 
 
 
 
 
536
  </div>
537
  </div>
 
 
538
 
539
+ <div class="capabilities">
540
+ <div class="cap-card">
541
+ <strong>Verified lookup</strong>
542
+ Exact and base-form dictionary matches are returned first.
 
 
543
  </div>
544
+ <div class="cap-card">
545
+ <strong>Semantic fallback</strong>
546
+ Fine-tuned E5 suggests candidates when verified matches are unavailable.
547
  </div>
548
+ <div class="cap-card">
549
+ <strong>Safe output</strong>
550
+ Low-confidence semantic results are not claimed as confirmed translations.
551
+ </div>
552
+ </div>
553
+ """
554
+ )
555
 
556
+ with gr.Column(elem_classes=["search-panel"]):
557
  query = gr.Textbox(
558
  label="Source word",
559
+ placeholder="Message CrossTalk AI...",
560
  lines=1,
561
+ show_label=False
562
  )
563
 
564
+ with gr.Row():
 
565
  clear_btn = gr.Button("Clear", elem_classes=["clear-button"])
566
+ submit_btn = gr.Button("Search", elem_classes=["send-button"])
567
 
568
  gr.Examples(
569
  examples=[
 
575
  ["unknown tribal word"]
576
  ],
577
  inputs=query,
578
+ label="Try examples",
579
  elem_id="examples-block"
580
  )
581
 
582
+ with gr.Column(elem_classes=["output-panel"]):
583
+ answer = gr.HTML(
 
 
 
 
 
 
 
 
 
584
  """
585
+ <div class="answer-card">
586
+ <div class="answer-title">Ready.</div>
587
+ <div class="answer-note">Enter a source word to retrieve verified dictionary matches or semantic candidates.</div>
 
 
 
 
 
 
 
 
 
 
588
  </div>
589
  """
590
  )
591
 
592
  results = gr.Dataframe(
593
+ label="Retrieved results",
594
  value=EMPTY_RESULTS,
595
  interactive=False,
596
  wrap=True,
597
+ max_height=470
598
  )
599
 
600
  gr.HTML(
601
  """
602
  <div class="footer">
603
+ Exact/base-form matches are verified dictionary outputs. Fine-tuned semantic fallback results are candidate suggestions only, not confirmed translations.
 
604
  </div>
605
  """
606
  )
607
 
608
  submit_btn.click(
609
+ fn=chat_style_search,
610
  inputs=query,
611
+ outputs=[answer, results]
612
  )
613
 
614
  query.submit(
615
+ fn=chat_style_search,
616
  inputs=query,
617
+ outputs=[answer, results]
618
  )
619
 
620
  clear_btn.click(
621
+ fn=clear_interface,
 
 
 
 
 
622
  inputs=None,
623
+ outputs=[query, answer, results]
624
  )
625
 
626