feat: 重新设计“帮我写清楚”弹框
- 标题从“调教提示词”改成“帮我把要求写清楚”,开头说明怎么用 - 学生每次发的标成“你的要求 · 第 N 版”,AI 的回复标成“AI 的建议” - 输入框明确是“你的要求”;按钮改成“让 AI 再看看”和“用这段生成网页”, 没改过的要求不会重复发;AI 回复时不能直接生成 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
51ee246a6f
commit
1ba31844a9
1 file changed
+109
-81
@@ -2,83 +2,75 @@
|
|||||||
<n-modal
|
<n-modal
|
||||||
:show="isOpen"
|
:show="isOpen"
|
||||||
preset="card"
|
preset="card"
|
||||||
title="调教提示词"
|
title="帮我把要求写清楚"
|
||||||
style="width: min(560px, 92vw)"
|
style="width: min(640px, 92vw)"
|
||||||
:mask-closable="!streaming"
|
:mask-closable="!streaming"
|
||||||
@update:show="onModalClose"
|
@update:show="onModalClose"
|
||||||
>
|
>
|
||||||
<div class="guidance-body">
|
<p class="intro">
|
||||||
<div class="guidance-messages" ref="messagesRef">
|
AI 会看看你的要求哪里还不够清楚,给你提建议。照着建议改好下面的要求,再让它看看;
|
||||||
<div
|
觉得可以了就点“用这段生成网页”。
|
||||||
v-for="(msg, i) in messages"
|
</p>
|
||||||
:key="msg.id ?? i"
|
|
||||||
class="guidance-msg"
|
<div ref="messagesRef" class="messages">
|
||||||
:class="msg.role"
|
<div v-for="(msg, i) in messages" :key="msg.id ?? i" :class="['msg', msg.role]">
|
||||||
>
|
<div class="who">
|
||||||
<div class="msg-role">{{ msg.role === "user" ? "你" : "AI" }}</div>
|
{{ msg.role === "user" ? `你的要求 · 第 ${versionOf(i)} 版` : "AI 的建议" }}
|
||||||
<div class="msg-content" v-html="renderMarkdown(msg.content)"></div>
|
</div>
|
||||||
|
<div class="content markdown-body" v-html="renderMarkdown(msg.content)"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="streaming" class="guidance-msg assistant">
|
<div v-if="streaming" class="msg assistant">
|
||||||
<div class="msg-role">AI</div>
|
<div class="who">AI 的建议</div>
|
||||||
<div v-if="!displayStreamingContent" class="typing-indicator">
|
<div v-if="!displayStreamingContent" class="typing">
|
||||||
<span></span><span></span><span></span>
|
<span></span><span></span><span></span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="msg-content"
|
class="content markdown-body"
|
||||||
v-html="renderMarkdown(displayStreamingContent)"
|
v-html="renderMarkdown(displayStreamingContent)"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="isReady" class="ready-hint">
|
<div v-if="isReady" class="ready">
|
||||||
<Icon icon="lucide:check-circle" :width="16" />
|
<Icon icon="lucide:check-circle" :width="16" />
|
||||||
提示词已优化,可以生成代码了!
|
AI 觉得要求已经写清楚了,可以生成网页了
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="guidance-input">
|
<div class="draft">
|
||||||
|
<div class="draft-label">你的要求</div>
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="draftPrompt"
|
v-model:value="draftPrompt"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
:autosize="{ minRows: 2, maxRows: 5 }"
|
:autosize="{ minRows: 3, maxRows: 6 }"
|
||||||
placeholder="修改你的提示词后发送..."
|
placeholder="按 AI 的建议把要求改得更具体:要什么内容、什么颜色和布局、点了以后会怎样……"
|
||||||
: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="space-between" align="center" class="draft-actions">
|
||||||
<div>
|
<n-flex align="center" :size="8">
|
||||||
<n-button
|
<n-button v-if="streaming" size="small" @click="stopGuidance">停止</n-button>
|
||||||
v-if="streaming"
|
|
||||||
type="error"
|
|
||||||
size="small"
|
|
||||||
@click="stopGuidance"
|
|
||||||
>
|
|
||||||
停止
|
|
||||||
</n-button>
|
|
||||||
<n-button
|
<n-button
|
||||||
v-else
|
v-else
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="!draftPrompt.trim() || !connected"
|
:disabled="!draftPrompt.trim() || !connected || draftPrompt.trim() === lastSent"
|
||||||
@click="send"
|
@click="send"
|
||||||
>
|
>
|
||||||
发送
|
让 AI 再看看
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
<span class="muted">Enter 发送,Shift + Enter 换行</span>
|
||||||
|
</n-flex>
|
||||||
<n-button
|
<n-button
|
||||||
:type="isReady ? 'primary' : 'default'"
|
:type="isReady ? 'primary' : 'default'"
|
||||||
size="small"
|
:disabled="!draftPrompt.trim() || streaming"
|
||||||
:disabled="!draftPrompt.trim()"
|
|
||||||
@click="generate"
|
@click="generate"
|
||||||
>
|
>
|
||||||
<template #icon>
|
<template #icon><Icon icon="lucide:play" :width="14" /></template>
|
||||||
<Icon icon="lucide:play" :width="14" />
|
用这段生成网页
|
||||||
</template>
|
|
||||||
生成代码
|
|
||||||
</n-button>
|
</n-button>
|
||||||
</n-flex>
|
</n-flex>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</n-modal>
|
</n-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -106,13 +98,23 @@ const emit = defineEmits<{
|
|||||||
const draftPrompt = ref("")
|
const draftPrompt = ref("")
|
||||||
const messagesRef = ref<HTMLElement>()
|
const messagesRef = ref<HTMLElement>()
|
||||||
|
|
||||||
|
// 第几版要求:只数学生发的
|
||||||
|
function versionOf(index: number) {
|
||||||
|
return messages.value.slice(0, index + 1).filter((m) => m.role === "user").length
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没改过就不用再发一遍
|
||||||
|
const lastSent = computed(
|
||||||
|
() => [...messages.value].reverse().find((m) => m.role === "user")?.content.trim() ?? "",
|
||||||
|
)
|
||||||
|
|
||||||
const displayStreamingContent = computed(() =>
|
const displayStreamingContent = computed(() =>
|
||||||
streamingContent.value.replace(/^\[READY\]\n?/, "")
|
streamingContent.value.replace(/^\[READY\]\n?/, "")
|
||||||
)
|
)
|
||||||
|
|
||||||
function send() {
|
function send() {
|
||||||
const text = draftPrompt.value.trim()
|
const text = draftPrompt.value.trim()
|
||||||
if (!text || streaming.value) return
|
if (!text || streaming.value || text === lastSent.value) return
|
||||||
sendGuidance(text)
|
sendGuidance(text)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,87 +143,113 @@ watch([() => messages.value.length, streamingContent], () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.guidance-body {
|
.intro {
|
||||||
display: flex;
|
margin: 0 0 12px;
|
||||||
flex-direction: column;
|
font-size: 13px;
|
||||||
gap: 12px;
|
line-height: 1.7;
|
||||||
|
color: var(--wp-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.guidance-messages {
|
.messages {
|
||||||
min-height: 160px;
|
min-height: 120px;
|
||||||
max-height: 360px;
|
max-height: 45vh;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
padding-right: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.guidance-msg .msg-role {
|
.who {
|
||||||
font-size: 12px;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
color: var(--wp-muted);
|
color: var(--wp-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.guidance-msg.user .msg-role {
|
.msg.assistant .who {
|
||||||
color: var(--wp-css);
|
|
||||||
}
|
|
||||||
|
|
||||||
.guidance-msg.assistant .msg-role {
|
|
||||||
color: var(--wp-brand);
|
color: var(--wp-brand);
|
||||||
}
|
}
|
||||||
|
|
||||||
.guidance-msg.assistant .msg-content {
|
.content {
|
||||||
background: var(--wp-css-soft);
|
|
||||||
border-left: 3px solid var(--wp-css);
|
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
border-radius: 0 6px 6px 0;
|
border-radius: 8px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.7;
|
||||||
}
|
|
||||||
|
|
||||||
.guidance-msg.user .msg-content {
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.6;
|
|
||||||
color: var(--wp-ink);
|
color: var(--wp-ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
.msg-content :deep(p) {
|
.msg.user .content {
|
||||||
margin: 0;
|
border: 1px solid var(--wp-line);
|
||||||
|
background: var(--wp-paper);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ready-hint {
|
.msg.assistant .content {
|
||||||
|
border-left: 3px solid var(--wp-brand);
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
background: var(--wp-brand-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content :deep(p) {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content :deep(:last-child) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ready {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
color: var(--wp-brand);
|
margin-top: 12px;
|
||||||
font-size: 13px;
|
|
||||||
font-weight: 500;
|
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background: var(--wp-brand-soft);
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid var(--wp-brand-soft);
|
font-size: 13px;
|
||||||
|
color: var(--wp-brand);
|
||||||
|
background: var(--wp-brand-soft);
|
||||||
}
|
}
|
||||||
|
|
||||||
.typing-indicator {
|
.draft {
|
||||||
|
margin-top: 14px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid var(--wp-line);
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-label {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.draft-actions {
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.muted {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--wp-faint);
|
||||||
|
}
|
||||||
|
|
||||||
|
.typing {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
padding: 8px 0;
|
padding: 10px 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typing-indicator span {
|
.typing span {
|
||||||
width: 7px;
|
width: 7px;
|
||||||
height: 7px;
|
height: 7px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: var(--wp-css);
|
background: var(--wp-brand);
|
||||||
animation: bounce 1.4s ease-in-out infinite;
|
animation: bounce 1.4s ease-in-out infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typing-indicator span:nth-child(2) {
|
.typing span:nth-child(2) {
|
||||||
animation-delay: 0.2s;
|
animation-delay: 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.typing-indicator span:nth-child(3) {
|
.typing span:nth-child(3) {
|
||||||
animation-delay: 0.4s;
|
animation-delay: 0.4s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user