File size: 11,794 Bytes
ffa3318
 
 
 
 
 
 
 
 
a1d82a9
 
ffa3318
9a71750
 
70fc399
ffa3318
 
 
 
323f858
70fc399
c72f969
6717c4b
 
323f858
6717c4b
a041742
9a71750
834925c
a041742
c72f969
7fdd5af
ffa3318
 
 
 
 
 
3af6c22
ffa3318
 
 
 
3af6c22
ffa3318
 
a1d82a9
70fc399
c72f969
a1d82a9
 
 
c72f969
a1d82a9
3af6c22
70fc399
ffa3318
 
a1d82a9
ffa3318
 
 
 
 
a1d82a9
 
 
 
 
d6dd2d3
 
 
 
 
 
 
ffa3318
c72f969
ffa3318
0becd9b
ffa3318
a041742
6717c4b
3af6c22
ffa3318
70fc399
ffa3318
c72f969
ffa3318
b543d62
 
 
 
 
 
a041742
6717c4b
3af6c22
ffa3318
70fc399
ffa3318
c72f969
ffa3318
b543d62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ffa3318
b543d62
a041742
6717c4b
3af6c22
ffa3318
70fc399
ffa3318
c72f969
ffa3318
b543d62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ffa3318
a041742
6717c4b
3af6c22
ffa3318
70fc399
ffa3318
c72f969
ffa3318
b543d62
 
 
 
 
ffa3318
a041742
6717c4b
3af6c22
ffa3318
70fc399
ffa3318
c72f969
70fc399
a1d82a9
 
ffa3318
a1d82a9
ffa3318
70fc399
 
 
 
 
ffa3318
 
 
c72f969
a1d82a9
 
 
 
 
ffa3318
 
c72f969
ffa3318
 
 
 
 
 
70fc399
ffa3318
70fc399
 
 
a1d82a9
 
 
 
 
 
ffa3318
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
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", "")

# —— 指定代理站 & API Key —— #
#openai.api_key  = "Magic-123456"
#openai.base_url = "https://freeapi.oaiopen.com/v1/"
openai.api_key = OPENAI_API_KEY
openai.base_url = "https://free.v36.cm/v1/"
openai.default_headers = {"x-foo": "true"}

# —— 初始化 v1 客户端 —— #
#client = OpenAI(api_key=openai.api_key)
# 同步 base_url
#client.api_base = openai.api_base

# —— Baidu OCR —— #
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()

    #token      = get_access_token(BAIDU_API_KEY, BAIDU_SECRET_KEY)
    #words      = ocr_image(image_bytes, token)
    #essay_text = "\n".join([w["words"] for w in words])
    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.
    """

    # 1. 格式化原文
    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

    # 2. 批改
    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

    # 3. 批改意见
    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

    # 4. 评分
    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

    # 5. 优秀范文
    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

    # 6. 渲染 HTML
    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)

    # 7. 写文件并返回
    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

# —— Gradio 接口 —— #
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)