Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
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"[
|
| 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:
|
| 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 |
-
#
|
| 361 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
force_triggered = True
|
| 363 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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,
|
| 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)
|
| 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(
|