fix
This commit is contained in:
15
src/api.ts
15
src/api.ts
@@ -157,14 +157,14 @@ export const Submission = {
|
|||||||
html?: string
|
html?: string
|
||||||
css?: string
|
css?: string
|
||||||
js?: string
|
js?: string
|
||||||
conversationId?: string
|
prompt?: string
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
const { conversationId, ...rest } = code
|
const { prompt, ...rest } = code
|
||||||
const data = {
|
const data = {
|
||||||
task_id: taskId,
|
task_id: taskId,
|
||||||
...rest,
|
...rest,
|
||||||
conversation_id: conversationId || null,
|
prompt: prompt || null,
|
||||||
}
|
}
|
||||||
const res = await http.post("/submission/", data)
|
const res = await http.post("/submission/", data)
|
||||||
return res.data
|
return res.data
|
||||||
@@ -250,6 +250,15 @@ export const Prompt = {
|
|||||||
)
|
)
|
||||||
).data
|
).data
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getMessagesByUserTask(
|
||||||
|
taskId: number,
|
||||||
|
userId: number,
|
||||||
|
): Promise<PromptMessage[]> {
|
||||||
|
const convs = await this.listConversations(taskId, userId)
|
||||||
|
if (!convs.length) return []
|
||||||
|
return this.getMessages(convs[0].id)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Helper = {
|
export const Helper = {
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ async function submit() {
|
|||||||
html: splitResult.value.html,
|
html: splitResult.value.html,
|
||||||
css: splitResult.value.css,
|
css: splitResult.value.css,
|
||||||
js: splitResult.value.js,
|
js: splitResult.value.js,
|
||||||
|
prompt: promptText.value.trim() || undefined,
|
||||||
})
|
})
|
||||||
message.success("提交成功")
|
message.success("提交成功")
|
||||||
promptText.value = ""
|
promptText.value = ""
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="prompt-panel">
|
<div class="prompt-panel">
|
||||||
<div class="messages" ref="messagesRef">
|
<div class="messages" ref="messagesRef">
|
||||||
<div v-if="historyLoading" class="history-loading">
|
|
||||||
<n-spin size="small" />
|
|
||||||
<span>加载历史记录…</span>
|
|
||||||
</div>
|
|
||||||
<div v-for="(msg, i) in messages" :key="i" :class="['message', msg.role]">
|
<div v-for="(msg, i) in messages" :key="i" :class="['message', msg.role]">
|
||||||
<div class="message-role">{{ msg.role === "user" ? "我" : "AI" }}</div>
|
<div class="message-role">{{ msg.role === "user" ? "我" : "AI" }}</div>
|
||||||
<div class="message-content" v-html="renderContent(msg)"></div>
|
<div class="message-content" v-html="renderContent(msg)"></div>
|
||||||
@@ -31,15 +27,7 @@
|
|||||||
:disabled="streaming"
|
:disabled="streaming"
|
||||||
@keydown.enter.exact.prevent="send"
|
@keydown.enter.exact.prevent="send"
|
||||||
/>
|
/>
|
||||||
<n-flex justify="space-between" align="center" style="margin-top: 8px">
|
<n-flex justify="flex-end" align="center" style="margin-top: 8px">
|
||||||
<n-button
|
|
||||||
text
|
|
||||||
size="small"
|
|
||||||
@click="newConversation"
|
|
||||||
:disabled="streaming"
|
|
||||||
>
|
|
||||||
新对话
|
|
||||||
</n-button>
|
|
||||||
<n-button
|
<n-button
|
||||||
type="primary"
|
type="primary"
|
||||||
:loading="streaming"
|
:loading="streaming"
|
||||||
@@ -61,8 +49,6 @@ import {
|
|||||||
streaming,
|
streaming,
|
||||||
streamingContent,
|
streamingContent,
|
||||||
sendPrompt,
|
sendPrompt,
|
||||||
newConversation,
|
|
||||||
historyLoading,
|
|
||||||
} from "../store/prompt"
|
} from "../store/prompt"
|
||||||
|
|
||||||
const input = ref("")
|
const input = ref("")
|
||||||
@@ -285,13 +271,4 @@ watch([() => messages.value.length, streamingContent], () => {
|
|||||||
border-top: 1px solid #e0e0e0;
|
border-top: 1px solid #e0e0e0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-loading {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 12px;
|
|
||||||
color: #aaa;
|
|
||||||
font-size: 13px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -129,7 +129,8 @@ import type { PromptMessage } from "../../utils/type"
|
|||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
show: boolean
|
show: boolean
|
||||||
conversationId?: string
|
userId: number
|
||||||
|
taskId: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
defineEmits<{ "update:show": [value: boolean] }>()
|
defineEmits<{ "update:show": [value: boolean] }>()
|
||||||
@@ -188,37 +189,22 @@ const selectedPageHtml = computed(() => {
|
|||||||
return `<!DOCTYPE html><html><head><meta charset="utf-8">${style}</head><body>${round.html}${script}</body></html>`
|
return `<!DOCTYPE html><html><head><meta charset="utf-8">${style}</head><body>${round.html}${script}</body></html>`
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(
|
async function loadMessages() {
|
||||||
() => props.conversationId,
|
if (!props.userId || !props.taskId) return
|
||||||
async (id) => {
|
loading.value = true
|
||||||
if (!id || !props.show) return
|
messages.value = []
|
||||||
loading.value = true
|
selectedRound.value = 0
|
||||||
messages.value = []
|
try {
|
||||||
selectedRound.value = 0
|
messages.value = await Prompt.getMessagesByUserTask(props.taskId, props.userId)
|
||||||
try {
|
const last = rounds.value.length - 1
|
||||||
messages.value = await Prompt.getMessages(id)
|
if (last >= 0) selectedRound.value = last
|
||||||
const last = rounds.value.length - 1
|
} finally {
|
||||||
if (last >= 0) selectedRound.value = last
|
loading.value = false
|
||||||
} finally {
|
}
|
||||||
loading.value = false
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.show,
|
() => [props.show, props.userId, props.taskId] as const,
|
||||||
async (visible) => {
|
([visible]) => { if (visible) loadMessages() },
|
||||||
if (!visible || !props.conversationId) return
|
|
||||||
loading.value = true
|
|
||||||
messages.value = []
|
|
||||||
selectedRound.value = 0
|
|
||||||
try {
|
|
||||||
messages.value = await Prompt.getMessages(props.conversationId)
|
|
||||||
const last = rounds.value.length - 1
|
|
||||||
if (last >= 0) selectedRound.value = last
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -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": [conversationId: string]
|
"show-chain": [userId: number, taskId: number]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const isChallenge = computed(() => props.row.task_type === TASK_TYPE.Challenge)
|
const isChallenge = computed(() => props.row.task_type === TASK_TYPE.Challenge)
|
||||||
@@ -80,23 +80,21 @@ const subColumns = computed((): DataTableColumn<SubmissionOut>[] => [
|
|||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
title: "提示词",
|
title: "提示词",
|
||||||
key: "conversation_id",
|
key: "prompt",
|
||||||
width: 70,
|
width: 70,
|
||||||
render: (r: SubmissionOut) => {
|
render: (r: SubmissionOut) =>
|
||||||
if (!r.conversation_id) return "-"
|
h(
|
||||||
return h(
|
|
||||||
NButton,
|
NButton,
|
||||||
{
|
{
|
||||||
text: true,
|
text: true,
|
||||||
type: "primary",
|
type: "primary",
|
||||||
onClick: (e: Event) => {
|
onClick: (e: Event) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
emit("show-chain", r.conversation_id!)
|
emit("show-chain", r.userid, r.task_id)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
() => "查看",
|
() => "查看",
|
||||||
)
|
),
|
||||||
},
|
|
||||||
} as DataTableColumn<SubmissionOut>,
|
} as DataTableColumn<SubmissionOut>,
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
|
|||||||
@@ -82,10 +82,8 @@ import { authed, roleSuper } from "../store/user"
|
|||||||
import {
|
import {
|
||||||
connectPrompt,
|
connectPrompt,
|
||||||
disconnectPrompt,
|
disconnectPrompt,
|
||||||
conversationId,
|
|
||||||
streaming,
|
streaming,
|
||||||
setOnCodeComplete,
|
setOnCodeComplete,
|
||||||
loadHistory,
|
|
||||||
} from "../store/prompt"
|
} from "../store/prompt"
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -106,16 +104,13 @@ async function loadChallenge() {
|
|||||||
taskId.value = data.task_ptr
|
taskId.value = data.task_ptr
|
||||||
challengeContent.value = await marked.parse(data.content, { async: true })
|
challengeContent.value = await marked.parse(data.content, { async: true })
|
||||||
if (!authed.value) return
|
if (!authed.value) return
|
||||||
loadHistory(data.task_ptr) // HTTP preload — async, non-blocking
|
connectPrompt(data.task_ptr)
|
||||||
connectPrompt(data.task_ptr) // WebSocket — synchronous open
|
|
||||||
setOnCodeComplete(async (code) => {
|
setOnCodeComplete(async (code) => {
|
||||||
if (!conversationId.value) return
|
|
||||||
try {
|
try {
|
||||||
await Submission.create(taskId.value, {
|
await Submission.create(taskId.value, {
|
||||||
html: code.html ?? "",
|
html: code.html ?? "",
|
||||||
css: code.css ?? "",
|
css: code.css ?? "",
|
||||||
js: code.js ?? "",
|
js: code.js ?? "",
|
||||||
conversationId: conversationId.value,
|
|
||||||
})
|
})
|
||||||
message.success("已自动提交本次对话生成的代码")
|
message.success("已自动提交本次对话生成的代码")
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -95,7 +95,8 @@
|
|||||||
|
|
||||||
<ChainModal
|
<ChainModal
|
||||||
v-model:show="chainModal"
|
v-model:show="chainModal"
|
||||||
:conversation-id="chainConversationId"
|
:user-id="chainUserId"
|
||||||
|
:task-id="chainTaskId"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -147,7 +148,8 @@ const js = computed(() => submission.value.js)
|
|||||||
// Modal 状态
|
// Modal 状态
|
||||||
const codeModal = ref(false)
|
const codeModal = ref(false)
|
||||||
const chainModal = ref(false)
|
const chainModal = ref(false)
|
||||||
const chainConversationId = ref<string | undefined>()
|
const chainUserId = ref<number>(0)
|
||||||
|
const chainTaskId = ref<number>(0)
|
||||||
|
|
||||||
// 展开行
|
// 展开行
|
||||||
const expandedKeys = ref<string[]>([])
|
const expandedKeys = ref<string[]>([])
|
||||||
@@ -189,8 +191,9 @@ async function clearAllFlags() {
|
|||||||
query.flag = null
|
query.flag = null
|
||||||
}
|
}
|
||||||
|
|
||||||
function showChain(conversationId: string) {
|
function showChain(userId: number, taskId: number) {
|
||||||
chainConversationId.value = conversationId
|
chainUserId.value = userId
|
||||||
|
chainTaskId.value = taskId
|
||||||
chainModal.value = true
|
chainModal.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,7 +209,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": (id) => showChain(id),
|
"onShow-chain": (userId, taskId) => showChain(userId, taskId),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { WS_BASE_URL } from "../utils/const"
|
import { WS_BASE_URL } from "../utils/const"
|
||||||
import { html, css, js } from "./editors"
|
import { html, css, js } from "./editors"
|
||||||
import { Prompt } from "../api"
|
|
||||||
import type { PromptMessage as RawMessage } from "../utils/type"
|
|
||||||
import { user } from "./user"
|
|
||||||
|
|
||||||
export interface PromptMessage {
|
export interface PromptMessage {
|
||||||
role: "user" | "assistant"
|
role: "user" | "assistant"
|
||||||
@@ -16,8 +13,6 @@ export const messages = ref<PromptMessage[]>([])
|
|||||||
export const conversationId = ref<string>("")
|
export const conversationId = ref<string>("")
|
||||||
export const connected = ref(false)
|
export const connected = ref(false)
|
||||||
export const streaming = ref(false)
|
export const streaming = ref(false)
|
||||||
export const historyLoading = ref(false)
|
|
||||||
let _historyLoadId = 0
|
|
||||||
export const streamingContent = ref("")
|
export const streamingContent = ref("")
|
||||||
let _onCodeComplete:
|
let _onCodeComplete:
|
||||||
| ((code: {
|
| ((code: {
|
||||||
@@ -96,8 +91,6 @@ export function connectPrompt(taskId: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function disconnectPrompt() {
|
export function disconnectPrompt() {
|
||||||
_historyLoadId++ // cancel any in-flight loadHistory
|
|
||||||
historyLoading.value = false // reset here; finally block won't (loadId mismatch)
|
|
||||||
if (ws) {
|
if (ws) {
|
||||||
ws.close()
|
ws.close()
|
||||||
ws = null
|
ws = null
|
||||||
@@ -110,68 +103,12 @@ export function disconnectPrompt() {
|
|||||||
_onCodeComplete = null
|
_onCodeComplete = null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadHistory(taskId: number) {
|
|
||||||
const loadId = ++_historyLoadId
|
|
||||||
historyLoading.value = true
|
|
||||||
try {
|
|
||||||
const convs = await Prompt.listConversations(taskId)
|
|
||||||
console.log(
|
|
||||||
"[loadHistory] convs:",
|
|
||||||
convs.map((c: any) => ({
|
|
||||||
id: c.id,
|
|
||||||
is_active: c.is_active,
|
|
||||||
message_count: c.message_count,
|
|
||||||
username: c.username,
|
|
||||||
})),
|
|
||||||
"user.username:",
|
|
||||||
user.username,
|
|
||||||
)
|
|
||||||
if (loadId !== _historyLoadId) return // navigated away, abort
|
|
||||||
const active = convs.find(
|
|
||||||
(c: { is_active: boolean; message_count: number; username: string }) =>
|
|
||||||
c.is_active && c.message_count > 0 && c.username === user.username,
|
|
||||||
)
|
|
||||||
console.log("[loadHistory] active:", active)
|
|
||||||
if (!active) return
|
|
||||||
const raw: RawMessage[] = await Prompt.getMessages(active.id)
|
|
||||||
console.log("[loadHistory] raw messages:", raw.length)
|
|
||||||
if (loadId !== _historyLoadId) return // navigated away, abort
|
|
||||||
// Only apply if nothing has arrived via WebSocket yet
|
|
||||||
if (messages.value.length > 0) return
|
|
||||||
conversationId.value = active.id
|
|
||||||
messages.value = raw.map((m) => ({
|
|
||||||
role: m.role as "user" | "assistant",
|
|
||||||
content: m.content,
|
|
||||||
code:
|
|
||||||
m.role === "assistant"
|
|
||||||
? { html: m.code_html, css: m.code_css, js: m.code_js }
|
|
||||||
: undefined,
|
|
||||||
created: m.created,
|
|
||||||
}))
|
|
||||||
// Apply code from last assistant message to editors
|
|
||||||
const lastAssistant = [...messages.value]
|
|
||||||
.reverse()
|
|
||||||
.find((m) => m.role === "assistant" && m.code)
|
|
||||||
if (lastAssistant?.code) {
|
|
||||||
applyCode(lastAssistant.code)
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// 静默失败,不影响 WebSocket 正常流程
|
|
||||||
} finally {
|
|
||||||
if (loadId === _historyLoadId) historyLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function sendPrompt(content: string) {
|
export function sendPrompt(content: string) {
|
||||||
if (!ws || ws.readyState !== WebSocket.OPEN) return
|
if (!ws || ws.readyState !== WebSocket.OPEN) return
|
||||||
messages.value.push({ role: "user", content })
|
messages.value.push({ role: "user", content })
|
||||||
ws.send(JSON.stringify({ type: "message", content }))
|
ws.send(JSON.stringify({ type: "message", content }))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function newConversation() {
|
|
||||||
if (!ws || ws.readyState !== WebSocket.OPEN) return
|
|
||||||
ws.send(JSON.stringify({ type: "new_conversation" }))
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyCode(code: {
|
function applyCode(code: {
|
||||||
html: string | null
|
html: string | null
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ export interface SubmissionOut {
|
|||||||
task_title: string
|
task_title: string
|
||||||
score: number
|
score: number
|
||||||
my_score: number
|
my_score: number
|
||||||
conversation_id?: string
|
|
||||||
flag?: FlagType
|
flag?: FlagType
|
||||||
zone?: "featured" | "low" | "pending" | null
|
zone?: "featured" | "low" | "pending" | null
|
||||||
submit_count: number
|
submit_count: number
|
||||||
|
|||||||
Reference in New Issue
Block a user