Spaces:
Runtime error
Runtime error
| import os | |
| import json | |
| import tempfile | |
| import gradio as gr | |
| import openai | |
| from transformers import pipeline | |
| # ========================= | |
| # ๐ง ์ค์ | |
| # ========================= | |
| openai.api_key = os.environ.get("OPENAI_API_KEY") | |
| # Hugging Face ๋ฒ์ญ ํ์ดํ๋ผ์ธ (์๋ฐฉํฅ ํฌํจ) | |
| translator_ko_to_en = pipeline("translation", model="Helsinki-NLP/opus-mt-ko-en") | |
| translator_ko_to_de = pipeline("translation", model="Helsinki-NLP/opus-mt-ko-de") | |
| translator_en_to_ko = pipeline("translation", model="Helsinki-NLP/opus-mt-en-ko") | |
| translator_de_to_ko = pipeline("translation", model="Helsinki-NLP/opus-mt-de-ko") | |
| # ========================= | |
| # ๐ง ์ ํธ: OpenAI ํธ์ถ | |
| # ========================= | |
| def gpt(messages, temperature=0.7, model="gpt-4"): | |
| """๋จ์ผ ChatCompletion ๋ํผ""" | |
| resp = openai.ChatCompletion.create( | |
| model=model, | |
| messages=messages, | |
| temperature=temperature | |
| ) | |
| return resp.choices[0].message["content"].strip() | |
| # ========================= | |
| # ๐งฉ ํต์ฌ ๋ก์ง | |
| # ========================= | |
| def make_variants(input_text, source_lang, target_lang, direct_translation): | |
| """ | |
| ์ง์ญ์ ๊ธฐ์ค์ผ๋ก ์์ด๋ฏผ์ด ์์ฐ์ค๋ฝ๊ฒ ์ฐ๋ ๋ณํ ๋ฒ์ญ 2๊ฐ๋ฅผ ์ถ๊ฐ๋ก ์์ฑ (์ด 3๊ฐ) | |
| """ | |
| sys_msg = "You are a bilingual translator who produces concise, natural alternatives." | |
| user_msg = f""" | |
| [์๋ฌธ] ({source_lang}): {input_text} | |
| [์ง์ญ] ({target_lang}): {direct_translation} | |
| ์ ์ง์ญ์ ๊ธฐ์ค์ผ๋ก, {target_lang} ์์ด๋ฏผ์ด ์ค์ ๋ก ๋ง์ด ์ฐ๋ ์์ฐ์ค๋ฌ์ด ๋ณํ 2๊ฐ์ง๋ฅผ ๋ง๋ค์ด์ค. | |
| - ๋งฅ๋ฝ: ์ผ์ ๋ํ ๊ธฐ์ค | |
| - ๊ฐ ๋ณํ์ 1๋ฌธ์ฅ | |
| - ๊ณผ์ฅ/์ฌ๋ญ์ ๊ณผํ์ง ์๊ฒ | |
| - ์ถ๋ ฅ ํ์: | |
| 1) ๋ณํA: ... | |
| 2) ๋ณํB: ... | |
| """ | |
| out = gpt([{"role":"system","content":sys_msg},{"role":"user","content":user_msg}], temperature=0.6) | |
| # ๊ฐ๋จ ํ์ฑ | |
| variants = [direct_translation] | |
| for line in out.splitlines(): | |
| line = line.strip() | |
| if line.startswith("1)") or line.lower().startswith("๋ณํa"): | |
| variants.append(line.split(":",1)[1].strip() if ":" in line else line) | |
| elif line.startswith("2)") or line.lower().startswith("๋ณํb"): | |
| variants.append(line.split(":",1)[1].strip() if ":" in line else line) | |
| # fallback | |
| return variants[:3] if len(variants)>=3 else (variants + ["", ""])[:3] | |
| def back_translate_list(variants, source_lang, target_lang): | |
| """๊ฐ ๋ณํ ๋ฒ์ญ์ ๋ชจ๊ตญ์ด๋ก ์ญ๋ฒ์ญํ์ฌ ๋น๊ต ํ ์ด๋ธ์ฉ ๋ฐ์ดํฐ ์์ฑ""" | |
| back_list = [] | |
| for v in variants: | |
| if not v: | |
| back_list.append("") | |
| continue | |
| if source_lang == "ํ๊ตญ์ด" and target_lang == "์์ด": | |
| back_ = translator_en_to_ko(v)[0]["translation_text"] | |
| elif source_lang == "ํ๊ตญ์ด" and target_lang == "๋ ์ผ์ด": | |
| back_ = translator_de_to_ko(v)[0]["translation_text"] | |
| else: | |
| back_ = "(์ญ๋ฒ์ญ ๋ฏธ์ง์)" | |
| back_list.append(back_) | |
| return back_list | |
| def build_explanations(input_text, variants, source_lang, target_lang): | |
| """ํํ/๋ฌธ๋ฒ/๋จ์ด/๋ฌธํ ์ค๋ช ์ ์น์ ๋ณ ๋งํฌ๋ค์ด์ผ๋ก ์์ฑ""" | |
| best = variants[0] if variants else "" | |
| sys_msg = "You are a concise yet friendly language tutor who explains in Korean with clear headings and bullet points." | |
| user_msg = f""" | |
| ๋ค์ ํํ์ ๋ํด ํ๊ตญ์ด๋ก ์ค๋ช ํด์ค. ๊ฐ๊ฒฐํ์ง๋ง ํต์ฌ์ ๋น ์ง์์ด. | |
| [์๋ฌธ] ({source_lang}): {input_text} | |
| [๋ํ ๋ฒ์ญ] ({target_lang}): {best} | |
| [๋ค๋ฅธ ๋ณํ ๋ฒ์ญ๋ค]: {variants[1:]} | |
| ์๋ ์น์ ์ ๋ชฉ์ ๊ทธ๋๋ก ์ฌ์ฉํด: | |
| ## ํํ ์ค๋ช | |
| - ์ด๋ค ์ํฉ/๊ด๊ณ์์ ์ฐ๋์ง, ๋์์ค(๊ฒฉ์/์น๊ทผ๊ฐ) | |
| ## ๋ฌธ๋ฒ ํฌ์ธํธ | |
| - ํต์ฌ ๋ฌธ๋ฒ ์์ 2~3๊ฐ (์กฐ์ฌ/์ ์น์ฌ, ์์ , ์ด์ ๋ฑ) | |
| - ๊ฐ๋จ ์๋ฌธ ๊ฐ 1๊ฐ | |
| ## ๋จ์ด/ํํ ์ค๋ช | |
| - ์ด๋ ค์ธ ์ ์๋ ๋จ์ด/๊ตฌ์ 3๊ฐ: ์๋ฏธ + ์งง์ ์๋ฌธ | |
| ## ๋ฌธํ์ ์ฐจ์ด | |
| - ํ๊ตญ์ด์ ๋์ ์ธ์ด ์ฌ์ด์ ๊ธฐ๋/์์/๊ด์ต ์ฐจ์ด 2~3๊ฐ์ง | |
| """ | |
| return gpt([{"role":"system","content":sys_msg},{"role":"user","content":user_msg}], temperature=0.5) | |
| def build_pronunciation(input_text, variants, source_lang, target_lang): | |
| """๋ฐ์ ๊ฐ์ด๋(ํ ์คํธ). IPA/๊ฐ์ธ/๋ฆฌ๋ฌ ํฌ์ธํธ""" | |
| best = variants[0] if variants else "" | |
| sys_msg = "You provide compact pronunciation guides (IPA-ish, stress, rhythm)." | |
| user_msg = f""" | |
| ๋ค์ ๋ ๋ฌธ์ฅ์ ๋ํ ๋ฐ์ ๊ฐ์ด๋๋ฅผ ํ๊ตญ์ด๋ก ๊ฐ๋จํ ์ ์ด์ค. | |
| [์๋ฌธ] ({source_lang}): {input_text} | |
| [๋ํ ๋ฒ์ญ] ({target_lang}): {best} | |
| ํ์: | |
| - ์๋ฌธ: (๊ฐ๋ฅํ๋ฉด ๊ฐ๋จ IPA/ํ๊ธํ๊ธฐ) + ๊ฐ์ธ/๋ฆฌ๋ฌ ํฌ์ธํธ | |
| - ๋ฒ์ญ: (IPA/๊ฐ์ธ) + ์์ฐ์ค๋ฌ์ด ์ต์ ํ | |
| """ | |
| return gpt([{"role":"system","content":sys_msg},{"role":"user","content":user_msg}], temperature=0.4) | |
| def build_roleplay(input_text, variants, target_lang): | |
| """๊ฒฉ์/์น๊ทผ 2๊ฐ์ง ํค์ ์งง์ Role Play""" | |
| best = variants[0] if variants else "" | |
| sys_msg = "You create short, practical role-play dialogues for language learners." | |
| user_msg = f""" | |
| ๋ค์ ํํ์ ํ์ฉํ ์งง์ ๋ํ 2๊ฐ์ง๋ฅผ ๋ง๋ค์ด์ค. ๊ฐ ๋ํ๋ 6~8 ํด. | |
| - ํค1: ๊ฒฉ์(์ง์ฅ/๊ณต์ ์ธ ์ํฉ) | |
| - ํค2: ์น๊ทผ(์น๊ตฌ/๊ฐ๋ฒผ์ด ์ํฉ) | |
| - ๋์ ์ธ์ด: {target_lang} | |
| - ๋ํ ํ ํ๊ตญ์ด ์์ฝ ํ ์ค | |
| ํํ: "{best}" | |
| """ | |
| return gpt([{"role":"system","content":sys_msg},{"role":"user","content":user_msg}], temperature=0.7) | |
| def suggest_resources(input_text, target_lang): | |
| """ํ์ต ์๋ฃ ์ถ์ฒ: ์ ํ๋ธ/๊ฒ์ ํค์๋""" | |
| sys_msg = "You suggest search keywords for YouTube and web to find usage contexts." | |
| user_msg = f""" | |
| ์๋ ํํ์ ์ค์ ๋งฅ๋ฝ์์ ๋ณผ ์ ์๋ ์๋ฃ๋ฅผ ์ฐพ๊ธฐ ์ํ ๊ฒ์ ํค์๋๋ฅผ ์ ์ํด์ค. | |
| - ์ธ์ด: {target_lang} | |
| - 5~7๊ฐ ํค์๋, ๋ฐ์ดํ ์์ด, ํ ์ค์ ํ๋ | |
| ํํ: {input_text} | |
| """ | |
| out = gpt([{"role":"system","content":sys_msg},{"role":"user","content":user_msg}], temperature=0.5) | |
| # ํด๋ฆญ ๊ฐ๋ฅํ ๊ฒ์ URL ๋ฌธ์์ด ์์ฑ | |
| items = [s.strip("-โข ").strip() for s in out.splitlines() if s.strip()] | |
| md_lines = [] | |
| base = "https://www.youtube.com/results?search_query=" | |
| for k in items: | |
| url = base + k.replace(" ", "+") | |
| md_lines.append(f"- [{k}]({url})") | |
| return "\n".join(md_lines) | |
| # ========================= | |
| # ๐ ๋ฉ์ธ ํจ์ (Gradio์ ์ฐ๊ฒฐ) | |
| # ========================= | |
| def run_pipeline(input_text, source_lang, target_lang, favorites_state): | |
| if not input_text.strip(): | |
| return ( | |
| "", [], None, "", "", "", favorites_state, gr.update(visible=False), None | |
| ) | |
| # 1) ๊ธฐ๋ณธ ๋ฒ์ญ | |
| if source_lang == "ํ๊ตญ์ด" and target_lang == "์์ด": | |
| direct = translator_ko_to_en(input_text)[0]['translation_text'] | |
| elif source_lang == "ํ๊ตญ์ด" and target_lang == "๋ ์ผ์ด": | |
| direct = translator_ko_to_de(input_text)[0]['translation_text'] | |
| else: | |
| return ( | |
| input_text, ["(์ง์๋์ง ์๋ ์ธ์ด์์ ๋๋ค.)"], None, "(์ง์๋์ง ์๋ ์ธ์ด์)", "", "", favorites_state, gr.update(visible=False), None | |
| ) | |
| # 2) ๋ณํ 3๊ฐ์ง | |
| variants = make_variants(input_text, source_lang, target_lang, direct) | |
| # 3) ์ญ๋ฒ์ญ ํ ์ด๋ธ ๋ฐ์ดํฐ | |
| backs = back_translate_list(variants, source_lang, target_lang) | |
| back_table = { | |
| "๋ฒ์ญ(Variant)": variants, | |
| "์ญ๋ฒ์ญ(๋ชจ๊ตญ์ด)": backs | |
| } | |
| # 4) ์ค๋ช ์น์ | |
| explanations_md = build_explanations(input_text, variants, source_lang, target_lang) | |
| # 5) ๋ฐ์ ๊ฐ์ด๋ | |
| pron_md = build_pronunciation(input_text, variants, source_lang, target_lang) | |
| # 6) Role Play | |
| roleplay_md = build_roleplay(input_text, variants, target_lang) | |
| # 7) ์๋ฃ ์ถ์ฒ | |
| resources_md = suggest_resources(input_text, target_lang) | |
| # 8) ์ฆ๊ฒจ์ฐพ๊ธฐ ์นด๋(ํ์ฌ ๊ฒฐ๊ณผ) | |
| current_card = { | |
| "์๋ฌธ": input_text, | |
| "๋ํ ๋ฒ์ญ": variants[0], | |
| "๋ค๋ฅธ ๋ณํ": variants[1:], | |
| "์ญ๋ฒ์ญ": backs, | |
| "์ค๋ช ": explanations_md, | |
| "๋ฐ์": pron_md, | |
| "role_play": roleplay_md | |
| } | |
| # ๋ค์ด๋ก๋ ํ์ผ์ Save ๋ฒํผ ํด๋ฆญ ์ ์์ฑํ๋๋ก ํ๋ฏ๋ก ์ฌ๊ธฐ์๋ None | |
| return ( | |
| input_text, | |
| variants, | |
| back_table, | |
| explanations_md, | |
| pron_md, | |
| roleplay_md, | |
| favorites_state, | |
| gr.update(visible=True), | |
| resources_md | |
| ) | |
| def save_to_favorites(input_text, variants, backs, explanations_md, pron_md, roleplay_md, favorites_state): | |
| if favorites_state is None: | |
| favorites_state = [] | |
| entry = { | |
| "์๋ฌธ": input_text, | |
| "๋ณํ๋ฒ์ญ": variants, | |
| "์ญ๋ฒ์ญ": backs, | |
| "์ค๋ช ": explanations_md, | |
| "๋ฐ์": pron_md, | |
| "role_play": roleplay_md | |
| } | |
| favorites_state.append(entry) | |
| return favorites_state, f"์ ์ฅ ์๋ฃ! (์ด {len(favorites_state)}๊ฑด)" | |
| def export_favorites(favorites_state): | |
| if not favorites_state: | |
| return None | |
| fd, path = tempfile.mkstemp(suffix=".json") | |
| with os.fdopen(fd, "w", encoding="utf-8") as f: | |
| json.dump(favorites_state, f, ensure_ascii=False, indent=2) | |
| return path | |
| def load_sample(sample_text): | |
| return gr.update(value=sample_text) | |
| # ========================= | |
| # ๐๏ธ Gradio UI | |
| # ========================= | |
| with gr.Blocks(title="๐ ๋ฌธํ ๊ฐ ํํ ๋น๊ต + ๋ฌธ๋ฒ & ์ดํ ๋์ฐ๋ฏธ (ํ์ฅํ)") as demo: | |
| gr.Markdown("## ๐ ๋ฌธํ ๊ฐ ํํ ๋น๊ต + ๋ฌธ๋ฒ & ์ดํ ๋์ฐ๋ฏธ\n์ ๋ ฅํ ํํ์ ๊ธฐ๋ฐ์ผ๋ก **์์ฐ์ค๋ฌ์ด ๋ฒ์ญ 3๊ฐ์ง, ์ญ๋ฒ์ญ ๋น๊ต, ๋ฌธ๋ฒ/๋ฌธํ ์ค๋ช , Role Play, ๋ฐ์ ๊ฐ์ด๋**๊น์ง ํ ๋ฒ์!") | |
| with gr.Row(): | |
| with gr.Column(scale=5): | |
| input_text = gr.Textbox(label="๋น๊ตํ ๋ฌธ์ฅ ์ ๋ ฅ", placeholder="์: ๊ณ ์ํ์ด!", lines=2) | |
| with gr.Row(): | |
| src_dd = gr.Dropdown(["ํ๊ตญ์ด"], label="๋ชจ๊ตญ์ด ์ ํ", value="ํ๊ตญ์ด") | |
| tgt_dd = gr.Dropdown(["์์ด", "๋ ์ผ์ด"], label="๋น๊ต ์ธ์ด ์ ํ", value="์์ด") | |
| with gr.Accordion("์ํ ๋ฌธ์ฅ ๋ถ๋ฌ์ค๊ธฐ", open=False): | |
| gr.Markdown("- ์ํฉ๋ณ๋ก ๋ฐ๋ก ํ ์คํธํด๋ณด์ธ์.") | |
| with gr.Row(): | |
| b1 = gr.Button("์น๊ตฌ ์๋ก: ๊ณ ์ํ์ด!") | |
| b2 = gr.Button("๊ฒฉ๋ ค: ์๊ณ ๋ง์์ด, ์ ๋ง ๊ณ ๋ง์.") | |
| b3 = gr.Button("์ ๋ฌด: ์ค๋ ์ผ์ ํ์ธ ๋ถํ๋๋ฆฝ๋๋ค.") | |
| submit = gr.Button("๐ Submit", variant="primary") | |
| with gr.Column(scale=7): | |
| tabs = gr.Tabs() | |
| with tabs: | |
| with gr.Tab("๊ฒฐ๊ณผ ์์ฝ"): | |
| orig_out = gr.Textbox(label="์๋ฌธ", interactive=False) | |
| variants_out = gr.HighlightedText( | |
| label="๋ฒ์ญ 3๊ฐ์ง (์ง์ญ + ์์ฐ์ค๋ฌ์ด ๋ณํ)", | |
| combine_adjacent=True | |
| ) | |
| resources_md = gr.Markdown(visible=False) | |
| with gr.Tab("์ญ๋ฐฉํฅ ๋น๊ต"): | |
| back_table = gr.Dataframe(headers=["๋ฒ์ญ(Variant)", "์ญ๋ฒ์ญ(๋ชจ๊ตญ์ด)"], interactive=False) | |
| with gr.Tab("์ค๋ช "): | |
| explain_out = gr.Markdown() | |
| with gr.Tab("๋ฐ์ ๊ฐ์ด๋"): | |
| pron_out = gr.Markdown() | |
| with gr.Tab("Role Play"): | |
| role_out = gr.Markdown() | |
| with gr.Tab("์ฆ๊ฒจ์ฐพ๊ธฐ"): | |
| fav_state = gr.State([]) | |
| save_btn = gr.Button("โญ ํ์ฌ ๊ฒฐ๊ณผ ์ ์ฅ") | |
| save_status = gr.Markdown("") | |
| export_btn = gr.Button("โฌ๏ธ ์ฆ๊ฒจ์ฐพ๊ธฐ JSON ๋ด๋ณด๋ด๊ธฐ") | |
| export_file = gr.File(label="๋ค์ด๋ก๋ ํ์ผ") | |
| # ---------- ์ด๋ฒคํธ ๋ฐ์ธ๋ฉ ---------- | |
| submit.click( | |
| fn=run_pipeline, | |
| inputs=[input_text, src_dd, tgt_dd, fav_state], | |
| outputs=[orig_out, variants_out, back_table, explain_out, pron_out, role_out, fav_state, resources_md, resources_md], | |
| ) | |
| # ์ํ ๋ฒํผ | |
| b1.click(fn=load_sample, inputs=None, outputs=input_text, _js=None, kwargs={"sample_text":"๊ณ ์ํ์ด!"}) | |
| b2.click(fn=load_sample, inputs=None, outputs=input_text, kwargs={"sample_text":"์๊ณ ๋ง์์ด, ์ ๋ง ๊ณ ๋ง์."}) | |
| b3.click(fn=load_sample, inputs=None, outputs=input_text, kwargs={"sample_text":"์ค๋ ์ผ์ ํ์ธ ๋ถํ๋๋ฆฝ๋๋ค."}) | |
| # ์ฆ๊ฒจ์ฐพ๊ธฐ ์ ์ฅ | |
| save_btn.click( | |
| fn=save_to_favorites, | |
| inputs=[orig_out, variants_out, back_table, explain_out, pron_out, role_out, fav_state], | |
| outputs=[fav_state, save_status] | |
| ) | |
| # ๋ด๋ณด๋ด๊ธฐ | |
| export_btn.click(fn=export_favorites, inputs=[fav_state], outputs=[export_file]) | |
| demo.launch() | |