feat(docs): add NavGrid/NavCard components and restructure stage pages
- Add NavGrid.vue and NavCard.vue components for better navigation layout - Restructure stage-0 index pages across languages into intro.md with new navigation components - Remove old stage-0 index.md files and update stage-3 pages similarly - Add new dependencies 'claude' and 'codex' to package.json - Improve code formatting in multiple Vue components for better readability - Update documentation content and structure for better user experience
This commit is contained in:
+496
-206
@@ -1,57 +1,151 @@
|
||||
<!--
|
||||
PromptQuickStartDemo.vue
|
||||
提示词“先玩后讲”快速体验:同一任务,切换提示词写法,看输出质量变化。
|
||||
|
||||
交互:
|
||||
- 选择任务(写文案/总结/写代码)
|
||||
- 选择提示词等级(随口一句 / 清晰版 / 专业版)
|
||||
- 展示“你写的提示词”和“AI 输出”,并提示改进点
|
||||
-->
|
||||
<template>
|
||||
<div class="quick">
|
||||
<div class="header">
|
||||
<div>
|
||||
<div class="title">先玩一下:同一个需求,换一种说法</div>
|
||||
<div class="subtitle">你改的不是“字数”,而是“边界”和“标准”。</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<select v-model="taskId">
|
||||
<option v-for="t in tasks" :key="t.id" :value="t.id">
|
||||
{{ t.label }}
|
||||
</option>
|
||||
</select>
|
||||
<div class="levels">
|
||||
<button
|
||||
v-for="l in levels"
|
||||
:key="l.id"
|
||||
:class="['level', { active: levelId === l.id }]"
|
||||
@click="levelId = l.id"
|
||||
<div class="quick-start-demo-container">
|
||||
<el-card class="quick-start-card" shadow="hover">
|
||||
<template #header>
|
||||
<div class="header-content">
|
||||
<div class="title-group">
|
||||
<div class="title">🕹️ 互动体验:提示词进化论</div>
|
||||
<div class="subtitle">不要一次性写好,试着像搭积木一样优化你的指令。</div>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<span class="label">选择任务:</span>
|
||||
<el-select v-model="taskId" @change="reset" style="width: 160px" size="large">
|
||||
<el-option
|
||||
v-for="t in tasks"
|
||||
:key="t.id"
|
||||
:label="t.label"
|
||||
:value="t.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 游戏区 -->
|
||||
<div class="game-area">
|
||||
<!-- 左侧:提示词构建 -->
|
||||
<div class="prompt-builder">
|
||||
<div class="section-title">你的指令 (Prompt)</div>
|
||||
|
||||
<div class="prompt-box">
|
||||
<!-- 基础层 -->
|
||||
<div class="block base" :class="{ active: true }">
|
||||
<span class="icon">📝</span>
|
||||
<span class="text">{{ basePrompt }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 进阶层:清晰指令 -->
|
||||
<div v-if="level >= 1" class="block clear animate-in">
|
||||
<span class="icon">🎯</span>
|
||||
<span class="text">{{ clearPromptAddon }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 专家层:结构化 -->
|
||||
<div v-if="level >= 2" class="block pro animate-in">
|
||||
<span class="icon">🧠</span>
|
||||
<span class="text">{{ proPromptAddon }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 升级按钮 -->
|
||||
<div class="upgrade-controls">
|
||||
<div class="level-info">
|
||||
<el-tag :type="levelColor" effect="dark" size="small" style="margin-bottom: 4px;">Level {{ level }}</el-tag>
|
||||
<span class="level-desc" :style="{ color: levelColorCode }">{{ levelLabel }}</span>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<el-button-group>
|
||||
<el-button
|
||||
:disabled="level === 0"
|
||||
@click="downgrade"
|
||||
icon="Minus"
|
||||
>
|
||||
➖ 降级
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:disabled="level === 2"
|
||||
@click="upgrade"
|
||||
icon="Plus"
|
||||
>
|
||||
升级 ➕
|
||||
</el-button>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="isRunning"
|
||||
@click="run"
|
||||
style="width: 100%; font-weight: bold; font-size: 1.1rem;"
|
||||
>
|
||||
{{ l.label }}
|
||||
</button>
|
||||
{{ isRunning ? '生成中...' : '🚀 发送给 AI' }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:AI 模拟输出 -->
|
||||
<div class="chat-preview">
|
||||
<div class="section-title">
|
||||
<span>AI 回复 (Output)</span>
|
||||
<!-- 历史记录切换 -->
|
||||
<div class="history-tabs" v-if="hasAnyHistory">
|
||||
<el-radio-group v-model="viewLevel" size="small">
|
||||
<el-radio-button
|
||||
v-for="l in availableLevels"
|
||||
:key="l"
|
||||
:label="l"
|
||||
>
|
||||
L{{ l }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chat-window">
|
||||
<!-- 空状态 -->
|
||||
<div v-if="!hasRun && !hasAnyHistory" class="empty-state">
|
||||
<el-empty description="点击左侧“发送”按钮,看看 AI 会怎么回。" :image-size="100" />
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div v-else>
|
||||
<!-- 比较模式提示 -->
|
||||
<el-alert
|
||||
v-if="viewLevel !== level"
|
||||
type="info"
|
||||
show-icon
|
||||
:closable="false"
|
||||
style="margin-bottom: 12px;"
|
||||
>
|
||||
<template #title>
|
||||
正在查看 Level {{ viewLevel }} 的历史记录 (当前是 L{{ level }})
|
||||
<el-button link type="primary" @click="viewLevel = level" style="padding: 0; vertical-align: baseline;">回到当前</el-button>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<div class="message-bubble" :class="{ typing: isRunning && viewLevel === level }">
|
||||
<div class="avatar">🤖</div>
|
||||
<div class="content">
|
||||
<div v-if="isRunning && viewLevel === level" class="typing-indicator">
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<div v-else class="markdown-body" v-html="renderMarkdown(getOutputForLevel(viewLevel))"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 点评气泡 -->
|
||||
<div v-if="(!isRunning || viewLevel !== level) && getOutputForLevel(viewLevel)" class="feedback-bubble animate-pop">
|
||||
<div class="feedback-title">💡 {{ getFeedbackForLevel(viewLevel).title }}</div>
|
||||
<div class="feedback-text">{{ getFeedbackForLevel(viewLevel).text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="panel">
|
||||
<div class="panel-title">提示词 / Prompt</div>
|
||||
<pre><code>{{ prompt }}</code></pre>
|
||||
<div class="hint">{{ promptHint }}</div>
|
||||
</div>
|
||||
<div class="panel">
|
||||
<div class="panel-title">AI 输出 / Output(示意)</div>
|
||||
<div class="output">{{ output }}</div>
|
||||
<div class="hint">{{ outputHint }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tips">
|
||||
<div class="tip" v-for="t in tips" :key="t.title">
|
||||
<div class="tip-title">{{ t.title }}</div>
|
||||
<div class="tip-body">{{ t.body }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -59,210 +153,406 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
const tasks = [
|
||||
{ id: 'copy', label: '写一段小红书文案' },
|
||||
{ id: 'summary', label: '把一段文字总结成要点' },
|
||||
{ id: 'code', label: '写一个小函数' }
|
||||
]
|
||||
|
||||
const levels = [
|
||||
{ id: 'vague', label: '随口一句' },
|
||||
{ id: 'clear', label: '清晰版' },
|
||||
{ id: 'pro', label: '专业版' }
|
||||
{ id: 'copy', label: '写小红书文案' },
|
||||
{ id: 'summary', label: '总结会议纪要' },
|
||||
{ id: 'code', label: '写代码函数' }
|
||||
]
|
||||
|
||||
const taskId = ref('copy')
|
||||
const levelId = ref('vague')
|
||||
const level = ref(0) // 0: vague, 1: clear, 2: pro
|
||||
const isRunning = ref(false)
|
||||
const hasRun = ref(false)
|
||||
const displayedOutput = ref('')
|
||||
|
||||
const prompt = computed(() => {
|
||||
if (taskId.value === 'copy') {
|
||||
if (levelId.value === 'vague') return '写个咖啡杯文案'
|
||||
if (levelId.value === 'clear')
|
||||
return '写一段小红书风格文案,主题:保温咖啡杯。语气:轻松。长度:120-160 字。'
|
||||
return `你是小红书资深种草博主。\n任务:写一段保温咖啡杯的种草文案。\n受众:通勤上班族。\n要求:\n- 120-160 字\n- 3 个卖点(颜值/密封/保温)\n- 结尾加一句行动号召\n输出:一段中文文案,不要标题。`
|
||||
}
|
||||
if (taskId.value === 'summary') {
|
||||
if (levelId.value === 'vague') return '帮我总结一下这段文字'
|
||||
if (levelId.value === 'clear')
|
||||
return '把下面内容总结成 3-5 个要点,每点不超过 15 个字。'
|
||||
return `任务:把输入文本总结成要点。\n要求:\n- 5 个以内\n- 每点 <= 15 字\n- 只输出要点列表,不要解释\n格式:Markdown 无序列表`
|
||||
}
|
||||
// code
|
||||
if (levelId.value === 'vague') return '写个排序函数'
|
||||
if (levelId.value === 'clear')
|
||||
return '用 JavaScript 写一个快速排序函数,并给一个使用示例。'
|
||||
return `你是资深前端工程师。\n任务:实现 quickSort(arr)。\n要求:\n- 纯函数(不修改原数组)\n- 处理重复值\n- 代码加简短注释\n- 给一个示例输入输出\n输出:只给 JS 代码块`
|
||||
})
|
||||
// 存储历史输出:{ 0: "...", 1: "..." }
|
||||
const outputs = ref({})
|
||||
const viewLevel = ref(0) // 当前查阅的 Level
|
||||
|
||||
const output = computed(() => {
|
||||
if (taskId.value === 'copy') {
|
||||
if (levelId.value === 'vague')
|
||||
return '这是一款很好用的咖啡杯,适合日常使用...'
|
||||
if (levelId.value === 'clear')
|
||||
return '早八通勤救星!这只保温杯颜值在线,放包里不漏,热咖啡到下午还温温的...'
|
||||
return '通勤党必备!奶油配色超耐看,密封圈一拧就稳,放包里也不怕洒;保温够久,早上冲的拿铁下午还是温热...想要链接评论区见~'
|
||||
}
|
||||
if (taskId.value === 'summary') {
|
||||
if (levelId.value === 'vague') return '这段文字主要讲了……(可能很长)'
|
||||
if (levelId.value === 'clear')
|
||||
return '- 核心观点 1\n- 核心观点 2\n- 核心观点 3'
|
||||
return '- 关键结论\n- 主要原因\n- 影响范围\n- 建议行动'
|
||||
}
|
||||
// code
|
||||
if (levelId.value === 'vague') return 'function sort(arr) { /* ... */ }'
|
||||
if (levelId.value === 'clear')
|
||||
return 'function quickSort(arr) { /* ... */ }\nconsole.log(quickSort([3,1,2]))'
|
||||
return `function quickSort(arr) {\n const a = [...arr]\n if (a.length <= 1) return a\n const pivot = a[0]\n const left = a.slice(1).filter(x => x < pivot)\n const right = a.slice(1).filter(x => x >= pivot)\n return [...quickSort(left), pivot, ...quickSort(right)]\n}\n\nconsole.log(quickSort([3, 1, 2, 2])) // [1,2,2,3]`
|
||||
})
|
||||
const hasAnyHistory = computed(() => Object.keys(outputs.value).length > 0)
|
||||
const availableLevels = computed(() => Object.keys(outputs.value).map(Number).sort())
|
||||
|
||||
const promptHint = computed(() => {
|
||||
if (levelId.value === 'vague') return '问题:AI 不知道你要什么标准。'
|
||||
if (levelId.value === 'clear')
|
||||
return '好一点:有风格/长度,但仍缺少“检查标准”。'
|
||||
return '最好:角色 + 任务 + 要求 + 输出格式,AI 很难跑偏。'
|
||||
})
|
||||
const reset = () => {
|
||||
level.value = 0
|
||||
hasRun.value = false
|
||||
displayedOutput.value = ''
|
||||
outputs.value = {}
|
||||
viewLevel.value = 0
|
||||
}
|
||||
|
||||
const outputHint = computed(() => {
|
||||
if (levelId.value === 'vague')
|
||||
return '常见结果:泛泛而谈、风格不稳、格式不对。'
|
||||
if (levelId.value === 'clear')
|
||||
return '常见结果:更像你要的,但细节/格式可能还会飘。'
|
||||
return '常见结果:风格稳定、结构清晰、可直接复制使用。'
|
||||
})
|
||||
const upgrade = () => {
|
||||
if (level.value < 2) level.value++
|
||||
hasRun.value = false
|
||||
viewLevel.value = level.value // 切换到新等级时,视角跟随
|
||||
}
|
||||
|
||||
const tips = computed(() => {
|
||||
if (levelId.value === 'vague') {
|
||||
return [
|
||||
{ title: '先补 3 件事', body: '你要做什么?给谁看?最后要什么格式?' },
|
||||
{ title: '别怕写长', body: '长不是目的,“可检查”才是目的。' }
|
||||
]
|
||||
const downgrade = () => {
|
||||
if (level.value > 0) level.value--
|
||||
hasRun.value = false
|
||||
viewLevel.value = level.value
|
||||
}
|
||||
|
||||
const levelLabel = computed(() => ['随口一说', '清晰指令', '结构化 Prompt'][level.value])
|
||||
const levelColor = computed(() => ['info', 'warning', 'success'][level.value])
|
||||
const levelColorCode = computed(() => ['#909399', '#e6a23c', '#67c23a'][level.value])
|
||||
|
||||
// Prompt 内容配置
|
||||
const promptConfig = {
|
||||
copy: {
|
||||
base: '写个咖啡杯文案',
|
||||
clear: '+ 风格:小红书,轻松活泼。长度:100字左右。卖点:颜值高、保温好。',
|
||||
pro: '+ 角色:资深种草博主\n+ 结构:痛点 -> 卖点 -> 场景 -> 结尾互动\n+ 格式:多用 Emoji,分段清晰'
|
||||
},
|
||||
summary: {
|
||||
base: '帮我总结一下这段文字',
|
||||
clear: '+ 要求:提炼 3 个核心要点,每点不超过 20 字。',
|
||||
pro: '+ 角色:专业秘书\n+ 格式:Markdown 无序列表\n+ 排除:不要客套话,只要干货'
|
||||
},
|
||||
code: {
|
||||
base: '写个排序函数',
|
||||
clear: '+ 语言:JavaScript (ES6)。要求:快速排序,带注释。',
|
||||
pro: '+ 角色:资深前端架构师\n+ 健壮性:处理边界情况(空数组、非数组)\n+ 示例:附带一个测试用例'
|
||||
}
|
||||
if (levelId.value === 'clear') {
|
||||
return [
|
||||
{ title: '再加一条', body: '加“输出格式”或“要点数量”,能明显更稳。' },
|
||||
{ title: '再加一个例子', body: '给 1 个示例,AI 会更像你的口吻。' }
|
||||
]
|
||||
}
|
||||
return [
|
||||
{ title: '记住模板', body: '角色 / 任务 / 输入 / 要求 / 输出格式。' },
|
||||
{ title: '写完就测', body: '同一输入跑 2-3 次,看是否稳定。' }
|
||||
}
|
||||
|
||||
const basePrompt = computed(() => promptConfig[taskId.value].base)
|
||||
const clearPromptAddon = computed(() => promptConfig[taskId.value].clear)
|
||||
const proPromptAddon = computed(() => promptConfig[taskId.value].pro)
|
||||
|
||||
// 模拟输出内容
|
||||
const outputConfig = {
|
||||
copy: [
|
||||
'这个咖啡杯真的很好用,推荐给大家。它颜色很好看,而且保温效果也不错。快去买吧。',
|
||||
'✨ 早八人必备!这个保温杯颜值真的绝绝子!💖 拿在手里超有质感,而且保温效果超级好,早上装的咖啡下午还是热的!☕️ 放在包里也不漏水,集美们冲鸭!',
|
||||
'👋 还在为冷咖啡烦恼?\n\n😫 **痛点**:早起冲的咖啡,还没到公司就凉了?\n\n🌟 **安利**:这款“拿铁杯”必须拥有!\n1️⃣ **颜值主义**:奶油白配色,随手一拍就是大片 📸\n2️⃣ **硬核保温**:实测 6 小时依然烫嘴 🔥\n3️⃣ **办公绝配**:密封圈设计,随便塞包里不漏洒 🎒\n\n👇 评论区告诉我,你最喜欢哪个颜色?'
|
||||
],
|
||||
summary: [
|
||||
'这段文字主要讲了关于...(此处省略500字流水账)...总之就是这些内容。',
|
||||
'- 核心观点:用户增长放缓\n- 主要原因:市场竞争加剧\n- 建议:加大投放力度',
|
||||
'### 📝 会议核心摘要\n\n* **📉 现状**:Q3 用户增长率下降 15%\n* **🔍 原因**:竞品推出低价策略,分流明显\n* **🚀 行动**:下周启动“老用户回馈”专项活动'
|
||||
],
|
||||
code: [
|
||||
'function sort(arr) { return arr.sort() } // 没写快排,或者写了但没注释',
|
||||
'// 快速排序\nconst quickSort = (arr) => {\n if (arr.length <= 1) return arr;\n const p = arr[0];\n const left = arr.slice(1).filter(x => x < p);\n const right = arr.slice(1).filter(x => x >= p);\n return [...quickSort(left), p, ...quickSort(right)];\n}',
|
||||
'/**\n * 快速排序 (ES6+)\n * @param {Array} arr - 输入数组\n * @returns {Array} - 排序后的新数组\n */\nconst quickSort = (arr) => {\n // 🛡️ 边界检查\n if (!Array.isArray(arr)) throw new Error("Input must be an array");\n if (arr.length <= 1) return arr;\n\n const pivot = arr[0];\n const left = [];\n const right = [];\n\n // 分区\n for (let i = 1; i < arr.length; i++) {\n arr[i] < pivot ? left.push(arr[i]) : right.push(arr[i]);\n }\n\n return [...quickSort(left), pivot, ...quickSort(right)];\n};\n\n// ✅ 测试用例\nconsole.log(quickSort([3, 1, 4, 1, 5, 9])); // [1, 1, 3, 4, 5, 9]'
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
const feedbackConfig = {
|
||||
copy: [
|
||||
{ title: '太泛了', text: 'AI 不知道你要什么风格,只能给你“说明书”式的文案。' },
|
||||
{ title: '好多了', text: '有了风格和卖点,AI 知道怎么“说话”了,但结构还不够抓人。' },
|
||||
{ title: '专业级', text: '指定了角色和结构(痛点-卖点),输出逻辑清晰,转化率更高。' }
|
||||
],
|
||||
summary: [
|
||||
{ title: '抓不住重点', text: '没有字数和格式限制,AI 可能会罗嗦一大堆。' },
|
||||
{ title: '清晰明了', text: '限制了字数和要点数量,可读性大幅提升。' },
|
||||
{ title: '结构化交付', text: '指定 Markdown 格式和角色,直接可用,无需二次编辑。' }
|
||||
],
|
||||
code: [
|
||||
{ title: '不可用', text: '可能偷懒用内置函数,或者缺少注释,难以维护。' },
|
||||
{ title: '可用', text: '代码正确,有基本注释,但缺乏健壮性考虑。' },
|
||||
{ title: '生产级', text: '考虑了边界情况和类型检查,直接复制就能进项目。' }
|
||||
]
|
||||
}
|
||||
|
||||
const getFeedbackForLevel = (l) => feedbackConfig[taskId.value][l]
|
||||
|
||||
// 获取某等级的输出(如果是当前等级正在运行,显示实时打字内容;否则显示历史记录)
|
||||
const getOutputForLevel = (l) => {
|
||||
if (l === level.value && isRunning.value) return displayedOutput.value
|
||||
return outputs.value[l] || ''
|
||||
}
|
||||
|
||||
const renderMarkdown = (text) => {
|
||||
if (!text) return ''
|
||||
|
||||
// 1. HTML Escape (Basic)
|
||||
let html = text
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
|
||||
// 2. Bold: **text** -> <strong>text</strong>
|
||||
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
const run = () => {
|
||||
if (isRunning.value) return
|
||||
// 直接显示结果,不进行模拟等待
|
||||
hasRun.value = true
|
||||
viewLevel.value = level.value // 强制看当前
|
||||
|
||||
const fullText = outputConfig[taskId.value][level.value]
|
||||
displayedOutput.value = fullText
|
||||
outputs.value[level.value] = fullText
|
||||
isRunning.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.quick {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
padding: 16px;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
.quick-start-demo-container {
|
||||
margin: 24px 0;
|
||||
}
|
||||
|
||||
.header {
|
||||
.quick-start-card {
|
||||
border-radius: 12px;
|
||||
overflow: visible; /* Allow selects to overflow if needed, though el-select uses popper */
|
||||
}
|
||||
|
||||
.header-content {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 800;
|
||||
margin-bottom: 4px;
|
||||
background: linear-gradient(120deg, var(--vp-c-brand) 30%, var(--vp-c-brand-dark));
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
select {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
background: var(--vp-c-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.levels {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.level {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg);
|
||||
padding: 8px 12px;
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.level.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
color: var(--vp-c-brand);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 12px;
|
||||
.label {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
.panel {
|
||||
|
||||
.game-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.game-area {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
/* 左侧构建区 */
|
||||
.prompt-builder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
color: var(--vp-c-text-2);
|
||||
letter-spacing: 0.5px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.prompt-box {
|
||||
background: var(--vp-c-bg-alt);
|
||||
border: 2px dashed var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
min-height: 140px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
padding: 10px;
|
||||
border-radius: 8px;
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
.panel-title {
|
||||
font-weight: 700;
|
||||
margin-bottom: 6px;
|
||||
|
||||
.block.base {
|
||||
border-left: 3px solid var(--vp-c-text-2);
|
||||
}
|
||||
pre {
|
||||
margin: 0;
|
||||
background: #0b1221;
|
||||
color: #e5e7eb;
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 13px;
|
||||
overflow-x: auto;
|
||||
|
||||
.block.clear {
|
||||
background: rgba(var(--vp-c-brand-rgb), 0.05);
|
||||
border: 1px solid rgba(var(--vp-c-brand-rgb), 0.2);
|
||||
border-left: 3px solid var(--vp-c-brand);
|
||||
}
|
||||
|
||||
.block.pro {
|
||||
background: rgba(100, 108, 255, 0.05); /* Indigo-ish */
|
||||
border: 1px solid rgba(100, 108, 255, 0.2);
|
||||
border-left: 3px solid #646cff;
|
||||
}
|
||||
|
||||
.block .icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.block .text {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
.output {
|
||||
color: var(--vp-c-text-1);
|
||||
|
||||
.animate-in {
|
||||
animation: slideIn 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.upgrade-controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: var(--vp-c-bg-alt);
|
||||
padding: 12px 16px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.level-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.level-desc {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* 右侧预览区 */
|
||||
.chat-preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.chat-window {
|
||||
background: var(--vp-c-bg-alt);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
min-height: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.message-bubble {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: linear-gradient(135deg, #3b82f6, #8b5cf6);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
background: var(--vp-c-bg);
|
||||
padding: 12px 16px;
|
||||
border-radius: 0 12px 12px 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
white-space: pre-wrap;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.hint {
|
||||
margin-top: 6px;
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 13px;
|
||||
|
||||
.message-bubble.typing .content {
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 10px;
|
||||
.typing-indicator span {
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: var(--vp-c-text-2);
|
||||
border-radius: 50%;
|
||||
margin: 0 2px;
|
||||
animation: bounce 1.4s infinite ease-in-out both;
|
||||
}
|
||||
.tip {
|
||||
background: var(--vp-c-bg);
|
||||
border: 1px dashed var(--vp-c-divider);
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
|
||||
.typing-indicator span:nth-child(1) { animation-delay: -0.32s; }
|
||||
.typing-indicator span:nth-child(2) { animation-delay: -0.16s; }
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 80%, 100% { transform: scale(0); }
|
||||
40% { transform: scale(1); }
|
||||
}
|
||||
.tip-title {
|
||||
|
||||
.feedback-bubble {
|
||||
background: rgba(var(--vp-c-yellow-rgb), 0.1);
|
||||
border: 1px solid rgba(var(--vp-c-yellow-rgb), 0.3);
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.feedback-title {
|
||||
font-weight: 700;
|
||||
color: var(--vp-c-yellow-1);
|
||||
margin-bottom: 4px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.tip-body {
|
||||
color: var(--vp-c-text-2);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
|
||||
.feedback-text {
|
||||
font-size: 0.9rem;
|
||||
color: var(--vp-c-text-1);
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
|
||||
.animate-pop {
|
||||
animation: popIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
|
||||
@keyframes popIn {
|
||||
from { opacity: 0; transform: scale(0.9) translateY(10px); }
|
||||
to { opacity: 1; transform: scale(1) translateY(0); }
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user