feat: add ExternalAIPanel component for external AI code submission

This commit is contained in:
2026-03-29 09:25:45 -06:00
parent 83537face1
commit 5f95b88914

View File

@@ -0,0 +1,135 @@
<template>
<div class="external-panel">
<div class="content-area">
<div class="field-label">提示词</div>
<n-input
v-model:value="promptText"
type="textarea"
:autosize="{ minRows: 3, maxRows: 8 }"
placeholder="粘贴你发给外部 AI 的提示词..."
/>
<div class="field-label" style="margin-top: 12px">AI 代码</div>
<n-input
v-model:value="rawCode"
type="textarea"
:autosize="{ minRows: 6, maxRows: 16 }"
placeholder="粘贴外部 AI 返回的完整 HTML 代码..."
/>
<div v-if="splitResult" class="split-result">
<n-tag size="small" type="success">HTML · {{ splitResult.html.length }} 字符</n-tag>
<n-tag size="small" type="info">CSS · {{ splitResult.css.length }} 字符</n-tag>
<n-tag size="small" type="warning">JS · {{ splitResult.js.length }} 字符</n-tag>
</div>
</div>
<div class="action-bar">
<n-button :disabled="!rawCode.trim()" @click="applyPreview">应用预览</n-button>
<n-button type="primary" :disabled="!splitResult" :loading="submitting" @click="submit">
提交
</n-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
import { useMessage } from "naive-ui"
import { html, css, js } from "../store/editors"
import { Submission } from "../api"
const props = defineProps<{ taskId: number }>()
const message = useMessage()
const promptText = ref("")
const rawCode = ref("")
const splitResult = ref<{ html: string; css: string; js: string } | null>(null)
const submitting = ref(false)
function splitHtml(raw: string): { html: string; css: string; js: string } {
let result = raw
const cssBlocks: string[] = []
const jsBlocks: string[] = []
result = result.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (_, content) => {
cssBlocks.push(content.trim())
return ""
})
result = result.replace(
/<script(?![^>]*\bsrc\b)[^>]*>([\s\S]*?)<\/script>/gi,
(_, content) => {
jsBlocks.push(content.trim())
return ""
},
)
return {
html: result.trim(),
css: cssBlocks.join("\n\n"),
js: jsBlocks.join("\n\n"),
}
}
function applyPreview() {
const result = splitHtml(rawCode.value)
splitResult.value = result
html.value = result.html
css.value = result.css
js.value = result.js
}
async function submit() {
if (!splitResult.value) return
submitting.value = true
try {
await Submission.create(props.taskId, {
html: splitResult.value.html,
css: splitResult.value.css,
js: splitResult.value.js,
})
message.success("提交成功")
promptText.value = ""
rawCode.value = ""
splitResult.value = null
} catch {
// 静默失败,保留表单内容以便重试
} finally {
submitting.value = false
}
}
</script>
<style scoped>
.external-panel {
display: flex;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.content-area {
flex: 1;
min-height: 0;
overflow-y: auto;
padding: 12px;
}
.field-label {
font-size: 12px;
font-weight: bold;
color: #666;
margin-bottom: 6px;
}
.split-result {
display: flex;
gap: 8px;
margin-top: 12px;
flex-wrap: wrap;
}
.action-bar {
padding: 12px;
border-top: 1px solid #e0e0e0;
display: flex;
justify-content: space-between;
}
</style>