Compare commits
7 Commits
596ceec880
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 681c6ff4f4 | |||
| 5bb8a1eaa3 | |||
| c12c77ac7e | |||
| 3102e1178a | |||
| a15b3d9c76 | |||
| 05ecf6bebf | |||
| 54d9861bd8 |
@@ -20,6 +20,7 @@ import type {
|
|||||||
ShowcaseSubmissionLookupOut,
|
ShowcaseSubmissionLookupOut,
|
||||||
ShowcaseDetail,
|
ShowcaseDetail,
|
||||||
PromptRound,
|
PromptRound,
|
||||||
|
RandomRatingItem,
|
||||||
} from "./utils/type"
|
} from "./utils/type"
|
||||||
import { BASE_URL, STORAGE_KEY } from "./utils/const"
|
import { BASE_URL, STORAGE_KEY } from "./utils/const"
|
||||||
|
|
||||||
@@ -237,6 +238,13 @@ export const Submission = {
|
|||||||
return res.data
|
return res.data
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async randomForRating(excludeId?: string) {
|
||||||
|
const res = await http.get("/submission/random-for-rating/", {
|
||||||
|
params: excludeId ? { exclude_id: excludeId } : {},
|
||||||
|
})
|
||||||
|
return res.data as RandomRatingItem | null
|
||||||
|
},
|
||||||
|
|
||||||
async updateFlag(id: string, flag: FlagType) {
|
async updateFlag(id: string, flag: FlagType) {
|
||||||
const res = await http.put(`/submission/${id}/flag`, { flag })
|
const res = await http.put(`/submission/${id}/flag`, { flag })
|
||||||
return res.data
|
return res.data
|
||||||
|
|||||||
@@ -194,8 +194,8 @@ async function deleteItem(item: HistoryViewItem, e: Event) {
|
|||||||
}
|
}
|
||||||
emit("deleted", item.assistant_message_id)
|
emit("deleted", item.assistant_message_id)
|
||||||
naiveMessage.success("已删除")
|
naiveMessage.success("已删除")
|
||||||
} catch {
|
} catch (error: any) {
|
||||||
naiveMessage.error("删除失败,请重试")
|
naiveMessage.error(error.response?.data?.detail ?? "删除失败,请重试")
|
||||||
} finally {
|
} finally {
|
||||||
deletingId.value = null
|
deletingId.value = null
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,16 @@
|
|||||||
<div v-if="pair.assistantMsg" class="message assistant">
|
<div v-if="pair.assistantMsg" class="message assistant">
|
||||||
<div class="message-role">AI</div>
|
<div class="message-role">AI</div>
|
||||||
<div class="message-content" v-html="renderContent(pair.assistantMsg)"></div>
|
<div class="message-content" v-html="renderContent(pair.assistantMsg)"></div>
|
||||||
|
<div v-if="hasCode(pair.assistantMsg)" class="message-actions">
|
||||||
|
<n-button
|
||||||
|
size="tiny"
|
||||||
|
:disabled="pair.assistantMsg.submitted"
|
||||||
|
:loading="submittingId === pair.assistantMsg.id"
|
||||||
|
@click="submitVersion(pair.assistantMsg)"
|
||||||
|
>
|
||||||
|
{{ pair.assistantMsg.submitted ? "已提交" : "提交此版本" }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -113,11 +123,12 @@ import {
|
|||||||
stopPrompt,
|
stopPrompt,
|
||||||
currentTaskId,
|
currentTaskId,
|
||||||
removeMessagePair,
|
removeMessagePair,
|
||||||
|
markMessageSubmitted,
|
||||||
} from "../../store/prompt"
|
} from "../../store/prompt"
|
||||||
import { Prompt } from "../../api"
|
import { Prompt, Submission } from "../../api"
|
||||||
import { renderMarkdown } from "../../utils/markdown"
|
import { renderMarkdown } from "../../utils/markdown"
|
||||||
|
|
||||||
const emit = defineEmits<{ deleted: [] }>()
|
const emit = defineEmits<{ deleted: []; submitted: [] }>()
|
||||||
|
|
||||||
const input = ref("")
|
const input = ref("")
|
||||||
const messagesRef = ref<HTMLElement>()
|
const messagesRef = ref<HTMLElement>()
|
||||||
@@ -131,10 +142,18 @@ const modelOptions = [
|
|||||||
const selectedModel = useStorage("prompt-model", "deepseek-v4-flash")
|
const selectedModel = useStorage("prompt-model", "deepseek-v4-flash")
|
||||||
|
|
||||||
// Group messages into user+assistant pairs
|
// Group messages into user+assistant pairs
|
||||||
|
const submittingId = ref<number | null>(null)
|
||||||
|
|
||||||
const pairs = computed(() => {
|
const pairs = computed(() => {
|
||||||
const result: Array<{
|
const result: Array<{
|
||||||
userMsg: { role: string; content: string; id?: number }
|
userMsg: { role: string; content: string; id?: number }
|
||||||
assistantMsg: { role: string; content: string; id?: number; code?: any } | null
|
assistantMsg: {
|
||||||
|
role: string
|
||||||
|
content: string
|
||||||
|
id?: number
|
||||||
|
code?: any
|
||||||
|
submitted?: boolean
|
||||||
|
} | null
|
||||||
index: number
|
index: number
|
||||||
}> = []
|
}> = []
|
||||||
const msgs = messages.value
|
const msgs = messages.value
|
||||||
@@ -151,14 +170,45 @@ const pairs = computed(() => {
|
|||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function hasCode(msg: { code?: any } | null): boolean {
|
||||||
|
if (!msg?.code) return false
|
||||||
|
return !!(msg.code.html || msg.code.css || msg.code.js)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function submitVersion(msg: {
|
||||||
|
id?: number
|
||||||
|
code?: { html: string | null; css: string | null; js: string | null }
|
||||||
|
}) {
|
||||||
|
if (!msg.id || !msg.code || !currentTaskId.value) return
|
||||||
|
submittingId.value = msg.id
|
||||||
|
try {
|
||||||
|
await Submission.create(
|
||||||
|
currentTaskId.value,
|
||||||
|
{
|
||||||
|
html: msg.code.html ?? "",
|
||||||
|
css: msg.code.css ?? "",
|
||||||
|
js: msg.code.js ?? "",
|
||||||
|
},
|
||||||
|
msg.id,
|
||||||
|
)
|
||||||
|
markMessageSubmitted(msg.id)
|
||||||
|
naiveMessage.success("提交成功")
|
||||||
|
emit("submitted")
|
||||||
|
} catch {
|
||||||
|
naiveMessage.error("提交失败,请重试")
|
||||||
|
} finally {
|
||||||
|
submittingId.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function deletePair(assistantMsgId: number) {
|
async function deletePair(assistantMsgId: number) {
|
||||||
try {
|
try {
|
||||||
await Prompt.deleteMessagePair(assistantMsgId)
|
await Prompt.deleteMessagePair(assistantMsgId)
|
||||||
removeMessagePair(assistantMsgId)
|
removeMessagePair(assistantMsgId)
|
||||||
naiveMessage.success("已删除")
|
naiveMessage.success("已删除")
|
||||||
emit("deleted")
|
emit("deleted")
|
||||||
} catch {
|
} catch (error: any) {
|
||||||
naiveMessage.error("删除失败,请重试")
|
naiveMessage.error(error.response?.data?.detail ?? "删除失败,请重试")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,6 +290,10 @@ watch([() => messages.value.length, streamingContent], () => {
|
|||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-actions {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%,
|
0%,
|
||||||
|
|||||||
135
src/components/ai/RandomRatingModal.vue
Normal file
135
src/components/ai/RandomRatingModal.vue
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
<template>
|
||||||
|
<n-modal
|
||||||
|
preset="card"
|
||||||
|
:show="show"
|
||||||
|
title="等待 AI 回复时,给同学的作品打个分吧"
|
||||||
|
style="width: 90vw; max-width: 960px"
|
||||||
|
@update:show="onUpdateShow"
|
||||||
|
>
|
||||||
|
<n-text
|
||||||
|
v-if="current"
|
||||||
|
depth="3"
|
||||||
|
style="font-size: 12px; display: block; margin-bottom: 8px"
|
||||||
|
>
|
||||||
|
{{ current.task_title }} · 提交者 {{ current.username }}
|
||||||
|
</n-text>
|
||||||
|
<div class="preview-wrapper">
|
||||||
|
<iframe class="preview-iframe" :srcdoc="previewContent"></iframe>
|
||||||
|
</div>
|
||||||
|
<div class="rate-row">
|
||||||
|
<n-rate
|
||||||
|
v-if="current"
|
||||||
|
:key="current.submission_id"
|
||||||
|
:size="32"
|
||||||
|
@update:value="onRate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<n-text
|
||||||
|
depth="3"
|
||||||
|
style="font-size: 11px; display: block; text-align: center; margin-top: 8px"
|
||||||
|
>
|
||||||
|
打分后自动换下一个作品;AI 回复完成后弹窗会自动关闭
|
||||||
|
</n-text>
|
||||||
|
</n-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, onUnmounted } from "vue"
|
||||||
|
import { useMessage } from "naive-ui"
|
||||||
|
import { Submission } from "../../api"
|
||||||
|
import { streaming } from "../../store/prompt"
|
||||||
|
import { buildPreviewDocument } from "../../utils/previewDocument"
|
||||||
|
import type { RandomRatingItem } from "../../utils/type"
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const current = ref<RandomRatingItem | null>(null)
|
||||||
|
const previewContent = ref("")
|
||||||
|
let reshowTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
|
|
||||||
|
function clearReshowTimer() {
|
||||||
|
if (reshowTimer !== null) {
|
||||||
|
clearTimeout(reshowTimer)
|
||||||
|
reshowTimer = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchAndShow() {
|
||||||
|
try {
|
||||||
|
const item = await Submission.randomForRating(current.value?.submission_id)
|
||||||
|
if (item) {
|
||||||
|
current.value = item
|
||||||
|
previewContent.value = buildPreviewDocument({
|
||||||
|
html: item.html ?? "",
|
||||||
|
css: item.css ?? "",
|
||||||
|
js: item.js ?? "",
|
||||||
|
})
|
||||||
|
show.value = true
|
||||||
|
} else {
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
message.error(err.response?.data?.detail ?? "获取作品失败,请重试")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onRate(score: number) {
|
||||||
|
if (!current.value) return
|
||||||
|
try {
|
||||||
|
await Submission.updateScore(current.value.submission_id, score)
|
||||||
|
message.success("感谢评分!")
|
||||||
|
await fetchAndShow()
|
||||||
|
} catch (err: any) {
|
||||||
|
message.error(err.response?.data?.detail ?? "打分失败,请重试")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function onUpdateShow(value: boolean) {
|
||||||
|
show.value = value
|
||||||
|
if (!value) {
|
||||||
|
clearReshowTimer()
|
||||||
|
if (streaming.value) {
|
||||||
|
reshowTimer = setTimeout(fetchAndShow, 30_000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
streaming,
|
||||||
|
(val) => {
|
||||||
|
clearReshowTimer()
|
||||||
|
if (val) {
|
||||||
|
fetchAndShow()
|
||||||
|
} else {
|
||||||
|
show.value = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearReshowTimer()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.preview-wrapper {
|
||||||
|
height: 70vh;
|
||||||
|
border: 1px solid #eee;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rate-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -238,13 +238,15 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
import { NPopconfirm, NButton } from "naive-ui"
|
import { NPopconfirm, NButton, useMessage } from "naive-ui"
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { marked } from "marked"
|
import { marked } from "marked"
|
||||||
import { Prompt, Submission } from "../../api"
|
import { Prompt, Submission } from "../../api"
|
||||||
import type { PromptRound } from "../../utils/type"
|
import type { PromptRound } from "../../utils/type"
|
||||||
import { user, roleSuper } from "../../store/user"
|
import { user, roleSuper } from "../../store/user"
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
submissionId: string
|
submissionId: string
|
||||||
@@ -270,7 +272,12 @@ const rounds = ref<ChainRound[]>([])
|
|||||||
async function deleteRound(index: number) {
|
async function deleteRound(index: number) {
|
||||||
const round = rounds.value[index]
|
const round = rounds.value[index]
|
||||||
if (!round.assistantMsgId) return
|
if (!round.assistantMsgId) return
|
||||||
|
try {
|
||||||
await Prompt.deleteMessagePair(round.assistantMsgId)
|
await Prompt.deleteMessagePair(round.assistantMsgId)
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.response?.data?.detail ?? "删除失败,请重试")
|
||||||
|
return
|
||||||
|
}
|
||||||
await loadMessages()
|
await loadMessages()
|
||||||
if (selectedRound.value >= rounds.value.length) {
|
if (selectedRound.value >= rounds.value.length) {
|
||||||
selectedRound.value = Math.max(0, rounds.value.length - 1)
|
selectedRound.value = Math.max(0, rounds.value.length - 1)
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<n-flex vertical>
|
<n-flex vertical>
|
||||||
<n-flex align="center">
|
<n-button size="small" @click="showUpload = true">图片素材</n-button>
|
||||||
<n-text strong>图片素材</n-text>
|
|
||||||
<n-button size="small" @click="showUpload = true">上传</n-button>
|
|
||||||
</n-flex>
|
|
||||||
<n-flex v-if="assets.length" wrap>
|
<n-flex v-if="assets.length" wrap>
|
||||||
<n-card
|
<n-card
|
||||||
v-for="asset in assets"
|
v-for="asset in assets"
|
||||||
|
|||||||
@@ -9,9 +9,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<n-flex style="margin: 0 8px">
|
<n-flex style="margin: 0 8px">
|
||||||
<n-button v-if="assets.length" text @click="showAssets = true">
|
|
||||||
<Icon :width="16" icon="lucide:image" />
|
|
||||||
</n-button>
|
|
||||||
<n-button
|
<n-button
|
||||||
v-if="roleAdmin || roleSuper"
|
v-if="roleAdmin || roleSuper"
|
||||||
text
|
text
|
||||||
@@ -40,6 +37,14 @@
|
|||||||
<n-text depth="3">
|
<n-text depth="3">
|
||||||
出题人:{{ challengeAuthor || "未设置" }}
|
出题人:{{ challengeAuthor || "未设置" }}
|
||||||
</n-text>
|
</n-text>
|
||||||
|
<n-flex align="center" size="small">
|
||||||
|
<n-button
|
||||||
|
v-if="assets.length"
|
||||||
|
size="small"
|
||||||
|
@click="showAssets = true"
|
||||||
|
>
|
||||||
|
看素材
|
||||||
|
</n-button>
|
||||||
<n-button
|
<n-button
|
||||||
v-if="exampleCode"
|
v-if="exampleCode"
|
||||||
size="small"
|
size="small"
|
||||||
@@ -48,6 +53,7 @@
|
|||||||
看示例
|
看示例
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
|
</n-flex>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="markdown-body content no-select"
|
class="markdown-body content no-select"
|
||||||
@@ -58,7 +64,10 @@
|
|||||||
</div>
|
</div>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="chat" tab="AI 对话" display-directive="show">
|
<n-tab-pane name="chat" tab="AI 对话" display-directive="show">
|
||||||
<PromptPanel @deleted="historyRefreshKey++" />
|
<PromptPanel
|
||||||
|
@deleted="historyRefreshKey++"
|
||||||
|
@submitted="historyRefreshKey++"
|
||||||
|
/>
|
||||||
</n-tab-pane>
|
</n-tab-pane>
|
||||||
<n-tab-pane name="external" tab="手动提交" display-directive="show">
|
<n-tab-pane name="external" tab="手动提交" display-directive="show">
|
||||||
<ExternalAIPanel :task-id="taskId" @submitted="historyRefreshKey++" />
|
<ExternalAIPanel :task-id="taskId" @submitted="historyRefreshKey++" />
|
||||||
@@ -89,6 +98,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TaskStatsModal v-model:show="showStats" :task-id="taskId" />
|
<TaskStatsModal v-model:show="showStats" :task-id="taskId" />
|
||||||
|
<RandomRatingModal v-if="authed" />
|
||||||
<n-modal
|
<n-modal
|
||||||
v-model:show="showAssets"
|
v-model:show="showAssets"
|
||||||
preset="card"
|
preset="card"
|
||||||
@@ -138,6 +148,7 @@ import ExternalAIPanel from "../components/ai/ExternalAIPanel.vue"
|
|||||||
import PromptHistoryPanel from "../components/ai/PromptHistoryPanel.vue"
|
import PromptHistoryPanel from "../components/ai/PromptHistoryPanel.vue"
|
||||||
import Preview from "../components/editor/Preview.vue"
|
import Preview from "../components/editor/Preview.vue"
|
||||||
import TaskStatsModal from "../components/task/TaskStatsModal.vue"
|
import TaskStatsModal from "../components/task/TaskStatsModal.vue"
|
||||||
|
import RandomRatingModal from "../components/ai/RandomRatingModal.vue"
|
||||||
import { Challenge, Submission, TaskAssets } from "../api"
|
import { Challenge, Submission, TaskAssets } from "../api"
|
||||||
import type { TaskAsset } from "../utils/type"
|
import type { TaskAsset } from "../utils/type"
|
||||||
import { html, css, js } from "../store/editors"
|
import { html, css, js } from "../store/editors"
|
||||||
@@ -150,6 +161,7 @@ import {
|
|||||||
streaming,
|
streaming,
|
||||||
setOnCodeComplete,
|
setOnCodeComplete,
|
||||||
removeMessagePair,
|
removeMessagePair,
|
||||||
|
markMessageSubmitted,
|
||||||
} from "../store/prompt"
|
} from "../store/prompt"
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -238,10 +250,11 @@ async function loadChallenge() {
|
|||||||
},
|
},
|
||||||
messageId,
|
messageId,
|
||||||
)
|
)
|
||||||
|
markMessageSubmitted(messageId)
|
||||||
historyRefreshKey.value++
|
historyRefreshKey.value++
|
||||||
message.success("已自动提交本次对话生成的代码")
|
message.success("已自动提交本次对话生成的代码")
|
||||||
} catch {
|
} catch {
|
||||||
// 静默失败,不打扰用户
|
message.error("自动提交失败,请稍后重试")
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ const menu = computed(() =>
|
|||||||
{
|
{
|
||||||
label: "成绩",
|
label: "成绩",
|
||||||
route: { name: "gradebook" },
|
route: { name: "gradebook" },
|
||||||
show: roleAdmin.value || roleSuper.value,
|
show: roleSuper.value,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "提交",
|
label: "提交",
|
||||||
|
|||||||
@@ -79,10 +79,12 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted, ref } from "vue"
|
import { onMounted, ref } from "vue"
|
||||||
|
import { useRouter } from "vue-router"
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { Showcase, Submission } from "../api"
|
import { Showcase } from "../api"
|
||||||
import type { AwardSection, ShowcaseItem } from "../utils/type"
|
import type { AwardSection, ShowcaseItem } from "../utils/type"
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const awards = ref<AwardSection[]>([])
|
const awards = ref<AwardSection[]>([])
|
||||||
|
|
||||||
@@ -93,14 +95,11 @@ function buildSrcdoc(item: ShowcaseItem): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function openDetail(item: ShowcaseItem) {
|
function openDetail(item: ShowcaseItem) {
|
||||||
const srcdoc = buildSrcdoc(item)
|
const { href } = router.resolve({
|
||||||
const win = window.open("", "_blank")
|
name: "submission",
|
||||||
if (win) {
|
params: { id: item.submission_id },
|
||||||
win.document.open()
|
})
|
||||||
win.document.write(srcdoc)
|
window.open(href, "_blank")
|
||||||
win.document.close()
|
|
||||||
}
|
|
||||||
void Submission.incrementView(item.submission_id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
|
|||||||
@@ -97,7 +97,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, h, onMounted, onUnmounted, reactive, ref, watch } from "vue"
|
import { computed, h, onMounted, onUnmounted, reactive, ref, watch } from "vue"
|
||||||
import { NButton, NDataTable, NTag, type DataTableColumn } from "naive-ui"
|
import { NButton, NDataTable, NTag, useMessage, type DataTableColumn } from "naive-ui"
|
||||||
import { Icon } from "@iconify/vue"
|
import { Icon } from "@iconify/vue"
|
||||||
import { Submission } from "../api"
|
import { Submission } from "../api"
|
||||||
import type { SubmissionOut, FlagType } from "../utils/type"
|
import type { SubmissionOut, FlagType } from "../utils/type"
|
||||||
@@ -122,6 +122,7 @@ import { roleAdmin, roleSuper, user } from "../store/user"
|
|||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
// 列表数据
|
// 列表数据
|
||||||
const data = ref<SubmissionOut[]>([])
|
const data = ref<SubmissionOut[]>([])
|
||||||
@@ -304,7 +305,12 @@ async function handleExpand(keys: (string | number)[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleDelete(row: SubmissionOut, parentId: string) {
|
async function handleDelete(row: SubmissionOut, parentId: string) {
|
||||||
|
try {
|
||||||
await Submission.delete(row.id)
|
await Submission.delete(row.id)
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(error.response?.data?.detail ?? "删除失败,请重试")
|
||||||
|
return
|
||||||
|
}
|
||||||
const items = expandedData.get(parentId)
|
const items = expandedData.get(parentId)
|
||||||
if (items)
|
if (items)
|
||||||
expandedData.set(
|
expandedData.set(
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface PromptMessage {
|
|||||||
content: string
|
content: string
|
||||||
id?: number // assistant message backend pk (for deletion)
|
id?: number // assistant message backend pk (for deletion)
|
||||||
code?: { html: string | null; css: string | null; js: string | null }
|
code?: { html: string | null; css: string | null; js: string | null }
|
||||||
|
submitted?: boolean // whether this assistant message's code has been submitted
|
||||||
created?: string
|
created?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ export function connectPrompt(taskId: number) {
|
|||||||
content: streamingContent.value,
|
content: streamingContent.value,
|
||||||
id: data.message_id,
|
id: data.message_id,
|
||||||
code: data.code,
|
code: data.code,
|
||||||
|
submitted: false,
|
||||||
})
|
})
|
||||||
streamingContent.value = ""
|
streamingContent.value = ""
|
||||||
if (data.code) {
|
if (data.code) {
|
||||||
@@ -124,6 +126,11 @@ export function stopPrompt() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function markMessageSubmitted(assistantMsgId: number) {
|
||||||
|
const msg = messages.value.find((m) => m.id === assistantMsgId)
|
||||||
|
if (msg) msg.submitted = true
|
||||||
|
}
|
||||||
|
|
||||||
export function removeMessagePair(assistantMsgId: number) {
|
export function removeMessagePair(assistantMsgId: number) {
|
||||||
const idx = messages.value.findIndex((m) => m.id === assistantMsgId)
|
const idx = messages.value.findIndex((m) => m.id === assistantMsgId)
|
||||||
if (idx >= 1) {
|
if (idx >= 1) {
|
||||||
|
|||||||
@@ -335,3 +335,14 @@ export interface PromptRound {
|
|||||||
css: string | null
|
css: string | null
|
||||||
js: string | null
|
js: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RandomRatingItem {
|
||||||
|
submission_id: string
|
||||||
|
username: string
|
||||||
|
task_title: string
|
||||||
|
task_display: number
|
||||||
|
task_type: TASK_TYPE
|
||||||
|
html: string | null
|
||||||
|
css: string | null
|
||||||
|
js: string | null
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user