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:
sanbuphy
2026-02-24 18:22:58 +08:00
parent b5a55811cc
commit 3af119a598
86 changed files with 20311 additions and 340 deletions
@@ -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 思考时就能看到部分结果,大幅降低感知等待时间。技术上通过 SSEServer-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>