init: AI Grading Assistant MVP
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
<template>
|
||||
<div class="floating-toolbar" :class="{ 'is-hidden': !appStore.isToolbarVisible }">
|
||||
<div class="toolbar-buttons">
|
||||
<el-button class="toolbar-btn" @click="startScreenshot">
|
||||
<span class="btn-icon">📷</span>
|
||||
<span class="btn-label">截图</span>
|
||||
</el-button>
|
||||
|
||||
<el-button class="toolbar-btn" @click="selectImage">
|
||||
<span class="btn-icon">📁</span>
|
||||
<span class="btn-label">图片</span>
|
||||
</el-button>
|
||||
|
||||
<el-button class="toolbar-btn" @click="startGrading" :loading="appStore.isGrading">
|
||||
<span class="btn-icon">⚡</span>
|
||||
<span class="btn-label">{{ appStore.isGrading ? '阅卷中...' : '阅卷' }}</span>
|
||||
</el-button>
|
||||
|
||||
<el-button class="toolbar-btn" @click="openRubric">
|
||||
<span class="btn-icon">📋</span>
|
||||
<span class="btn-label">标准</span>
|
||||
</el-button>
|
||||
|
||||
<el-button class="toolbar-btn" @click="openTemplates">
|
||||
<span class="btn-icon">📚</span>
|
||||
<span class="btn-label">模板</span>
|
||||
</el-button>
|
||||
|
||||
<el-button class="toolbar-btn" @click="openHistory">
|
||||
<span class="btn-icon">📜</span>
|
||||
<span class="btn-label">历史</span>
|
||||
</el-button>
|
||||
|
||||
<el-button class="toolbar-btn" @click="openSettings">
|
||||
<span class="btn-icon">⚙</span>
|
||||
<span class="btn-label">设置</span>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useAppStore } from '@/stores/app'
|
||||
import { useGradingStore } from '@/stores/grading'
|
||||
import { useSettingsStore } from '@/stores/settings'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const gradingStore = useGradingStore()
|
||||
const settingsStore = useSettingsStore()
|
||||
|
||||
let cleanupScreenshotListener: (() => void) | null = null
|
||||
|
||||
onMounted(() => {
|
||||
if (window.electronAPI) {
|
||||
cleanupScreenshotListener = window.electronAPI.screenshot.onCompleted((base64: string) => {
|
||||
if (!appStore.isGrading) {
|
||||
gradingStore.setImage(base64)
|
||||
startGrading()
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
cleanupScreenshotListener?.()
|
||||
})
|
||||
|
||||
function startScreenshot() {
|
||||
if (window.electronAPI) window.electronAPI.screenshot.start()
|
||||
}
|
||||
|
||||
async function selectImage() {
|
||||
if (!window.electronAPI) return
|
||||
const data = await window.electronAPI.dialog.openImage()
|
||||
if (data) {
|
||||
gradingStore.setImage(data.base64)
|
||||
ElMessage.success('已选择图片')
|
||||
}
|
||||
}
|
||||
|
||||
async function startGrading() {
|
||||
if (!gradingStore.currentImage) {
|
||||
ElMessage.info('请先截图或选择图片')
|
||||
return
|
||||
}
|
||||
if (!settingsStore.settings.apiKey) {
|
||||
ElMessage.warning('请先在设置中配置 API Key')
|
||||
if (window.electronAPI) window.electronAPI.dialog.open('settings')
|
||||
return
|
||||
}
|
||||
|
||||
appStore.isGrading = true
|
||||
try {
|
||||
// load latest rubric data from main process (may have been updated by rubric dialog)
|
||||
const rubricData = await window.electronAPI?.rubric.getData()
|
||||
if (rubricData) {
|
||||
if (rubricData.rubric) gradingStore.setRubric(rubricData.rubric)
|
||||
if (rubricData.questionTitle) gradingStore.setQuestionTitle(rubricData.questionTitle)
|
||||
if (rubricData.maxScore) gradingStore.setMaxScore(rubricData.maxScore)
|
||||
if (rubricData.referenceAnswer) gradingStore.setReferenceAnswer(rubricData.referenceAnswer)
|
||||
}
|
||||
const result = await window.electronAPI.ai.grade({
|
||||
imageBase64: gradingStore.currentImage,
|
||||
rubric: gradingStore.rubric || `请根据答案内容评分,满分${gradingStore.maxScore}分`,
|
||||
maxScore: gradingStore.maxScore,
|
||||
referenceAnswer: gradingStore.referenceAnswer,
|
||||
questionTitle: gradingStore.questionTitle,
|
||||
apiConfig: {
|
||||
apiKey: settingsStore.settings.apiKey,
|
||||
baseUrl: settingsStore.settings.baseUrl,
|
||||
model: settingsStore.settings.model
|
||||
}
|
||||
})
|
||||
gradingStore.setResult(result)
|
||||
if (window.electronAPI) {
|
||||
await window.electronAPI.grading.storeData({
|
||||
result,
|
||||
image: gradingStore.currentImage,
|
||||
rubric: gradingStore.rubric,
|
||||
questionTitle: gradingStore.questionTitle,
|
||||
maxScore: gradingStore.maxScore,
|
||||
referenceAnswer: gradingStore.referenceAnswer
|
||||
})
|
||||
// also update rubric bridge
|
||||
await window.electronAPI.rubric.storeData({
|
||||
questionTitle: gradingStore.questionTitle,
|
||||
maxScore: gradingStore.maxScore,
|
||||
referenceAnswer: gradingStore.referenceAnswer,
|
||||
rubric: gradingStore.rubric
|
||||
})
|
||||
await window.electronAPI.dialog.open('result')
|
||||
}
|
||||
} catch (err) {
|
||||
ElMessage.error('AI 阅卷失败:' + (err as Error).message)
|
||||
} finally {
|
||||
appStore.isGrading = false
|
||||
}
|
||||
}
|
||||
|
||||
function openRubric() {
|
||||
window.electronAPI?.dialog.open('rubric')
|
||||
}
|
||||
|
||||
async function openTemplates() { window.electronAPI?.dialog.open('templates') }
|
||||
function openHistory() { window.electronAPI?.dialog.open('history') }
|
||||
function openSettings() { window.electronAPI?.dialog.open('settings') }
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.floating-toolbar {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 99999;
|
||||
user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
background: rgba(30, 30, 30, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.3);
|
||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.toolbar-buttons {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
padding: 8px 14px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.toolbar-btn {
|
||||
-webkit-app-region: no-drag;
|
||||
border: none !important;
|
||||
background: transparent !important;
|
||||
color: rgba(255, 255, 255, 0.85) !important;
|
||||
height: 46px !important;
|
||||
padding: 4px 14px !important;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 1px;
|
||||
transition: all 0.2s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toolbar-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.12) !important;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 16px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.btn-label {
|
||||
font-size: 10px;
|
||||
line-height: 1;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.is-hidden {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user