AI 评阅:DeepSeek Flash 给老师评分项出建议分和评语,老师采纳后生效

- 学生提交时浏览器给每个窗体截图(运行时同一套 FormWindow 在屏幕外渲染),随提交保存
- 自动评分后进入 AI 评阅队列:按书上评价表给老师评分项打建议分、写评语(JSON 输出,服务器再校验、限制分数);
  发出的内容不含姓名学号;学生代码里像指令的文字一律当作品内容;学生又交了新的只评最新的
- 提示词按教材命名习惯校准("缩写_含义"、书上规定的控件名、界面上不用的标签保留默认名不扣分):
  教材参考答案 28/30,默认控件名且没有注释 15/30
- 评阅页:界面截图标签页、AI 建议卡片(采纳、重新评阅)、全部采纳(不覆盖老师已评的分)、评阅后又提交的提醒;
  成绩表显示"AI 建议,待确认";学生在老师确认前看不到 AI 的建议
- 规范提示移到 shared,服务端组装提示词时也用
- 配置 DEEPSEEK_API_KEY、AI_MODEL;部署说明提醒 NPM 调大请求体上限

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
xuyueandClaude Opus 5.5 committed 2026-10-22 19:37:50 +08:00
1 parent 302eae4f13
commit 14a82a3e24
25 files changed
+700 -44

No files matched your search

+18 -6
View File
@@ -1,7 +1,8 @@
// 老师评阅和成绩:给老师评分项打分、写评语;全班成绩表。
// 一个学生的总分 = 最近一次提交的自动评分 + 老师评分(没提交时没有总分)。
import type { Database } from 'bun:sqlite'
import type { Review, Scoring, SourceFile } from '@iot/shared'
import type { Review, Scoring, Screenshot, SourceFile } from '@iot/shared'
import { getAiReview } from './ai-review'
import { getAssignment, latestSubmission } from './assignments'
/**
@@ -52,11 +53,19 @@ export function studentDetail(db: Database, assignmentId: number, userId: number
const student = db.query<{ id: number; username: string; name: string }, [number]>('SELECT id, username, name FROM users WHERE id = ?').get(userId)
if (!student) return null
const latest = latestSubmission(db, assignmentId, userId)
const files = latest
? (JSON.parse(db.query<{ files: string }, [number]>('SELECT files FROM submissions WHERE id = ?').get(latest.id)!.files) as SourceFile[])
: null
const row = latest && db.query<{ files: string; screenshots: string | null }, [number]>('SELECT files, screenshots FROM submissions WHERE id = ?').get(latest.id)
const files = row ? (JSON.parse(row.files) as SourceFile[]) : null
const screenshots = row?.screenshots ? (JSON.parse(row.screenshots) as Screenshot[]) : []
const count = db.query<{ n: number }, [number, number]>('SELECT COUNT(*) AS n FROM submissions WHERE assignment_id = ? AND user_id = ?').get(assignmentId, userId)!.n
return { student, submission: latest, files, review: getReview(db, assignmentId, userId), submissionCount: count }
return {
student,
submission: latest,
files,
screenshots,
aiReview: latest ? getAiReview(db, latest.id) : null,
review: getReview(db, assignmentId, userId),
submissionCount: count,
}
}
/** 一份提交的代码和它属于谁(老师在实验页里运行学生的程序时用) */
@@ -96,7 +105,10 @@ export function gradebook(db: Database, classId: number) {
const review = getReview(db, a.id, s.id)
const auto = latest?.status === 'done' ? latest.autoScore : null
const manual = manualScore(review)
return [a.id, { submitted: latest !== null, auto, manual, total: latest ? (auto ?? 0) + (manual ?? 0) : null }]
// 老师还没评阅时,带上 AI 的建议分(成绩表里标"待确认")
const ai = latest && !review ? getAiReview(db, latest.id) : null
const aiManual = ai?.status === 'done' ? ai.scores.reduce((sum, s) => sum + s.score, 0) : null
return [a.id, { submitted: latest !== null, auto, manual, aiManual, total: latest ? (auto ?? 0) + (manual ?? 0) : null }]
}),
),
})),