Spaces:
Configuration error
Configuration error
Upload 2 files
Browse files- app.py +261 -0
- requirements.txt +3 -0
app.py
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
# 財政部財政資訊中心 江信宗
|
| 3 |
+
|
| 4 |
+
import gradio as gr
|
| 5 |
+
import os
|
| 6 |
+
from openai import OpenAI
|
| 7 |
+
import resend
|
| 8 |
+
import time
|
| 9 |
+
import html
|
| 10 |
+
import tempfile
|
| 11 |
+
import re
|
| 12 |
+
|
| 13 |
+
custom_css = """
|
| 14 |
+
.center-aligned {
|
| 15 |
+
text-align: center !important;
|
| 16 |
+
color: #ff4081;
|
| 17 |
+
text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
|
| 18 |
+
margin-bottom: 0px !important;
|
| 19 |
+
}
|
| 20 |
+
.input-background {
|
| 21 |
+
background-color: #B7E0FF !important;
|
| 22 |
+
padding: 15px !important;
|
| 23 |
+
border-radius: 10px !important;
|
| 24 |
+
margin: 0 !important;
|
| 25 |
+
height: auto;
|
| 26 |
+
}
|
| 27 |
+
.input-background textarea {
|
| 28 |
+
font-size: 18px !important;
|
| 29 |
+
background-color: #ffffff;
|
| 30 |
+
border: 1px solid #f0f8ff;
|
| 31 |
+
border-radius: 8px !important;
|
| 32 |
+
}
|
| 33 |
+
.script-background {
|
| 34 |
+
background-color: #FEF9D9 !important;
|
| 35 |
+
padding: 15px !important;
|
| 36 |
+
border-radius: 10px !important;
|
| 37 |
+
margin: 0 !important;
|
| 38 |
+
}
|
| 39 |
+
.api-background {
|
| 40 |
+
background-color: #FFCFB3 !important;
|
| 41 |
+
padding: 15px !important;
|
| 42 |
+
border-radius: 10px !important;
|
| 43 |
+
}
|
| 44 |
+
.text-background {
|
| 45 |
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica', 'Arial', sans-serif;
|
| 46 |
+
font-size: 18px !important;
|
| 47 |
+
line-height: 1.6 !important;
|
| 48 |
+
padding: 30px !important;
|
| 49 |
+
border-radius: 20px !important;
|
| 50 |
+
background-color: #FFFED3 !important;
|
| 51 |
+
margin: 0 !important;
|
| 52 |
+
transition: all 0.3s ease;
|
| 53 |
+
position: relative;
|
| 54 |
+
z-index: 1;
|
| 55 |
+
overflow: hidden;
|
| 56 |
+
}
|
| 57 |
+
.translation-header {
|
| 58 |
+
font-size: 24px;
|
| 59 |
+
font-weight: 600;
|
| 60 |
+
color: #1d1d1f;
|
| 61 |
+
margin-bottom: 20px;
|
| 62 |
+
text-align: center;
|
| 63 |
+
}
|
| 64 |
+
.translation-content {
|
| 65 |
+
color: #000000;
|
| 66 |
+
font-size: 20px;
|
| 67 |
+
text-align: justify;
|
| 68 |
+
hyphens: auto;
|
| 69 |
+
word-wrap: break-word;
|
| 70 |
+
overflow-wrap: break-word;
|
| 71 |
+
}
|
| 72 |
+
.translation-content p {
|
| 73 |
+
margin-bottom: 15px;
|
| 74 |
+
}
|
| 75 |
+
@media (max-width: 768px) {
|
| 76 |
+
.text-background {
|
| 77 |
+
font-size: 16px !important;
|
| 78 |
+
padding: 0px !important;
|
| 79 |
+
}
|
| 80 |
+
|
| 81 |
+
.translation-header {
|
| 82 |
+
font-size: 20px;
|
| 83 |
+
}
|
| 84 |
+
}
|
| 85 |
+
.submit-btn {
|
| 86 |
+
border-radius: 10px !important;
|
| 87 |
+
border: none !important;
|
| 88 |
+
background-color: #ff4081 !important;
|
| 89 |
+
color: white !important;
|
| 90 |
+
font-weight: bold !important;
|
| 91 |
+
transition: all 0.3s ease !important;
|
| 92 |
+
margin: 0 !important;
|
| 93 |
+
}
|
| 94 |
+
.submit-btn:hover {
|
| 95 |
+
background-color: #f50057 !important;
|
| 96 |
+
transform: scale(1.05);
|
| 97 |
+
}
|
| 98 |
+
.clear-button {
|
| 99 |
+
border-radius: 10px !important;
|
| 100 |
+
border: none !important;
|
| 101 |
+
background-color: #333333 !important;
|
| 102 |
+
color: white !important;
|
| 103 |
+
font-weight: bold !important;
|
| 104 |
+
transition: all 0.3s ease !important;
|
| 105 |
+
}
|
| 106 |
+
.clear-button:hover {
|
| 107 |
+
background-color: #000000 !important;
|
| 108 |
+
transform: scale(1.05);
|
| 109 |
+
}
|
| 110 |
+
"""
|
| 111 |
+
|
| 112 |
+
def translate(content_text, language, api_key):
|
| 113 |
+
start_time = time.time()
|
| 114 |
+
language_dict = {"繁體中文": "English", "English": "繁體中文"}
|
| 115 |
+
corr_language = language_dict[language]
|
| 116 |
+
if not api_key:
|
| 117 |
+
resend.api_key = os.environ["YOUR_API_TOKEN"]
|
| 118 |
+
params: resend.Emails.SendParams = {
|
| 119 |
+
"from": "Trans_API <onboarding@resend.dev>",
|
| 120 |
+
"to": ["antivir7@gmail.com"],
|
| 121 |
+
"subject": "精緻翻譯",
|
| 122 |
+
"html": f"""
|
| 123 |
+
<strong>翻譯文章</strong><br>
|
| 124 |
+
文章:{content_text}
|
| 125 |
+
""",
|
| 126 |
+
}
|
| 127 |
+
try:
|
| 128 |
+
email_response = resend.Emails.send(params)
|
| 129 |
+
print(f"Email sent successfully. Response:{email_response}")
|
| 130 |
+
api_key = os.getenv("YOUR_API_KEY")
|
| 131 |
+
except Exception as e:
|
| 132 |
+
gr.Warning(f"請輸入正確的API Key!!")
|
| 133 |
+
return "請輸入正確的API Key!!"
|
| 134 |
+
client = OpenAI(
|
| 135 |
+
api_key=api_key,
|
| 136 |
+
base_url="https://api.chatanywhere.org/v1",
|
| 137 |
+
)
|
| 138 |
+
system_prompt = f"""你是一位精通繁體中文與英文的專業翻譯,具有40年翻譯經驗且擁有豐富的跨學術專業知識,深度參與《The New York Times》及《Bloomberg》的中文版翻譯工作,對於時事新聞和論文的翻譯有深入的理解。我希望你能幫我將以下{corr_language}全文內容翻譯成{language},風格與上述雜誌的中文版本相似。
|
| 139 |
+
|
| 140 |
+
Remember: 翻譯規則:
|
| 141 |
+
# 翻譯時要準確傳達{corr_language}原文內容的事實和背景。
|
| 142 |
+
# 保留特定的英文術語、數字或名字,並在其前後加上空格,例如:"中 UN 文","不超過 10 秒"。
|
| 143 |
+
# 依據步驟來翻譯原文,並且列印每一次的輸出結果:
|
| 144 |
+
## 根據{corr_language}全文內容直譯,旨在忠實呈現原文,不要遺漏任何訊息,並保持原文的專業性和精準性。
|
| 145 |
+
## 根據直譯的結果重新意譯(意譯稿),遵守{corr_language}原意的前提下讓內容更通俗易懂,提高文字的文學美感,符合《The Wall Street Journal》與《The Economist》中網的中文表達習慣
|
| 146 |
+
## 根據重新意譯的結果反向翻譯成{corr_language}(回譯稿)。
|
| 147 |
+
## 校對回譯稿及{corr_language}原稿中的區別,重點檢查回譯稿與{corr_language}原稿有表達歧義的部分,並確保您的回應客觀且避免使用刻板印象。
|
| 148 |
+
## 根據上一步校對意見,修改意譯稿產生翻譯終稿。
|
| 149 |
+
# 每輪翻譯後,都要重新比對{corr_language}原文,找到扭曲原意或遺漏的內容,然後再補充到下一輪的翻譯當中。
|
| 150 |
+
# 針對翻譯為繁體中文的翻譯終稿,請依照臺灣用詞對照表:["人工智能":"人工智慧","計算機":"電腦","訪問":"存取","設置":"設定","數據":"資料","社交媒體":"社群媒體","私人帳戶":"個人帳號","帳戶":"帳號","博客":"部落格","谷歌":"Google","用戶":"使用者","信息":"訊息","視頻":"影片","軟件":"軟體","硬盤":"硬碟","攝影機":"攝像頭","渠道":"管道","多維":"多元","宇航員":"太空人","短信":"簡訊","查體":"體檢","台球":"撞球","塔樓":"大廈","包間":"包廂","出租車":"計程車","公安局":"警察局","充值卡":"儲值卡","塑料":"塑膠","城鐵":"捷運","鼠標":"滑鼠","網絡":"網路","互聯網":"網際網路","U盤":"隨身碟","燃氣灶":"瓦斯爐","晶體管":"半導體","屏幕":"螢幕","電飯煲":"電鍋","洗面奶":"洗面乳","移動電話":"行動電話","菠蘿":"鳳梨","頭腦風暴":"腦力激盪"]進行修正文字。
|
| 151 |
+
|
| 152 |
+
你理解翻譯規則後,user將會給你發送完整{corr_language}內容,收到後請按照上面的翻譯規則和下面的格式輸出翻譯結果及摘要,回傳格式如下,"{{{{xxx}}}}"表示預留位置:
|
| 153 |
+
|
| 154 |
+
### 第一階段:直譯
|
| 155 |
+
{{{{直譯結果}}}}
|
| 156 |
+
|
| 157 |
+
### 第二階段:意譯初稿
|
| 158 |
+
{{{{意譯初稿}}}}
|
| 159 |
+
|
| 160 |
+
### 第三階段:回譯稿
|
| 161 |
+
{{{{回譯稿}}}}
|
| 162 |
+
|
| 163 |
+
### 第四階段:校對意見
|
| 164 |
+
|
| 165 |
+
以下是在{language}翻譯中缺失的部分:
|
| 166 |
+
|
| 167 |
+
{{{{重複以下列表,直到列出所有缺失的內容}}}}
|
| 168 |
+
- 對比原文缺失或表達歧義部分{{1...n}}:
|
| 169 |
+
- 原文:"{language}"
|
| 170 |
+
- 譯文:"{corr_language}"
|
| 171 |
+
- 建議:{{{{新增翻譯 or 修改翻譯}}}}
|
| 172 |
+
|
| 173 |
+
### 第五階段:翻譯終稿
|
| 174 |
+
{{{{翻譯終稿}}}}
|
| 175 |
+
"""
|
| 176 |
+
try:
|
| 177 |
+
gr.Info("Stepwise翻譯中....")
|
| 178 |
+
response = client.chat.completions.create(
|
| 179 |
+
model="gpt-4o-mini",
|
| 180 |
+
messages=[
|
| 181 |
+
{"role": "system", "content": system_prompt},
|
| 182 |
+
{"role": "user", "content": content_text}
|
| 183 |
+
],
|
| 184 |
+
temperature=0.7
|
| 185 |
+
)
|
| 186 |
+
result = response.choices[0].message.content.strip()
|
| 187 |
+
final_translation = result.split("### 第五階段:翻譯終稿")[-1].strip()
|
| 188 |
+
try:
|
| 189 |
+
with tempfile.NamedTemporaryFile(mode='w+', encoding='utf-8', suffix='.txt', delete=False) as temp_file:
|
| 190 |
+
temp_file.write(f"【原稿({language_dict[language]})】\n\n{content_text}\n\n")
|
| 191 |
+
temp_file.write(f"\n\n【翻譯稿({language})】\n\n{final_translation}")
|
| 192 |
+
temp_file_path = temp_file.name
|
| 193 |
+
except Exception as e:
|
| 194 |
+
gr.Info(f"翻譯完成,執行時間: {(time.time() - start_time):.2f} 秒。")
|
| 195 |
+
return final_translation, None
|
| 196 |
+
gr.Info(f"翻譯完成並提供翻譯結果下載,執行時間: {(time.time() - start_time):.2f} 秒。")
|
| 197 |
+
return final_translation, temp_file_path
|
| 198 |
+
except Exception as e:
|
| 199 |
+
return f"Error: {e}", None
|
| 200 |
+
|
| 201 |
+
with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as iface:
|
| 202 |
+
gr.Markdown("""
|
| 203 |
+
# 文章解碼重構 - 財政部財政資訊中心
|
| 204 |
+
> ### **※ 學習 Chain-of-Thought 思維,逐步探索字詞的深意,細心揣摩原文的情感,重構出忠實且動人心弦的作品。系統部署:江信宗,LLM:GPT-4o-mini。**
|
| 205 |
+
""", elem_classes="center-aligned")
|
| 206 |
+
content = gr.Textbox(
|
| 207 |
+
label="輸入您的文章",
|
| 208 |
+
placeholder="Enter your text here",
|
| 209 |
+
interactive=True,
|
| 210 |
+
autofocus=True,
|
| 211 |
+
max_lines=10,
|
| 212 |
+
elem_classes="input-background"
|
| 213 |
+
)
|
| 214 |
+
with gr.Row():
|
| 215 |
+
Language = gr.Dropdown(
|
| 216 |
+
choices = ["繁體中文","English"],
|
| 217 |
+
value="繁體中文",
|
| 218 |
+
label="翻譯成...語言",
|
| 219 |
+
interactive=True,
|
| 220 |
+
elem_classes="script-background"
|
| 221 |
+
)
|
| 222 |
+
file_output = gr.File(label="下載翻譯結果", elem_classes="script-background", visible=False)
|
| 223 |
+
api_key_input = gr.Textbox(label="輸入您的 API Key", type="password", placeholder="API authentication key", scale=1, elem_classes="api-background")
|
| 224 |
+
with gr.Row():
|
| 225 |
+
submit_btn = gr.Button("傳送", variant="primary", scale=2, elem_classes="submit-btn")
|
| 226 |
+
clear_button = gr.Button("清除", variant="secondary", scale=1, elem_classes="clear-button")
|
| 227 |
+
translate_result = gr.HTML(elem_classes="text-background", visible=False)
|
| 228 |
+
|
| 229 |
+
def on_submit(content_text, language, api_key):
|
| 230 |
+
result, file_path = translate(content_text, language, api_key)
|
| 231 |
+
formatted_result = (
|
| 232 |
+
'<div class="translation-header">※ 解碼重構結果 ※</div>'
|
| 233 |
+
'<div class="translation-content">'
|
| 234 |
+
'{}'
|
| 235 |
+
'</div>'
|
| 236 |
+
).format(html.escape(result).replace('\n', '</p><p>'))
|
| 237 |
+
return gr.update(
|
| 238 |
+
value=formatted_result,
|
| 239 |
+
visible=True
|
| 240 |
+
), gr.update(value=file_path, visible=True)
|
| 241 |
+
|
| 242 |
+
submit_btn.click(
|
| 243 |
+
fn=on_submit,
|
| 244 |
+
inputs=[content, Language, api_key_input],
|
| 245 |
+
outputs=[translate_result, file_output]
|
| 246 |
+
)
|
| 247 |
+
|
| 248 |
+
def clear_inputs():
|
| 249 |
+
return "", "繁體中文", "", gr.update(value="", visible=False), gr.update(value=None, visible=False)
|
| 250 |
+
|
| 251 |
+
clear_button.click(
|
| 252 |
+
fn=clear_inputs,
|
| 253 |
+
inputs=[],
|
| 254 |
+
outputs=[content, Language, api_key_input, translate_result, file_output]
|
| 255 |
+
)
|
| 256 |
+
|
| 257 |
+
if __name__ == "__main__":
|
| 258 |
+
if "SPACE_ID" in os.environ:
|
| 259 |
+
iface.launch()
|
| 260 |
+
else:
|
| 261 |
+
iface.launch(share=True, show_api=False)
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
openai
|
| 3 |
+
resend
|