|
import os |
|
import base64 |
|
import requests |
|
import markdown |
|
import pdfkit |
|
import gradio as gr |
|
import re |
|
import random |
|
from datetime import datetime |
|
from io import BytesIO |
|
from urllib.parse import quote_plus |
|
from jinja2 import Template |
|
|
|
import openai |
|
from openai import OpenAI |
|
|
|
|
|
BAIDU_API_KEY = os.getenv("BAIDU_API_KEY", "") |
|
BAIDU_SECRET_KEY = os.getenv("BAIDU_SECRET_KEY", "") |
|
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "") |
|
|
|
|
|
|
|
|
|
openai.api_key = OPENAI_API_KEY |
|
openai.base_url = "https://free.v36.cm/v1/" |
|
openai.default_headers = {"x-foo": "true"} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_access_token(api_key, secret_key): |
|
resp = requests.post( |
|
"https://aip.baidubce.com/oauth/2.0/token", |
|
params={ |
|
"grant_type": "client_credentials", |
|
"client_id": api_key, |
|
"client_secret": secret_key |
|
} |
|
) |
|
resp.raise_for_status() |
|
return resp.json().get("access_token") |
|
|
|
def ocr_image(image_bytes: bytes, token: str): |
|
img_b64 = base64.b64encode(image_bytes).decode() |
|
img_encoded = quote_plus(img_b64) |
|
resp = requests.post( |
|
f"https://aip.baidubce.com/rest/2.0/ocr/v1/handwriting?access_token={token}", |
|
headers={"Content-Type": "application/x-www-form-urlencoded"}, |
|
data={"image": img_encoded, "language_type": "ENG"} |
|
) |
|
resp.raise_for_status() |
|
return resp.json().get("words_result", []) |
|
|
|
|
|
def highlight_brackets(text: str) -> str: |
|
text = re.sub(r'\[([^\[\]]+)\]', r'<span class="highlight-bracket-green">\1</span>', text) |
|
text = re.sub(r'\(([^\(\)]+)\)', r'<span class="highlight-bracket">\1</span>', text) |
|
return text.replace("\n", "<br>") |
|
|
|
|
|
def process(image_pil): |
|
buf = BytesIO() |
|
image_pil.save(buf, format="PNG") |
|
image_bytes = buf.getvalue() |
|
|
|
|
|
|
|
|
|
essay_text = """ |
|
I hurried into the theater, only to find there were even more bikers inside. At that moment, I couldn’t believe my eyes, with my mind immediately getting blank and my mouth opening wide. So frozen and still at the theater gate was I, which made the bikers inside all throw/cast their gazes on me that I felt nothing but my heart pounding even more strongly, completely unaware of what action I would adopt. I was not sure how much time had passed, but only greeted by each of the biker coming in from the outside who I had encountered in the deserted street. Suddenly, the curtain opened. |
|
When the film began, I realized that the bikers were an animal rescue group. With a start, recalling how ridiculous my overreaction had been instantly helped me no longer feel dangerous. Naturally, by the light from the film, I found a seat and sat next to a tough-look biker. After a short conversation with him, I expressed my wish to save abandoned dogs. Hearing this, he enthusiastically welcomed me to join the animal rescue group. Then, with the film ending, he introduced me to all the members. Seeing so many animal lovers around, I did believe we would find a way to help the wild live with us harmoniously together. |
|
""" |
|
|
|
|
|
fmt_prompt = ( |
|
"请帮我整理下面的英语作文文本格式,只整理英文正文部分(忽略英文正文外所有部分),保证原汁原味(明显错误空格换行、乱码、非常用字符比如☰需要改正除外),出现的拼写错误也不要帮助改正:\n\n"+ essay_text |
|
) |
|
fm_resp = openai.chat.completions.create( |
|
model="gpt-4o-mini", |
|
messages=[{"role": "user", "content": fmt_prompt}] |
|
) |
|
revised = fm_resp.choices[0].message.content |
|
|
|
|
|
corr_prompt = ( |
|
"请帮我把下面的英语作文的语法错误改正,输出改正后的文章(只改错误和不流畅之处),请参照下面的格式要求\n\n" |
|
+ "这是格式要求:原文修改部分用()括起来,修改的部分用[]括起来,修改单词括单词、修改短语括短语、修改句子括句子,括的部分精准一些,能反映问题\n\n" |
|
+ "例如:The (rabbish) [rubbish] thrown by visitors has piled up and its lush (verdure no longer flourish) [verdure no longer flourishes] as it (did once) [once did].\n\n" |
|
+ "下面是需要批改的英语习作:\n\n" |
|
+ revised |
|
) |
|
cm_resp = openai.chat.completions.create( |
|
model="gpt-4o-mini", |
|
messages=[{"role": "user", "content": corr_prompt}] |
|
) |
|
corrected = cm_resp.choices[0].message.content |
|
|
|
|
|
review_prompt = ( |
|
"""下面是一份已经批改过的英语作文(改正了一些语法错误和不流畅不准确之处),其中小括号表示原文的错误,中括号表示原文的修改部分。请你根据修改的批注,按照如下格式给出批改意见: |
|
|
|
> 英文有错误的原文1 |
|
|
|
- 错误1和解决方案 |
|
- 错误2和解决方案,后面的以此类推 |
|
|
|
> 英文有错误的原文2 |
|
|
|
- 错误1和解决方案 |
|
- 错误2和解决方案,后面的以此类推 |
|
|
|
一个错误的句子使用一个“>”开头,错误的句子下面是批注,批注的内容是对错误的解释和修改建议。 |
|
|
|
比如批注过的原文里面出现了“The (rabbish) [rubbish] thrown by visitors has piled up and its lush (verdure no longer flourish) [verdure no longer flourishes] as it (did once) [once did].”就需要点评下面的内容: |
|
|
|
> The rabbish thrown by visitors has piled up and its lush verdure no longer flourish as it did once. |
|
|
|
- rabbish:拼写错误 |
|
- verdure no longer flourish:动词单复数错误,其中verdure是单数,动词使用第三人称单数 |
|
- as it did once:语序错误 |
|
|
|
接下来是经过批注过的原文:\n\n""" + corrected |
|
) |
|
|
|
rm_resp = openai.chat.completions.create( |
|
model="gpt-4o-mini", |
|
messages=[{"role": "user", "content": review_prompt}] |
|
) |
|
review = rm_resp.choices[0].message.content |
|
|
|
|
|
rate_prompt = ( |
|
""" |
|
你是⼀位资深英语写作批改专家,熟悉 IELTS、CEFR 及 EFL 写作评价体系。特别注意语言准确度。 |
|
请按照下列三个维度,详细具体分析,写明原因(结合原文相关内容,提到原文中哪里有问题哪里写得好),并且需要提出改进方向和方案: |
|
|
|
## 语言通顺度(Fluency)&可读性与风格(Readability & Style) |
|
|
|
## 上下文连贯度(Coherence) |
|
|
|
## 词汇多样性(Lexical Resource)&语法准确性(Grammatical Accuracy) |
|
|
|
例如: |
|
|
|
## 语言通顺度 (Fluency) & 可读性与风格 (Readability & Style) |
|
|
|
- 文中的句子结构变化适中,使用了一些复杂的句子和短语,如“first and foremost”和“in other words”,展现了作者对语言的掌握。 |
|
- 语句的流畅性有待提高。例如,“making an adequately choosing”该句结构不正确,应为“making an adequate choice”。此外,部分句子显得较为冗长,可能会影响读者的理解。 |
|
- 适当分割冗长的句子,确保每个句子传递清晰的信息。例如,可以将“making an adequately choosing, which presents us with more faith when facing arduous challenges and high-demanding tasks”改为“making an adequate choice boosts our confidence when facing arduous challenges and high-demanding tasks”。 |
|
|
|
## 上下文连贯度 (Coherence) |
|
|
|
- 文章整体上能够进行较为清晰的逻辑展开,提纲挈领地介绍了选择的重要性。 |
|
- 尽管主要论点较为明确,但论述的衔接和转折有时显得不够流畅。例如,从“none of our outstanding achievements would be obtained if we haven't preserved the preciousness of this virtue”到“choosing is an action word”的过渡有些突兀,缺乏自然的逻辑连接。 |
|
- 使用更有效的过渡词或短语来增强段落之间的连贯性。例如,增加“Furthermore”或“Moreover”来连接不同的思想,使其流畅过渡。 |
|
|
|
## 词汇多样性 (Lexical Resource) & 语法准确性 (Grammatical Accuracy) |
|
|
|
- 使用了一些较为高级的词汇,如“prerequisite”和“unleash”,展现了较好的词汇量。 |
|
- 词汇使用上还有提升空间,存在一些使用不当和语法错误,如“making an adequately choosing”不符合语法,应为“making an adequate choice”;“the possibility to meet triumph”应为“the possibility of achieving success”。 |
|
- 清晰检查词汇的搭配和语法使用,确保语法结构的正确性,尽量避免错误。建议多做语法练习,增强对词汇搭配的理解,在写作中多样化词汇的使用。 |
|
|
|
下面是这篇英文习作的原文:\n\n |
|
""" + revised |
|
) |
|
rr_resp = openai.chat.completions.create( |
|
model="gpt-4o-mini", |
|
messages=[{"role": "user", "content": rate_prompt}] |
|
) |
|
rating = rr_resp.choices[0].message.content |
|
|
|
|
|
rewrite_prompt = ( |
|
""" |
|
你是⼀位资深英语写作批改专家,熟悉 IELTS、CEFR 及 EFL 写作评价体系。特别注意语言准确度。 |
|
使用优秀的英语表达重写下面这篇英文习作,要展现优秀的词汇和语法,使用地道的表达方式,尽量使用多样化的句式、短语和词汇(但是不要通篇生僻,可以使用一两个表情达意的优秀词汇,需要保证流畅度)。 |
|
加粗可供学习的部分。 |
|
下面是这篇英文习作原文:\n\n""" + revised |
|
) |
|
wm_resp = openai.chat.completions.create( |
|
model="gpt-4o-mini", |
|
messages=[{"role": "user", "content": rewrite_prompt}] |
|
) |
|
perfect = wm_resp.choices[0].message.content |
|
|
|
|
|
code = f"{random.randint(0,9999):04}-{datetime.now().strftime('%Y%m%d%H%M%S')}" |
|
tpl_path = os.path.join("app", "templates", "base.html") |
|
with open(tpl_path, encoding="utf-8") as f: |
|
tpl = Template(f.read()) |
|
|
|
html_content = ( |
|
"<h2>原文格式化</h2>" + markdown.markdown(revised) + |
|
"<h2>批改结果</h2>" + highlight_brackets(corrected) + |
|
"<h2>批改意见</h2>" + markdown.markdown(review) + |
|
"<h2>评分</h2>" + markdown.markdown(rating) + |
|
"<h2>优秀范文</h2>" + markdown.markdown(perfect) |
|
) |
|
full_html = tpl.render(code=code, content=html_content) |
|
|
|
|
|
output_dir = os.path.join("app", "output") |
|
os.makedirs(output_dir, exist_ok=True) |
|
html_path = os.path.join(output_dir, f"{code}.html") |
|
pdf_path = os.path.join(output_dir, f"{code}.pdf") |
|
|
|
with open(html_path, "w", encoding="utf-8") as f: |
|
f.write(full_html) |
|
pdfkit.from_string(full_html, pdf_path, options={"enable-local-file-access": ""}) |
|
|
|
return full_html, html_path, pdf_path |
|
|
|
|
|
with gr.Blocks(title="英语作文批改") as demo: |
|
gr.Markdown("## 上传英语作文照片,等待批改完成后下载 HTML 或 PDF") |
|
image_in = gr.Image(type="pil", label="上传照片") |
|
output_html = gr.HTML() |
|
btn = gr.Button("开始批改") |
|
file_html = gr.File(label="下载 HTML") |
|
file_pdf = gr.File(label="下载 PDF") |
|
|
|
btn.click( |
|
fn=process, |
|
inputs=image_in, |
|
outputs=[output_html, file_html, file_pdf] |
|
) |
|
|
|
if __name__ == "__main__": |
|
demo.launch(server_name="0.0.0.0", server_port=7860) |
|
|