优化废弃的遗留代码和修复API密钥重复加密的问题

This commit is contained in:
Eric-Terminal
2025-09-05 02:03:32 +08:00
parent 939473716c
commit de7c8359ac
5 changed files with 183 additions and 111 deletions

162
README.md
View File

@@ -1,90 +1,136 @@
# AI 作文批改助手 (´。• ᵕ •。`) ♡
# AI 作文批改助手
(^▽^)ノ゙ 哈罗!欢迎来到AI作文批改助手的世界
(^▽^)ノ゙ 欢迎使用 AI 作文批改助手!这是一款专为教育工作者和学生设计的智能桌面工具,能够像经验丰富的英语老师一样,自动批改手写英文作文图片,并生成专业详细的批改报告。
这是一款用爱发电的桌面小工具,它能像一位经验丰富的英语老师一样,帮你自动批改手写的英文作文图片,并生成超级详细的批改报告哟~
### ☆ 应用界面预览 ☆
![alt text](photo/1.png)
![alt text](photo/2.png)
![alt text](photo/3.png)
![alt text](photo/4.png)
---
## ☆ 亮点闪闪 (Features) ☆
* **双AI引擎驱动 (Dual AI Engine)**
* 首先,由专业的 **视觉模型(VLM)** 来进行高精度的手写文字识别(OCR),并像老师看卷面一样,给出一个专业的“书写质量分”!(๑•̀ㅂ•́)و✧
* 然后,强大的 **大语言模型(LLM)** 会接棒,结合识别出的文本、作文题目和书写分,给你一份内容详实、有深度、有温度的批改报告!
* **超级自由的可配置性 (Super Flexible)**
* **API随便换**VLM和LLM的服务地址、API Key、模型名称都可以分开设置不管你用哪家的AI服务只要兼容OpenAI格式都能轻松适配
* **逻辑你来定**:觉得书写分太严或者太松?在设置里调一下“敏感度因子”就好啦~
* **模板全开放**:核心的批改指令(Prompt)模板完全开放给你你可以在设置里直接修改它调整评分标准、总分、或者让AI老师的语气更温柔或者更严格打造你专属的批改风格ദ്ദി(•ω•´)
* **闪电般的并发处理 (Lightning-Fast Concurrency)**
* 内置了超高效的多线程引擎不管你一次选一张还是一百张图片小助手都会派出多位AI老师**同时开始批改**再也不用漫长地等待啦大幅节省你的宝贵时间O(∩_∩)O
* **企业级的安全感 (Enterprise-Grade Security)**
* 你的API Key是非常重要的秘密我们使用强大的 `cryptography` 库对所有密钥都进行了**加密处理**,才会保存在本地的配置文件里。绝对不会明文泄露,让你的账户安全感满满!(。・ω・。)ノ♡
* **开箱即用的小可爱 (Ready to Use)**
* 项目已经为 `PyInstaller` 打包做好了万全准备,可以轻松编译成一个独立的 `.exe``.app` 文件,方便地分享给不太懂技术的朋友们使用~ (^ω^)
## 🎯 应用界面预览
![应用界面](photo/1.png)
![设置界面](photo/2.png)
![批改过程](photo/3.png)
![批改报告](photo/4.png)
---
## 使用教程 (ゝ∀・)
## ✨ 核心特色功能
1. **下载程序**: 前往本仓库的 [Releases 页面](https://github.com/Eric-Terminal/Pro_llm_correct/releases) 下载适用于你操作系统的最新版本。
2. **首次配置**:
* 运行程序它会发现你还没填API Key然后自动弹出“设置”窗口。
* 在这里填上你的 VLM 和 LLM 服务提供商的 **URL**、**API Key** 和 **模型名称**
* 点击“确定”保存,你的小秘密就被安全地加密保存好啦!
3. **开始批改**:
* 在主界面的大框框里,输入本次作文的“**题目**”。
* 点击“**选择图片**”,可以一次选中所有学生的作文图片哟。
* 点击“**开始批改**”,然后就可以泡杯茶,看日志区和进度条实时更新啦!
4. **获取报告**: 任务完成后,每一张图片对应的 `.md` 格式批改报告,都会自动出现在原图片所在的文件夹里,是不是很方便呀?
### 🤖 双AI引擎智能处理
- **视觉语言模型(VLM)**: 专业的手写文字识别(OCR)和书写质量评估,给出精准的卷面分数
- **大语言模型(LLM)**: 深度内容分析,提供专业的语法纠错和写作建议
- **智能作文类型识别**: 自动识别应用文(15分制)和读后续写(25分制)两种高考作文类型
### ⚙️ 极致灵活配置
- **服务独立配置**: VLM和LLM支持完全独立的API服务、密钥和模型配置
- **评分标准可调**: 书写质量"敏感度因子"自由调节,适应不同评分要求
- **Prompt模板开放**: 核心批改指令完全可自定义,打造个性化批改风格
### 🚀 高效并发处理
- 多线程并发引擎,支持批量处理任意数量的作文图片
- 智能任务调度,大幅提升批改效率,节省宝贵时间
- 实时进度显示和详细日志输出,随时掌握处理状态
### 🔒 企业级安全保障
- 军事级加密算法保护API密钥防止敏感信息泄露
- 本地配置文件加密存储,确保账户安全无忧
- 透明的Token使用统计方便成本控制
### 📊 专业输出格式
- **Markdown源文件**: 完整的批改报告,支持进一步编辑和定制
- **HTML可视化报告**: 美观易读的网页格式,方便分享和查看
- **详细错误分析**: 语法错误、表达问题、修改建议一应俱全
- **精准分数评估**: 专业的评分体系,符合高考评分标准
---
## 给开发者小伙伴 (づ。◕‿‿◕。)づ
## 📖 使用指南
想从源码运行或者一起把它变得更好吗?欢迎欢迎!
### 快速开始
1. **下载程序**: 前往 [Releases页面](https://github.com/Eric-Terminal/Pro_llm_correct/releases) 下载最新版本
2. **首次配置**:
- 运行程序,自动弹出设置窗口
- 配置VLM和LLM服务的URL、API密钥和模型名称
- 点击确定保存,密钥自动加密存储
3. **开始批改**:
- 在主界面输入作文题目
- 点击"选择图片",多选需要批改的作文图片
- 点击"开始批改",程序自动进行并发处理
4. **查看报告**: 处理完成后Markdown和HTML格式报告自动保存在原图片目录
### 输出文件说明
- `原文件名_report.md`: Markdown格式详细批改报告
- `原文件名_report.html`: HTML可视化批改报告
- 包含: 作文内容、综合评价、亮点优点、问题建议、分数评估
---
## 🛠️ 开发者指南
### 环境搭建
```bash
# 1. 把这个可爱的仓库克隆到本地
# 1. 克隆仓库
git clone https://github.com/Eric-Terminal/Pro_llm_correct.git
cd Pro_llm_correct
# 2. (推荐) 创建一个专属的虚拟环境
# 2. 创建虚拟环境(推荐)
python3 -m venv venv
source venv/bin/activate # Windows上是 `venv\Scripts\activate` 哦
source venv/bin/activate # Linux/Mac
# venv\Scripts\activate # Windows
# 3. 安装所有需要的魔法咒语 (依赖)
# 3. 安装依赖
pip install -r requirements.txt
# 4. 运行
# 4. 运行程序
python main.py
```
### 打包成魔法小盒子 (o´ω`o)ノ
想把它打包成一个独立的程序吗?用 `PyInstaller` 就行啦!
### 项目打包
```bash
# --noconsole: 这样运行时就不会弹出黑乎乎的命令行窗口啦
# --onefile: 把所有东西都打包成一个文件,干净!
# 打包为独立可执行文件
pyinstaller --noconsole --onefile main.py
# 打包好的程序在 dist/ 目录
```
打包好的魔法小盒子会出现在 `dist` 文件夹里。
### 技术架构
- **前端**: Tkinter GUI界面
- **核心**: 双AI引擎架构 (VLM + LLM)
- **安全**: cryptography加密库
- **并发**: threading + concurrent.futures
- **输出**: Markdown + HTML渲染
---
## 开源许可证 (License) ( ´ ▽ ` )ノ
## 📝 配置说明
本项目采用 [MIT License](LICENSE) 开源。简单来说,就是你可以自由地使用、修改和分享它,只要保留原始的版权声明就好啦~
### 必需配置项
- `VlmUrl`: VLM服务地址
- `VlmApiKey`: VLM服务密钥自动加密
- `VlmModel`: VLM模型名称
- `LlmUrl`: LLM服务地址
- `LlmApiKey`: LLM服务密钥自动加密
- `LlmModel`: LLM模型名称
### 可选配置项
- `SensitivityFactor`: 书写评分敏感度因子默认1.5
- `MaxWorkers`: 最大并发数默认4
- `MaxRetries`: 最大重试次数默认3
- `RetryDelay`: 重试延迟秒数默认5
- `SaveMarkdown`: 是否保存Markdown文件默认True
- `RenderMarkdown`: 是否渲染HTML报告默认True
---
*由 Eric-Terminal 精心创造。如果你喜欢这个项目,请不要吝啬你的 Star ☆ 哦!( ⁄•⁄ω⁄•⁄ )*
## 📄 开源协议
本项目采用 [MIT License](LICENSE) 开源协议。您可以自由地使用、修改和分发本软件,只需保留原始的版权声明即可。
---
## 🤝 贡献与支持
如果您在使用过程中遇到问题或有改进建议,欢迎:
- 提交 [Issue](https://github.com/Eric-Terminal/Pro_llm_correct/issues)
- 发起 [Pull Request](https://github.com/Eric-Terminal/Pro_llm_correct/pulls)
- 给项目点个 ⭐ Star 支持一下!
---
*由 Eric-Terminal 精心开发。希望这个工具能够帮助更多的教育工作者和学生!(。・ω・。)ノ♡*

View File

@@ -53,6 +53,7 @@ Based on the identified essay type, apply the corresponding grading logic. The H
Analyze the text, identify the essay type, calculate the scores, and present your complete feedback in **Simplified Chinese** using the precise Markdown format specified in the "OUTPUT SPECIFICATION" section. Ensure the final score correctly reflects the total points possible (15 or 25).
#--- End of English Instructions ---
# OUTPUT SPECIFICATION (MUST BE IN SIMPLIFIED CHINESE)
#你应该综合考量书写和内容的评分内容是主要的字体是次要的例如对于写的内容和作文毫无关系但是字很好看的不应该给3分而是直接给0分
# 请使用以下Markdown格式并用简体中文填充所有内容优点找不到不要硬找问题建议要把全部问题找出来并且解析都要遵循类似格式。对于分数的总分则必须由你选择是15分还是25分(不一定是下面的15分)。

118
app_ui.py
View File

@@ -42,39 +42,47 @@ class AboutDialog(tk.Toplevel):
llm_out = config_manager.get('UsageLlmOutput', 0)
about_text = f"""
欢迎使用 AI 作文批改助手!这是一款专为教育者和学生设计的智能工具,旨在利用前沿的人工智能技术,提供高效、精准、个性化的英文作文批改体验。
欢迎使用 AI 作文批改助手!这是一款专为教育工作者和学生设计的智能工具,利用前沿的人工智能技术,提供高效、精准、个性化的英文作文批改体验。
核心亮点:
- **双核AI引擎:** 采用先进的两步式处理流程。首先由专用的视觉语言模型(VLM)对作文图片进行高精度手写识别(OCR)与专业的书写质量评估;随后,强大的大语言模型(LLM)会结合识别出的文本、作文题目以及书写评分,进行深度、全面的分析与批改。
✨ 核心特色:
- **极致的灵活性与可配置性:**
* **服务分离:** 您可以为VLM和LLM设置完全独立的API服务地址、密钥和模型名称轻松适配不同的AI提供商需要兼容OpenAI格式或自建服务。
* **逻辑定制:** 书写评分的“敏感度因子”可在设置中调整,以适应不同年级或要求的评分标准。
* **模板开放:** 核心的LLM批改指令模板Prompt完全开放给用户。您可以在设置中自由修改调整评分维度、总分、反馈风格等实现高度个性化的批改要求。
- **双AI引擎架构:** 采用创新的两步式处理流程。首先由专业的视觉语言模型(VLM)进行高精度手写文字识别(OCR)和专业的书写质量评估;然后由强大的大语言模型(LLM)结合识别文本、作文题目和书写评分,进行深度内容分析和专业批改。
- **闪电般的并发处理:** 内置高效的多线程并发引擎,无论您选择一张还是上百张图片,程序都能同时处理,大幅缩短批量批改的等待时间。最大并发任务数亦可在设置中自由调整。
- **极致灵活性:**
* **服务独立配置:** VLM和LLM支持完全独立的API服务地址、密钥和模型名称轻松适配各种AI服务提供商兼容OpenAI格式
* **智能评分调节:** 书写质量"敏感度因子"可自由调整,适应不同年级和评分标准要求
* **Prompt完全开放:** 核心批改指令模板完全可自定义,支持调整评分标准、总分设置和反馈风格
- **企业级的安全保障:** 我们深知API密钥的敏感性。所有密钥信息在保存到本地配置文件时均经过强大的加密算法处理有效防止明文泄露保障您的账户安全。
- **高效并发处理:** 内置多线程并发引擎,支持批量处理任意数量的图片,大幅提升批改效率,最大并发数可配置
- **人性化的评分策略:** 卷面书写分采用更符合教学直觉的“向上取整至0.5分”规则,确保评分结果既精确又公平。
- **企业级安全保障:** 所有API密钥均采用军事级加密算法存储确保您的账户信息安全
使用说明:
1. **初次配置:** 点击“设置”分别填入您的VLM和LLM服务提供商的URL、API密钥和模型名称。
2. **输入题目:** 在主界面上方的文本框中,输入本次批改的“作文题目”。
3. **选择文件:** 点击“选择图片”,一次性选择所有需要批改的学生作文图片。
4. **开始批改:** 点击“开始批改”,程序将自动在后台进行并发处理,您可以在日志区看到实时进度。
5. **获取报告:** 任务完成后每一张图片对应的Markdown格式详细批改报告都会自动生成在原图片所在的目录下。
- **专业评分体系:** 针对高考英语作文场景设计,支持应用文(15分制)和读后续写(25分制)两种评分标准
📋 使用指南:
1. **首次设置:** 点击"设置"配置VLM和LLM服务的URL、API密钥和模型
2. **输入题目:** 在主界面文本框中输入本次批改的作文题目
3. **选择图片:** 点击"选择图片",可多选需要批改的作文图片
4. **开始批改:** 点击"开始批改",程序自动进行并发处理
5. **查看报告:** 处理完成后Markdown和HTML格式的详细批改报告将保存在原图片目录
🎯 输出格式:
- Markdown源文件可编辑
- HTML可视化报告美观易读
- 详细的语法错误分析
- 专业的写作建议
- 精准的分数评估
作者: Eric_Terminal
https://github.com/Eric-Terminal/Pro_llm_correct
版本: 3.0
项目地址: https://github.com/Eric-Terminal/Pro_llm_correct
版本: 3.1
---
历史Token使用量 (仅供参考):
- VLM 输入: {vlm_in:,}
- VLM 输出: {vlm_out:,}
- LLM 输入: {llm_in:,}
- LLM 输出: {llm_out:,}
历史Token使用统计:
- VLM 输入Token: {vlm_in:,}
- VLM 输出Token: {vlm_out:,}
- LLM 输入Token: {llm_in:,}
- LLM 输出Token: {llm_out:,}
"""
text_widget.insert("1.0", about_text)
@@ -93,28 +101,31 @@ https://github.com/Eric-Terminal/Pro_llm_correct
class SettingsDialog(tk.Toplevel):
"""“设置”对话框允许用户配置VLM、LLM服务及其他应用参数。"""
def __init__(self, parent, current_config: dict):
""""设置"对话框允许用户配置VLM、LLM服务及其他应用参数。"""
def __init__(self, parent, config_manager: ConfigManager):
super().__init__(parent)
self.transient(parent)
self.title("设置")
self.result = None
self.config_manager = config_manager
# 为VLM和LLM服务分别创建Tkinter字符串变量
self.vlm_url = tk.StringVar(value=current_config.get("VlmUrl", "https://api.siliconflow.cn/v1/"))
self.vlm_api_key = tk.StringVar(value=current_config.get("VlmApiKey", ""))
self.vlm_model = tk.StringVar(value=current_config.get("VlmModel", "Pro/THUDM/GLM-4.1V-9B-Thinking"))
self.llm_url = tk.StringVar(value=current_config.get("LlmUrl", "https://api.siliconflow.cn/v1/"))
self.llm_api_key = tk.StringVar(value=current_config.get("LlmApiKey", ""))
self.llm_model = tk.StringVar(value=current_config.get("LlmModel", "moonshotai/Kimi-K2-Instruct"))
self.sensitivity_factor = tk.StringVar(value=current_config.get("SensitivityFactor", "1.5"))
self.max_workers = tk.StringVar(value=current_config.get("MaxWorkers", "4"))
self.max_retries = tk.StringVar(value=current_config.get("MaxRetries", "3"))
self.retry_delay = tk.StringVar(value=current_config.get("RetryDelay", "5"))
self.render_markdown = tk.BooleanVar(value=current_config.get("RenderMarkdown", True))
# 使用config_manager.get()方法获取解密后的值用于显示
self.vlm_url = tk.StringVar(value=config_manager.get("VlmUrl", "https://api.siliconflow.cn/v1"))
self.vlm_api_key = tk.StringVar(value=config_manager.get("VlmApiKey", ""))
self.vlm_model = tk.StringVar(value=config_manager.get("VlmModel", "Pro/THUDM/GLM-4.1V-9B-Thinking"))
self.llm_url = tk.StringVar(value=config_manager.get("LlmUrl", "https://api.siliconflow.cn/v1"))
self.llm_api_key = tk.StringVar(value=config_manager.get("LlmApiKey", ""))
self.llm_model = tk.StringVar(value=config_manager.get("LlmModel", "moonshotai/Kimi-K2-Instruct"))
self.sensitivity_factor = tk.StringVar(value=config_manager.get("SensitivityFactor", "1.5"))
self.max_workers = tk.StringVar(value=config_manager.get("MaxWorkers", "4"))
self.max_retries = tk.StringVar(value=config_manager.get("MaxRetries", "3"))
self.retry_delay = tk.StringVar(value=config_manager.get("RetryDelay", "5"))
self.save_markdown = tk.BooleanVar(value=config_manager.get("SaveMarkdown", True))
self.render_markdown = tk.BooleanVar(value=config_manager.get("RenderMarkdown", True))
# 智能加载Prompt模板优先使用用户自定义模板否则使用默认模板
user_template = current_config.get("LlmPromptTemplate")
user_template = config_manager.get("LlmPromptTemplate")
self.llm_prompt_template_str = user_template if user_template else DEFAULT_LLM_PROMPT_TEMPLATE
@@ -156,8 +167,10 @@ class SettingsDialog(tk.Toplevel):
ttk.Entry(other_frame, textvariable=self.max_retries, width=40).grid(column=1, row=2, sticky=(tk.W, tk.E))
ttk.Label(other_frame, text="重试延迟(秒):").grid(column=0, row=3, sticky=tk.W, pady=2)
ttk.Entry(other_frame, textvariable=self.retry_delay, width=40).grid(column=1, row=3, sticky=(tk.W, tk.E))
ttk.Label(other_frame, text="渲染Markdown报告:").grid(column=0, row=4, sticky=tk.W, pady=2)
ttk.Checkbutton(other_frame, variable=self.render_markdown).grid(column=1, row=4, sticky=tk.W)
ttk.Label(other_frame, text="保存Markdown文件:").grid(column=0, row=4, sticky=tk.W, pady=2)
ttk.Checkbutton(other_frame, variable=self.save_markdown).grid(column=1, row=4, sticky=tk.W)
ttk.Label(other_frame, text="渲染HTML报告:").grid(column=0, row=5, sticky=tk.W, pady=2)
ttk.Checkbutton(other_frame, variable=self.render_markdown).grid(column=1, row=5, sticky=tk.W)
# LLM Prompt模板编辑区域
prompt_frame = ttk.LabelFrame(frame, text="LLM Prompt 模板 (可在此修改,请勿修改{}占位符内容导致程序参数无法正常传递,通常情况下修改总分即可)", padding="10")
@@ -191,6 +204,9 @@ class SettingsDialog(tk.Toplevel):
"LlmModel": self.llm_model.get(),
"SensitivityFactor": self.sensitivity_factor.get(),
"MaxWorkers": self.max_workers.get(),
"MaxRetries": self.max_retries.get(),
"RetryDelay": self.retry_delay.get(),
"SaveMarkdown": self.save_markdown.get(),
"RenderMarkdown": self.render_markdown.get(),
"LlmPromptTemplate": self.llm_prompt_text.get("1.0", "end-1c")
}
@@ -318,7 +334,7 @@ class MainApp:
def _open_settings_dialog(self):
"""打开设置对话框,并根据返回结果更新和保存配置。"""
dialog = SettingsDialog(self.root, self.config_manager.config)
dialog = SettingsDialog(self.root, self.config_manager)
if dialog.result:
# 清理旧的、统一的AI配置和OCR配置以兼容新版分离的配置
self.config_manager.config.pop("AiUrl", None)
@@ -413,10 +429,14 @@ class MainApp:
try:
final_report, vlm_usage, llm_usage, html_path = self.api_service.process_essay_image(file_path, topic)
# 保存Markdown文件
# 检查是否保存Markdown文件
save_markdown = self.config_manager.get("SaveMarkdown", True)
report_filename_md = os.path.splitext(file_path)[0] + "_report.md"
with open(report_filename_md, 'w', encoding='utf-8') as f:
f.write(final_report)
# 保存Markdown源文件如果配置开启
if save_markdown:
with open(report_filename_md, 'w', encoding='utf-8') as f:
f.write(final_report)
vlm_in = vlm_usage.get("prompt_tokens", 0)
vlm_out = vlm_usage.get("completion_tokens", 0)
@@ -426,11 +446,21 @@ class MainApp:
usage_log = f"Token用量: VLM(in:{vlm_in}, out:{vlm_out}), LLM(in:{llm_in}, out:{llm_out})"
# 记录所有生成的文件
output_files = [os.path.basename(report_filename_md)]
output_files = []
if save_markdown:
output_files.append(os.path.basename(report_filename_md))
# 检查是否只勾选了HTML如果是则删除Markdown文件
render_html = self.config_manager.get("RenderMarkdown", True)
if html_path and os.path.exists(html_path):
output_files.append(os.path.basename(html_path))
self.ui_queue.put(("log", f"已生成HTML报告: {os.path.basename(html_path)}"))
# 如果只勾选HTML不勾选Markdown则删除Markdown文件
if not save_markdown and render_html and os.path.exists(report_filename_md):
os.remove(report_filename_md)
self.ui_queue.put(("log", f"已删除Markdown文件仅保留HTML"))
self.ui_queue.put(("log", f"完成批改: {base_name} -> {', '.join(output_files)}"))
self.ui_queue.put(("log", usage_log))

View File

@@ -38,14 +38,10 @@ class ConfigManager:
def _ensure_default_render_settings(self):
"""确保渲染相关的默认设置存在"""
if self.get("RenderMarkdownToImage") is None:
self.set("RenderMarkdownToImage", True) # 默认开启渲染功能
if self.get("RenderImageFormat") is None:
self.set("RenderImageFormat", "png") # 默认PNG格式
if self.get("RenderImageWidth") is None:
self.set("RenderImageWidth", 800) # 默认图片宽度
if self.get("RenderImageQuality") is None:
self.set("RenderImageQuality", 90) # 默认图片质量
if self.get("SaveMarkdown") is None:
self.set("SaveMarkdown", True) # 默认保存Markdown文件
if self.get("RenderMarkdown") is None:
self.set("RenderMarkdown", True) # 默认开启HTML渲染功能
def _decrypt(self, encrypted_value: str) -> str:
"""

View File

@@ -119,7 +119,6 @@ class MarkdownRenderer:
margin: 10px 0;
}
</style>
</style>
"""
return f"""
<!DOCTYPE html>