update
This commit is contained in:
@@ -163,6 +163,14 @@ export function getSQLTestcaseScripts(problemId: number) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AI 根据标准答案生成一个 SQL 测试点初始化脚本
|
||||||
|
export function generateSQLTestcase(data: {
|
||||||
|
ref_sql: string
|
||||||
|
mode: "query" | "modify"
|
||||||
|
}) {
|
||||||
|
return http.post<{ sql: string }>("admin/sql_test_case_ai_gen", data)
|
||||||
|
}
|
||||||
|
|
||||||
export function createProblem(problem: BlankProblem) {
|
export function createProblem(problem: BlankProblem) {
|
||||||
return http.post("admin/problem", problem)
|
return http.post("admin/problem", problem)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { downloadZip } from "client-zip"
|
|||||||
import type { LANGUAGE, SQLDisplay, Testcase } from "utils/types"
|
import type { LANGUAGE, SQLDisplay, Testcase } from "utils/types"
|
||||||
import SQLDataTable from "oj/problem/components/SQLDataTable.vue"
|
import SQLDataTable from "oj/problem/components/SQLDataTable.vue"
|
||||||
import {
|
import {
|
||||||
|
generateSQLTestcase,
|
||||||
getSQLTestcaseScripts,
|
getSQLTestcaseScripts,
|
||||||
previewSQLTestcase,
|
previewSQLTestcase,
|
||||||
uploadTestcases,
|
uploadTestcases,
|
||||||
@@ -35,7 +36,7 @@ function blankEntry(): ScriptEntry {
|
|||||||
return { id: nextId++, sql: "", display: null, error: "", stale: false }
|
return { id: nextId++, sql: "", display: null, error: "", stale: false }
|
||||||
}
|
}
|
||||||
|
|
||||||
const scripts = ref<ScriptEntry[]>([blankEntry(), blankEntry()])
|
const scripts = ref<ScriptEntry[]>([blankEntry(), blankEntry(), blankEntry()])
|
||||||
|
|
||||||
const refSQL = computed(
|
const refSQL = computed(
|
||||||
() =>
|
() =>
|
||||||
@@ -45,8 +46,10 @@ const refSQL = computed(
|
|||||||
|
|
||||||
const isPreviewing = ref(false)
|
const isPreviewing = ref(false)
|
||||||
const isUploading = ref(false)
|
const isUploading = ref(false)
|
||||||
|
const isGenerating = ref(false)
|
||||||
|
|
||||||
const hasAnyScript = computed(() => scripts.value.some((s) => s.sql.trim()))
|
const hasAnyScript = computed(() => scripts.value.some((s) => s.sql.trim()))
|
||||||
|
const hasBlankScript = computed(() => scripts.value.some((s) => !s.sql.trim()))
|
||||||
|
|
||||||
const canUpload = computed(() => {
|
const canUpload = computed(() => {
|
||||||
const filled = scripts.value.filter((s) => s.sql.trim())
|
const filled = scripts.value.filter((s) => s.sql.trim())
|
||||||
@@ -83,7 +86,7 @@ function remove(index: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function reset() {
|
function reset() {
|
||||||
scripts.value = [blankEntry(), blankEntry()]
|
scripts.value = [blankEntry(), blankEntry(), blankEntry()]
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectedQuery(d: SQLDisplay) {
|
function expectedQuery(d: SQLDisplay) {
|
||||||
@@ -94,6 +97,28 @@ function changedTables(d: SQLDisplay) {
|
|||||||
return "changed_tables" in d.expected ? d.expected.changed_tables : []
|
return "changed_tables" in d.expected ? d.expected.changed_tables : []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function generate() {
|
||||||
|
const blanks = scripts.value.filter((s) => !s.sql.trim())
|
||||||
|
if (!blanks.length) return
|
||||||
|
isGenerating.value = true
|
||||||
|
await Promise.all(
|
||||||
|
blanks.map(async (s) => {
|
||||||
|
try {
|
||||||
|
const res = await generateSQLTestcase({
|
||||||
|
ref_sql: refSQL.value,
|
||||||
|
mode: props.mode,
|
||||||
|
})
|
||||||
|
s.sql = res.data.sql
|
||||||
|
} catch (err) {
|
||||||
|
const data = (err as { data?: unknown })?.data
|
||||||
|
message.error(typeof data === "string" ? data : "AI 生成失败")
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
isGenerating.value = false
|
||||||
|
await preview()
|
||||||
|
}
|
||||||
|
|
||||||
async function preview() {
|
async function preview() {
|
||||||
// 丢弃空脚本
|
// 丢弃空脚本
|
||||||
scripts.value = scripts.value.filter((s) => s.sql.trim())
|
scripts.value = scripts.value.filter((s) => s.sql.trim())
|
||||||
@@ -168,15 +193,33 @@ async function upload() {
|
|||||||
还没有填写 SQL 标准答案,请先在上方"本题参考答案"中填写,再来编写测试点
|
还没有填写 SQL 标准答案,请先在上方"本题参考答案"中填写,再来编写测试点
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-flex align="center" wrap>
|
<n-flex align="center" wrap>
|
||||||
<n-button :disabled="isPreviewing" @click="reset">清空</n-button>
|
<n-button :disabled="isPreviewing || isGenerating" @click="reset">
|
||||||
<n-button :disabled="isPreviewing" @click="add">+1</n-button>
|
清空
|
||||||
|
</n-button>
|
||||||
|
<n-button :disabled="isPreviewing || isGenerating" @click="add">
|
||||||
|
+1
|
||||||
|
</n-button>
|
||||||
|
<n-tooltip :disabled="!!refSQL && hasBlankScript">
|
||||||
|
<template #trigger>
|
||||||
|
<span>
|
||||||
|
<n-button
|
||||||
|
:loading="isGenerating"
|
||||||
|
:disabled="!refSQL || !hasBlankScript || isPreviewing"
|
||||||
|
@click="generate"
|
||||||
|
>
|
||||||
|
AI 生成
|
||||||
|
</n-button>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
{{ !refSQL ? "请先填写 SQL 标准答案" : "所有脚本都写好了,无需生成" }}
|
||||||
|
</n-tooltip>
|
||||||
<n-tooltip :disabled="!!refSQL && hasAnyScript">
|
<n-tooltip :disabled="!!refSQL && hasAnyScript">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<span>
|
<span>
|
||||||
<n-button
|
<n-button
|
||||||
type="success"
|
type="success"
|
||||||
:loading="isPreviewing"
|
:loading="isPreviewing"
|
||||||
:disabled="!refSQL || !hasAnyScript"
|
:disabled="!refSQL || !hasAnyScript || isGenerating"
|
||||||
@click="preview"
|
@click="preview"
|
||||||
>
|
>
|
||||||
预览验证
|
预览验证
|
||||||
@@ -191,7 +234,7 @@ async function upload() {
|
|||||||
<n-button
|
<n-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="isUploading"
|
:loading="isUploading"
|
||||||
:disabled="!canUpload"
|
:disabled="!canUpload || isGenerating"
|
||||||
@click="upload"
|
@click="upload"
|
||||||
>
|
>
|
||||||
上传
|
上传
|
||||||
@@ -207,7 +250,7 @@ async function upload() {
|
|||||||
<strong>{{ index + 1 }}.sql</strong>
|
<strong>{{ index + 1 }}.sql</strong>
|
||||||
<n-button
|
<n-button
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="scripts.length === 1 || isPreviewing"
|
:disabled="scripts.length === 1 || isPreviewing || isGenerating"
|
||||||
@click="remove(index)"
|
@click="remove(index)"
|
||||||
>
|
>
|
||||||
删除
|
删除
|
||||||
|
|||||||
@@ -784,10 +784,10 @@ watch(
|
|||||||
|
|
||||||
<h2 class="title">测试用例区域</h2>
|
<h2 class="title">测试用例区域</h2>
|
||||||
|
|
||||||
<n-flex align="center" style="margin-bottom: 12px">
|
<n-flex v-if="!isSQLProblem" align="center" style="margin-bottom: 12px">
|
||||||
<div>
|
<div>
|
||||||
<n-button type="success" @click="showGeneratorModal = true">
|
<n-button type="success" @click="showGeneratorModal = true">
|
||||||
{{ isSQLProblem ? "(新)在线编写" : "(新)直接生成" }}
|
(新)直接生成
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -796,26 +796,25 @@ watch(
|
|||||||
accept=".zip"
|
accept=".zip"
|
||||||
:custom-request="handleUploadTestcases"
|
:custom-request="handleUploadTestcases"
|
||||||
>
|
>
|
||||||
<n-button type="info">
|
<n-button type="info">(老)手动上传</n-button>
|
||||||
{{ isSQLProblem ? "上传数据脚本压缩包" : "(老)手动上传" }}
|
|
||||||
</n-button>
|
|
||||||
</n-upload>
|
</n-upload>
|
||||||
</div>
|
</div>
|
||||||
<n-tooltip placement="right" style="max-width: 320px; white-space: normal">
|
<n-tooltip placement="right" style="max-width: 320px; white-space: normal">
|
||||||
<template #trigger>
|
<template #trigger>
|
||||||
<n-button text>温馨提醒</n-button>
|
<n-button text>温馨提醒</n-button>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="isSQLProblem">
|
|
||||||
压缩包内放 1.sql、2.sql…(每个文件是一个测试点的建表+插入数据脚本,
|
|
||||||
判题时对每个测试点分别运行标准答案和学生 SQL 比对结果)。 建议至少 2-3
|
|
||||||
个数据不同的测试点,防止学生硬编码答案
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
【测试用例】最好要有10个,要考虑边界情况,且不要跟【测试样例】一模一样
|
【测试用例】最好要有10个,要考虑边界情况,且不要跟【测试样例】一模一样
|
||||||
</template>
|
|
||||||
</n-tooltip>
|
</n-tooltip>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
|
||||||
|
<SQLTestcaseEditor
|
||||||
|
v-if="isSQLProblem"
|
||||||
|
:answers="problem.answers"
|
||||||
|
:mode="problem.sql_config?.mode ?? 'query'"
|
||||||
|
:problem-id="problem.id"
|
||||||
|
@uploaded="handleTestcasesGenerated"
|
||||||
|
/>
|
||||||
|
|
||||||
<n-alert
|
<n-alert
|
||||||
class="box"
|
class="box"
|
||||||
v-if="problem.test_case_score.length"
|
v-if="problem.test_case_score.length"
|
||||||
@@ -845,20 +844,12 @@ watch(
|
|||||||
<n-modal
|
<n-modal
|
||||||
v-model:show="showGeneratorModal"
|
v-model:show="showGeneratorModal"
|
||||||
preset="card"
|
preset="card"
|
||||||
:title="isSQLProblem ? 'SQL 测试点编辑器' : '测试用例生成器'"
|
title="测试用例生成器"
|
||||||
style="width: 80vw; max-width: 900px"
|
style="width: 80vw; max-width: 900px"
|
||||||
:mask-closable="false"
|
:mask-closable="false"
|
||||||
display-directive="show"
|
display-directive="show"
|
||||||
>
|
>
|
||||||
<SQLTestcaseEditor
|
|
||||||
v-if="isSQLProblem"
|
|
||||||
:answers="problem.answers"
|
|
||||||
:mode="problem.sql_config?.mode ?? 'query'"
|
|
||||||
:problem-id="problem.id"
|
|
||||||
@uploaded="handleTestcasesGenerated"
|
|
||||||
/>
|
|
||||||
<TestcaseGenerator
|
<TestcaseGenerator
|
||||||
v-else
|
|
||||||
:answers="problem.answers"
|
:answers="problem.answers"
|
||||||
:samples="problem.samples"
|
:samples="problem.samples"
|
||||||
@uploaded="handleTestcasesGenerated"
|
@uploaded="handleTestcasesGenerated"
|
||||||
@@ -909,7 +900,7 @@ watch(
|
|||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
</template>
|
</template>
|
||||||
<n-flex style="margin-bottom: 120px" align="center" justify="end">
|
<n-flex style="margin: 16px 0 120px" align="center" justify="end">
|
||||||
<n-button type="primary" @click="submit">提交</n-button>
|
<n-button type="primary" @click="submit">提交</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
Reference in New Issue
Block a user