feat: add teaching enhancement features
1. AI personalized hints after 3 failures (streaming SSE) 2. Submission error distribution panel in "my submissions" tab 3. Similar problem recommendations on AC or 3+ failures 4. Admin stuck problems analysis page Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,11 +3,24 @@ import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
|
||||
import { submissionMemoryFormat, submissionTimeFormat } from "utils/functions"
|
||||
import type { Submission } from "utils/types"
|
||||
import SubmissionResultTag from "shared/components/SubmissionResultTag.vue"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import { consumeJSONEventStream } from "utils/stream"
|
||||
import { MdPreview } from "md-editor-v3"
|
||||
import "md-editor-v3/lib/preview.css"
|
||||
import { useDark } from "@vueuse/core"
|
||||
|
||||
const props = defineProps<{
|
||||
submission?: Submission
|
||||
}>()
|
||||
|
||||
const isDark = useDark()
|
||||
const problemStore = useProblemStore()
|
||||
|
||||
// AI 提示状态
|
||||
const hintContent = ref("")
|
||||
const hintLoading = ref(false)
|
||||
const hintError = ref("")
|
||||
|
||||
// 错误信息格式化
|
||||
const msg = computed(() => {
|
||||
if (!props.submission) return ""
|
||||
@@ -30,6 +43,50 @@ const msg = computed(() => {
|
||||
return msg
|
||||
})
|
||||
|
||||
// 是否显示AI提示区域
|
||||
const showAIHint = computed(() => {
|
||||
if (!props.submission) return false
|
||||
return (
|
||||
problemStore.totalFailCount >= 3 &&
|
||||
props.submission.result !== SubmissionStatus.accepted &&
|
||||
props.submission.result !== SubmissionStatus.pending &&
|
||||
props.submission.result !== SubmissionStatus.judging &&
|
||||
props.submission.result !== SubmissionStatus.submitting
|
||||
)
|
||||
})
|
||||
|
||||
async function fetchHint(submissionId: string) {
|
||||
hintLoading.value = true
|
||||
hintContent.value = ""
|
||||
hintError.value = ""
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/ai/hint", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ submission_id: submissionId }),
|
||||
})
|
||||
|
||||
await consumeJSONEventStream(response, {
|
||||
onMessage: (data: {
|
||||
type: string
|
||||
content?: string
|
||||
message?: string
|
||||
}) => {
|
||||
if (data.type === "delta" && data.content) {
|
||||
hintContent.value += data.content
|
||||
} else if (data.type === "error") {
|
||||
hintError.value = data.message || "AI 提示生成失败"
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (e: any) {
|
||||
hintError.value = e.message || "请求失败"
|
||||
} finally {
|
||||
hintLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 测试用例表格数据(只在部分通过时显示)
|
||||
const infoTable = computed(() => {
|
||||
if (!props.submission?.info?.data?.length) return []
|
||||
@@ -87,6 +144,36 @@ const columns: DataTableColumn<Submission["info"]["data"][number]>[] = [
|
||||
:columns="columns"
|
||||
/>
|
||||
</n-flex>
|
||||
|
||||
<!-- AI 提示区域 -->
|
||||
<template v-if="showAIHint">
|
||||
<n-divider />
|
||||
<n-card size="small">
|
||||
<template #header>
|
||||
<span class="gradient-text">AI 提示</span>
|
||||
</template>
|
||||
<n-alert
|
||||
v-if="hintError"
|
||||
type="error"
|
||||
:title="hintError"
|
||||
class="mb-3"
|
||||
/>
|
||||
<n-button
|
||||
v-if="!hintContent && !hintLoading"
|
||||
type="primary"
|
||||
@click="fetchHint(submission.id)"
|
||||
>
|
||||
让 AI 分析我的代码
|
||||
</n-button>
|
||||
<n-spin v-else-if="hintLoading && !hintContent" size="small" />
|
||||
<MdPreview
|
||||
v-if="hintContent"
|
||||
:model-value="hintContent"
|
||||
preview-theme="vuepress"
|
||||
:theme="isDark ? 'dark' : 'light'"
|
||||
/>
|
||||
</n-card>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -96,4 +183,12 @@ const columns: DataTableColumn<Submission["info"]["data"][number]>[] = [
|
||||
word-break: break-all;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.gradient-text {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user