feat(appendix): 添加多个交互式演示组件,完善 AI/Infra 等章节内容
- 新增 Vibe Coding 全栈相关演示组件 (DeveloperSkillShift, FrontendTriad, BackendCore 等) - 新增 RAG 相关组件 (RAGPipeline, ChunkingStrategy, Retrieval 等) - 新增 Embedding & Vector 相关组件 (EmbeddingConcept, VectorSimilarity 等) - 新增 AI Native App 设计组件 (AINativeArch, PromptDesign 等) - 新增 Infrastructure as Code 组件 (IaCConcept, TerraformWorkflow 等) - 新增 DNS & HTTPS 演示组件 (DnsResolution, HttpsHandshake 等) - 新增 Model Finetuning 组件 (FinetuningPipeline 等) - 更新多个章节的 markdown 内容,集成交互式演示
This commit is contained in:
@@ -0,0 +1,245 @@
|
||||
<template>
|
||||
<div class="flow-demo">
|
||||
<div class="header">
|
||||
<div class="title">AI 应用请求处理流程</div>
|
||||
<div class="subtitle">点击"发送请求",观察一次 AI 请求的完整生命周期</div>
|
||||
</div>
|
||||
|
||||
<div class="pipeline">
|
||||
<div
|
||||
v-for="(step, idx) in steps"
|
||||
:key="step.id"
|
||||
:class="['pipe-step', {
|
||||
active: currentStep === idx,
|
||||
done: currentStep > idx
|
||||
}]"
|
||||
>
|
||||
<div class="step-icon">{{ currentStep > idx ? '✅' : step.icon }}</div>
|
||||
<div class="step-info">
|
||||
<div class="step-name">{{ step.name }}</div>
|
||||
<div class="step-en">{{ step.en }}</div>
|
||||
</div>
|
||||
<div v-if="idx < steps.length - 1" class="arrow">→</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-bar">
|
||||
<button
|
||||
v-if="!isRunning && currentStep < 0"
|
||||
class="action-btn"
|
||||
@click="startFlow"
|
||||
>
|
||||
▶ 发送请求
|
||||
</button>
|
||||
<button
|
||||
v-else-if="!isRunning && currentStep >= steps.length"
|
||||
class="action-btn reset"
|
||||
@click="resetFlow"
|
||||
>
|
||||
🔄 重置
|
||||
</button>
|
||||
<div v-else-if="isRunning" class="running-hint">
|
||||
⏳ 处理中...
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="currentStep >= 0" class="detail-area">
|
||||
<div class="detail-card">
|
||||
<div class="detail-title">
|
||||
{{ activeStep.icon }} {{ activeStep.name }}
|
||||
</div>
|
||||
<div class="detail-desc">{{ activeStep.detail }}</div>
|
||||
|
||||
<div class="io-section">
|
||||
<div class="io-block">
|
||||
<div class="io-label">输入</div>
|
||||
<pre class="io-code"><code>{{ activeStep.input }}</code></pre>
|
||||
</div>
|
||||
<div class="io-block">
|
||||
<div class="io-label">输出</div>
|
||||
<pre class="io-code"><code>{{ activeStep.output }}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="latency-bar">
|
||||
<span class="latency-label">耗时</span>
|
||||
<div class="latency-track">
|
||||
<div
|
||||
class="latency-fill"
|
||||
:style="{ width: activeStep.latencyPct + '%' }"
|
||||
/>
|
||||
</div>
|
||||
<span class="latency-val">{{ activeStep.latency }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="insight-bar">
|
||||
<span class="insight-label">💡 关键洞察:</span>
|
||||
<span class="insight-text">
|
||||
AI 应用的请求链路比传统应用更长,模型推理通常占总耗时的 60-80%。
|
||||
优化重点在于:Prompt 缓存、流式输出、异步处理。
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const steps = [
|
||||
{
|
||||
id: 'input', icon: '👤', name: '用户输入', en: 'User Input',
|
||||
detail: '用户通过自然语言输入请求。系统需要处理多种输入形式:文本、语音转文字、图片描述等。与传统应用的表单提交不同,输入是开放式的、非结构化的。',
|
||||
input: '"帮我总结这篇文章的核心观点"',
|
||||
output: '{ text: "帮我总结...", type: "text", lang: "zh" }',
|
||||
latency: '~0ms', latencyPct: 2
|
||||
},
|
||||
{
|
||||
id: 'preprocess', icon: '🔧', name: '预处理', en: 'Preprocessing',
|
||||
detail: '对用户输入进行清洗和增强:意图识别、关键词提取、上下文拼接、RAG 检索相关文档片段、构建完整的 Prompt。这一步决定了模型能获得多少有效信息。',
|
||||
input: '{ text: "帮我总结...", context: [...历史对话] }',
|
||||
output: '{ system_prompt: "你是...", user_prompt: "...", retrieved_docs: [...] }',
|
||||
latency: '~200ms', latencyPct: 15
|
||||
},
|
||||
{
|
||||
id: 'model', icon: '🧠', name: '模型推理', en: 'Model Inference',
|
||||
detail: '将构建好的 Prompt 发送给大语言模型进行推理。这是整个链路中耗时最长的环节。模型会根据 Prompt 中的指令、上下文和检索到的知识,生成回答。',
|
||||
input: '{ messages: [...], model: "gpt-4", temperature: 0.7 }',
|
||||
output: '{ content: "这篇文章的核心观点有三个...", tokens: 256 }',
|
||||
latency: '~2-8s', latencyPct: 75
|
||||
},
|
||||
{
|
||||
id: 'postprocess', icon: '🛡️', name: '后处理', en: 'Post-processing',
|
||||
detail: '对模型输出进行安全检查和格式化:内容审核过滤、幻觉检测、格式转换(Markdown 渲染)、引用来源标注、敏感信息脱敏等。',
|
||||
input: '{ raw_output: "这篇文章的核心观点有三个..." }',
|
||||
output: '{ safe: true, formatted: "## 核心观点\\n1. ...", sources: [...] }',
|
||||
latency: '~100ms', latencyPct: 8
|
||||
},
|
||||
{
|
||||
id: 'response', icon: '💬', name: '响应输出', en: 'Response',
|
||||
detail: '将处理后的结果以流式方式返回给用户。前端逐步渲染 Markdown 内容,同时展示引用来源和置信度。用户可以在生成过程中随时中断或追问。',
|
||||
input: '{ formatted: "## 核心观点\\n1. ...", stream: true }',
|
||||
output: '用户看到逐字出现的回答 + 来源引用',
|
||||
latency: '~50ms (首字节)', latencyPct: 5
|
||||
}
|
||||
]
|
||||
|
||||
const currentStep = ref(-1)
|
||||
const isRunning = ref(false)
|
||||
|
||||
const activeStep = computed(() => {
|
||||
const idx = Math.min(currentStep.value, steps.length - 1)
|
||||
return idx >= 0 ? steps[idx] : steps[0]
|
||||
})
|
||||
|
||||
const startFlow = async () => {
|
||||
isRunning.value = true
|
||||
for (let i = 0; i < steps.length; i++) {
|
||||
currentStep.value = i
|
||||
await new Promise(r => setTimeout(r, 1200))
|
||||
}
|
||||
currentStep.value = steps.length
|
||||
isRunning.value = false
|
||||
}
|
||||
|
||||
const resetFlow = () => {
|
||||
currentStep.value = -1
|
||||
isRunning.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.flow-demo {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px; padding: 20px; margin: 20px 0;
|
||||
}
|
||||
.header { text-align: center; margin-bottom: 16px; }
|
||||
.title {
|
||||
font-size: 17px; font-weight: 700;
|
||||
background: linear-gradient(120deg, #10b981, #3b82f6);
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
}
|
||||
.subtitle { font-size: 12px; color: var(--vp-c-text-2); margin-top: 4px; }
|
||||
|
||||
.pipeline {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
gap: 4px; flex-wrap: wrap; margin-bottom: 16px;
|
||||
}
|
||||
.pipe-step {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 8px 12px; border-radius: 8px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg); transition: all 0.3s;
|
||||
font-size: 12px;
|
||||
}
|
||||
.pipe-step.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
}
|
||||
.pipe-step.done {
|
||||
border-color: #86efac; background: #f0fdf4;
|
||||
}
|
||||
.step-icon { font-size: 18px; }
|
||||
.step-name { font-weight: 600; font-size: 12px; }
|
||||
.step-en { font-size: 10px; color: var(--vp-c-text-3); }
|
||||
.arrow { color: var(--vp-c-text-3); font-size: 14px; margin: 0 2px; }
|
||||
|
||||
.control-bar { text-align: center; margin-bottom: 16px; }
|
||||
.action-btn {
|
||||
padding: 10px 28px; background: var(--vp-c-brand);
|
||||
color: white; border: none; border-radius: 8px;
|
||||
font-size: 13px; cursor: pointer; transition: background 0.2s;
|
||||
}
|
||||
.action-btn:hover { background: var(--vp-c-brand-dark); }
|
||||
.action-btn.reset { background: #6b7280; }
|
||||
.action-btn.reset:hover { background: #4b5563; }
|
||||
.running-hint { color: var(--vp-c-brand); font-size: 13px; }
|
||||
|
||||
.detail-area { margin-bottom: 16px; }
|
||||
.detail-card {
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px; padding: 16px;
|
||||
}
|
||||
.detail-title { font-weight: 700; font-size: 15px; margin-bottom: 8px; }
|
||||
.detail-desc {
|
||||
color: var(--vp-c-text-2); font-size: 13px;
|
||||
line-height: 1.7; margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.io-section {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||
gap: 10px; margin-bottom: 12px;
|
||||
}
|
||||
.io-label { font-weight: 600; font-size: 11px; margin-bottom: 4px; color: var(--vp-c-text-2); }
|
||||
.io-code {
|
||||
margin: 0; background: #0b1221; color: #e5e7eb;
|
||||
border-radius: 8px; padding: 10px;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
font-size: 11px; overflow-x: auto; white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.latency-bar {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
}
|
||||
.latency-label { font-size: 11px; font-weight: 600; color: var(--vp-c-text-2); }
|
||||
.latency-track {
|
||||
flex: 1; height: 8px; background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px; overflow: hidden;
|
||||
}
|
||||
.latency-fill {
|
||||
height: 100%; border-radius: 4px;
|
||||
background: var(--vp-c-brand); transition: width 0.5s;
|
||||
}
|
||||
.latency-val { font-size: 11px; font-weight: 600; min-width: 80px; text-align: right; }
|
||||
|
||||
.insight-bar {
|
||||
padding: 12px 16px; background: var(--vp-c-brand-soft);
|
||||
border-radius: 6px; font-size: 13px;
|
||||
}
|
||||
.insight-label { font-weight: 600; color: var(--vp-c-brand-dark); }
|
||||
.insight-text { color: var(--vp-c-text-1); }
|
||||
</style>
|
||||
@@ -0,0 +1,222 @@
|
||||
<template>
|
||||
<div class="principle-demo">
|
||||
<div class="header">
|
||||
<div class="title">AI 原生设计原则</div>
|
||||
<div class="subtitle">点击卡片,深入了解每条设计原则</div>
|
||||
</div>
|
||||
|
||||
<div class="principle-grid">
|
||||
<div
|
||||
v-for="p in principles"
|
||||
:key="p.id"
|
||||
:class="['principle-card', { active: selected === p.id }]"
|
||||
@click="selected = p.id"
|
||||
>
|
||||
<div class="p-icon">{{ p.icon }}</div>
|
||||
<div class="p-name">{{ p.name }}</div>
|
||||
<div class="p-brief">{{ p.brief }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selected" class="detail-panel">
|
||||
<div class="detail-header">
|
||||
<span>{{ currentPrinciple.icon }} {{ currentPrinciple.name }}</span>
|
||||
</div>
|
||||
|
||||
<div class="detail-body">
|
||||
<div class="detail-desc">{{ currentPrinciple.detail }}</div>
|
||||
|
||||
<div class="example-section">
|
||||
<div class="example-title">实践对比</div>
|
||||
<div class="compare-grid">
|
||||
<div class="compare-bad">
|
||||
<div class="compare-label bad-label">❌ 反面示例</div>
|
||||
<div class="compare-text">{{ currentPrinciple.bad }}</div>
|
||||
</div>
|
||||
<div class="compare-good">
|
||||
<div class="compare-label good-label">✅ 正确做法</div>
|
||||
<div class="compare-text">{{ currentPrinciple.good }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="checklist">
|
||||
<div class="checklist-title">检查清单</div>
|
||||
<div
|
||||
v-for="(item, idx) in currentPrinciple.checklist"
|
||||
:key="idx"
|
||||
:class="['check-item', { checked: checkedItems[selected]?.[idx] }]"
|
||||
@click="toggleCheck(idx)"
|
||||
>
|
||||
<span class="check-box">
|
||||
{{ checkedItems[selected]?.[idx] ? '☑' : '☐' }}
|
||||
</span>
|
||||
<span>{{ item }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, reactive } from 'vue'
|
||||
|
||||
const principles = [
|
||||
{
|
||||
id: 'graceful',
|
||||
icon: '🛡️',
|
||||
name: '优雅降级',
|
||||
brief: 'AI 失败时,系统仍然可用',
|
||||
detail: 'AI 模型可能超时、返回错误、产生幻觉。优雅降级意味着:当 AI 不可用时,系统应该有兜底方案,而不是直接崩溃。这是 AI 原生应用与玩具项目的分水岭。',
|
||||
bad: '模型 API 超时后,页面显示空白错误页,用户只能刷新重试。',
|
||||
good: '模型超时后,显示缓存的上一次回答或推荐相关文档,同时后台自动重试。',
|
||||
checklist: [
|
||||
'设置合理的 API 超时时间(通常 30-60s)',
|
||||
'准备降级方案:缓存、规则引擎、人工转接',
|
||||
'向用户透明地展示当前状态',
|
||||
'记录失败日志用于后续优化'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'human',
|
||||
icon: '🤝',
|
||||
name: '人机协作',
|
||||
brief: '关键决策由人类确认',
|
||||
detail: 'AI 擅长生成和建议,但不应该在高风险场景中自主决策。人机协作(Human-in-the-Loop)模式让 AI 负责草稿和推荐,人类负责审核和确认。',
|
||||
bad: 'AI 自动发送邮件给客户,内容未经人工审核,导致错误信息传播。',
|
||||
good: 'AI 生成邮件草稿并高亮不确定的部分,用户审核修改后手动发送。',
|
||||
checklist: [
|
||||
'识别哪些操作是"高风险"的(发送、删除、支付)',
|
||||
'高风险操作前必须有人工确认步骤',
|
||||
'AI 输出标注置信度,低置信内容需人工复核',
|
||||
'提供便捷的编辑和修改界面'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'transparent',
|
||||
icon: '🔍',
|
||||
name: '透明可解释',
|
||||
brief: '让用户理解 AI 的推理过程',
|
||||
detail: 'AI 不是黑盒魔法。用户需要知道 AI 为什么给出这个回答、依据了哪些信息、有多大把握。透明性建立信任,也帮助用户判断何时该相信 AI、何时该质疑。',
|
||||
bad: 'AI 直接给出一个结论,没有任何解释或来源引用,用户无法判断可靠性。',
|
||||
good: '回答附带推理过程、引用来源链接、置信度指示,用户可以追溯验证。',
|
||||
checklist: [
|
||||
'展示 AI 的推理链路或思考过程',
|
||||
'标注信息来源和引用',
|
||||
'显示置信度或不确定性指标',
|
||||
'提供"为什么这样回答"的解释入口'
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'feedback',
|
||||
icon: '🔄',
|
||||
name: '反馈闭环',
|
||||
brief: '用户反馈驱动持续改进',
|
||||
detail: '每一次用户交互都是改进的机会。通过收集用户对 AI 输出的评价(点赞/点踩、修改记录、追问模式),持续优化 Prompt、微调模型、改进检索策略。',
|
||||
bad: 'AI 回答错误后,没有任何反馈渠道,同样的错误会反复出现。',
|
||||
good: '用户可以标记错误回答,系统自动收集并用于优化 Prompt 和检索策略。',
|
||||
checklist: [
|
||||
'提供简单的反馈机制(👍👎 按钮)',
|
||||
'记录用户的修改和追问作为隐式反馈',
|
||||
'定期分析反馈数据,优化 Prompt 模板',
|
||||
'建立 A/B 测试机制验证改进效果'
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
const selected = ref('graceful')
|
||||
const checkedItems = reactive({})
|
||||
|
||||
const currentPrinciple = computed(() =>
|
||||
principles.find(p => p.id === selected.value) || principles[0]
|
||||
)
|
||||
|
||||
const toggleCheck = (idx) => {
|
||||
if (!checkedItems[selected.value]) {
|
||||
checkedItems[selected.value] = {}
|
||||
}
|
||||
checkedItems[selected.value][idx] = !checkedItems[selected.value][idx]
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.principle-demo {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px; padding: 20px; margin: 20px 0;
|
||||
}
|
||||
.header { text-align: center; margin-bottom: 16px; }
|
||||
.title {
|
||||
font-size: 17px; font-weight: 700;
|
||||
background: linear-gradient(120deg, #ef4444, #f59e0b);
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
}
|
||||
.subtitle { font-size: 12px; color: var(--vp-c-text-2); margin-top: 4px; }
|
||||
|
||||
.principle-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
|
||||
gap: 10px; margin-bottom: 16px;
|
||||
}
|
||||
.principle-card {
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px; padding: 14px; cursor: pointer;
|
||||
transition: all 0.2s; text-align: center;
|
||||
}
|
||||
.principle-card:hover { background: var(--vp-c-bg-alt); }
|
||||
.principle-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
}
|
||||
.p-icon { font-size: 24px; margin-bottom: 6px; }
|
||||
.p-name { font-weight: 600; font-size: 13px; }
|
||||
.p-brief { font-size: 11px; color: var(--vp-c-text-2); margin-top: 4px; }
|
||||
|
||||
.detail-panel {
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px; overflow: hidden;
|
||||
}
|
||||
.detail-header {
|
||||
padding: 14px 16px; font-weight: 700; font-size: 15px;
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
.detail-body { padding: 16px; }
|
||||
.detail-desc {
|
||||
color: var(--vp-c-text-2); font-size: 13px;
|
||||
line-height: 1.7; margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.example-section { margin-bottom: 16px; }
|
||||
.example-title { font-weight: 600; font-size: 13px; margin-bottom: 8px; }
|
||||
.compare-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.compare-bad, .compare-good {
|
||||
padding: 12px; border-radius: 8px; font-size: 13px; line-height: 1.6;
|
||||
}
|
||||
.compare-bad { background: #fef2f2; border: 1px solid #fecaca; }
|
||||
.compare-good { background: #f0fdf4; border: 1px solid #bbf7d0; }
|
||||
.compare-label {
|
||||
font-weight: 600; font-size: 11px; margin-bottom: 6px;
|
||||
}
|
||||
.bad-label { color: #dc2626; }
|
||||
.good-label { color: #16a34a; }
|
||||
.compare-text { color: var(--vp-c-text-1); }
|
||||
|
||||
.checklist-title { font-weight: 600; font-size: 13px; margin-bottom: 8px; }
|
||||
.check-item {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 8px 10px; border-radius: 6px; font-size: 13px;
|
||||
cursor: pointer; transition: background 0.2s;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.check-item:hover { background: var(--vp-c-bg-soft); }
|
||||
.check-item.checked {
|
||||
background: #f0fdf4; border-color: #bbf7d0;
|
||||
text-decoration: line-through; color: var(--vp-c-text-3);
|
||||
}
|
||||
.check-box { font-size: 16px; flex-shrink: 0; }
|
||||
</style>
|
||||
@@ -0,0 +1,237 @@
|
||||
<template>
|
||||
<div class="arch-demo">
|
||||
<div class="header">
|
||||
<div class="title">传统应用 vs AI 原生应用</div>
|
||||
<div class="subtitle">切换视图,对比两种架构的核心差异</div>
|
||||
</div>
|
||||
|
||||
<div class="toggle-bar">
|
||||
<button
|
||||
:class="['toggle-btn', { active: mode === 'traditional' }]"
|
||||
@click="mode = 'traditional'"
|
||||
>
|
||||
<span>🏗️</span>
|
||||
<span>传统应用</span>
|
||||
</button>
|
||||
<button
|
||||
:class="['toggle-btn', { active: mode === 'ai-native' }]"
|
||||
@click="mode = 'ai-native'"
|
||||
>
|
||||
<span>🤖</span>
|
||||
<span>AI 原生应用</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="arch-grid">
|
||||
<div class="stack">
|
||||
<div class="stack-title">{{ currentArch.label }}</div>
|
||||
<div
|
||||
v-for="(layer, idx) in currentArch.layers"
|
||||
:key="idx"
|
||||
:class="['layer', { highlight: selectedLayer === idx }]"
|
||||
:style="{ borderLeftColor: layer.color }"
|
||||
@click="selectedLayer = idx"
|
||||
>
|
||||
<div class="layer-icon">{{ layer.icon }}</div>
|
||||
<div class="layer-info">
|
||||
<div class="layer-name">{{ layer.name }}</div>
|
||||
<div class="layer-desc">{{ layer.brief }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-panel">
|
||||
<div v-if="selectedLayer !== null" class="detail-content">
|
||||
<div class="detail-title">
|
||||
{{ currentArch.layers[selectedLayer].icon }}
|
||||
{{ currentArch.layers[selectedLayer].name }}
|
||||
</div>
|
||||
<div class="detail-desc">
|
||||
{{ currentArch.layers[selectedLayer].detail }}
|
||||
</div>
|
||||
<div class="detail-example">
|
||||
<div class="example-label">典型技术</div>
|
||||
<div class="tech-tags">
|
||||
<span
|
||||
v-for="t in currentArch.layers[selectedLayer].techs"
|
||||
:key="t"
|
||||
class="tech-tag"
|
||||
>{{ t }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="detail-placeholder">
|
||||
👆 点击左侧层级查看详情
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison-bar">
|
||||
<span class="compare-label">💡 核心区别:</span>
|
||||
<span class="compare-text">{{ mode === 'traditional'
|
||||
? '传统应用的逻辑由开发者用 if/else 硬编码,行为完全确定。'
|
||||
: 'AI 原生应用的核心逻辑由模型驱动,行为具有概率性,需要全新的设计思维。' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const mode = ref('traditional')
|
||||
const selectedLayer = ref(0)
|
||||
|
||||
const architectures = {
|
||||
traditional: {
|
||||
label: '传统应用架构',
|
||||
layers: [
|
||||
{
|
||||
icon: '🖥️', name: '前端 UI', color: '#3b82f6',
|
||||
brief: '用户界面与交互',
|
||||
detail: '基于确定性的表单、按钮、页面路由。用户操作触发固定的业务流程,所有交互路径在开发时已经确定。',
|
||||
techs: ['React', 'Vue', 'HTML/CSS']
|
||||
},
|
||||
{
|
||||
icon: '⚙️', name: '业务逻辑层', color: '#8b5cf6',
|
||||
brief: '硬编码的规则引擎',
|
||||
detail: '开发者用 if/else、switch/case 编写所有业务规则。每一条路径都需要人工预设,无法处理规则之外的情况。',
|
||||
techs: ['Node.js', 'Java', 'Python']
|
||||
},
|
||||
{
|
||||
icon: '🗄️', name: '数据存储', color: '#06b6d4',
|
||||
brief: '结构化数据管理',
|
||||
detail: '关系型数据库存储结构化数据,Schema 固定。数据的读写遵循严格的 CRUD 模式。',
|
||||
techs: ['MySQL', 'PostgreSQL', 'Redis']
|
||||
},
|
||||
{
|
||||
icon: '🔌', name: 'API 接口', color: '#10b981',
|
||||
brief: '固定的请求/响应',
|
||||
detail: '每个 API 端点返回确定性的结果。相同的输入永远产生相同的输出,行为完全可预测。',
|
||||
techs: ['REST', 'GraphQL', 'gRPC']
|
||||
}
|
||||
]
|
||||
},
|
||||
'ai-native': {
|
||||
label: 'AI 原生应用架构',
|
||||
layers: [
|
||||
{
|
||||
icon: '💬', name: '自然语言交互层', color: '#f59e0b',
|
||||
brief: '对话式 + 流式输出',
|
||||
detail: '用户通过自然语言表达意图,系统以流式方式逐步生成响应。交互不再是固定的表单,而是开放式的对话。',
|
||||
techs: ['Streaming UI', 'Markdown 渲染', 'SSE']
|
||||
},
|
||||
{
|
||||
icon: '🧠', name: '模型推理层', color: '#ef4444',
|
||||
brief: 'LLM 驱动的决策引擎',
|
||||
detail: '核心逻辑不再是 if/else,而是由大语言模型根据 Prompt 和上下文进行推理。输出具有概率性,同样的输入可能产生不同的结果。',
|
||||
techs: ['GPT-4', 'Claude', 'Prompt 工程']
|
||||
},
|
||||
{
|
||||
icon: '🔗', name: '编排与工具层', color: '#8b5cf6',
|
||||
brief: 'Agent 编排 + 工具调用',
|
||||
detail: '模型可以调用外部工具(搜索、数据库、API)来获取实时信息。编排层负责管理多步推理、工具选择和结果整合。',
|
||||
techs: ['LangChain', 'Function Calling', 'RAG']
|
||||
},
|
||||
{
|
||||
icon: '📦', name: '上下文管理层', color: '#06b6d4',
|
||||
brief: '向量数据库 + 记忆系统',
|
||||
detail: '使用向量数据库存储和检索非结构化知识。通过 Embedding 将文本转化为语义向量,实现基于含义的搜索而非关键词匹配。',
|
||||
techs: ['Pinecone', 'ChromaDB', 'Embedding']
|
||||
},
|
||||
{
|
||||
icon: '🛡️', name: '安全与护栏层', color: '#10b981',
|
||||
brief: '输出过滤 + 幻觉检测',
|
||||
detail: 'AI 输出不可完全信任,需要护栏机制:内容过滤、事实核查、幻觉检测、敏感信息脱敏等。这是传统应用不需要的全新层级。',
|
||||
techs: ['Guardrails', '内容审核', '事实校验']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const currentArch = computed(() => architectures[mode.value])
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.arch-demo {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.header { text-align: center; margin-bottom: 16px; }
|
||||
.title {
|
||||
font-size: 17px; font-weight: 700;
|
||||
background: linear-gradient(120deg, var(--vp-c-brand), #f59e0b);
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
}
|
||||
.subtitle { font-size: 12px; color: var(--vp-c-text-2); margin-top: 4px; }
|
||||
|
||||
.toggle-bar {
|
||||
display: flex; gap: 8px; justify-content: center; margin-bottom: 16px;
|
||||
}
|
||||
.toggle-btn {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 8px 18px; border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 20px; background: var(--vp-c-bg);
|
||||
cursor: pointer; transition: all 0.2s; font-size: 13px;
|
||||
}
|
||||
.toggle-btn:hover { background: var(--vp-c-bg-alt); }
|
||||
.toggle-btn.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
.arch-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.stack {
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px; padding: 12px;
|
||||
display: flex; flex-direction: column; gap: 8px;
|
||||
}
|
||||
.stack-title { font-weight: 700; font-size: 14px; margin-bottom: 4px; }
|
||||
|
||||
.layer {
|
||||
display: flex; align-items: center; gap: 10px;
|
||||
padding: 10px 12px; border-radius: 8px;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-left: 3px solid; background: var(--vp-c-bg);
|
||||
cursor: pointer; transition: all 0.2s;
|
||||
}
|
||||
.layer:hover { background: var(--vp-c-bg-alt); }
|
||||
.layer.highlight {
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
}
|
||||
.layer-icon { font-size: 20px; flex-shrink: 0; }
|
||||
.layer-name { font-weight: 600; font-size: 13px; }
|
||||
.layer-desc { font-size: 11px; color: var(--vp-c-text-2); margin-top: 2px; }
|
||||
|
||||
.detail-panel {
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px; padding: 16px;
|
||||
}
|
||||
.detail-title { font-weight: 700; font-size: 15px; margin-bottom: 10px; }
|
||||
.detail-desc { color: var(--vp-c-text-2); line-height: 1.7; font-size: 13px; margin-bottom: 12px; }
|
||||
.example-label { font-weight: 600; font-size: 12px; margin-bottom: 6px; }
|
||||
.tech-tags { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.tech-tag {
|
||||
padding: 3px 10px; border-radius: 12px; font-size: 11px;
|
||||
background: var(--vp-c-brand-soft); color: var(--vp-c-brand-dark);
|
||||
border: 1px solid var(--vp-c-brand-dimm);
|
||||
}
|
||||
.detail-placeholder {
|
||||
color: var(--vp-c-text-3); text-align: center; padding: 40px 0; font-size: 13px;
|
||||
}
|
||||
|
||||
.comparison-bar {
|
||||
margin-top: 16px; padding: 12px 16px;
|
||||
background: var(--vp-c-brand-soft); border-radius: 6px; font-size: 13px;
|
||||
}
|
||||
.compare-label { font-weight: 600; color: var(--vp-c-brand-dark); }
|
||||
.compare-text { color: var(--vp-c-text-1); }
|
||||
</style>
|
||||
@@ -0,0 +1,301 @@
|
||||
<template>
|
||||
<div class="ux-demo">
|
||||
<div class="header">
|
||||
<div class="title">AI 原生交互模式</div>
|
||||
<div class="subtitle">点击卡片,体验每种 AI 交互模式的效果</div>
|
||||
</div>
|
||||
|
||||
<div class="pattern-grid">
|
||||
<div
|
||||
v-for="p in patterns"
|
||||
:key="p.id"
|
||||
:class="['pattern-card', { active: activePattern === p.id }]"
|
||||
@click="activatePattern(p.id)"
|
||||
>
|
||||
<div class="card-icon">{{ p.icon }}</div>
|
||||
<div class="card-name">{{ p.name }}</div>
|
||||
<div class="card-desc">{{ p.brief }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="activePattern" class="preview-area">
|
||||
<div class="preview-header">
|
||||
<span>{{ currentPattern.icon }} {{ currentPattern.name }} 演示</span>
|
||||
<button class="replay-btn" @click="replayDemo">🔄 重播</button>
|
||||
</div>
|
||||
|
||||
<!-- 流式输出演示 -->
|
||||
<div v-if="activePattern === 'streaming'" class="demo-box">
|
||||
<div class="chat-bubble ai">
|
||||
<span class="stream-text">{{ streamText }}</span>
|
||||
<span v-if="isStreaming" class="cursor-blink">|</span>
|
||||
</div>
|
||||
<div class="demo-note">逐字输出,用户无需等待完整响应</div>
|
||||
</div>
|
||||
|
||||
<!-- 加载状态演示 -->
|
||||
<div v-if="activePattern === 'loading'" class="demo-box">
|
||||
<div class="loading-stages">
|
||||
<div
|
||||
v-for="(s, idx) in loadingStages"
|
||||
:key="idx"
|
||||
:class="['stage', { done: loadingStep > idx, current: loadingStep === idx }]"
|
||||
>
|
||||
<span class="stage-icon">
|
||||
{{ loadingStep > idx ? '✅' : loadingStep === idx ? '⏳' : '⬜' }}
|
||||
</span>
|
||||
<span>{{ s }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo-note">分阶段展示进度,而非单一的"加载中"</div>
|
||||
</div>
|
||||
|
||||
<!-- 置信度指示器演示 -->
|
||||
<div v-if="activePattern === 'confidence'" class="demo-box">
|
||||
<div class="confidence-list">
|
||||
<div v-for="c in confidenceItems" :key="c.text" class="conf-item">
|
||||
<div class="conf-bar-wrap">
|
||||
<div
|
||||
class="conf-bar"
|
||||
:style="{ width: c.score + '%', background: c.color }"
|
||||
/>
|
||||
</div>
|
||||
<div class="conf-score">{{ c.score }}%</div>
|
||||
<div class="conf-label">{{ c.level }}</div>
|
||||
<div class="conf-text">{{ c.text }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo-note">让用户知道 AI 对自己的回答有多"确定"</div>
|
||||
</div>
|
||||
|
||||
<!-- 降级处理演示 -->
|
||||
<div v-if="activePattern === 'fallback'" class="demo-box">
|
||||
<div class="fallback-flow">
|
||||
<div :class="['fb-step', { active: fallbackStep >= 0 }]">
|
||||
<span class="fb-icon">🤖</span>
|
||||
<span>AI 尝试回答...</span>
|
||||
</div>
|
||||
<div class="fb-arrow" v-if="fallbackStep >= 1">↓ 检测到不确定</div>
|
||||
<div :class="['fb-step warn', { active: fallbackStep >= 1 }]">
|
||||
<span class="fb-icon">⚠️</span>
|
||||
<span>提示用户:此回答可能不准确</span>
|
||||
</div>
|
||||
<div class="fb-arrow" v-if="fallbackStep >= 2">↓ 提供替代方案</div>
|
||||
<div :class="['fb-step safe', { active: fallbackStep >= 2 }]">
|
||||
<span class="fb-icon">🔄</span>
|
||||
<span>转接人工 / 推荐文档 / 换个方式提问</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="demo-note">AI 不确定时,优雅降级而非强行回答</div>
|
||||
</div>
|
||||
|
||||
<div class="pattern-detail">
|
||||
<div class="detail-label">设计要点</div>
|
||||
<div class="detail-text">{{ currentPattern.detail }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const patterns = [
|
||||
{
|
||||
id: 'streaming', icon: '💬', name: '流式输出',
|
||||
brief: '逐字生成,即时反馈',
|
||||
detail: '流式输出让用户在 AI 思考时就能看到部分结果,大幅降低感知等待时间。技术上通过 SSE(Server-Sent Events)或 WebSocket 实现,前端逐步渲染 Markdown 内容。'
|
||||
},
|
||||
{
|
||||
id: 'loading', icon: '⏳', name: '智能加载态',
|
||||
brief: '分阶段展示处理进度',
|
||||
detail: 'AI 请求通常需要数秒,传统的转圈加载会让用户焦虑。智能加载态将处理过程拆解为可见的步骤(理解问题 → 检索知识 → 生成回答),让等待变得可预期。'
|
||||
},
|
||||
{
|
||||
id: 'confidence', icon: '📊', name: '置信度指示',
|
||||
brief: '展示 AI 的确定程度',
|
||||
detail: 'AI 的输出具有概率性,不同回答的可靠程度不同。通过置信度指示器,用户可以判断哪些信息可以直接采纳,哪些需要二次验证。这是 AI 原生应用透明性的核心体现。'
|
||||
},
|
||||
{
|
||||
id: 'fallback', icon: '🛡️', name: '优雅降级',
|
||||
brief: '不确定时的兜底策略',
|
||||
detail: '当 AI 无法给出可靠回答时,不应该硬编一个答案。优雅降级策略包括:坦诚告知不确定性、提供替代信息源、转接人工服务、引导用户换个方式提问。'
|
||||
}
|
||||
]
|
||||
|
||||
const activePattern = ref('')
|
||||
const currentPattern = computed(() => patterns.find(p => p.id === activePattern.value) || {})
|
||||
|
||||
// Streaming demo
|
||||
const streamText = ref('')
|
||||
const isStreaming = ref(false)
|
||||
const fullText = 'React 是一个用于构建用户界面的 JavaScript 库。它采用组件化的开发模式,让你可以将复杂的 UI 拆分成独立的、可复用的小模块。'
|
||||
|
||||
// Loading demo
|
||||
const loadingStages = ['理解用户意图...', '检索相关知识...', '组织回答内容...', '生成最终响应']
|
||||
const loadingStep = ref(-1)
|
||||
|
||||
// Confidence demo
|
||||
const confidenceItems = [
|
||||
{ text: 'React 由 Meta 开发', score: 98, level: '高置信', color: '#10b981' },
|
||||
{ text: '全球约 40% 的网站使用 React', score: 72, level: '中置信', color: '#f59e0b' },
|
||||
{ text: 'React 19 将在下月发布', score: 35, level: '低置信', color: '#ef4444' }
|
||||
]
|
||||
|
||||
// Fallback demo
|
||||
const fallbackStep = ref(-1)
|
||||
|
||||
let timer = null
|
||||
|
||||
const clearTimers = () => {
|
||||
if (timer) { clearInterval(timer); timer = null }
|
||||
}
|
||||
|
||||
const activatePattern = (id) => {
|
||||
clearTimers()
|
||||
activePattern.value = id
|
||||
replayDemo()
|
||||
}
|
||||
|
||||
const replayDemo = () => {
|
||||
clearTimers()
|
||||
if (activePattern.value === 'streaming') {
|
||||
streamText.value = ''
|
||||
isStreaming.value = true
|
||||
let i = 0
|
||||
timer = setInterval(() => {
|
||||
if (i < fullText.length) {
|
||||
streamText.value += fullText[i]
|
||||
i++
|
||||
} else {
|
||||
isStreaming.value = false
|
||||
clearTimers()
|
||||
}
|
||||
}, 50)
|
||||
} else if (activePattern.value === 'loading') {
|
||||
loadingStep.value = 0
|
||||
let step = 0
|
||||
timer = setInterval(() => {
|
||||
step++
|
||||
loadingStep.value = step
|
||||
if (step >= loadingStages.length) clearTimers()
|
||||
}, 900)
|
||||
} else if (activePattern.value === 'fallback') {
|
||||
fallbackStep.value = 0
|
||||
let step = 0
|
||||
timer = setInterval(() => {
|
||||
step++
|
||||
fallbackStep.value = step
|
||||
if (step >= 2) clearTimers()
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ux-demo {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px; padding: 20px; margin: 20px 0;
|
||||
}
|
||||
.header { text-align: center; margin-bottom: 16px; }
|
||||
.title {
|
||||
font-size: 17px; font-weight: 700;
|
||||
background: linear-gradient(120deg, #06b6d4, #8b5cf6);
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
}
|
||||
.subtitle { font-size: 12px; color: var(--vp-c-text-2); margin-top: 4px; }
|
||||
|
||||
.pattern-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 10px; margin-bottom: 16px;
|
||||
}
|
||||
.pattern-card {
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px; padding: 14px; cursor: pointer;
|
||||
transition: all 0.2s; text-align: center;
|
||||
}
|
||||
.pattern-card:hover { background: var(--vp-c-bg-alt); }
|
||||
.pattern-card.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
}
|
||||
.card-icon { font-size: 24px; margin-bottom: 6px; }
|
||||
.card-name { font-weight: 600; font-size: 13px; }
|
||||
.card-desc { font-size: 11px; color: var(--vp-c-text-2); margin-top: 4px; }
|
||||
|
||||
.preview-area {
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px; padding: 16px;
|
||||
}
|
||||
.preview-header {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
font-weight: 700; font-size: 14px; margin-bottom: 12px;
|
||||
}
|
||||
.replay-btn {
|
||||
padding: 4px 12px; border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 6px; background: var(--vp-c-bg-soft);
|
||||
cursor: pointer; font-size: 12px;
|
||||
}
|
||||
|
||||
.demo-box {
|
||||
background: var(--vp-c-bg-soft); border-radius: 8px;
|
||||
padding: 16px; margin-bottom: 12px;
|
||||
}
|
||||
.demo-note {
|
||||
font-size: 11px; color: var(--vp-c-text-3);
|
||||
text-align: center; margin-top: 10px;
|
||||
}
|
||||
|
||||
/* Streaming */
|
||||
.chat-bubble.ai {
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 10px; padding: 12px; font-size: 13px; line-height: 1.7;
|
||||
}
|
||||
.cursor-blink { animation: blink 0.8s infinite; color: var(--vp-c-brand); }
|
||||
@keyframes blink { 50% { opacity: 0; } }
|
||||
|
||||
/* Loading */
|
||||
.loading-stages { display: flex; flex-direction: column; gap: 8px; }
|
||||
.stage {
|
||||
display: flex; align-items: center; gap: 8px;
|
||||
padding: 8px 12px; border-radius: 6px; font-size: 13px;
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
opacity: 0.4; transition: all 0.3s;
|
||||
}
|
||||
.stage.current { opacity: 1; border-color: var(--vp-c-brand); background: var(--vp-c-brand-soft); }
|
||||
.stage.done { opacity: 1; border-color: #86efac; background: #f0fdf4; }
|
||||
|
||||
/* Confidence */
|
||||
.confidence-list { display: flex; flex-direction: column; gap: 10px; }
|
||||
.conf-item {
|
||||
display: grid; grid-template-columns: 1fr 40px 60px 1fr;
|
||||
align-items: center; gap: 8px; font-size: 12px;
|
||||
}
|
||||
.conf-bar-wrap {
|
||||
height: 8px; background: var(--vp-c-bg);
|
||||
border-radius: 4px; overflow: hidden;
|
||||
}
|
||||
.conf-bar { height: 100%; border-radius: 4px; transition: width 0.6s; }
|
||||
.conf-score { font-weight: 600; text-align: right; }
|
||||
.conf-label { font-size: 11px; color: var(--vp-c-text-2); }
|
||||
.conf-text { color: var(--vp-c-text-1); }
|
||||
|
||||
/* Fallback */
|
||||
.fallback-flow { display: flex; flex-direction: column; align-items: center; gap: 6px; }
|
||||
.fb-step {
|
||||
display: flex; align-items: center; gap: 8px; width: 100%;
|
||||
padding: 10px 14px; border-radius: 8px; font-size: 13px;
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
opacity: 0.3; transition: all 0.4s;
|
||||
}
|
||||
.fb-step.active { opacity: 1; }
|
||||
.fb-step.warn.active { border-color: #fbbf24; background: #fef3c7; }
|
||||
.fb-step.safe.active { border-color: #86efac; background: #f0fdf4; }
|
||||
.fb-arrow { font-size: 12px; color: var(--vp-c-text-3); }
|
||||
|
||||
.pattern-detail { margin-top: 12px; }
|
||||
.detail-label { font-weight: 600; font-size: 12px; margin-bottom: 4px; }
|
||||
.detail-text { font-size: 13px; color: var(--vp-c-text-2); line-height: 1.7; }
|
||||
</style>
|
||||
@@ -0,0 +1,260 @@
|
||||
<template>
|
||||
<div class="prompt-demo">
|
||||
<div class="header">
|
||||
<div class="title">Prompt 工程实验室</div>
|
||||
<div class="subtitle">修改 Prompt 结构,观察输出质量的变化</div>
|
||||
</div>
|
||||
|
||||
<div class="template-tabs">
|
||||
<button
|
||||
v-for="t in templates"
|
||||
:key="t.id"
|
||||
:class="['tab-btn', { active: currentTemplate === t.id }]"
|
||||
@click="selectTemplate(t.id)"
|
||||
>
|
||||
<span>{{ t.icon }}</span>
|
||||
<span>{{ t.name }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="editor-grid">
|
||||
<div class="editor-panel">
|
||||
<div class="panel-label">System Prompt(系统指令)</div>
|
||||
<textarea
|
||||
v-model="systemPrompt"
|
||||
class="prompt-input"
|
||||
rows="3"
|
||||
placeholder="设定 AI 的角色和行为规则..."
|
||||
/>
|
||||
|
||||
<div class="panel-label">User Prompt(用户输入)</div>
|
||||
<textarea
|
||||
v-model="userPrompt"
|
||||
class="prompt-input"
|
||||
rows="3"
|
||||
placeholder="用户的具体问题或指令..."
|
||||
/>
|
||||
|
||||
<button class="run-btn" @click="runPrompt">
|
||||
▶ 模拟生成
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="output-panel">
|
||||
<div class="panel-label">模拟输出</div>
|
||||
<div class="output-box">
|
||||
<div v-if="isGenerating" class="generating">
|
||||
<span class="dot-anim">●●●</span> 生成中...
|
||||
</div>
|
||||
<div v-else-if="output" class="output-text">
|
||||
{{ output }}
|
||||
</div>
|
||||
<div v-else class="output-placeholder">
|
||||
点击"模拟生成"查看效果
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="output" class="quality-bar">
|
||||
<div class="quality-label">输出质量评估</div>
|
||||
<div class="quality-metrics">
|
||||
<div
|
||||
v-for="m in currentQuality"
|
||||
:key="m.name"
|
||||
class="metric"
|
||||
>
|
||||
<div class="metric-name">{{ m.name }}</div>
|
||||
<div class="meter">
|
||||
<div
|
||||
class="meter-fill"
|
||||
:style="{ width: m.score + '%', background: m.color }"
|
||||
/>
|
||||
</div>
|
||||
<div class="metric-score">{{ m.score }}%</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tips-bar">
|
||||
<span class="tips-label">💡 Prompt 技巧:</span>
|
||||
<span class="tips-text">{{ currentTip }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
const templates = [
|
||||
{ id: 'bad', icon: '❌', name: '模糊提问' },
|
||||
{ id: 'basic', icon: '📝', name: '基础结构' },
|
||||
{ id: 'good', icon: '✅', name: '最佳实践' }
|
||||
]
|
||||
|
||||
const currentTemplate = ref('bad')
|
||||
const systemPrompt = ref('')
|
||||
const userPrompt = ref('')
|
||||
const output = ref('')
|
||||
const isGenerating = ref(false)
|
||||
|
||||
const templateData = {
|
||||
bad: {
|
||||
system: '',
|
||||
user: '帮我写个介绍',
|
||||
output: '好的,这是一个介绍:\n\n大家好,我是一个人,我喜欢很多东西。我觉得生活很有趣。谢谢大家。\n\n(输出模糊、缺乏方向、没有实际价值)',
|
||||
quality: [
|
||||
{ name: '相关性', score: 20, color: '#ef4444' },
|
||||
{ name: '具体性', score: 10, color: '#ef4444' },
|
||||
{ name: '可用性', score: 15, color: '#ef4444' }
|
||||
],
|
||||
tip: '没有 System Prompt,没有上下文,问题过于模糊 —— AI 只能猜测你的意图。'
|
||||
},
|
||||
basic: {
|
||||
system: '你是一个技术文档写手。',
|
||||
user: '帮我写一段关于 React 的介绍,大约100字。',
|
||||
output: 'React 是由 Meta(原 Facebook)开发的开源 JavaScript 库,用于构建用户界面。它采用组件化开发模式和虚拟 DOM 技术,能够高效地更新和渲染页面。React 的声明式编程风格让开发者可以更直观地描述 UI 状态,广泛应用于现代 Web 开发中。',
|
||||
quality: [
|
||||
{ name: '相关性', score: 70, color: '#f59e0b' },
|
||||
{ name: '具体性', score: 65, color: '#f59e0b' },
|
||||
{ name: '可用性', score: 60, color: '#f59e0b' }
|
||||
],
|
||||
tip: '有了角色设定和基本约束(字数),输出质量明显提升,但仍缺少格式和受众定义。'
|
||||
},
|
||||
good: {
|
||||
system: '你是一位资深前端技术博主。写作风格:简洁专业,善用类比。目标读者:编程初学者。输出格式:先用一句话总结,再展开说明,最后给出一个生活类比。',
|
||||
user: '请介绍 React 是什么,以及为什么它如此流行。控制在150字以内。',
|
||||
output: '一句话总结:React 是一个帮你高效搭建网页界面的 JavaScript 工具库。\n\n展开说明:React 由 Meta 团队开发,核心理念是"组件化"——把复杂页面拆成独立的小积木,每个积木管理自己的状态和外观。它的虚拟 DOM 机制能智能计算最小更新范围,避免不必要的页面重绘。\n\n生活类比:如果网页是一面乐高墙,React 就是那套标准化的乐高积木系统——你可以独立替换任何一块,而不用推倒重来。',
|
||||
quality: [
|
||||
{ name: '相关性', score: 95, color: '#10b981' },
|
||||
{ name: '具体性', score: 90, color: '#10b981' },
|
||||
{ name: '可用性', score: 95, color: '#10b981' }
|
||||
],
|
||||
tip: '角色 + 风格 + 受众 + 格式 + 约束 = 高质量输出。好的 Prompt 就是好的需求文档。'
|
||||
}
|
||||
}
|
||||
|
||||
const currentQuality = ref([])
|
||||
const currentTip = computed(() => templateData[currentTemplate.value].tip)
|
||||
|
||||
const selectTemplate = (id) => {
|
||||
currentTemplate.value = id
|
||||
const data = templateData[id]
|
||||
systemPrompt.value = data.system
|
||||
userPrompt.value = data.user
|
||||
output.value = ''
|
||||
currentQuality.value = []
|
||||
}
|
||||
|
||||
const runPrompt = async () => {
|
||||
isGenerating.value = true
|
||||
output.value = ''
|
||||
currentQuality.value = []
|
||||
await new Promise(r => setTimeout(r, 1200))
|
||||
const data = templateData[currentTemplate.value]
|
||||
output.value = data.output
|
||||
currentQuality.value = data.quality
|
||||
isGenerating.value = false
|
||||
}
|
||||
|
||||
// Initialize
|
||||
selectTemplate('bad')
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.prompt-demo {
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.header { text-align: center; margin-bottom: 16px; }
|
||||
.title {
|
||||
font-size: 17px; font-weight: 700;
|
||||
background: linear-gradient(120deg, #8b5cf6, var(--vp-c-brand));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
}
|
||||
.subtitle { font-size: 12px; color: var(--vp-c-text-2); margin-top: 4px; }
|
||||
|
||||
.template-tabs {
|
||||
display: flex; gap: 8px; justify-content: center;
|
||||
margin-bottom: 16px; flex-wrap: wrap;
|
||||
}
|
||||
.tab-btn {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 8px 14px; border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 20px; background: var(--vp-c-bg);
|
||||
cursor: pointer; transition: all 0.2s; font-size: 13px;
|
||||
}
|
||||
.tab-btn:hover { background: var(--vp-c-bg-alt); }
|
||||
.tab-btn.active {
|
||||
border-color: var(--vp-c-brand);
|
||||
background: var(--vp-c-brand-soft);
|
||||
color: var(--vp-c-brand-dark);
|
||||
}
|
||||
|
||||
.editor-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
.editor-panel, .output-panel {
|
||||
background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 12px; padding: 14px;
|
||||
}
|
||||
.panel-label {
|
||||
font-weight: 600; font-size: 12px; margin-bottom: 6px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
.prompt-input {
|
||||
width: 100%; padding: 10px; border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px; background: var(--vp-c-bg-soft);
|
||||
font-size: 13px; line-height: 1.5; resize: vertical;
|
||||
font-family: var(--vp-font-family-mono);
|
||||
color: var(--vp-c-text-1); margin-bottom: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.prompt-input:focus {
|
||||
outline: none; border-color: var(--vp-c-brand);
|
||||
}
|
||||
.run-btn {
|
||||
width: 100%; padding: 10px; background: var(--vp-c-brand);
|
||||
color: white; border: none; border-radius: 8px;
|
||||
font-size: 13px; cursor: pointer; transition: background 0.2s;
|
||||
}
|
||||
.run-btn:hover { background: var(--vp-c-brand-dark); }
|
||||
|
||||
.output-box {
|
||||
background: var(--vp-c-bg-soft); border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 8px; padding: 14px; min-height: 120px;
|
||||
font-size: 13px; line-height: 1.7;
|
||||
}
|
||||
.output-text { white-space: pre-wrap; color: var(--vp-c-text-1); }
|
||||
.output-placeholder { color: var(--vp-c-text-3); text-align: center; padding: 30px 0; }
|
||||
.generating { color: var(--vp-c-brand); text-align: center; padding: 30px 0; }
|
||||
.dot-anim { animation: blink 1s infinite; }
|
||||
@keyframes blink { 50% { opacity: 0.3; } }
|
||||
|
||||
.quality-bar { margin-top: 12px; }
|
||||
.quality-label { font-weight: 600; font-size: 12px; margin-bottom: 8px; }
|
||||
.quality-metrics { display: flex; flex-direction: column; gap: 6px; }
|
||||
.metric { display: flex; align-items: center; gap: 8px; }
|
||||
.metric-name { font-size: 11px; width: 50px; color: var(--vp-c-text-2); }
|
||||
.meter {
|
||||
flex: 1; height: 8px; background: var(--vp-c-bg-soft);
|
||||
border-radius: 4px; overflow: hidden;
|
||||
}
|
||||
.meter-fill {
|
||||
height: 100%; border-radius: 4px;
|
||||
transition: width 0.6s ease;
|
||||
}
|
||||
.metric-score { font-size: 11px; font-weight: 600; width: 36px; text-align: right; }
|
||||
|
||||
.tips-bar {
|
||||
margin-top: 16px; padding: 12px 16px;
|
||||
background: var(--vp-c-brand-soft); border-radius: 6px; font-size: 13px;
|
||||
}
|
||||
.tips-label { font-weight: 600; color: var(--vp-c-brand-dark); }
|
||||
.tips-text { color: var(--vp-c-text-1); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user