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

+19 -7
View File
@@ -1,7 +1,7 @@
// 作业:老师把一个实验布置给一个班,学生提交后自动评分。
// 布置时保存起点工程和评分规则的快照,以后改示例不影响已布置的作业。
import type { Database } from 'bun:sqlite'
import type { Assignment, GradeResult, Scoring, SourceFile, StartMode, Submission } from '@iot/shared'
import type { Assignment, GradeResult, Scoring, Screenshot, SourceFile, StartMode, Submission } from '@iot/shared'
import { parseChecks } from './grading/checks'
interface AssignmentRow {
@@ -155,11 +155,11 @@ export function latestSubmission(db: Database, assignmentId: number, userId: num
}
/** 新建一份等待评分的提交;同一个人上一份还在评分时返回 null */
export function createSubmission(db: Database, assignmentId: number, userId: number, files: SourceFile[]): number | null {
export function createSubmission(db: Database, assignmentId: number, userId: number, files: SourceFile[], screenshots: Screenshot[] = []): number | null {
if (latestSubmission(db, assignmentId, userId)?.status === 'pending') return null
const result = db
.query(`INSERT INTO submissions (assignment_id, user_id, files, status, created_at) VALUES (?, ?, ?, 'pending', ?)`)
.run(assignmentId, userId, JSON.stringify(files), Date.now())
.query(`INSERT INTO submissions (assignment_id, user_id, files, screenshots, status, created_at) VALUES (?, ?, ?, ?, 'pending', ?)`)
.run(assignmentId, userId, JSON.stringify(files), JSON.stringify(screenshots), Date.now())
return Number(result.lastInsertRowid)
}
@@ -190,8 +190,11 @@ export function saveGrade(db: Database, id: number, result: GradeResult | null)
/** 每个学生最近一次提交的状态(老师看提交情况) */
export function classSubmissions(db: Database, assignmentId: number) {
return db
.query<{ user_id: number; username: string; name: string; status: Submission['status'] | null; auto_score: number | null; created_at: number | null; count: number }, [number]>(
`SELECT u.id AS user_id, u.username, u.name, s.status, s.auto_score, s.created_at,
.query<
{ user_id: number; username: string; name: string; submission_id: number | null; status: Submission['status'] | null; auto_score: number | null; created_at: number | null; count: number },
[number]
>(
`SELECT u.id AS user_id, u.username, u.name, s.id AS submission_id, s.status, s.auto_score, s.created_at,
(SELECT COUNT(*) FROM submissions x WHERE x.assignment_id = ?1 AND x.user_id = u.id) AS count
FROM users u
JOIN assignments a ON a.id = ?1 AND u.class_id = a.class_id AND u.role = 'student'
@@ -199,5 +202,14 @@ export function classSubmissions(db: Database, assignmentId: number) {
ORDER BY u.username`,
)
.all(assignmentId)
.map((r) => ({ userId: r.user_id, username: r.username, name: r.name, status: r.status, autoScore: r.auto_score, submittedAt: r.created_at, count: r.count }))
.map((r) => ({
userId: r.user_id,
username: r.username,
name: r.name,
submissionId: r.submission_id,
status: r.status,
autoScore: r.auto_score,
submittedAt: r.created_at,
count: r.count,
}))
}