SagarVelamuri commited on
Commit
a0b8bba
·
verified ·
1 Parent(s): 725dcc4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +76 -99
app.py CHANGED
@@ -2,13 +2,13 @@ import os, traceback, types, torch
2
  import gradio as gr
3
  from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
4
 
5
- # Robust import for IndicProcessor (fallback path too)
6
  try:
7
- from IndicTransToolkit import IndicProcessor # preferred
8
  except Exception:
9
- from IndicTransToolkit.IndicTransToolkit import IndicProcessor # fallback
10
 
11
- # --------- Config ----------
12
  TOKENIZER_ID = os.getenv("TOKENIZER_ID", "ai4bharat/indictrans2-en-indic-1B")
13
  MODEL_ID = os.getenv("MODEL_ID", "law-ai/InLegalTrans-En2Indic-1B")
14
  TOKENIZER_REV = os.getenv("TOKENIZER_REV", None)
@@ -18,28 +18,20 @@ SRC_CODE = "eng_Latn"
18
  HI_CODE = "hin_Deva"
19
  TE_CODE = "tel_Telu"
20
 
21
- # -------------------- Load model & tokenizer --------------------------
22
  device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
23
  dtype = torch.float16 if torch.cuda.is_available() else torch.float32
24
 
25
  tok_kwargs = dict(trust_remote_code=True, use_fast=True)
26
- if TOKENIZER_REV:
27
- tok_kwargs["revision"] = TOKENIZER_REV
28
  tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_ID, **tok_kwargs)
29
 
30
- mdl_kwargs = dict(
31
- trust_remote_code=True,
32
- attn_implementation="eager",
33
- low_cpu_mem_usage=True,
34
- dtype=dtype,
35
- )
36
- if MODEL_REV:
37
- mdl_kwargs["revision"] = MODEL_REV
38
-
39
  model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_ID, **mdl_kwargs).to(device)
40
  model.eval()
41
 
42
- # Ensure generation ids are set
43
  if getattr(model.generation_config, "pad_token_id", None) is None:
44
  model.generation_config.pad_token_id = (
45
  getattr(tokenizer, "pad_token_id", None) or getattr(tokenizer, "eos_token_id", 0)
@@ -47,7 +39,6 @@ if getattr(model.generation_config, "pad_token_id", None) is None:
47
  if getattr(model.generation_config, "eos_token_id", None) is None and getattr(tokenizer, "eos_token_id", None) is not None:
48
  model.generation_config.eos_token_id = tokenizer.eos_token_id
49
 
50
- # ---- Runtime compatibility patches ----
51
  def _ensure_vocab_consistency(md, tok):
52
  try:
53
  actual_vocab = md.get_output_embeddings().weight.shape[0]
@@ -58,40 +49,34 @@ def _ensure_vocab_consistency(md, tok):
58
  try: md.generation_config.vocab_size = actual_vocab
59
  except Exception: pass
60
  else:
61
- fallback_size = getattr(tok, "vocab_size", None)
62
- if fallback_size is None:
63
- try: fallback_size = len(tok)
64
- except Exception: fallback_size = 64000
65
- md.config.vocab_size = fallback_size
66
- try: md.generation_config.vocab_size = fallback_size
67
  except Exception: pass
68
  if not hasattr(md.config, "get_text_config") or not callable(getattr(md.config, "get_text_config", None)):
69
  def _get_text_config(self): return self
70
  md.config.get_text_config = types.MethodType(_get_text_config, md.config)
71
- _ensure_vocab_consistency(model, tokenizer)
72
 
73
- # Disable KV cache to avoid custom decoder's past_key_values shape bug
74
  for obj in (model.config, model.generation_config):
75
  try: setattr(obj, "use_cache", False)
76
  except Exception: pass
77
 
78
  ip = IndicProcessor(inference=True)
79
 
80
- # -------------------- Inference helpers -------------------------------
81
  @torch.inference_mode()
82
- def _translate_to_lang(text: str, tgt_code: str, num_beams: int, max_new_tokens: int,
83
- temperature: float, top_p: float, top_k: int):
84
  batch = ip.preprocess_batch([text], src_lang=SRC_CODE, tgt_lang=tgt_code)
85
  enc = tokenizer(
86
- batch,
87
- max_length=256,
88
- truncation=True,
89
- padding="longest",
90
- return_tensors="pt",
91
- return_attention_mask=True,
92
  ).to(device)
93
  do_sample = (temperature is not None) and (float(temperature) > 0)
94
- outputs = model.generate(
95
  **enc,
96
  max_new_tokens=int(max_new_tokens),
97
  num_beams=int(num_beams),
@@ -99,18 +84,16 @@ def _translate_to_lang(text: str, tgt_code: str, num_beams: int, max_new_tokens:
99
  temperature=float(temperature) if do_sample else None,
100
  top_p=float(top_p) if do_sample else None,
101
  top_k=int(top_k) if do_sample else None,
102
- use_cache=False,
103
- early_stopping=False,
104
  pad_token_id=model.generation_config.pad_token_id,
105
  )
106
- decoded = tokenizer.batch_decode(outputs, skip_special_tokens=True, clean_up_tokenization_spaces=True)
107
  final = ip.postprocess_batch(decoded, lang=tgt_code)
108
  return final[0].strip()
109
 
110
  def translate_dual(text, num_beams, max_new_tokens, temperature, top_p, top_k):
111
  text = (text or "").strip()
112
- if not text:
113
- return "", ""
114
  try:
115
  hi = _translate_to_lang(text, HI_CODE, num_beams, max_new_tokens, temperature, top_p, top_k)
116
  except Exception as e:
@@ -123,11 +106,8 @@ def translate_dual(text, num_beams, max_new_tokens, temperature, top_p, top_k):
123
  te = f"⚠️ Telugu translation failed: {type(e).__name__}: {str(e).splitlines()[-1]}"
124
  return hi, te
125
 
126
- # -------------------- THEME --------------------
127
- THEME = gr.themes.Soft(
128
- primary_hue="blue",
129
- neutral_hue="slate",
130
- ).set(
131
  body_background_fill="#0b1220",
132
  body_text_color="#f2f6ff",
133
  body_text_color_subdued="#cbd5e1",
@@ -140,9 +120,8 @@ THEME = gr.themes.Soft(
140
  button_primary_text_color="#ffffff",
141
  )
142
 
143
- # -------------------- LAYOUT CSS (20/40/40 exact; crisp headers) ------
144
  CUSTOM_CSS = """
145
- /* Full-bleed app with no side gutters */
146
  * { box-sizing: border-box; }
147
  html, body { height: 100%; background:#0b1220; margin:0; padding:0; }
148
  .gradio-container { height: 100vh !important; width: 100vw !important; max-width: 100vw !important; margin: 0; padding: 8px; }
@@ -153,7 +132,7 @@ html, body { height: 100%; background:#0b1220; margin:0; padding:0; }
153
  #title { color:#ffffff; font-weight:900; font-size:20px; margin:0; letter-spacing:.2px; }
154
  #subtitle { color:#b8cae1; font-size:12.5px; margin:0; }
155
 
156
- /* Main grid: 20% / 40% / 40% widths; fills remaining viewport height */
157
  #main {
158
  height: calc(100vh - 60px - 16px); /* header + outer padding */
159
  display: grid;
@@ -162,23 +141,23 @@ html, body { height: 100%; background:#0b1220; margin:0; padding:0; }
162
  }
163
 
164
  /* Panels */
165
- .panel { position:relative; border:1px solid #223144; border-radius:12px; background:#0f172a;
166
- display:flex; flex-direction:column; min-height:0; overflow: hidden; }
167
  .panel-h {
168
  display:flex; align-items:center; justify-content:space-between;
169
  padding:10px 12px; background:#081422; border-bottom:1px solid #243244;
170
  color:#ffffff; font-weight:900; letter-spacing:.25px; font-size:15px;
171
  }
172
- .panel-b { flex:1 1 auto; min-height:0; padding:10px 12px; position:relative; z-index:1; }
173
 
174
- /* Left column (Advanced) scroll inside if needed */
175
  #left { height: 100%; }
176
- #adv-inner { height: 100%; overflow: auto; padding-right: 6px; }
177
 
178
- /* Kill chip-like label backgrounds (make text crisp) */
179
- .gradio-container .panel .label,
180
- .gradio-container .panel .label > span,
181
- .gradio-container label {
182
  background: transparent !important;
183
  box-shadow: none !important;
184
  border: none !important;
@@ -186,102 +165,102 @@ html, body { height: 100%; background:#0b1220; margin:0; padding:0; }
186
  font-weight: 800 !important;
187
  }
188
 
189
- /* Middle column splits 75% (input) / 25% (buttons) */
190
  #middle { display:grid; grid-template-rows: 75% 25%; height:100%; gap:10px; }
191
 
192
- /* Right column splits 50% / 50% */
193
  #right { display:grid; grid-template-rows: 1fr 1fr; height:100%; gap:10px; }
194
 
195
- /* Textareas fill their panel; no spillover */
196
  .textwrap { height:100%; min-height:0; display:flex; }
197
  .textwrap > div { flex:1 1 auto; min-height:0; }
198
  .textwrap textarea { height:100% !important; }
199
 
200
- /* Inputs (high contrast) */
201
  textarea, textarea:focus {
202
  background:#0b1220 !important; color:#f9fbff !important;
203
- font-size: 17px !important; line-height:1.55 !important;
204
- padding: 10px 12px !important; border:1.6px solid #3b516c !important; border-radius: 10px !important;
205
  }
206
  textarea::placeholder { color:#a6bdd9 !important; }
207
  textarea:hover { border-color:#6b8db6 !important; }
208
  textarea:focus { border-color:#60a5fa !important; outline:none !important; }
209
 
210
- /* Buttons row */
211
  #btnrow { display:flex; align-items:center; justify-content:center; gap:16px; height:100%; }
212
- #btnrow > button { min-width: 180px; height: 46px; font-weight:800; border-radius:10px; }
213
 
214
- /* Maximize buttons (Hindi/Telugu) */
215
  .max { font-weight:900; padding:4px 10px; border-radius:10px; border:1px solid #3c5a86;
216
  background:#122037; color:#ffffff; }
217
  .max:hover { border-color:#60a5fa; }
218
 
219
  /* Modal */
220
- #modal { position: fixed; inset: 0; z-index: 9999; background: rgba(2,6,23,.88); display: none; align-items: center; justify-content: center; padding: 12px; }
221
- #modal[style*="display: block"] { display: flex !important; }
222
  .modal-card { width:min(1280px,96vw); height:min(92vh,900px); background:#0f172a; border:1px solid #335070; border-radius:14px;
223
  box-shadow:0 18px 40px rgba(2,6,23,.6); display:flex; flex-direction:column; gap:8px; padding:10px; }
224
- .modal-title { color:#ffffff; font-weight:800; font-size:18px; letter-spacing:.2px; margin:0; }
225
  #fs_box textarea { height: calc(100% - 52px) !important; }
226
  .modal-actions { display:flex; gap:8px; justify-content:flex-end; }
227
  """
228
 
229
- # -------------------- Build UI --------------------
230
  with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="EN→HI / EN→TE Translator") as demo:
231
- modal_state = gr.State(value="") # '', 'hi', 'te'
232
 
233
- # Header
234
- with gr.Column(elem_id="hdr"):
235
  gr.Markdown('<p id="title">English → Hindi & Telugu Translator</p>')
236
  gr.Markdown('<p id="subtitle">IndicTrans2 pipeline · law-ai/InLegalTrans-En2Indic-1B</p>')
237
 
238
- # Main 20/40/40 layout
239
- with gr.Row(elem_id="main"):
240
- # LEFT: Advanced (20% width, 100% height)
241
- with gr.Column(elem_id="left", elem_classes=["panel"]):
242
  gr.Markdown('<div class="panel-h">Advanced Settings</div>')
243
  with gr.Group(elem_id="adv-inner", elem_classes=["panel-b"]):
244
  num_beams = gr.Slider(1, 8, value=4, step=1, label="Beam search: num_beams")
245
  max_new = gr.Slider(16, 512, value=128, step=8, label="Max new tokens")
246
  temperature = gr.Slider(0.0, 1.5, value=0.0, step=0.05, label="Temperature (0 = deterministic)")
247
- top_p = gr.Slider(0.0, 1.0, value=1.0, step=0.01, label="Top-p")
248
- top_k = gr.Slider(0, 100, value=50, step=1, label="Top-k")
249
 
250
- # MIDDLE: English (40% width) 75% input, 25% buttons
251
- with gr.Column(elem_id="middle"):
252
- with gr.Column(elem_classes=["panel"]):
253
  gr.Markdown('<div class="panel-h">English Text</div>')
254
  with gr.Group(elem_classes=["panel-b","textwrap"]):
255
  src = gr.Textbox(placeholder="Type English here…", show_label=False, lines=14)
256
- with gr.Column(elem_classes=["panel"]):
257
  gr.Markdown('<div class="panel-h">Actions</div>')
258
- with gr.Row(elem_id="btnrow", elem_classes=["panel-b"]):
259
  translate_btn = gr.Button("Translate", variant="primary")
260
  clear_btn = gr.Button("Clear", variant="secondary")
261
 
262
- # RIGHT: outputs (40% width) 50% Hindi, 50% Telugu
263
- with gr.Column(elem_id="right"):
264
- with gr.Column(elem_classes=["panel"]):
265
- with gr.Row(elem_classes=["panel-h"]):
266
- gr.Markdown("Hindi (hin_Deva)")
267
- hi_max = gr.Button("⤢", elem_classes=["max"])
268
  with gr.Group(elem_classes=["panel-b","textwrap"]):
269
  hi_out = gr.Textbox(show_copy_button=True, show_label=False, lines=10)
270
- with gr.Column(elem_classes=["panel"]):
271
- with gr.Row(elem_classes=["panel-h"]):
272
- gr.Markdown("Telugu (tel_Telu)")
273
- te_max = gr.Button("⤢", elem_classes=["max"])
 
274
  with gr.Group(elem_classes=["panel-b","textwrap"]):
275
  te_out = gr.Textbox(show_copy_button=True, show_label=False, lines=10)
 
 
276
 
277
- # ---- Fullscreen modal (hidden by default) ----
278
  with gr.Group(visible=False, elem_id="modal") as modal:
279
  modal_title = gr.Markdown('<div class="modal-title">Fullscreen</div>')
280
  fs_text = gr.Textbox(lines=22, elem_id="fs_box")
281
  with gr.Row(elem_classes=["modal-actions"]):
282
  fs_close = gr.Button("Close", variant="secondary")
283
 
284
- # ---- Wiring ----
285
  translate_btn.click(
286
  translate_dual,
287
  inputs=[src, num_beams, max_new, temperature, top_p, top_k],
@@ -290,12 +269,10 @@ with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="EN→HI / EN→TE Translator"
290
  )
291
  clear_btn.click(lambda: ("", "", ""), outputs=[src, hi_out, te_out])
292
 
293
- # Maximize handlers (read-only preview; no save-back)
294
  def open_hi(h): return gr.update(visible=True), "hi", '<div class="modal-title">Hindi (Fullscreen)</div>', h
295
  def open_te(t): return gr.update(visible=True), "te", '<div class="modal-title">Telugu (Fullscreen)</div>', t
296
  hi_max.click(open_hi, inputs=[hi_out], outputs=[modal, modal_state, modal_title, fs_text])
297
  te_max.click(open_te, inputs=[te_out], outputs=[modal, modal_state, modal_title, fs_text])
298
  fs_close.click(lambda: (gr.update(visible=False), ""), outputs=[modal, modal_state])
299
 
300
- # Keep queue to enable buffering; omit unsupported args on older Gradio
301
  demo.queue(max_size=48).launch()
 
2
  import gradio as gr
3
  from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
4
 
5
+ # Robust import for IndicProcessor (fallback too)
6
  try:
7
+ from IndicTransToolkit import IndicProcessor
8
  except Exception:
9
+ from IndicTransToolkit.IndicTransToolkit import IndicProcessor
10
 
11
+ # -------- Config --------
12
  TOKENIZER_ID = os.getenv("TOKENIZER_ID", "ai4bharat/indictrans2-en-indic-1B")
13
  MODEL_ID = os.getenv("MODEL_ID", "law-ai/InLegalTrans-En2Indic-1B")
14
  TOKENIZER_REV = os.getenv("TOKENIZER_REV", None)
 
18
  HI_CODE = "hin_Deva"
19
  TE_CODE = "tel_Telu"
20
 
21
+ # ------- Load model -------
22
  device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
23
  dtype = torch.float16 if torch.cuda.is_available() else torch.float32
24
 
25
  tok_kwargs = dict(trust_remote_code=True, use_fast=True)
26
+ if TOKENIZER_REV: tok_kwargs["revision"] = TOKENIZER_REV
 
27
  tokenizer = AutoTokenizer.from_pretrained(TOKENIZER_ID, **tok_kwargs)
28
 
29
+ mdl_kwargs = dict(trust_remote_code=True, attn_implementation="eager",
30
+ low_cpu_mem_usage=True, dtype=dtype)
31
+ if MODEL_REV: mdl_kwargs["revision"] = MODEL_REV
 
 
 
 
 
 
32
  model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_ID, **mdl_kwargs).to(device)
33
  model.eval()
34
 
 
35
  if getattr(model.generation_config, "pad_token_id", None) is None:
36
  model.generation_config.pad_token_id = (
37
  getattr(tokenizer, "pad_token_id", None) or getattr(tokenizer, "eos_token_id", 0)
 
39
  if getattr(model.generation_config, "eos_token_id", None) is None and getattr(tokenizer, "eos_token_id", None) is not None:
40
  model.generation_config.eos_token_id = tokenizer.eos_token_id
41
 
 
42
  def _ensure_vocab_consistency(md, tok):
43
  try:
44
  actual_vocab = md.get_output_embeddings().weight.shape[0]
 
49
  try: md.generation_config.vocab_size = actual_vocab
50
  except Exception: pass
51
  else:
52
+ vs = getattr(tok, "vocab_size", None)
53
+ if vs is None:
54
+ try: vs = len(tok)
55
+ except Exception: vs = 64000
56
+ md.config.vocab_size = vs
57
+ try: md.generation_config.vocab_size = vs
58
  except Exception: pass
59
  if not hasattr(md.config, "get_text_config") or not callable(getattr(md.config, "get_text_config", None)):
60
  def _get_text_config(self): return self
61
  md.config.get_text_config = types.MethodType(_get_text_config, md.config)
 
62
 
63
+ _ensure_vocab_consistency(model, tokenizer)
64
  for obj in (model.config, model.generation_config):
65
  try: setattr(obj, "use_cache", False)
66
  except Exception: pass
67
 
68
  ip = IndicProcessor(inference=True)
69
 
70
+ # -------- Inference --------
71
  @torch.inference_mode()
72
+ def _translate_to_lang(text, tgt_code, num_beams, max_new_tokens, temperature, top_p, top_k):
 
73
  batch = ip.preprocess_batch([text], src_lang=SRC_CODE, tgt_lang=tgt_code)
74
  enc = tokenizer(
75
+ batch, max_length=256, truncation=True, padding="longest",
76
+ return_tensors="pt", return_attention_mask=True
 
 
 
 
77
  ).to(device)
78
  do_sample = (temperature is not None) and (float(temperature) > 0)
79
+ out = model.generate(
80
  **enc,
81
  max_new_tokens=int(max_new_tokens),
82
  num_beams=int(num_beams),
 
84
  temperature=float(temperature) if do_sample else None,
85
  top_p=float(top_p) if do_sample else None,
86
  top_k=int(top_k) if do_sample else None,
87
+ use_cache=False, early_stopping=False,
 
88
  pad_token_id=model.generation_config.pad_token_id,
89
  )
90
+ decoded = tokenizer.batch_decode(out, skip_special_tokens=True, clean_up_tokenization_spaces=True)
91
  final = ip.postprocess_batch(decoded, lang=tgt_code)
92
  return final[0].strip()
93
 
94
  def translate_dual(text, num_beams, max_new_tokens, temperature, top_p, top_k):
95
  text = (text or "").strip()
96
+ if not text: return "", ""
 
97
  try:
98
  hi = _translate_to_lang(text, HI_CODE, num_beams, max_new_tokens, temperature, top_p, top_k)
99
  except Exception as e:
 
106
  te = f"⚠️ Telugu translation failed: {type(e).__name__}: {str(e).splitlines()[-1]}"
107
  return hi, te
108
 
109
+ # -------- Theme --------
110
+ THEME = gr.themes.Soft(primary_hue="blue", neutral_hue="slate").set(
 
 
 
111
  body_background_fill="#0b1220",
112
  body_text_color="#f2f6ff",
113
  body_text_color_subdued="#cbd5e1",
 
120
  button_primary_text_color="#ffffff",
121
  )
122
 
123
+ # -------- CSS --------
124
  CUSTOM_CSS = """
 
125
  * { box-sizing: border-box; }
126
  html, body { height: 100%; background:#0b1220; margin:0; padding:0; }
127
  .gradio-container { height: 100vh !important; width: 100vw !important; max-width: 100vw !important; margin: 0; padding: 8px; }
 
132
  #title { color:#ffffff; font-weight:900; font-size:20px; margin:0; letter-spacing:.2px; }
133
  #subtitle { color:#b8cae1; font-size:12.5px; margin:0; }
134
 
135
+ /* Main grid (use Group, not Row -> no split-handles) */
136
  #main {
137
  height: calc(100vh - 60px - 16px); /* header + outer padding */
138
  display: grid;
 
141
  }
142
 
143
  /* Panels */
144
+ .panel { border:1px solid #223144; border-radius:12px; background:#0f172a;
145
+ display:flex; flex-direction:column; min-height:0; overflow:hidden; }
146
  .panel-h {
147
  display:flex; align-items:center; justify-content:space-between;
148
  padding:10px 12px; background:#081422; border-bottom:1px solid #243244;
149
  color:#ffffff; font-weight:900; letter-spacing:.25px; font-size:15px;
150
  }
151
+ .panel-b { flex:1 1 auto; min-height:0; padding:10px 12px; }
152
 
153
+ /* Left column: internal scroll only */
154
  #left { height: 100%; }
155
+ #adv-inner { height: 100%; overflow:auto; padding-right:6px; }
156
 
157
+ /* Remove pill-like label chips; make labels crisp */
158
+ .gradio-container label,
159
+ .gradio-container .label,
160
+ .gradio-container .label > span {
161
  background: transparent !important;
162
  box-shadow: none !important;
163
  border: none !important;
 
165
  font-weight: 800 !important;
166
  }
167
 
168
+ /* Middle split: 75% input / 25% buttons */
169
  #middle { display:grid; grid-template-rows: 75% 25%; height:100%; gap:10px; }
170
 
171
+ /* Right split: 50% / 50% */
172
  #right { display:grid; grid-template-rows: 1fr 1fr; height:100%; gap:10px; }
173
 
174
+ /* Text areas fill */
175
  .textwrap { height:100%; min-height:0; display:flex; }
176
  .textwrap > div { flex:1 1 auto; min-height:0; }
177
  .textwrap textarea { height:100% !important; }
178
 
179
+ /* Inputs */
180
  textarea, textarea:focus {
181
  background:#0b1220 !important; color:#f9fbff !important;
182
+ font-size:17px !important; line-height:1.55 !important;
183
+ padding:10px 12px !important; border:1.6px solid #3b516c !important; border-radius:10px !important;
184
  }
185
  textarea::placeholder { color:#a6bdd9 !important; }
186
  textarea:hover { border-color:#6b8db6 !important; }
187
  textarea:focus { border-color:#60a5fa !important; outline:none !important; }
188
 
189
+ /* Buttons area */
190
  #btnrow { display:flex; align-items:center; justify-content:center; gap:16px; height:100%; }
191
+ #btnrow > button { min-width:180px; height:46px; font-weight:800; border-radius:10px; }
192
 
193
+ /* Maximize buttons */
194
  .max { font-weight:900; padding:4px 10px; border-radius:10px; border:1px solid #3c5a86;
195
  background:#122037; color:#ffffff; }
196
  .max:hover { border-color:#60a5fa; }
197
 
198
  /* Modal */
199
+ #modal { position: fixed; inset: 0; z-index: 9999; background: rgba(2,6,23,.88); display:none; align-items:center; justify-content:center; padding:12px; }
200
+ #modal[style*="display: block"] { display:flex !important; }
201
  .modal-card { width:min(1280px,96vw); height:min(92vh,900px); background:#0f172a; border:1px solid #335070; border-radius:14px;
202
  box-shadow:0 18px 40px rgba(2,6,23,.6); display:flex; flex-direction:column; gap:8px; padding:10px; }
203
+ .modal-title { color:#ffffff; font-weight:800; font-size:18px; margin:0; }
204
  #fs_box textarea { height: calc(100% - 52px) !important; }
205
  .modal-actions { display:flex; gap:8px; justify-content:flex-end; }
206
  """
207
 
208
+ # ------------- UI -------------
209
  with gr.Blocks(theme=THEME, css=CUSTOM_CSS, title="EN→HI / EN→TE Translator") as demo:
210
+ modal_state = gr.State(value="") # 'hi' or 'te'
211
 
212
+ with gr.Group(elem_id="hdr"):
 
213
  gr.Markdown('<p id="title">English → Hindi & Telugu Translator</p>')
214
  gr.Markdown('<p id="subtitle">IndicTrans2 pipeline · law-ai/InLegalTrans-En2Indic-1B</p>')
215
 
216
+ # Main grid built with Group (no split handles)
217
+ with gr.Group(elem_id="main"):
218
+ # LEFT (20%) Advanced Settings
219
+ with gr.Group(elem_id="left", elem_classes=["panel"]):
220
  gr.Markdown('<div class="panel-h">Advanced Settings</div>')
221
  with gr.Group(elem_id="adv-inner", elem_classes=["panel-b"]):
222
  num_beams = gr.Slider(1, 8, value=4, step=1, label="Beam search: num_beams")
223
  max_new = gr.Slider(16, 512, value=128, step=8, label="Max new tokens")
224
  temperature = gr.Slider(0.0, 1.5, value=0.0, step=0.05, label="Temperature (0 = deterministic)")
225
+ top_p = gr.Slider(0.0, 1.0, value=1.0, step=0.01, label="Top-p")
226
+ top_k = gr.Slider(0, 100, value=50, step=1, label="Top-k")
227
 
228
+ # MIDDLE (40%) English (75% input / 25% buttons)
229
+ with gr.Group(elem_id="middle"):
230
+ with gr.Group(elem_classes=["panel"]):
231
  gr.Markdown('<div class="panel-h">English Text</div>')
232
  with gr.Group(elem_classes=["panel-b","textwrap"]):
233
  src = gr.Textbox(placeholder="Type English here…", show_label=False, lines=14)
234
+ with gr.Group(elem_classes=["panel"]):
235
  gr.Markdown('<div class="panel-h">Actions</div>')
236
+ with gr.Group(elem_classes=["panel-b"], elem_id="btnrow"):
237
  translate_btn = gr.Button("Translate", variant="primary")
238
  clear_btn = gr.Button("Clear", variant="secondary")
239
 
240
+ # RIGHT (40%) Hindi (50%) / Telugu (50%)
241
+ with gr.Group(elem_id="right"):
242
+ with gr.Group(elem_classes=["panel"]):
243
+ gr.Markdown('<div class="panel-h">Hindi (hin_Deva)<span></span></div>')
 
 
244
  with gr.Group(elem_classes=["panel-b","textwrap"]):
245
  hi_out = gr.Textbox(show_copy_button=True, show_label=False, lines=10)
246
+ with gr.Row(): # small row under box for maximize
247
+ hi_max = gr.Button("⤢ Maximize", elem_classes=["max"])
248
+
249
+ with gr.Group(elem_classes=["panel"]):
250
+ gr.Markdown('<div class="panel-h">Telugu (tel_Telu)<span></span></div>')
251
  with gr.Group(elem_classes=["panel-b","textwrap"]):
252
  te_out = gr.Textbox(show_copy_button=True, show_label=False, lines=10)
253
+ with gr.Row():
254
+ te_max = gr.Button("⤢ Maximize", elem_classes=["max"])
255
 
256
+ # Modal
257
  with gr.Group(visible=False, elem_id="modal") as modal:
258
  modal_title = gr.Markdown('<div class="modal-title">Fullscreen</div>')
259
  fs_text = gr.Textbox(lines=22, elem_id="fs_box")
260
  with gr.Row(elem_classes=["modal-actions"]):
261
  fs_close = gr.Button("Close", variant="secondary")
262
 
263
+ # Wiring
264
  translate_btn.click(
265
  translate_dual,
266
  inputs=[src, num_beams, max_new, temperature, top_p, top_k],
 
269
  )
270
  clear_btn.click(lambda: ("", "", ""), outputs=[src, hi_out, te_out])
271
 
 
272
  def open_hi(h): return gr.update(visible=True), "hi", '<div class="modal-title">Hindi (Fullscreen)</div>', h
273
  def open_te(t): return gr.update(visible=True), "te", '<div class="modal-title">Telugu (Fullscreen)</div>', t
274
  hi_max.click(open_hi, inputs=[hi_out], outputs=[modal, modal_state, modal_title, fs_text])
275
  te_max.click(open_te, inputs=[te_out], outputs=[modal, modal_state, modal_title, fs_text])
276
  fs_close.click(lambda: (gr.update(visible=False), ""), outputs=[modal, modal_state])
277
 
 
278
  demo.queue(max_size=48).launch()