shayekh commited on
Commit
5e8a8a0
·
verified ·
1 Parent(s): 55a83fe

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +118 -30
app.py CHANGED
@@ -67,9 +67,15 @@ def set_stop_thinking():
67
 
68
  def set_kill_threads():
69
  global_kill_threads[0] = True
70
- print(f"[STOP-THINK] set_kill_threads CALLED! Flag is now: {global_kill_threads[0]}")
71
  return gr.update(value="🛑 Stopping...")
72
 
 
 
 
 
 
 
73
 
74
  def extract_pdf_content(pdf_path, max_pages=2):
75
  """Extract text and images from up to max_pages of a PDF."""
@@ -236,7 +242,7 @@ def get_base64_image(image):
236
  return f"data:image/jpeg;base64,{img_str}"
237
 
238
  @spaces.GPU(duration=120)
239
- def extract_vocabulary(pdf_text, images, translit_lang, translit_format, target_lang, max_text_char=1500, repetition_penalty_val=1.1, partial_assistant_text=None):
240
  """Use Transformers to extract vocabulary from text and images."""
241
  global model, processor
242
 
@@ -262,7 +268,7 @@ Return ONLY a valid JSON list of dictionaries, where each dictionary has four ke
262
 
263
  Just output raw JSON with ```json and ``` markers, as the user will load in python.
264
 
265
- CRITICAL: Answer quick without very long thinking. Output the JSON array IMMEDIATELY.
266
 
267
  Text:
268
 
@@ -300,7 +306,7 @@ Text:
300
  model.to("cuda")
301
  text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
302
  if partial_assistant_text:
303
- text += partial_assistant_text + "\n</think>\n\n```json\n[\n"
304
 
305
  inputs = processor(
306
  text=[text],
@@ -353,14 +359,24 @@ Text:
353
  thread.start()
354
 
355
  force_triggered = False
 
356
  for new_text in streamer:
357
  output_text += new_text
358
  yield output_text, None
359
 
360
- # Check if user clicked "Stop thinking"
361
- if global_stop_thinking[0] and not force_triggered:
 
 
 
 
 
 
 
 
362
  force_triggered = True
363
- print("[STOP-THINK] Flag detected inside streamer loop! Killing current generation...")
 
364
 
365
  # 1. Kill the current generation thread
366
  local_stop[0] = True
@@ -378,7 +394,7 @@ Text:
378
  local_stop[0] = False
379
 
380
  # 3. Append the think-closing + JSON prefix
381
- output_text += "\n</think>\n\n```json\n[\n"
382
  yield output_text, None
383
 
384
  # 4. Build new prompt with partial assistant text
@@ -391,7 +407,8 @@ Text:
391
  padding=True
392
  ).to("cuda")
393
 
394
- # 5. Start new generation thread
 
395
  streamer2 = TextIteratorStreamer(processor.tokenizer, skip_prompt=True, skip_special_tokens=True)
396
  thread2 = Thread(target=run_generation, args=(inputs2, streamer2, local_stop))
397
  thread2.start()
@@ -399,8 +416,45 @@ Text:
399
  for new_text2 in streamer2:
400
  output_text += new_text2
401
  yield output_text, None
402
-
403
- thread2.join(timeout=10)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
404
  break # Exit the outer streamer loop
405
 
406
  if not force_triggered:
@@ -453,7 +507,7 @@ Return ONLY a valid JSON list of dictionaries, where each dictionary has four ke
453
  - 'translation' (the translation into {target_lang.upper()})
454
  - 'explanation' (a brief grammar or context note in {target_lang.upper()}).
455
  No markdown formatting, just raw JSON with ```json and ``` markers.
456
- CRITICAL: Do NOT provide any conversational filler, thinking steps, or reasoning. Answer quick without very long thinking. Output the JSON array IMMEDIATELY.
457
 
458
  Korean words:
459
  {words_str}
@@ -537,7 +591,7 @@ def hash_file(filepath):
537
  return hashlib.md5(f.read(1024*1024)).hexdigest()
538
 
539
  @spaces.GPU(duration=120)
540
- def process_pdf(pdf_file, url_input, audio_file_input, yt_url_input, yt_cookies_file, translit_lang, translit_format, target_lang, max_text_char, repetition_penalty_val, last_source_hash, last_korean_words, active_tab, progress=gr.Progress()):
541
  global tts, voice_style
542
 
543
  # Clean language choices from "Family - Language" to just "Language"
@@ -548,6 +602,9 @@ def process_pdf(pdf_file, url_input, audio_file_input, yt_url_input, yt_cookies_
548
 
549
  os.makedirs("log", exist_ok=True)
550
 
 
 
 
551
  # Determine input source based on active tab
552
  is_url = (active_tab == "Website URL") and bool(url_input and url_input.strip())
553
  is_youtube = (active_tab == "YouTube Link") and bool(yt_url_input and yt_url_input.strip() and is_youtube_url(yt_url_input.strip()))
@@ -619,8 +676,11 @@ def process_pdf(pdf_file, url_input, audio_file_input, yt_url_input, yt_cookies_
619
  vocab_list = []
620
  stream_text = ""
621
  for attempt in range(1, 4):
 
 
 
622
  progress(0.2, desc=f"Extracting vocabulary (Attempt {attempt}/3)...")
623
- for stream_t, v_list in extract_vocabulary(content_text, images, translit_lang, translit_format, target_lang, max_text_char, repetition_penalty_val):
624
  stream_text = stream_t
625
  if v_list is not None:
626
  vocab_list = v_list
@@ -629,6 +689,31 @@ def process_pdf(pdf_file, url_input, audio_file_input, yt_url_input, yt_cookies_
629
  if vocab_list:
630
  break
631
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
632
  if not vocab_list:
633
  yield "<p>Failed to extract or translate vocabulary after 3 attempts.</p>", current_source_hash, None, stream_text, content_text, images, extracted_audio_path
634
  return
@@ -958,7 +1043,7 @@ def process_pdf(pdf_file, url_input, audio_file_input, yt_url_input, yt_cookies_
958
  # Return the iframe containing the whole SPA
959
  yield f'<iframe srcdoc="{safe_srcdoc}" style="width: 100%; height: 650px; border: none; overflow-y: auto;"></iframe>', current_source_hash, vocab_list, stream_text, content_text, images, extracted_audio_path
960
 
961
- LANGUAGE_DATA = """Indo-European English, French, Portuguese, German, Romanian, Swedish, Danish, Bulgarian, Russian, Czech, Greek, Ukrainian, Spanish, Dutch, Slovak, Croatian, Polish, Lithuanian, Norwegian Bokmål, Norwegian Nynorsk, Persian, Slovenian, Gujarati, Latvian, Italian, Occitan, Nepali, Marathi, Belarusian, Serbian, Luxembourgish, Venetian, Assamese, Welsh, Silesian, Asturian, Chhattisgarhi, Awadhi, Maithili, Bhojpuri, Sindhi, Irish, Faroese, Hindi, Punjabi, Bengali, Oriya, Tajik, Eastern Yiddish, Lombard, Ligurian, Sicilian, Friulian, Sardinian, Galician, Catalan, Icelandic, Tosk Albanian, Limburgish, Dari, Afrikaans, Macedonian, Sinhala, Urdu, Magahi, Bosnian, Armenian, Latgalian, Scottish Gaelic, Central Kurdish, Northern Kurdish, Southern Pashto, Sanskrit, Dhundari, Marwari, Ahirani, Bagheli, Bagri, Bundeli, Braj, Kumaoni, Kashmiri
962
  Sino-Tibetan Chinese (Simplified), Chinese (Traditional), Cantonese, Burmese, Standard Tibetan, Meitei
963
  Afro-Asiatic Arabic (Standard), Arabic (Najdi), Arabic (Levantine), Arabic (Egyptian), Arabic (Moroccan), Arabic (Mesopotamian), Arabic (Ta’izzi-Adeni), Arabic (Tunisian), Arabic (Gulf), Arabic (Algerian), Arabic (Sudanese), Arabic (Libyan), Hebrew, Maltese, Amharic, Tigrinya, Kabyle, Somali, West Central Oromo, Hausa
964
  Austronesian Indonesian, Malay, Tagalog, Cebuano, Javanese, Sundanese, Minangkabau, Balinese, Banjar, Pangasinan, Iloko, Waray (Philippines), Plateau Malagasy, Malagasy, Buginese, Maori, Samoan, Hawaiian, Fijian
@@ -967,8 +1052,8 @@ Turkic Turkish, North Azerbaijani, Northern Uzbek, Kazakh, Bashkir, Tatar, Crime
967
  Tai-Kadai Thai, Lao, Shan
968
  Uralic Finnish, Estonian, Hungarian, Meadow Mari
969
  Austroasiatic Vietnamese, Khmer
970
- Niger–Congo Yoruba, Ewe, Kinyarwanda, Lingala, Northern Sotho, Nyanja, Shona, Southern Sotho, Tswana, Xhosa, Zulu, Luganda, Swati, Tsonga, Tumbuka, Venda, Chokwe, Luba-Kasai, Rundi, Umbundu, Kikuyu, Kongo, Nigerian Fulfulde, Wolof, Fon, Kabiyè, Mossi, Akan, Twi, Bambara, Igbo
971
- Other Japanese, Korean, Georgian, Basque, Haitian, Papiamento, Kabuverdianu, Tok Pisin, Swahili, Central Aymara, Tulu, Nagamese, Nigerian Pidgin, Mauritian Creole, Sango, Ayacucho Quechua, Halh Mongolian, Southwestern Dinka, Nuer, Guarani"""
972
 
973
  LANGUAGE_CHOICES = []
974
  for line in LANGUAGE_DATA.strip().split('\n'):
@@ -1222,19 +1307,22 @@ def create_demo():
1222
  tab_yt.select(fn=lambda: "YouTube Link", inputs=None, outputs=active_tab)
1223
 
1224
  gr.Markdown("### ⚙️ Customization Settings")
1225
- translit_lang = gr.Dropdown(
1226
- label="Word Transliteration Language",
1227
- choices=LANGUAGE_CHOICES,
1228
- value="Indo-European - English"
1229
- )
1230
- translit_format = gr.Dropdown(label="Transliteration Format", choices=["dashed syllable", "regular word with space"], value="dashed syllable")
1231
- target_lang = gr.Dropdown(
1232
- label="Target Language (Full App)",
1233
- choices=LANGUAGE_CHOICES,
1234
- value="Indo-European - English"
1235
- )
1236
  max_text_char_input = gr.Slider(minimum=1000, maximum=30000, step=1000, value=1500, label="Max Input Text Length (Characters)")
1237
  repetition_penalty_input = gr.Slider(minimum=0.1, maximum=2.0, step=0.1, value=1.2, label="Repetition Penalty")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1238
 
1239
  with gr.Row():
1240
  submit_btn = gr.Button("✨ Generate Flashcards ✨", variant="primary")
@@ -1260,13 +1348,13 @@ def create_demo():
1260
 
1261
  generate_event = submit_btn.click(
1262
  fn=process_pdf,
1263
- inputs=[pdf_input, url_input, audio_file_input, yt_url_input, yt_cookies_input, translit_lang, translit_format, target_lang, max_text_char_input, repetition_penalty_input, last_source_state, last_korean_words_state, active_tab],
1264
  outputs=[output_html, last_source_state, last_korean_words_state, stream_box, extracted_text_box, extracted_images_gallery, extracted_audio_player]
1265
  )
1266
 
1267
  stop_thinking_btn.click(fn=set_stop_thinking, inputs=None, outputs=stop_thinking_btn, queue=False)
1268
 
1269
- stop_btn.click(fn=set_kill_threads, inputs=None, outputs=stop_btn, queue=False).then(fn=None, inputs=None, outputs=None, cancels=[generate_event])
1270
 
1271
  # Force autoscroll using Custom JS
1272
  stream_box.change(
 
67
 
68
  def set_kill_threads():
69
  global_kill_threads[0] = True
70
+ print(f"[KILL] set_kill_threads CALLED! Flag is now: {global_kill_threads[0]}")
71
  return gr.update(value="🛑 Stopping...")
72
 
73
+ def reset_generation_flags():
74
+ """Reset all generation control flags at the start of a new generation."""
75
+ global_stop_thinking[0] = False
76
+ global_kill_threads[0] = False
77
+ print("[FLAGS] Reset stop_thinking and kill_threads to False")
78
+
79
 
80
  def extract_pdf_content(pdf_path, max_pages=2):
81
  """Extract text and images from up to max_pages of a PDF."""
 
242
  return f"data:image/jpeg;base64,{img_str}"
243
 
244
  @spaces.GPU(duration=120)
245
+ def extract_vocabulary(pdf_text, images, translit_lang, translit_format, target_lang, max_text_char=1500, repetition_penalty_val=1.1, partial_assistant_text=None, auto_force_chars=1000):
246
  """Use Transformers to extract vocabulary from text and images."""
247
  global model, processor
248
 
 
268
 
269
  Just output raw JSON with ```json and ``` markers, as the user will load in python.
270
 
271
+ CRITICAL: Do NOT overthink. Do NOT deliberate over conditions, edge cases, or reasoning. Keep your thinking extremely brief (a few words at most). Output the JSON array IMMEDIATELY without lengthy analysis.
272
 
273
  Text:
274
 
 
306
  model.to("cuda")
307
  text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
308
  if partial_assistant_text:
309
+ text += partial_assistant_text + "\nReady to generate.\n</think>\n\n```json\n[\n"
310
 
311
  inputs = processor(
312
  text=[text],
 
359
  thread.start()
360
 
361
  force_triggered = False
362
+ AUTO_FORCE_CHARS = auto_force_chars
363
  for new_text in streamer:
364
  output_text += new_text
365
  yield output_text, None
366
 
367
+ # Auto-force JSON if thinking exceeds 300 chars without producing JSON
368
+ should_auto_force = (
369
+ not force_triggered
370
+ and not partial_assistant_text
371
+ and len(output_text) > AUTO_FORCE_CHARS
372
+ and '```json' not in output_text
373
+ )
374
+
375
+ # Check if user clicked "Stop thinking" OR auto-force threshold reached
376
+ if (global_stop_thinking[0] or should_auto_force) and not force_triggered:
377
  force_triggered = True
378
+ reason = "auto-force (>300 chars)" if should_auto_force else "user clicked stop"
379
+ print(f"[STOP-THINK] Force triggered ({reason})! Killing current generation...")
380
 
381
  # 1. Kill the current generation thread
382
  local_stop[0] = True
 
394
  local_stop[0] = False
395
 
396
  # 3. Append the think-closing + JSON prefix
397
+ output_text += "\nReady to generate.\n</think>\n\n```json\n[\n"
398
  yield output_text, None
399
 
400
  # 4. Build new prompt with partial assistant text
 
407
  padding=True
408
  ).to("cuda")
409
 
410
+ # 5. Start new generation thread with force-JSON context
411
+ # This loop also monitors stop_thinking so user can force again if model keeps thinking
412
  streamer2 = TextIteratorStreamer(processor.tokenizer, skip_prompt=True, skip_special_tokens=True)
413
  thread2 = Thread(target=run_generation, args=(inputs2, streamer2, local_stop))
414
  thread2.start()
 
416
  for new_text2 in streamer2:
417
  output_text += new_text2
418
  yield output_text, None
419
+
420
+ # Allow user to force again if model still isn't producing JSON
421
+ if global_stop_thinking[0] or global_kill_threads[0]:
422
+ print("[STOP-THINK] Flag detected in forced generation loop! Killing...")
423
+ local_stop[0] = True
424
+ while not streamer2.text_queue.empty():
425
+ try:
426
+ streamer2.text_queue.get_nowait()
427
+ except queue.Empty:
428
+ break
429
+ thread2.join(timeout=5)
430
+ global_stop_thinking[0] = False
431
+ local_stop[0] = False
432
+
433
+ # Force JSON prefix again
434
+ output_text += "\nReady to generate.\n</think>\n\n```json\n[\n"
435
+ yield output_text, None
436
+
437
+ text3 = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
438
+ text3 += output_text
439
+ inputs3 = processor(
440
+ text=[text3],
441
+ images=pil_images if pil_images else None,
442
+ return_tensors="pt",
443
+ padding=True
444
+ ).to("cuda")
445
+
446
+ streamer3 = TextIteratorStreamer(processor.tokenizer, skip_prompt=True, skip_special_tokens=True)
447
+ thread3 = Thread(target=run_generation, args=(inputs3, streamer3, local_stop))
448
+ thread3.start()
449
+
450
+ for new_text3 in streamer3:
451
+ output_text += new_text3
452
+ yield output_text, None
453
+
454
+ thread3.join(timeout=10)
455
+ break
456
+ else:
457
+ thread2.join(timeout=10)
458
  break # Exit the outer streamer loop
459
 
460
  if not force_triggered:
 
507
  - 'translation' (the translation into {target_lang.upper()})
508
  - 'explanation' (a brief grammar or context note in {target_lang.upper()}).
509
  No markdown formatting, just raw JSON with ```json and ``` markers.
510
+ CRITICAL: Do NOT overthink. Do NOT deliberate over conditions, edge cases, or reasoning. Keep your thinking extremely brief (5 paragraphs at most). Output the JSON array IMMEDIATELY without lengthy analysis.
511
 
512
  Korean words:
513
  {words_str}
 
591
  return hashlib.md5(f.read(1024*1024)).hexdigest()
592
 
593
  @spaces.GPU(duration=120)
594
+ def process_pdf(pdf_file, url_input, audio_file_input, yt_url_input, yt_cookies_file, translit_lang, translit_format, target_lang, max_text_char, repetition_penalty_val, auto_force_chars_val, last_source_hash, last_korean_words, active_tab, progress=gr.Progress()):
595
  global tts, voice_style
596
 
597
  # Clean language choices from "Family - Language" to just "Language"
 
602
 
603
  os.makedirs("log", exist_ok=True)
604
 
605
+ # Reset flags at start of new generation
606
+ reset_generation_flags()
607
+
608
  # Determine input source based on active tab
609
  is_url = (active_tab == "Website URL") and bool(url_input and url_input.strip())
610
  is_youtube = (active_tab == "YouTube Link") and bool(yt_url_input and yt_url_input.strip() and is_youtube_url(yt_url_input.strip()))
 
676
  vocab_list = []
677
  stream_text = ""
678
  for attempt in range(1, 4):
679
+ if global_kill_threads[0]:
680
+ print("[KILL] Kill flag detected, stopping extraction attempts.")
681
+ break
682
  progress(0.2, desc=f"Extracting vocabulary (Attempt {attempt}/3)...")
683
+ for stream_t, v_list in extract_vocabulary(content_text, images, translit_lang, translit_format, target_lang, max_text_char, repetition_penalty_val, auto_force_chars=auto_force_chars_val):
684
  stream_text = stream_t
685
  if v_list is not None:
686
  vocab_list = v_list
 
689
  if vocab_list:
690
  break
691
 
692
+ # Reset kill flag after extraction so TTS can proceed
693
+ global_kill_threads[0] = False
694
+
695
+ # If generation was killed but we don't have vocab yet, try to salvage JSON from stream_text
696
+ if not vocab_list and stream_text:
697
+ print("[KILL] Attempting to salvage JSON from partial generation output...")
698
+ try:
699
+ import re
700
+ json_matches = list(re.finditer(r'```(?:json)?\s*([\s\S]*?)```', stream_text))
701
+ if json_matches:
702
+ clean_text = json_matches[-1].group(1).strip()
703
+ else:
704
+ json_matches = list(re.finditer(r'(\[[\s\S]*\]|\{[\s\S]*\})', stream_text))
705
+ clean_text = json_matches[-1].group(1).strip() if json_matches else ""
706
+
707
+ if clean_text:
708
+ data = json.loads(clean_text)
709
+ if not isinstance(data, list):
710
+ data = [data]
711
+ if data and isinstance(data[0], dict) and 'korean' in data[0]:
712
+ vocab_list = data
713
+ print(f"[KILL] Salvaged {len(vocab_list)} vocab items from partial output!")
714
+ except Exception as e:
715
+ print(f"[KILL] Could not salvage JSON: {e}")
716
+
717
  if not vocab_list:
718
  yield "<p>Failed to extract or translate vocabulary after 3 attempts.</p>", current_source_hash, None, stream_text, content_text, images, extracted_audio_path
719
  return
 
1043
  # Return the iframe containing the whole SPA
1044
  yield f'<iframe srcdoc="{safe_srcdoc}" style="width: 100%; height: 650px; border: none; overflow-y: auto;"></iframe>', current_source_hash, vocab_list, stream_text, content_text, images, extracted_audio_path
1045
 
1046
+ LANGUAGE_DATA = """Indo-European Bengali, English, French, Portuguese, German, Romanian, Swedish, Danish, Bulgarian, Russian, Czech, Greek, Ukrainian, Spanish, Dutch, Slovak, Croatian, Polish, Lithuanian, Norwegian Bokmål, Norwegian Nynorsk, Persian, Slovenian, Gujarati, Latvian, Italian, Occitan, Nepali, Marathi, Belarusian, Serbian, Luxembourgish, Venetian, Assamese, Welsh, Silesian, Asturian, Chhattisgarhi, Awadhi, Maithili, Bhojpuri, Sindhi, Irish, Faroese, Hindi, Punjabi, Oriya, Tajik, Eastern Yiddish, Lombard, Ligurian, Sicilian, Friulian, Sardinian, Galician, Catalan, Icelandic, Tosk Albanian, Limburgish, Dari, Afrikaans, Macedonian, Sinhala, Urdu, Magahi, Bosnian, Armenian, Latgalian, Scottish Gaelic, Central Kurdish, Northern Kurdish, Southern Pashto, Sanskrit, Dhundari, Marwari, Ahirani, Bagheli, Bagri, Bundeli, Braj, Kumaoni, Kashmiri
1047
  Sino-Tibetan Chinese (Simplified), Chinese (Traditional), Cantonese, Burmese, Standard Tibetan, Meitei
1048
  Afro-Asiatic Arabic (Standard), Arabic (Najdi), Arabic (Levantine), Arabic (Egyptian), Arabic (Moroccan), Arabic (Mesopotamian), Arabic (Ta’izzi-Adeni), Arabic (Tunisian), Arabic (Gulf), Arabic (Algerian), Arabic (Sudanese), Arabic (Libyan), Hebrew, Maltese, Amharic, Tigrinya, Kabyle, Somali, West Central Oromo, Hausa
1049
  Austronesian Indonesian, Malay, Tagalog, Cebuano, Javanese, Sundanese, Minangkabau, Balinese, Banjar, Pangasinan, Iloko, Waray (Philippines), Plateau Malagasy, Malagasy, Buginese, Maori, Samoan, Hawaiian, Fijian
 
1052
  Tai-Kadai Thai, Lao, Shan
1053
  Uralic Finnish, Estonian, Hungarian, Meadow Mari
1054
  Austroasiatic Vietnamese, Khmer
1055
+ Niger–Congo Yoruba, Ewe, Kinyarwanda, Lingala, Northern Sotho, Nyanja, Shona, Southern Sotho, Tswana, Xhosa, Zulu, Luganda, Swati, Tsonga, Tumbuka, Venda, Chokwe, Luba-Kasai, Rundi, Umbundu, Kikuyu, Kongo, Nigerian Fulfulde, Wolof, Fon, Kabiyè, Mossi, Akan, Twi, Bambara, Igbo"""
1056
+ # Other Japanese, Korean, Georgian, Basque, Haitian, Papiamento, Kabuverdianu, Tok Pisin, Swahili, Central Aymara, Tulu, Nagamese, Nigerian Pidgin, Mauritian Creole, Sango, Ayacucho Quechua, Halh Mongolian, Southwestern Dinka, Nuer, Guarani
1057
 
1058
  LANGUAGE_CHOICES = []
1059
  for line in LANGUAGE_DATA.strip().split('\n'):
 
1307
  tab_yt.select(fn=lambda: "YouTube Link", inputs=None, outputs=active_tab)
1308
 
1309
  gr.Markdown("### ⚙️ Customization Settings")
 
 
 
 
 
 
 
 
 
 
 
1310
  max_text_char_input = gr.Slider(minimum=1000, maximum=30000, step=1000, value=1500, label="Max Input Text Length (Characters)")
1311
  repetition_penalty_input = gr.Slider(minimum=0.1, maximum=2.0, step=0.1, value=1.2, label="Repetition Penalty")
1312
+ auto_force_chars_input = gr.Slider(minimum=100, maximum=5000, step=100, value=1000, label="Auto-force JSON after (chars of thinking)")
1313
+
1314
+ with gr.Accordion("🔧 Advanced", open=False):
1315
+ translit_lang = gr.Dropdown(
1316
+ label="Word Transliteration Language",
1317
+ choices=LANGUAGE_CHOICES,
1318
+ value="Indo-European - English"
1319
+ )
1320
+ translit_format = gr.Dropdown(label="Transliteration Format", choices=["dashed syllable", "regular word with space"], value="dashed syllable")
1321
+ target_lang = gr.Dropdown(
1322
+ label="Target Language (Full App)",
1323
+ choices=LANGUAGE_CHOICES,
1324
+ value="Indo-European - English"
1325
+ )
1326
 
1327
  with gr.Row():
1328
  submit_btn = gr.Button("✨ Generate Flashcards ✨", variant="primary")
 
1348
 
1349
  generate_event = submit_btn.click(
1350
  fn=process_pdf,
1351
+ inputs=[pdf_input, url_input, audio_file_input, yt_url_input, yt_cookies_input, translit_lang, translit_format, target_lang, max_text_char_input, repetition_penalty_input, auto_force_chars_input, last_source_state, last_korean_words_state, active_tab],
1352
  outputs=[output_html, last_source_state, last_korean_words_state, stream_box, extracted_text_box, extracted_images_gallery, extracted_audio_player]
1353
  )
1354
 
1355
  stop_thinking_btn.click(fn=set_stop_thinking, inputs=None, outputs=stop_thinking_btn, queue=False)
1356
 
1357
+ stop_btn.click(fn=set_kill_threads, inputs=None, outputs=stop_btn, queue=False)
1358
 
1359
  # Force autoscroll using Custom JS
1360
  stream_box.change(