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

+49
View File
@@ -0,0 +1,49 @@
// 提交作业时给每个窗体截一张图:用设计器的预览树和运行时同一套 FormWindow 在屏幕外渲染,再转成 PNG。
// 截图随提交保存,老师在评阅页直接看界面,AI 评阅也据此评"窗体界面"。截图失败不影响提交。
import { createApp, h, nextTick } from 'vue'
import { domToPng } from 'modern-screenshot'
import type { Screenshot, SourceFile } from '@iot/shared'
import { parseDesigner } from '../designer/document'
import { previewTree } from '../designer/preview'
import FormWindow from '../winforms/FormWindow.vue'
const MAX_FORMS = 6
/** 和服务器的限制一致:base64 超过这个长度的截图不提交 */
const MAX_PNG_LENGTH = 700_000
export async function captureForms(files: SourceFile[]): Promise<Screenshot[]> {
const designers = files.filter((f) => f.path.endsWith('.Designer.cs')).slice(0, MAX_FORMS)
if (designers.length === 0) return []
const host = document.createElement('div')
host.style.cssText = 'position: fixed; left: -10000px; top: 0; width: 2000px; height: 2000px; pointer-events: none;'
document.body.append(host)
const shots: Screenshot[] = []
try {
for (const file of designers) {
let form
try {
form = previewTree(parseDesigner(file.content))
} catch {
continue // Designer.cs 被改得解析不了:这个窗体不截图
}
const holder = document.createElement('div')
host.append(holder)
const app = createApp({ render: () => h(FormWindow, { form, modal: false, offset: 0, act: () => {} }) })
app.mount(holder)
await nextTick()
const frame = holder.querySelector<HTMLElement>('.wf-window')
try {
if (frame) {
const png = (await domToPng(frame, { scale: 1 })).replace(/^data:image\/png;base64,/, '')
if (png.length <= MAX_PNG_LENGTH) shots.push({ form: file.path.replace(/\.Designer\.cs$/, ''), png })
}
} finally {
app.unmount()
holder.remove()
}
}
} finally {
host.remove()
}
return shots
}