ElMETRICO commited on
Commit
4ae0ebd
·
verified ·
1 Parent(s): 044718d

Update CrossTalk AI with translator-style visible output

Browse files
Files changed (1) hide show
  1. app.py +346 -162
app.py CHANGED
@@ -261,170 +261,218 @@ def safe_search(query):
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;
@@ -436,9 +484,9 @@ button.clear-button {
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 {
@@ -446,20 +494,24 @@ button.clear-button {
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
  """
@@ -467,54 +519,180 @@ label {
467
 
468
  EMPTY_RESULTS = pd.DataFrame(
469
  columns=[
470
- "query",
 
 
 
 
 
471
  "score",
472
  "method",
473
- "confidence",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
474
  "language",
475
  "source_text",
476
  "english_meaning",
477
  "bangla_meaning",
478
- "part_of_speech",
 
 
479
  "note"
480
  ]
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
  )
@@ -529,41 +707,26 @@ with gr.Blocks(
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=[
@@ -579,48 +742,69 @@ with gr.Blocks(
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
 
 
261
 
262
 
263
 
264
+
265
  custom_css = """
266
  .gradio-container {
267
+ background: #202124 !important;
268
+ color: #E8EAED !important;
269
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif !important;
270
  }
271
 
272
  #shell {
273
+ max-width: 1120px;
274
  margin: 0 auto;
275
+ padding: 28px 18px 44px 18px;
276
  }
277
 
278
  .topbar {
279
  display: flex;
280
  justify-content: space-between;
281
  align-items: center;
282
+ margin-bottom: 28px;
283
+ color: #BDC1C6;
284
  font-size: 14px;
285
  }
286
 
287
  .brand {
288
+ font-size: 20px;
289
+ font-weight: 750;
290
+ color: #F1F3F4;
291
  }
292
 
293
  .model-badge {
294
+ background: #303134;
295
+ border: 1px solid #3C4043;
296
+ color: #BDC1C6;
297
+ padding: 7px 12px;
298
  border-radius: 999px;
299
  font-size: 12px;
300
+ font-weight: 650;
301
  }
302
 
303
  .hero {
304
  text-align: center;
305
+ margin-bottom: 24px;
 
306
  }
307
 
308
  .title {
309
+ font-size: 34px;
310
+ font-weight: 760;
 
311
  letter-spacing: -0.035em;
312
  margin: 0;
313
+ color: #F1F3F4;
314
  }
315
 
316
  .subtitle {
317
+ max-width: 800px;
318
+ margin: 12px auto 0 auto;
319
+ color: #BDC1C6;
320
  font-size: 15px;
321
  line-height: 1.65;
322
  }
323
 
324
+ .search-box {
325
+ background: #303134 !important;
326
+ border: 1px solid #3C4043 !important;
327
+ border-radius: 22px !important;
328
+ padding: 16px !important;
329
+ margin-bottom: 20px !important;
330
+ box-shadow: 0 12px 28px rgba(0,0,0,0.18);
331
+ }
332
+
333
+ textarea, input {
334
+ background: #303134 !important;
335
+ color: #F1F3F4 !important;
336
+ border: 1px solid #5F6368 !important;
337
+ border-radius: 16px !important;
338
+ font-size: 17px !important;
339
+ }
340
+
341
+ textarea:focus, input:focus {
342
+ border-color: #8AB4F8 !important;
343
+ box-shadow: 0 0 0 2px rgba(138,180,248,0.18) !important;
344
+ }
345
+
346
+ button.search-button {
347
+ background: #8AB4F8 !important;
348
+ color: #202124 !important;
349
+ border: 1px solid #8AB4F8 !important;
350
+ border-radius: 14px !important;
351
+ font-weight: 850 !important;
352
+ min-height: 46px !important;
353
+ }
354
+
355
+ button.clear-button {
356
+ background: #303134 !important;
357
+ color: #E8EAED !important;
358
+ border: 1px solid #5F6368 !important;
359
+ border-radius: 14px !important;
360
+ font-weight: 750 !important;
361
+ min-height: 46px !important;
362
+ }
363
+
364
+ .translator-grid {
365
  display: grid;
366
+ grid-template-columns: 1fr 1fr;
367
+ gap: 14px;
368
+ margin-bottom: 18px;
369
  }
370
 
371
+ .translator-card {
372
+ background: #303134;
373
+ border: 1px solid #3C4043;
374
+ border-radius: 22px;
375
+ min-height: 230px;
376
+ padding: 20px;
377
+ box-shadow: 0 12px 28px rgba(0,0,0,0.16);
378
+ }
379
+
380
+ .card-label {
381
+ color: #8AB4F8;
382
+ font-weight: 750;
383
  font-size: 13px;
384
+ margin-bottom: 18px;
385
  }
386
 
387
+ .source-word {
388
+ font-size: 34px;
389
+ line-height: 1.25;
390
+ font-weight: 760;
391
+ color: #F1F3F4;
392
+ word-break: break-word;
393
  }
394
 
395
+ .output-main {
396
+ font-size: 36px;
397
+ line-height: 1.22;
398
+ font-weight: 780;
399
+ color: #FFFFFF;
400
+ word-break: break-word;
 
401
  }
402
 
403
+ .output-bangla {
404
+ margin-top: 14px;
405
+ font-size: 24px;
406
+ line-height: 1.35;
407
+ font-weight: 650;
408
+ color: #E8EAED;
409
+ word-break: break-word;
410
  }
411
 
412
+ .meta-row {
413
+ display: flex;
414
+ flex-wrap: wrap;
415
+ gap: 8px;
416
+ margin-top: 18px;
417
  }
418
 
419
+ .meta-pill {
420
+ background: #202124;
421
+ border: 1px solid #4B4F52;
422
+ color: #BDC1C6;
423
+ padding: 6px 10px;
424
+ border-radius: 999px;
425
+ font-size: 12px;
426
+ font-weight: 650;
427
  }
428
 
429
+ .status-good {
430
+ color: #81C995;
431
  }
432
 
433
+ .status-warn {
434
+ color: #FDD663;
 
 
 
 
 
435
  }
436
 
437
+ .status-bad {
438
+ color: #F28B82;
 
 
 
439
  }
440
 
441
+ .message-card {
442
+ background: #303134;
443
+ border: 1px solid #3C4043;
444
  border-radius: 18px;
445
+ padding: 15px 18px;
446
+ margin-bottom: 16px;
447
+ color: #E8EAED;
448
+ line-height: 1.6;
449
  }
450
 
451
+ .message-title {
452
  font-weight: 800;
453
+ margin-bottom: 4px;
 
454
  }
455
 
456
+ .message-subtitle {
457
+ color: #BDC1C6;
458
  font-size: 14px;
459
  }
460
 
461
+ .details-title {
462
+ color: #F1F3F4;
463
+ font-weight: 750;
464
+ margin: 18px 0 8px 0;
465
+ }
466
+
467
  .dataframe {
468
  border-radius: 16px !important;
469
  overflow: hidden !important;
470
+ border: 1px solid #3C4043 !important;
471
  }
472
 
473
  .footer {
474
  text-align: center;
475
+ color: #BDC1C6;
476
  font-size: 12.5px;
477
  line-height: 1.6;
478
  margin-top: 24px;
 
484
 
485
  #examples-block button {
486
  border-radius: 999px !important;
487
+ background: #303134 !important;
488
+ border: 1px solid #5F6368 !important;
489
+ color: #E8EAED !important;
490
  }
491
 
492
  .block {
 
494
  }
495
 
496
  label {
497
+ color: #BDC1C6 !important;
498
  }
499
 
500
  @media (max-width: 900px) {
501
+ .translator-grid {
502
+ grid-template-columns: 1fr;
503
+ }
504
+
505
+ .source-word {
506
+ font-size: 28px;
507
  }
508
 
509
+ .output-main {
510
  font-size: 30px;
511
  }
512
 
513
+ .output-bangla {
514
+ font-size: 21px;
515
  }
516
  }
517
  """
 
519
 
520
  EMPTY_RESULTS = pd.DataFrame(
521
  columns=[
522
+ "rank",
523
+ "language",
524
+ "source_text",
525
+ "english_meaning",
526
+ "bangla_meaning",
527
+ "confidence",
528
  "score",
529
  "method",
530
+ "note"
531
+ ]
532
+ )
533
+
534
+
535
+ def html_escape(x):
536
+ import html
537
+ return html.escape("" if x is None else str(x))
538
+
539
+
540
+ def simplify_results_table(df):
541
+ if df is None or len(df) == 0:
542
+ return EMPTY_RESULTS
543
+
544
+ temp = df.copy()
545
+
546
+ keep_cols = [
547
  "language",
548
  "source_text",
549
  "english_meaning",
550
  "bangla_meaning",
551
+ "confidence",
552
+ "score",
553
+ "method",
554
  "note"
555
  ]
556
+
557
+ for col in keep_cols:
558
+ if col not in temp.columns:
559
+ temp[col] = ""
560
+
561
+ temp = temp[keep_cols].copy()
562
+ temp.insert(0, "rank", range(1, len(temp) + 1))
563
+
564
+ return temp
565
 
566
 
567
+ def translator_style_search(query):
568
+ status, summary, df = safe_search(query)
 
 
 
569
 
570
+ query = clean_text(query)
571
+
572
+ if df is None or len(df) == 0:
573
+ left_html = f"""
574
+ <div class="translator-card">
575
+ <div class="card-label">Input</div>
576
+ <div class="source-word">{html_escape(query if query else "No input")}</div>
577
+ </div>
578
+ """
579
+
580
+ right_html = f"""
581
+ <div class="translator-card">
582
+ <div class="card-label">Output</div>
583
+ <div class="output-main status-bad">No verified match</div>
584
+ <div class="output-bangla">No translation is claimed.</div>
585
+ <div class="meta-row">
586
+ <div class="meta-pill">Safe output</div>
587
+ <div class="meta-pill">No verified dictionary result</div>
588
+ </div>
589
+ </div>
590
+ """
591
+
592
+ message_html = f"""
593
+ <div class="message-card">
594
+ <div class="message-title status-bad">{html_escape(status)}</div>
595
+ <div class="message-subtitle">{html_escape(summary)}</div>
596
+ </div>
597
+ """
598
+
599
+ return left_html + right_html, message_html, EMPTY_RESULTS
600
+
601
+ main = df.iloc[0]
602
+
603
+ language = html_escape(main.get("language", ""))
604
+ source_text = html_escape(main.get("source_text", query))
605
+ english_meaning = html_escape(main.get("english_meaning", ""))
606
+ bangla_meaning = html_escape(main.get("bangla_meaning", ""))
607
+ confidence = html_escape(main.get("confidence", ""))
608
+ method = html_escape(main.get("method", ""))
609
+ score = main.get("score", "")
610
+
611
+ try:
612
+ score_text = f"{float(score):.3f}"
613
+ except Exception:
614
+ score_text = html_escape(score)
615
+
616
+ if "Verified" in str(status) or "verified" in str(status):
617
+ status_class = "status-good"
618
+ output_type = "Verified dictionary output"
619
+ elif "Low-confidence" in str(status) or "low" in str(status).lower():
620
+ status_class = "status-warn"
621
+ output_type = "Semantic candidate only"
622
+ else:
623
+ status_class = "status-warn"
624
+ output_type = "Candidate output"
625
+
626
+ display_english = english_meaning if english_meaning else "No English meaning available"
627
+ display_bangla = bangla_meaning if bangla_meaning else "No Bangla meaning available"
628
+
629
+ left_html = f"""
630
+ <div class="translator-card">
631
+ <div class="card-label">Input word</div>
632
+ <div class="source-word">{source_text}</div>
633
+ <div class="meta-row">
634
+ <div class="meta-pill">Query: {html_escape(query)}</div>
635
+ </div>
636
+ </div>
637
+ """
638
 
639
+ right_html = f"""
640
+ <div class="translator-card">
641
+ <div class="card-label">Retrieved meaning</div>
642
+ <div class="output-main">{display_english}</div>
643
+ <div class="output-bangla">{display_bangla}</div>
644
+ <div class="meta-row">
645
+ <div class="meta-pill">Language: {language}</div>
646
+ <div class="meta-pill">{output_type}</div>
647
+ <div class="meta-pill">Confidence: {confidence}</div>
648
+ <div class="meta-pill">Score: {score_text}</div>
649
+ </div>
650
+ </div>
651
+ """
652
 
653
+ message_html = f"""
654
+ <div class="message-card">
655
+ <div class="message-title {status_class}">{html_escape(status)}</div>
656
+ <div class="message-subtitle">{html_escape(summary)}</div>
657
  </div>
658
  """
659
 
660
+ table = simplify_results_table(df)
661
+
662
+ return left_html + right_html, message_html, table
663
 
664
 
665
  def clear_interface():
666
+ empty_translator = """
667
+ <div class="translator-card">
668
+ <div class="card-label">Input word</div>
669
+ <div class="source-word">Enter a word</div>
670
+ </div>
671
+ <div class="translator-card">
672
+ <div class="card-label">Retrieved meaning</div>
673
+ <div class="output-main">Translation support output</div>
674
+ <div class="output-bangla">Results will appear here.</div>
675
+ <div class="meta-row">
676
+ <div class="meta-pill">Verified lookup first</div>
677
+ <div class="meta-pill">Semantic fallback if needed</div>
678
+ </div>
679
+ </div>
680
+ """
681
+
682
+ message_html = """
683
+ <div class="message-card">
684
+ <div class="message-title">Ready.</div>
685
+ <div class="message-subtitle">Enter a source word and click Search.</div>
686
  </div>
687
+ """
688
+
689
+ return "", empty_translator, message_html, EMPTY_RESULTS
690
 
691
 
692
  with gr.Blocks(
693
  css=custom_css,
694
  theme=gr.themes.Base(
695
+ primary_hue="blue",
696
  secondary_hue="slate",
697
  neutral_hue="zinc"
698
  )
 
707
  </div>
708
 
709
  <div class="hero">
710
+ <h1 class="title">Low-resource ethnic language retrieval</h1>
711
  <div class="subtitle">
712
+ Enter a source word to retrieve verified dictionary meanings first.
713
+ If no verified match is found, the fine-tuned E5 semantic retriever suggests possible candidates safely.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
  </div>
715
  </div>
716
  """
717
  )
718
 
719
+ with gr.Column(elem_classes=["search-box"]):
720
  query = gr.Textbox(
721
  label="Source word",
722
+ placeholder="Type a word, for example: kəkhyáŋ, Hula, Aina, aam, bajaoo",
723
  lines=1,
724
  show_label=False
725
  )
726
 
727
  with gr.Row():
728
  clear_btn = gr.Button("Clear", elem_classes=["clear-button"])
729
+ submit_btn = gr.Button("Search", elem_classes=["search-button"])
730
 
731
  gr.Examples(
732
  examples=[
 
742
  elem_id="examples-block"
743
  )
744
 
745
+ translator_view = gr.HTML(
746
+ """
747
+ <div class="translator-card">
748
+ <div class="card-label">Input word</div>
749
+ <div class="source-word">Enter a word</div>
750
+ </div>
751
+ <div class="translator-card">
752
+ <div class="card-label">Retrieved meaning</div>
753
+ <div class="output-main">Translation support output</div>
754
+ <div class="output-bangla">Results will appear here.</div>
755
+ <div class="meta-row">
756
+ <div class="meta-pill">Verified lookup first</div>
757
+ <div class="meta-pill">Semantic fallback if needed</div>
758
  </div>
759
+ </div>
760
+ """,
761
+ elem_classes=["translator-grid"]
762
+ )
763
 
764
+ message = gr.HTML(
765
+ """
766
+ <div class="message-card">
767
+ <div class="message-title">Ready.</div>
768
+ <div class="message-subtitle">Enter a source word and click Search.</div>
769
+ </div>
770
+ """
771
+ )
772
+
773
+ gr.HTML('<div class="details-title">Detailed candidates</div>')
774
+
775
+ results = gr.Dataframe(
776
+ label="",
777
+ value=EMPTY_RESULTS,
778
+ interactive=False,
779
+ wrap=True,
780
+ max_height=430
781
+ )
782
 
783
  gr.HTML(
784
  """
785
  <div class="footer">
786
+ Exact/base-form matches are verified dictionary outputs. Semantic fallback results are candidate suggestions only, not confirmed translations.
787
  </div>
788
  """
789
+
790
  )
791
 
792
  submit_btn.click(
793
+ fn=translator_style_search,
794
  inputs=query,
795
+ outputs=[translator_view, message, results]
796
  )
797
 
798
  query.submit(
799
+ fn=translator_style_search,
800
  inputs=query,
801
+ outputs=[translator_view, message, results]
802
  )
803
 
804
  clear_btn.click(
805
  fn=clear_interface,
806
  inputs=None,
807
+ outputs=[query, translator_view, message, results]
808
  )
809
 
810