新增作文区与 AI 解读开关,修复 CSV 导出。

系统设置可关闭成绩复盘 AI;学生详情增加作文区(OCR/手动题目、方案与范文、历史与 MD 下载);导出改用 UTF-8 文件名响应。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-28 17:42:17 +08:00
parent aaa08cdf38
commit 1cb3c7fad5
20 changed files with 1441 additions and 555 deletions
+78 -1
View File
@@ -306,4 +306,81 @@ async def generate_review_insight(
careless_hint=careless_hint,
subject_hints=subject_hints,
)
return await generate_text(prompt, cfg, temperature=0.2)
return await generate_text(prompt, cfg, temperature=0.2)
CURRICULUM_CHINESE_JUNIOR = """初中作文:记叙文、写人记事、简单议论文为主,通常 600-800 字。
语言平实,素材来自课内与日常生活,禁止成人化腔调与超纲典故堆砌。"""
CURRICULUM_CHINESE_SENIOR = """高中作文:记叙、议论、材料作文为主,通常 800-1000 字。
可适度展开论证,仍须符合课内要求,禁止大学论文式写法与超纲理论。"""
COMPOSITION_PROMPT = """你是一位{stage}语文老师,正在辅导{grade_text}学生完成作文。
【学段年级 — 严禁超纲】
{curriculum}
作文题目:
{topic}
请严格按以下 Markdown 结构输出(不要增加其他一级标题):
## 写作方案
(审题、立意、结构提纲、段落安排、可用素材方向,分条列出,贴合{grade_text}水平)
## 范文
(完整作文一篇,字数与语言风格必须符合{grade_text}课内要求,禁止超纲)
注意:范文必须是可直接参考的学生习作水准,不要写成评论或教案。
"""
def _chinese_curriculum(level, grade: str | None) -> str:
is_senior = level == SchoolLevel.senior_high or level == "senior_high"
return CURRICULUM_CHINESE_SENIOR if is_senior else CURRICULUM_CHINESE_JUNIOR
def _grade_text(grade: str | None) -> str:
if grade and grade.strip():
return grade.strip()
return "该学段学生"
def split_composition_sections(text: str) -> tuple[str, str]:
import re
text = text.strip()
if "## 范文" not in text:
return text.replace("## 写作方案", "").strip(), ""
parts = re.split(r"\n##\s*范文\s*\n", text, maxsplit=1)
plan = parts[0].replace("## 写作方案", "").strip()
essay = parts[1].strip() if len(parts) > 1 else ""
return plan, essay
async def generate_composition(
cfg: AIConfig,
topic: str,
school_level=None,
grade: str | None = None,
) -> tuple[str, str]:
stage = school_level_label(school_level)
grade_text = _grade_text(grade)
curriculum = _chinese_curriculum(school_level, grade)
prompt = COMPOSITION_PROMPT.format(
stage=stage,
grade_text=grade_text,
curriculum=curriculum,
topic=topic.strip(),
)
full = await generate_text(prompt, cfg, temperature=0.35)
return split_composition_sections(full)
def composition_markdown(topic: str, writing_plan: str | None, sample_essay: str | None) -> str:
parts = [f"# 作文题目\n\n{topic.strip()}", ""]
if writing_plan:
parts.extend(["## 写作方案", "", writing_plan.strip(), ""])
if sample_essay:
parts.extend(["## 范文", "", sample_essay.strip(), ""])
return "\n".join(parts).strip() + "\n"
+10
View File
@@ -91,3 +91,13 @@ def run_migrations() -> None:
if "review_statuses_json" not in ss_columns:
with engine.begin() as conn:
conn.execute(text("ALTER TABLE subject_scores ADD COLUMN review_statuses_json TEXT"))
if "system_settings" in tables:
ss_columns = {col["name"] for col in inspector.get_columns("system_settings")}
if "ai_review_enabled" not in ss_columns:
with engine.begin() as conn:
conn.execute(
text(
"ALTER TABLE system_settings ADD COLUMN ai_review_enabled BOOLEAN NOT NULL DEFAULT TRUE"
)
)