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:
1 parent
302eae4f13
commit
14a82a3e24
25 files changed
+700
-44
No files matched your search
@@ -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,
|
||||
}))
|
||||
}
|
||||
Reference in new issue
Block a user