「失败 3 次解锁 AI 提示」实际是「在当前这次页面会话里再失败 3 次」: problemStore.failCount 是个从 0 起数的内存计数器,刷新、切题、跳进跳出就归零, 而后端闸门数的是数据库里的历史失败数,两边根本不是一回事。昨天在这题上撞了 十次墙的学生今天进来照样看不到按钮。 - 数法收成一个 countFailedSubmissions(),题目详情的 myFailedCount 和 POST /ai/hint 共用。原来详情把「等待/正在评分」也算失败,连点三次提交就能 把按钮点亮,点下去却回 hint-locked - 阈值 3 挪进契约 HINT_MIN_FAILURES,两端引用同一个常量 - failCount 改成 myFailedCount + 本次会话增量;在题目页里登录的补拉一次详情, 否则停在匿名时的 0 - 结果面板改 display-directive="show",不再一收起来就把流式输出中的提示连同 那次 LLM 调用一起作废;补「上次结果」按钮,原来唯一的重开方式是再提交一次 - prompt 里的判题结果翻成中文,原来拼的是裸状态码,模型不知道 -1 是什么 - system_error 不计入失败数、也不显示按钮:判题机自己崩了不是学生的问题 - 比赛中不给提示,和「求助」按钮同一个口径 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RC5uL72UY9aZFuTvUKe2jv
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { Icon } from "@iconify/vue"
|
||||
import { useThemeVars } from "naive-ui"
|
||||
import { HINT_MIN_FAILURES } from "@oj2/contract"
|
||||
import { JUDGE_STATUS, SubmissionStatus } from "utils/constants"
|
||||
import {
|
||||
submissionMemoryFormat,
|
||||
@@ -54,19 +55,39 @@ const msg = computed(() => {
|
||||
return msg
|
||||
})
|
||||
|
||||
// 是否显示AI提示区域
|
||||
// 是否显示AI提示区域。
|
||||
// 阈值和后端 POST /ai/hint 共用契约里的 HINT_MIN_FAILURES,别在这里写死数字;
|
||||
// failCount 现在含服务端下发的历史失败数,刷新页面不会把进度清掉。
|
||||
// system_error 也要排掉:那是判题机自己崩了,学生代码没毛病,让 AI 去分析
|
||||
// 只会瞎编一通,后端的失败计数同样不认这个状态。
|
||||
const showAIHint = computed(() => {
|
||||
if (!props.submission) return false
|
||||
// 比赛题不给提示,和「求助」按钮一致。用 problem.contestId 而不是路由参数:
|
||||
// 带 contestId 的题目只可能从比赛入口进来(题库列表按 contest_id is null 过滤)。
|
||||
if (problemStore.problem?.contestId != null) return false
|
||||
return (
|
||||
problemStore.failCount >= 3 &&
|
||||
problemStore.failCount >= HINT_MIN_FAILURES &&
|
||||
props.submission.result !== SubmissionStatus.accepted &&
|
||||
props.submission.result !== SubmissionStatus.ast_check_failed &&
|
||||
props.submission.result !== SubmissionStatus.system_error &&
|
||||
props.submission.result !== SubmissionStatus.pending &&
|
||||
props.submission.result !== SubmissionStatus.judging &&
|
||||
props.submission.result !== SubmissionStatus.submitting
|
||||
)
|
||||
})
|
||||
|
||||
// 结果面板现在是 display-directive="show",关掉不再销毁组件,提示内容能留到重新打开。
|
||||
// 代价是换了一次提交它也留着,所以这里按提交 id 手动清一次 —— 否则新结果底下挂着
|
||||
// 上一次提交的提示,而且按钮已经被 v-if 藏了,学生没法重新分析。
|
||||
watch(
|
||||
() => props.submission?.id,
|
||||
() => {
|
||||
hintContent.value = ""
|
||||
hintError.value = ""
|
||||
hintLoading.value = false
|
||||
},
|
||||
)
|
||||
|
||||
async function fetchHint(submissionId: string) {
|
||||
hintLoading.value = true
|
||||
hintContent.value = ""
|
||||
|
||||
@@ -172,6 +172,9 @@ async function submit() {
|
||||
}
|
||||
|
||||
// ==================== 失败计数 ====================
|
||||
// 这里只数本次会话的增量,历史失败数由 problem.myFailedCount 带进来。
|
||||
// 排除的状态要和后端 judge/status.ts 的 NON_FAILURE_RESULTS 对齐,
|
||||
// 尤其是 system_error —— 判题机自己崩了不该推进 AI 提示的解锁进度。
|
||||
watch(
|
||||
() => submission.value?.result,
|
||||
(result) => {
|
||||
@@ -184,7 +187,8 @@ watch(
|
||||
return
|
||||
if (
|
||||
result !== SubmissionStatus.accepted &&
|
||||
result !== SubmissionStatus.ast_check_failed
|
||||
result !== SubmissionStatus.ast_check_failed &&
|
||||
result !== SubmissionStatus.system_error
|
||||
) {
|
||||
problemStore.incrementFailCount()
|
||||
}
|
||||
@@ -228,9 +232,13 @@ watch(
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 提交按钮 + 结果弹窗 -->
|
||||
<!-- 提交按钮 + 结果弹窗。
|
||||
display-directive 默认是 "if":面板一收起来整个 SubmissionResult 就被卸载,
|
||||
正在流式输出的 AI 提示连同已经生成的内容一起没了,那次 LLM 调用白花。
|
||||
改成 "show" 之后内容留着,重新打开还是原样。 -->
|
||||
<n-popover
|
||||
trigger="manual"
|
||||
display-directive="show"
|
||||
placement="bottom-end"
|
||||
scrollable
|
||||
:show-arrow="false"
|
||||
@@ -258,6 +266,17 @@ watch(
|
||||
<SubmissionResult :submission="submission" />
|
||||
</n-popover>
|
||||
|
||||
<!-- 结果面板点一下别处就收起来,而 showResult 只在提交时被置 true ——
|
||||
原来唯一的重开方式是「再提交一次」,AI 提示读到一半去看眼题面就回不来了。
|
||||
只在这次会话提交过之后才出现,没提交时工具栏保持原样。 -->
|
||||
<n-button
|
||||
v-if="submission && !showResult"
|
||||
:size="isDesktop ? 'medium' : 'small'"
|
||||
@click="showResult = true"
|
||||
>
|
||||
上次结果
|
||||
</n-button>
|
||||
|
||||
<!-- 评价弹窗 -->
|
||||
<n-modal
|
||||
preset="card"
|
||||
|
||||
@@ -5,6 +5,7 @@ import { storeToRefs } from "pinia"
|
||||
import { useProblemStore } from "oj/store/problem"
|
||||
import { useScreenModeStore } from "shared/store/screenMode"
|
||||
import { useMyFlowchartStore } from "shared/store/myFlowchart"
|
||||
import { useUserStore } from "shared/store/user"
|
||||
|
||||
// 抽成具名 loader,便于进页面时与接口并行预取编辑器 chunk
|
||||
const loadProblemEditor = () => import("./components/ProblemEditor.vue")
|
||||
@@ -128,6 +129,21 @@ async function init() {
|
||||
}
|
||||
onMounted(init)
|
||||
watch(() => problemID, init)
|
||||
|
||||
// 题目详情里的 myStatus / myFailedCount 是按当前用户算的,而登录不重新挂载这个页面 ——
|
||||
// 会话过期后直接在题目页登录的(机房里最常见的那条路)不补拉一次,AI 提示的解锁进度
|
||||
// 就还是匿名时的 0,等于白改。只换 problem,不走 init:那里还会重置分栏模式。
|
||||
watch(
|
||||
() => useUserStore().isAuthed,
|
||||
async (authed) => {
|
||||
if (!authed || !problem.value) return
|
||||
try {
|
||||
problem.value = await getProblem(problemID, contestID)
|
||||
} catch {
|
||||
// 拉不到就留着现在这份题面,不要把页面清空
|
||||
}
|
||||
},
|
||||
)
|
||||
onBeforeUnmount(() => {
|
||||
problem.value = null
|
||||
errMsg.value = "无数据"
|
||||
|
||||
@@ -5,7 +5,27 @@ export const useProblemStore = defineStore("problem", () => {
|
||||
const problem = ref<Problem | null>(null)
|
||||
const route = useRoute()
|
||||
|
||||
const failCount = ref(0)
|
||||
/**
|
||||
* 本次会话里新增的失败提交数。**只是增量**,历史失败数看 `problem.myFailedCount`。
|
||||
*
|
||||
* 原来 failCount 就是这一个从 0 起数的 ref,于是「失败 3 次解锁 AI 提示」实际变成了
|
||||
* 「在当前这次页面会话里再失败 3 次」:刷新一下、从题单跳进跳出一次就清零,
|
||||
* 昨天在这题上撞了十次墙的学生今天进来照样看不到按钮。而后端的闸门数的是数据库里
|
||||
* 的历史失败数,两边根本不是一回事。
|
||||
*/
|
||||
const sessionFailCount = ref(0)
|
||||
|
||||
/**
|
||||
* 这道题一共失败了几次 = 服务端算好的历史值 + 本次会话的增量。
|
||||
* 和后端 `countFailedSubmissions` 同一个口径,所以按钮亮起来的时刻就是
|
||||
* `POST /ai/hint` 放行的时刻。
|
||||
*
|
||||
* 题目详情只在 problemID 变化时重新拉(见 oj/problem/detail.vue 的 init),
|
||||
* 而那时下面的 watch 已经把增量清零了,不会和新的 myFailedCount 叠加。
|
||||
*/
|
||||
const failCount = computed(
|
||||
() => (problem.value?.myFailedCount ?? 0) + sessionFailCount.value,
|
||||
)
|
||||
|
||||
const languages = computed<LANGUAGE[]>(() => {
|
||||
if (route.name === "problem" && problem.value?.allowFlowchart) {
|
||||
@@ -15,13 +35,13 @@ export const useProblemStore = defineStore("problem", () => {
|
||||
})
|
||||
|
||||
function incrementFailCount() {
|
||||
failCount.value++
|
||||
sessionFailCount.value++
|
||||
}
|
||||
|
||||
watch(
|
||||
() => problem.value?.id,
|
||||
() => {
|
||||
failCount.value = 0
|
||||
sessionFailCount.value = 0
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user