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
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user