Update app.py
Browse files
app.py
CHANGED
|
@@ -153,7 +153,7 @@ def extract_youtube_audio(url, max_duration_sec=300, cookiefile=None):
|
|
| 153 |
|
| 154 |
return wav_path, title
|
| 155 |
|
| 156 |
-
@spaces.GPU(duration=
|
| 157 |
def transcribe_audio_with_asr(audio_path):
|
| 158 |
"""Transcribe audio file using Cohere ASR model via transformers."""
|
| 159 |
global asr_model, asr_processor
|
|
@@ -168,6 +168,14 @@ def transcribe_audio_with_asr(audio_path):
|
|
| 168 |
texts = asr_processor.decode(outputs, skip_special_tokens=True)
|
| 169 |
# text = texts[0] if isinstance(texts, list) else texts
|
| 170 |
# join texts
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
text = "\n".join(texts) if isinstance(texts, list) else texts
|
| 172 |
|
| 173 |
|
|
@@ -241,7 +249,7 @@ def get_base64_image(image):
|
|
| 241 |
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
| 242 |
return f"data:image/jpeg;base64,{img_str}"
|
| 243 |
|
| 244 |
-
@spaces.GPU(duration=
|
| 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
|
|
@@ -249,32 +257,54 @@ def extract_vocabulary(pdf_text, images, translit_lang, translit_format, target_
|
|
| 249 |
os.makedirs("log", exist_ok=True)
|
| 250 |
|
| 251 |
if len(pdf_text.strip()) == 0:
|
| 252 |
-
pdf_text = '''"No Text available, see provided
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
| 254 |
|
| 255 |
non_english = ""
|
| 256 |
if translit_lang.upper() != "ENGLISH":
|
| 257 |
non_english = f" CRITICAL: You MUST use the native alphabet/script of {translit_lang.upper()}, do NOT use English letters unless requested."
|
| 258 |
|
| 259 |
-
prompt_text = f"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
Focus on meaningful vocabulary that is highly helpful for a new language learner (e.g., common nouns, verbs, adjectives, or useful expressions).
|
| 261 |
CRITICAL: Do NOT extract website template words, navigation menus, boilerplate text, UI elements, or titles like 'Home page', 'News', 'Menu'.
|
| 262 |
|
| 263 |
Return ONLY a valid JSON list of dictionaries, where each dictionary has four keys:
|
| 264 |
- 'korean' (the Korean text)
|
| 265 |
- 'transliteration' (the pronunciation transliterated into {translit_lang.upper()} script/characters, formatted as {translit_format}.{non_english})
|
| 266 |
-
- 'translation' (the translation into {target_lang.upper()})
|
| 267 |
- 'explanation' (a brief grammar or context note in {target_lang.upper()}).
|
| 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 |
|
| 275 |
-
<scrpated-content>
|
| 276 |
-
{pdf_text[:int(max_text_char)]}
|
| 277 |
-
</scrpated-content>
|
| 278 |
"""
|
| 279 |
|
| 280 |
# DEBUG: Log prompt text
|
|
@@ -375,7 +405,7 @@ Text:
|
|
| 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 (>
|
| 379 |
print(f"[STOP-THINK] Force triggered ({reason})! Killing current generation...")
|
| 380 |
|
| 381 |
# 1. Kill the current generation thread
|
|
@@ -482,8 +512,13 @@ Text:
|
|
| 482 |
# Fallback: find last [ ... ] or { ... } block
|
| 483 |
json_matches = list(re.finditer(r'(\[[\s\S]*\]|\{[\s\S]*\})', output_text))
|
| 484 |
clean_text = json_matches[-1].group(1).strip() if json_matches else output_text.strip()
|
| 485 |
-
|
| 486 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
if not isinstance(data, list):
|
| 488 |
data = [data]
|
| 489 |
yield output_text, data
|
|
@@ -568,7 +603,12 @@ Korean words:
|
|
| 568 |
json_matches = list(re.finditer(r'(\[[\s\S]*\]|\{[\s\S]*\})', output_text))
|
| 569 |
clean_text = json_matches[-1].group(1).strip() if json_matches else output_text.strip()
|
| 570 |
|
| 571 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 572 |
if not isinstance(data, list):
|
| 573 |
data = [data]
|
| 574 |
return data
|
|
@@ -590,7 +630,7 @@ def hash_file(filepath):
|
|
| 590 |
with open(filepath, 'rb') as f:
|
| 591 |
return hashlib.md5(f.read(1024*1024)).hexdigest()
|
| 592 |
|
| 593 |
-
@spaces.GPU(duration=
|
| 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 |
|
|
@@ -705,7 +745,11 @@ def process_pdf(pdf_file, url_input, audio_file_input, yt_url_input, yt_cookies_
|
|
| 705 |
clean_text = json_matches[-1].group(1).strip() if json_matches else ""
|
| 706 |
|
| 707 |
if clean_text:
|
| 708 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 709 |
if not isinstance(data, list):
|
| 710 |
data = [data]
|
| 711 |
if data and isinstance(data[0], dict) and 'korean' in data[0]:
|
|
@@ -1083,7 +1127,7 @@ def get_example_audio():
|
|
| 1083 |
print(f"Failed to download example audio: {e}")
|
| 1084 |
return file_path if os.path.exists(file_path) else None
|
| 1085 |
|
| 1086 |
-
@spaces.GPU(duration=
|
| 1087 |
def process_pdf_force(partial_text, pdf_file, url_input, translit_lang, translit_format, target_lang, max_text_char, repetition_penalty_val, last_source_state, last_korean_words_state):
|
| 1088 |
"""Force JSON generation using the current partial stream_box text."""
|
| 1089 |
is_url = bool(url_input and url_input.strip())
|
|
@@ -1309,7 +1353,7 @@ def create_demo():
|
|
| 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=
|
| 1313 |
|
| 1314 |
with gr.Accordion("π§ Advanced", open=False):
|
| 1315 |
translit_lang = gr.Dropdown(
|
|
|
|
| 153 |
|
| 154 |
return wav_path, title
|
| 155 |
|
| 156 |
+
@spaces.GPU(duration=180)
|
| 157 |
def transcribe_audio_with_asr(audio_path):
|
| 158 |
"""Transcribe audio file using Cohere ASR model via transformers."""
|
| 159 |
global asr_model, asr_processor
|
|
|
|
| 168 |
texts = asr_processor.decode(outputs, skip_special_tokens=True)
|
| 169 |
# text = texts[0] if isinstance(texts, list) else texts
|
| 170 |
# join texts
|
| 171 |
+
# Filter the lines in texts which are english only and no korean
|
| 172 |
+
if isinstance(texts, list):
|
| 173 |
+
# Filter out lines that are purely English/symbols (no Korean characters)
|
| 174 |
+
# Korean Unicode range: AC00-D7A3 (Syllables), 1100-11FF (Jamo), 3130-318F (Compatibility Jamo)
|
| 175 |
+
korean_re = re_module.compile(r'[κ°-ν£γ±-γ
γ
-γ
£]')
|
| 176 |
+
texts = [line for line in texts if korean_re.search(line)]
|
| 177 |
+
|
| 178 |
+
|
| 179 |
text = "\n".join(texts) if isinstance(texts, list) else texts
|
| 180 |
|
| 181 |
|
|
|
|
| 249 |
img_str = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
| 250 |
return f"data:image/jpeg;base64,{img_str}"
|
| 251 |
|
| 252 |
+
@spaces.GPU(duration=180)
|
| 253 |
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):
|
| 254 |
"""Use Transformers to extract vocabulary from text and images."""
|
| 255 |
global model, processor
|
|
|
|
| 257 |
os.makedirs("log", exist_ok=True)
|
| 258 |
|
| 259 |
if len(pdf_text.strip()) == 0:
|
| 260 |
+
pdf_text = '''"No Text available, see provided Images only."'''
|
| 261 |
+
|
| 262 |
+
no_img = ""
|
| 263 |
+
if len(images) == 0:
|
| 264 |
+
no_img = '''\n"No Images available, see provided Text only."'''
|
| 265 |
|
| 266 |
non_english = ""
|
| 267 |
if translit_lang.upper() != "ENGLISH":
|
| 268 |
non_english = f" CRITICAL: You MUST use the native alphabet/script of {translit_lang.upper()}, do NOT use English letters unless requested."
|
| 269 |
|
| 270 |
+
prompt_text = f"""Text:
|
| 271 |
+
|
| 272 |
+
<scrpated-content>
|
| 273 |
+
{pdf_text[:int(max_text_char)]}
|
| 274 |
+
</scrpated-content>{no_img}
|
| 275 |
+
|
| 276 |
+
Extract at least 10 key Korean words or phrases from the following text and images.
|
| 277 |
Focus on meaningful vocabulary that is highly helpful for a new language learner (e.g., common nouns, verbs, adjectives, or useful expressions).
|
| 278 |
CRITICAL: Do NOT extract website template words, navigation menus, boilerplate text, UI elements, or titles like 'Home page', 'News', 'Menu'.
|
| 279 |
|
| 280 |
Return ONLY a valid JSON list of dictionaries, where each dictionary has four keys:
|
| 281 |
- 'korean' (the Korean text)
|
| 282 |
- 'transliteration' (the pronunciation transliterated into {translit_lang.upper()} script/characters, formatted as {translit_format}.{non_english})
|
| 283 |
+
- 'translation' (the brief translation into {target_lang.upper()})
|
| 284 |
- 'explanation' (a brief grammar or context note in {target_lang.upper()}).
|
| 285 |
|
| 286 |
+
Just output raw JSON with ```json and ``` markers, as the user will load in python. Example:
|
| 287 |
+
|
| 288 |
+
```json
|
| 289 |
+
[
|
| 290 |
+
{{
|
| 291 |
+
"korean": "λ μ¨",
|
| 292 |
+
"transliteration": "nal-ssi",
|
| 293 |
+
"translation": "weather",
|
| 294 |
+
"explanation": "Common noun used to describe weather conditions."
|
| 295 |
+
}},
|
| 296 |
+
{{
|
| 297 |
+
"korean": "λ§μλ€",
|
| 298 |
+
"transliteration": "ma-sit-da",
|
| 299 |
+
"translation": "to be delicious",
|
| 300 |
+
"explanation": "Descriptive verb. Polite form: λ§μμ΄μ. Used to compliment food."
|
| 301 |
+
}}
|
| 302 |
+
]
|
| 303 |
+
```
|
| 304 |
|
| 305 |
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.
|
| 306 |
|
|
|
|
| 307 |
|
|
|
|
|
|
|
|
|
|
| 308 |
"""
|
| 309 |
|
| 310 |
# DEBUG: Log prompt text
|
|
|
|
| 405 |
# Check if user clicked "Stop thinking" OR auto-force threshold reached
|
| 406 |
if (global_stop_thinking[0] or should_auto_force) and not force_triggered:
|
| 407 |
force_triggered = True
|
| 408 |
+
reason = f"auto-force (>{AUTO_FORCE_CHARS} chars)" if should_auto_force else "user clicked stop"
|
| 409 |
print(f"[STOP-THINK] Force triggered ({reason})! Killing current generation...")
|
| 410 |
|
| 411 |
# 1. Kill the current generation thread
|
|
|
|
| 512 |
# Fallback: find last [ ... ] or { ... } block
|
| 513 |
json_matches = list(re.finditer(r'(\[[\s\S]*\]|\{[\s\S]*\})', output_text))
|
| 514 |
clean_text = json_matches[-1].group(1).strip() if json_matches else output_text.strip()
|
| 515 |
+
try:
|
| 516 |
+
data = json.loads(clean_text)
|
| 517 |
+
except:
|
| 518 |
+
import jiter
|
| 519 |
+
# Get bytes from string
|
| 520 |
+
data = jiter.from_json(clean_text.encode("utf-8"), partial_mode=True)
|
| 521 |
+
|
| 522 |
if not isinstance(data, list):
|
| 523 |
data = [data]
|
| 524 |
yield output_text, data
|
|
|
|
| 603 |
json_matches = list(re.finditer(r'(\[[\s\S]*\]|\{[\s\S]*\})', output_text))
|
| 604 |
clean_text = json_matches[-1].group(1).strip() if json_matches else output_text.strip()
|
| 605 |
|
| 606 |
+
try:
|
| 607 |
+
data = json.loads(clean_text)
|
| 608 |
+
except:
|
| 609 |
+
import jiter
|
| 610 |
+
data = jiter.from_json(clean_text.encode("utf-8"), partial_mode=True)
|
| 611 |
+
|
| 612 |
if not isinstance(data, list):
|
| 613 |
data = [data]
|
| 614 |
return data
|
|
|
|
| 630 |
with open(filepath, 'rb') as f:
|
| 631 |
return hashlib.md5(f.read(1024*1024)).hexdigest()
|
| 632 |
|
| 633 |
+
@spaces.GPU(duration=180)
|
| 634 |
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()):
|
| 635 |
global tts, voice_style
|
| 636 |
|
|
|
|
| 745 |
clean_text = json_matches[-1].group(1).strip() if json_matches else ""
|
| 746 |
|
| 747 |
if clean_text:
|
| 748 |
+
try:
|
| 749 |
+
data = json.loads(clean_text)
|
| 750 |
+
except:
|
| 751 |
+
import jiter
|
| 752 |
+
data = jiter.from_json(clean_text.encode("utf-8"), partial_mode=True)
|
| 753 |
if not isinstance(data, list):
|
| 754 |
data = [data]
|
| 755 |
if data and isinstance(data[0], dict) and 'korean' in data[0]:
|
|
|
|
| 1127 |
print(f"Failed to download example audio: {e}")
|
| 1128 |
return file_path if os.path.exists(file_path) else None
|
| 1129 |
|
| 1130 |
+
@spaces.GPU(duration=180)
|
| 1131 |
def process_pdf_force(partial_text, pdf_file, url_input, translit_lang, translit_format, target_lang, max_text_char, repetition_penalty_val, last_source_state, last_korean_words_state):
|
| 1132 |
"""Force JSON generation using the current partial stream_box text."""
|
| 1133 |
is_url = bool(url_input and url_input.strip())
|
|
|
|
| 1353 |
gr.Markdown("### βοΈ Customization Settings")
|
| 1354 |
max_text_char_input = gr.Slider(minimum=1000, maximum=30000, step=1000, value=1500, label="Max Input Text Length (Characters)")
|
| 1355 |
repetition_penalty_input = gr.Slider(minimum=0.1, maximum=2.0, step=0.1, value=1.2, label="Repetition Penalty")
|
| 1356 |
+
auto_force_chars_input = gr.Slider(minimum=1_000, maximum=10_000, step=100, value=4_000, label="Auto-force JSON after (chars of thinking)")
|
| 1357 |
|
| 1358 |
with gr.Accordion("π§ Advanced", open=False):
|
| 1359 |
translit_lang = gr.Dropdown(
|