diff --git a/electron/main/ipc.ts b/electron/main/ipc.ts index 44d9d68..0983939 100644 --- a/electron/main/ipc.ts +++ b/electron/main/ipc.ts @@ -72,8 +72,10 @@ function getDb() { id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, course TEXT, + questions_json TEXT DEFAULT '', created_at DATETIME DEFAULT CURRENT_TIMESTAMP )`).run() + try { db.prepare(`ALTER TABLE exam ADD COLUMN questions_json TEXT DEFAULT ''`).run() } catch (_) {} db.prepare(`CREATE TABLE IF NOT EXISTS question ( id INTEGER PRIMARY KEY AUTOINCREMENT, exam_id INTEGER, @@ -161,7 +163,12 @@ export function setupIpc(win: BrowserWindow): void { return result } catch (err: any) { log.error('AI grade failed:', err) - throw new Error(err.message || 'AI grading failed') + const apiMsg = err?.response?.data?.error?.message || '' + const allMsg = (apiMsg + ' ' + (err.message || '')).toLowerCase() + if (allMsg.includes('does not support image') || allMsg.includes('image input') || allMsg.includes('not support image')) { + throw new Error('当前模型不支持图片输入,请在设置中更换为支持多模态的模型(如 gpt-4o、qwen-vl 等)') + } + throw new Error(apiMsg || err.message || 'AI grading failed') } }) @@ -258,17 +265,24 @@ export function setupIpc(win: BrowserWindow): void { ipcMain.handle('template:list', () => { try { const db = getDb() - const exams = db.prepare('SELECT * FROM exam ORDER BY created_at DESC').all() - return exams + const exams = db.prepare('SELECT * FROM exam ORDER BY created_at DESC').all() as any[] + const result = exams.map((e: any) => { + const questions = e.questions_json ? JSON.parse(e.questions_json) : [] + log.info('template:list item', { id: e.id, name: e.name, jsonLen: (e.questions_json || '').length, questionCount: questions.length }) + return { ...e, questions } + }) + return result } catch { return [] } }) - ipcMain.handle('template:save', (_event, data: { name: string; course: string }) => { + ipcMain.handle('template:save', (_event, data: { name: string; course: string; questions?: any[] }) => { try { const db = getDb() - const result = db.prepare('INSERT INTO exam (name, course) VALUES (?, ?)').run(data.name, data.course) + const questionsJson = JSON.stringify(data.questions || []) + log.info('template:save', { name: data.name, course: data.course, questionCount: data.questions?.length ?? 0, jsonLen: questionsJson.length }) + const result = db.prepare('INSERT INTO exam (name, course, questions_json) VALUES (?, ?, ?)').run(data.name, data.course, questionsJson) return result.lastInsertRowid } catch (err) { log.error('template:save error:', err) @@ -276,6 +290,38 @@ export function setupIpc(win: BrowserWindow): void { } }) + ipcMain.handle('template:delete', (_event, id: number) => { + try { + getDb().prepare('DELETE FROM exam WHERE id = ?').run(id) + return true + } catch { + return false + } + }) + + ipcMain.handle('template:apply', (_event, id: number) => { + try { + const db = getDb() + const row = db.prepare('SELECT * FROM exam WHERE id = ?').get(id) as any + if (!row) throw new Error('模板不存在') + const questions = row.questions_json ? JSON.parse(row.questions_json) : [] + if (!questions.length) throw new Error('模板中没有题目') + db.prepare('DELETE FROM rubric').run() + const insert = db.prepare( + 'INSERT INTO rubric (question_title, max_score, reference_answer, rubric, sort_order, reference_answer_image) VALUES (?, ?, ?, ?, ?, ?)' + ) + for (let i = 0; i < questions.length; i++) { + const q = questions[i] + insert.run(q.questionTitle || '', q.maxScore ?? 100, q.referenceAnswer || '', q.rubric || '', i, q.referenceAnswerImage || '') + } + rubricNextIndex = 0 + return true + } catch (err) { + log.error('template:apply error:', err) + return false + } + }) + // --- Dialog child windows --- ipcMain.handle('dialog:open', (_event, { type, data }: { type: string; data?: any }) => { log.info('IPC: dialog:open', type) diff --git a/electron/preload/preload.ts b/electron/preload/preload.ts index 1265a7f..8c814dc 100644 --- a/electron/preload/preload.ts +++ b/electron/preload/preload.ts @@ -62,7 +62,9 @@ contextBridge.exposeInMainWorld('electronAPI', { template: { list: () => ipcRenderer.invoke('template:list'), - save: (data: { name: string; course: string }) => ipcRenderer.invoke('template:save', data) + save: (data: { name: string; course: string; questions?: any[] }) => ipcRenderer.invoke('template:save', data), + delete: (id: number) => ipcRenderer.invoke('template:delete', id), + apply: (id: number) => ipcRenderer.invoke('template:apply', id) }, dialog: { diff --git a/src/components/RubricEditor.vue b/src/components/RubricEditor.vue index 173da9a..2a07e81 100644 --- a/src/components/RubricEditor.vue +++ b/src/components/RubricEditor.vue @@ -46,12 +46,28 @@
+