# -*- coding: utf-8 -*- import os import re import gradio as gr from deep_translator import GoogleTranslator from sentence_transformers import SentenceTransformer, util # model load (first run may take 1-2 minutes) embed_model = SentenceTransformer("sentence-transformers/paraphrase-MiniLM-L6-v2") def clean(s: str) -> str: if not s: return "" return re.sub(r"\s+", " ", s).strip() def ko2en(text: str) -> str: return GoogleTranslator(source="ko", target="en").translate(text) def en2ko(text: str) -> str: return GoogleTranslator(source="en", target="ko").translate(text) def sim_ko(a: str, b: str) -> float: if not a or not b: return 0.0 v = embed_model.encode([a, b], convert_to_tensor=True) return float(util.cos_sim(v[0], v[1]).item()) * 100.0 # tone rules (EN / KO) def tone_en(text: str, tone: str) -> str: t = clean(text) if tone == "쿨톤": t = re.sub(r"\b(very|really|so|truly)\b\s*", "", t, flags=re.I) t = re.sub(r"\b(Thank you so much|Thanks a lot)\b", "Thank you", t, flags=re.I) elif tone == "웜톤": if re.search(r"\b(Thank you|Thanks)\b", t, re.I) and not re.search(r"\bappreciat(e|ion)\b", t, re.I): t += " I sincerely appreciate your support." if not re.search(r"\bplease\b", t, re.I) and t: t = "Please " + t[0].lower() + t[1:] elif tone == "비즈니스톤": if not t.endswith("."): t += "." if not re.search(r"\b(Please let me know|I look forward to)\b", t, re.I): t += " Please let me know if you need any further information." return t def tone_ko(text: str, tone: str) -> str: t = clean(text) if tone == "쿨톤": t = re.sub(r"(정말|진심으로|너무|매우)\s*", "", t) t = re.sub(r"(부탁드립니다|감사드립니다)", "감사합니다", t) elif tone == "웜톤": if "감사" in t and "진심" not in t: t = t.replace("감사합니다", "진심으로 감사합니다") if not re.search(r"(부탁드립니다|도와주시면 감사하겠습니다)", t): t += " 도와주시면 감사하겠습니다." elif tone == "비즈니스톤": t = re.sub(r"(고마워요|고마워|감사해요)", "감사합니다", t) if not (t.endswith("습니다.") or t.endswith("드립니다.") or t.endswith("요.")): t += " 감사합니다." return t def td_translate(ko_text: str, tone: str, target: float = 98.2): ko_text = clean(ko_text) if not ko_text: return "원문을 입력하세요.", "", "", "", "", 0.0, "" try: en = ko2en(ko_text) back = en2ko(en) except Exception as e: return f"[번역 오류] {e}", "", "", "", "", 0.0, "" sim = sim_ko(ko_text, back) en_tuned = tone_en(en, tone) ko_tuned = tone_ko(back, tone) ok = sim >= float(target) flag = "기준 충족" if ok else "기준 미달 (표현을 구체화해 보세요)" final = f"{en_tuned}\n\n{flag} (유사도: {sim:.2f}%, 기준: {float(target):.1f}%)" guide = f"톤: {tone} | KO->EN->KO 재번역 기반 의미 검증" return "", en, back, en_tuned, ko_tuned, sim, final + "\n" + guide with gr.Blocks(title="Tha Deeply Translator") as app: gr.Markdown("Tha Deeply — 톤 스위치 / 재번역 / 유사도 분석기") default_tone = "비즈니스톤" default_threshold = 98.2 default_text = "정말 고맙습니다. 내일 오전까지 회신 부탁드립니다." with gr.Row(): tone = gr.Radio(["쿨톤", "웜톤", "비즈니스톤"], value=default_tone, label="톤 선택") thres = gr.Slider(0, 100, value=default_threshold, step=0.1, label="유사도 기준(%)") ko_in = gr.Textbox(lines=4, label="원문(한국어)", value=default_text, placeholder="한국어 문장을 입력하세요") run = gr.Button("실행") en_out = gr.Textbox(lines=4, label="1) 영문 번역(EN)") ko_back = gr.Textbox(lines=4, label="2) 재번역(KO)") en_tone = gr.Textbox(lines=4, label="3) 톤 보정 영문(EN)") ko_tone = gr.Textbox(lines=4, label="4) 톤 보정 한글(KO)") sim = gr.Number(label="의미 유사도(%)", precision=2) final = gr.Textbox(lines=6, label="완전한 영어 제안 + 상태") def _run(ko_text, t, thr): msg, en, back, en_tuned, ko_tuned, simv, fin = td_translate(ko_text, t, thr) if msg: return "", "", "", "", 0.0, msg return en, back, en_tuned, ko_tuned, simv, fin run.click(_run, [ko_in, tone, thres], [en_out, ko_back, en_tone, ko_tone, sim, final]) if __name__ == "__main__": port = int(os.getenv("PORT", "7860")) app.launch(server_name="0.0.0.0", server_port=port)