Compare commits

...

2 Commits

Author SHA1 Message Date
45b40f13ad fix delete
Some checks failed
Deploy / deploy (build, debian, 22) (push) Has been cancelled
Deploy / deploy (build:staging, school, 8822) (push) Has been cancelled
2026-04-16 01:32:47 -06:00
567da331f4 feat: add source field to PromptMessage type 2026-04-16 00:41:32 -06:00
4 changed files with 122 additions and 71 deletions

View File

@@ -131,17 +131,47 @@
" "
> >
<span>{{ round.question }}</span> <span>{{ round.question }}</span>
<div style="display: flex; flex-direction: row; align-items: center; gap: 4px; flex-shrink: 0">
<span
v-if="round.source"
:style="{
fontSize: '10px',
padding: '1px 5px',
borderRadius: '4px',
background: round.source === 'conversation' ? '#e8f0fe' : '#f0f0f0',
color: round.source === 'conversation' ? '#2060c0' : '#888',
fontWeight: 500,
whiteSpace: 'nowrap',
}"
>{{ round.source === "conversation" ? "对话" : "手动" }}</span>
<span <span
v-if="round.prompt_level" v-if="round.prompt_level"
:style="{ :style="{
flexShrink: 0,
fontSize: '11px', fontSize: '11px',
fontWeight: 'bold', fontWeight: 'bold',
color: levelColors[round.prompt_level], color: levelColors[round.prompt_level],
marginTop: '2px',
}" }"
>L{{ round.prompt_level }}</span >L{{ round.prompt_level }}</span
> >
<n-popconfirm
v-if="round.assistantMsgId && canDelete"
:show-icon="false"
@positive-click="deleteRound(index)"
>
<template #trigger>
<n-button
text
size="tiny"
type="error"
style="margin-left: 2px"
@click.stop
>
<Icon icon="lucide:trash-2" :width="12" />
</n-button>
</template>
确定删除这一轮
</n-popconfirm>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -171,16 +201,21 @@
<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 { Icon } from "@iconify/vue" import { Icon } from "@iconify/vue"
import { Prompt } from "../../api" import { Prompt } from "../../api"
import type { PromptMessage } from "../../utils/type" import type { PromptMessage } from "../../utils/type"
import { user, roleSuper } from "../../store/user"
const props = defineProps<{ const props = defineProps<{
show: boolean show: boolean
userId: number userId: number
taskId: number taskId: number
username?: string
}>() }>()
const canDelete = computed(() => roleSuper.value || (!!props.username && props.username === user.username))
defineEmits<{ "update:show": [value: boolean] }>() defineEmits<{ "update:show": [value: boolean] }>()
const loading = ref(false) const loading = ref(false)
@@ -190,7 +225,9 @@ const selectedRound = ref(0)
const rounds = computed(() => { const rounds = computed(() => {
const result: { const result: {
question: string question: string
source: string | null
prompt_level: number | null prompt_level: number | null
assistantMsgId: number | null
html: string | null html: string | null
css: string | null css: string | null
js: string | null js: string | null
@@ -199,19 +236,25 @@ const rounds = computed(() => {
if (msg.role !== "user") continue if (msg.role !== "user") continue
let html: string | null = null, let html: string | null = null,
css: string | null = null, css: string | null = null,
js: string | null = null js: string | null = null,
assistantMsgId: number | null = null
for (const reply of messages.value.slice(i + 1)) { for (const reply of messages.value.slice(i + 1)) {
if (reply.role === "user") break if (reply.role === "user") break
if (reply.role === "assistant" && reply.code_html) { if (reply.role === "assistant") {
assistantMsgId = reply.id
if (reply.code_html) {
html = reply.code_html html = reply.code_html
css = reply.code_css css = reply.code_css
js = reply.code_js js = reply.code_js
}
break break
} }
} }
result.push({ result.push({
question: msg.content, question: msg.content,
source: msg.source ?? null,
prompt_level: msg.prompt_level ?? null, prompt_level: msg.prompt_level ?? null,
assistantMsgId,
html, html,
css, css,
js, js,
@@ -220,6 +263,16 @@ const rounds = computed(() => {
return result return result
}) })
async function deleteRound(index: number) {
const round = rounds.value[index]
if (!round.assistantMsgId) return
await Prompt.deleteMessagePair(round.assistantMsgId)
await loadMessages()
if (selectedRound.value >= rounds.value.length) {
selectedRound.value = Math.max(0, rounds.value.length - 1)
}
}
const levelColors: Record<number, string> = { const levelColors: Record<number, string> = {
1: "#aaa", 1: "#aaa",
2: "#6aab9c", 2: "#6aab9c",

View File

@@ -24,7 +24,7 @@ import {
import type { SubmissionOut } from "../../utils/type" import type { SubmissionOut } from "../../utils/type"
import { TASK_TYPE } from "../../utils/const" import { TASK_TYPE } from "../../utils/const"
import { parseTime } from "../../utils/helper" import { parseTime } from "../../utils/helper"
import { user } from "../../store/user" import { user, roleSuper } from "../../store/user"
import { submission } from "../../store/submission" import { submission } from "../../store/submission"
const props = defineProps<{ const props = defineProps<{
@@ -36,7 +36,7 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
select: [id: string] select: [id: string]
delete: [row: SubmissionOut, parentId: string] delete: [row: SubmissionOut, parentId: string]
"show-chain": [userId: number, taskId: number] "show-chain": [userId: number, taskId: number, username: string]
}>() }>()
const isChallenge = computed(() => props.row.task_type === TASK_TYPE.Challenge) const isChallenge = computed(() => props.row.task_type === TASK_TYPE.Challenge)
@@ -76,13 +76,14 @@ const subColumns = computed((): DataTableColumn<SubmissionOut>[] => [
) )
}, },
}, },
{
title: "操作",
key: "actions",
width: isChallenge.value ? 110 : 60,
render: (r: SubmissionOut) =>
h("div", { style: { display: "flex", gap: "8px" } }, [
...(isChallenge.value ...(isChallenge.value
? [ ? [
{
title: "提示词",
key: "prompt",
width: 70,
render: (r: SubmissionOut) =>
h( h(
NButton, NButton,
{ {
@@ -90,23 +91,16 @@ const subColumns = computed((): DataTableColumn<SubmissionOut>[] => [
type: "primary", type: "primary",
onClick: (e: Event) => { onClick: (e: Event) => {
e.stopPropagation() e.stopPropagation()
emit("show-chain", r.userid, r.task_id) emit("show-chain", r.userid, r.task_id, r.username)
}, },
}, },
() => "查看", () => "查看",
), ),
} as DataTableColumn<SubmissionOut>,
] ]
: []), : []),
...(!isChallenge.value ...(r.username === user.username || roleSuper.value
? [ ? [
{ h(
title: "操作",
key: "actions",
width: 60,
render: (r: SubmissionOut) => {
if (r.username !== user.username) return null
return h(
NPopconfirm, NPopconfirm,
{ onPositiveClick: () => emit("delete", r, props.row.id) }, { onPositiveClick: () => emit("delete", r, props.row.id) },
{ {
@@ -123,10 +117,10 @@ const subColumns = computed((): DataTableColumn<SubmissionOut>[] => [
), ),
default: () => "确定删除这次提交", default: () => "确定删除这次提交",
}, },
) ),
},
} as DataTableColumn<SubmissionOut>,
] ]
: []), : []),
]),
} as DataTableColumn<SubmissionOut>,
]) ])
</script> </script>

View File

@@ -107,6 +107,7 @@
v-model:show="chainModal" v-model:show="chainModal"
:user-id="chainUserId" :user-id="chainUserId"
:task-id="chainTaskId" :task-id="chainTaskId"
:username="chainUsername"
/> />
</template> </template>
@@ -160,6 +161,7 @@ const codeModal = ref(false)
const chainModal = ref(false) const chainModal = ref(false)
const chainUserId = ref<number>(0) const chainUserId = ref<number>(0)
const chainTaskId = ref<number>(0) const chainTaskId = ref<number>(0)
const chainUsername = ref<string>("")
// 展开行 // 展开行
const expandedKeys = ref<string[]>([]) const expandedKeys = ref<string[]>([])
@@ -201,9 +203,10 @@ async function clearAllFlags() {
query.flag = null query.flag = null
} }
function showChain(userId: number, taskId: number) { function showChain(userId: number, taskId: number, username: string) {
chainUserId.value = userId chainUserId.value = userId
chainTaskId.value = taskId chainTaskId.value = taskId
chainUsername.value = username
chainModal.value = true chainModal.value = true
} }
@@ -219,7 +222,7 @@ const columns: DataTableColumn<SubmissionOut>[] = [
loading: expandedLoading.has(row.id), loading: expandedLoading.has(row.id),
onSelect: (id) => getSubmissionByID(id), onSelect: (id) => getSubmissionByID(id),
onDelete: (r, parentId) => handleDelete(r, parentId), onDelete: (r, parentId) => handleDelete(r, parentId),
"onShow-chain": (userId, taskId) => showChain(userId, taskId), "onShow-chain": (userId, taskId, username) => showChain(userId, taskId, username),
}), }),
}, },
{ {

View File

@@ -3,6 +3,7 @@ import type { TASK_TYPE } from "./const"
export interface PromptMessage { export interface PromptMessage {
id: number id: number
role: string role: string
source?: string
content: string content: string
code_html: string | null code_html: string | null
code_css: string | null code_css: string | null