feat: update prompt engineering docs and AI history demos

This commit is contained in:
sanbuphy
2026-01-19 12:22:02 +08:00
parent 7d86ba9504
commit cccc1d5cd4
7 changed files with 1297 additions and 1085 deletions
@@ -1,142 +1,78 @@
<template>
<div class="ai-evolution-demo">
<!-- Timeline -->
<div class="timeline-container">
<div class="timeline-track"></div>
<div class="header">
<div class="title">AI 进化规则 学习 生成</div>
<div class="subtitle">
点击切换阶段不自动推进避免点一下就连续发生很多事的误解
</div>
</div>
<div class="tabs" role="tablist" aria-label="AI Evolution Stages">
<button
v-for="(stage, index) in stages"
:key="index"
class="timeline-node"
:class="{
active: currentStage === index,
passed: currentStage > index
}"
:key="stage.key"
class="tab"
:class="{ active: currentStage === index }"
role="tab"
:aria-selected="currentStage === index"
@click="currentStage = index"
>
<div class="node-dot">
<div class="inner-dot"></div>
</div>
<div class="node-content">
<span class="year-badge">{{ stage.year }}</span>
<span class="node-label">{{ stage.label }}</span>
</div>
<div class="tab-year">{{ stage.year }}</div>
<div class="tab-label">{{ stage.label }}</div>
</button>
</div>
<!-- Content -->
<div class="content-wrapper">
<transition name="fade-slide" mode="out-in">
<div :key="currentStage" class="stage-content">
<div class="header-section">
<h3>
<span class="stage-index"
>{{ indexToRoman(currentStage + 1) }}.</span
>
{{ stages[currentStage].title }}
</h3>
<p>{{ stages[currentStage].desc }}</p>
<div class="stage">
<div class="stage-head">
<div class="stage-title">{{ stages[currentStage].title }}</div>
<div class="stage-desc">{{ stages[currentStage].desc }}</div>
</div>
<div class="grid">
<div class="card">
<div class="card-title">核心思想</div>
<ul class="list">
<li v-for="(item, i) in stages[currentStage].core" :key="i">
{{ item }}
</li>
</ul>
</div>
<div class="card">
<div class="card-title">代表应用</div>
<div class="pill-row">
<span
v-for="(tag, i) in stages[currentStage].examples"
:key="i"
class="pill"
>{{ tag }}</span
>
</div>
<div class="note">{{ stages[currentStage].appDesc }}</div>
</div>
<div class="visualization-grid">
<!-- Concept/Logic View -->
<div class="mac-window concept-window">
<div class="window-bar">
<div class="traffic-lights">
<span class="light red"></span>
<span class="light yellow"></span>
<span class="light green"></span>
</div>
<div class="window-title">Core Logic</div>
</div>
<div class="concept-canvas">
<!-- Stage 0: Symbolism -->
<div v-if="currentStage === 0" class="vis-symbolism">
<div class="logic-gate">
<div class="input-group">
<span class="input-val">A: True</span>
<span class="input-val">B: False</span>
</div>
<div class="gate-box">AND Rule</div>
<div class="output-val">Output: False</div>
</div>
<div class="math-note">If A and B then C</div>
</div>
<!-- Stage 1: Expert Systems -->
<div v-if="currentStage === 1" class="vis-expert">
<div class="decision-tree">
<div class="tree-node root">Is it raining?</div>
<div class="branches">
<div class="branch">
<span class="condition">Yes</span>
<div class="tree-node leaf">Take Umbrella</div>
</div>
<div class="branch">
<span class="condition">No</span>
<div class="tree-node leaf">Go Out</div>
</div>
</div>
</div>
<div class="kb-note">Knowledge Base + Inference Engine</div>
</div>
<!-- Stage 2: Deep Learning -->
<div v-if="currentStage === 2" class="vis-dl">
<div class="neural-net">
<div class="layer input">
<div class="neuron" v-for="n in 3" :key="`i-${n}`"></div>
</div>
<div class="layer hidden">
<div class="neuron" v-for="n in 4" :key="`h-${n}`"></div>
</div>
<div class="layer output">
<div class="neuron" v-for="n in 2" :key="`o-${n}`"></div>
</div>
<!-- Connections drawn via CSS/SVG ideally, simplified here -->
<svg class="connections">
<line x1="10" y1="20" x2="60" y2="10" />
<line x1="10" y1="20" x2="60" y2="30" />
<!-- Abstract lines -->
</svg>
</div>
<div class="dl-note">Feature Extraction (Black Box)</div>
</div>
<!-- Stage 3: GenAI -->
<div v-if="currentStage === 3" class="vis-genai">
<div class="transformer-block">
<div class="block-layer attn">Self-Attention</div>
<div class="block-layer ff">Feed Forward</div>
<div class="block-layer norm">Norm & Add</div>
</div>
<div class="chat-sim">
<div class="msg user">"Draw a cat"</div>
<div class="msg ai">Generates 🐱...</div>
</div>
</div>
</div>
<div class="card full">
<div class="card-title">优势 / 局限</div>
<div class="two-col">
<div class="col">
<div class="col-title">优势</div>
<ul class="list">
<li v-for="(item, i) in stages[currentStage].pros" :key="i">
{{ item }}
</li>
</ul>
</div>
<!-- Application/Impact View -->
<div class="mac-window app-window">
<div class="window-bar">
<div class="window-title">Real-world Impact</div>
</div>
<div class="app-canvas">
<div class="impact-card">
<div class="impact-icon">{{ stages[currentStage].icon }}</div>
<div class="impact-title">
{{ stages[currentStage].appTitle }}
</div>
<div class="impact-desc">
{{ stages[currentStage].appDesc }}
</div>
</div>
</div>
<div class="col">
<div class="col-title">局限</div>
<ul class="list">
<li v-for="(item, i) in stages[currentStage].cons" :key="i">
{{ item }}
</li>
</ul>
</div>
</div>
</div>
</transition>
</div>
</div>
</div>
</template>
@@ -146,475 +82,226 @@ import { ref } from 'vue'
const currentStage = ref(0)
const indexToRoman = (num) => {
const map = { 1: 'I', 2: 'II', 3: 'III', 4: 'IV' }
return map[num] || num
}
const stages = [
{
year: '1950s-1970s',
label: 'Symbolism',
title: 'The Dawn: Logic & Rules',
desc: 'AI started as "Symbolic AI". Scientists believed intelligence could be described by formal logic and rules. If we can write down all the rules of the world, a computer can be intelligent.',
icon: '♟️',
appTitle: 'Chess & Logic',
key: 'symbolic',
year: '1950s1980s',
label: '符号主义',
title: '规则与逻辑推理(专家系统)',
desc: '相信“智能 = 规则 + 推理”。把专家经验写成 If/Then 规则与知识库。',
core: [
'知识用“符号/规则”表达:If 条件 Then 结论',
'推理引擎按规则匹配、触发、推导',
'可解释:能指出用了哪条规则'
],
pros: ['可解释性强', '在边界明确的垂直领域有效'],
cons: [
'规则写不完(组合爆炸)',
'脆弱:世界稍变就失效',
'难处理不确定性与常识'
],
examples: ['专家系统', 'MYCIN', '逻辑推理'],
appDesc:
'Programs could solve logic puzzles and play simple chess, but failed at "common sense" or recognizing a cat in a photo.'
},
{
year: '1980s-1990s',
label: 'Expert Systems',
title: 'Knowledge Engineering',
desc: 'The era of "Expert Systems". We tried to hard-code human expertise (e.g., medical diagnosis rules) into databases. Useful for specific domains, but brittle and hard to maintain.',
icon: '🏥',
appTitle: 'MYCIN / Deep Blue',
appDesc:
'Systems that could diagnose blood infections or beat Garry Kasparov at chess (Deep Blue, 1997), but still lacked true learning capability.'
'适合“规则明确”的任务(如部分诊断流程、合规校验),但遇到现实世界的灰度与噪声会迅速失效。'
},
{
key: 'dl',
year: '2010s',
label: 'Deep Learning',
title: 'Connectionism & Big Data',
desc: 'The breakthrough of Neural Networks. Inspired by the human brain, computers learned patterns from massive data instead of being told rules. AlexNet (2012) changed everything.',
icon: '🧠',
appTitle: 'AlphaGo & FaceID',
label: '深度学习',
title: '从数据中学习(连接主义)',
desc: '相信“智能 = 表示学习 + 统计优化”。用神经网络从大量数据里自动学特征与决策边界。',
core: [
'用参数(权重)表示知识;通过优化让参数拟合数据',
'特征提取从“手写规则”变成“自动学习”',
'数据、算力、算法(GPU + 大数据 + 网络结构)共同推动'
],
pros: ['强大的模式识别能力', '同一范式覆盖多任务(视觉/语音/推荐等)'],
cons: ['数据需求大', '可解释性较弱', '对分布外/对抗样本敏感'],
examples: ['AlexNet', 'ImageNet', 'AlphaGo'],
appDesc:
'AI learned to see (ImageNet), hear (Siri), and play Go (AlphaGo). It surpassed humans in specific perceptual tasks.'
'擅长“感知类”任务(图像、语音、推荐);但对“为何这么判”解释不够直观,且对数据分布较敏感。'
},
{
key: 'genai',
year: '2020s+',
label: 'Generative AI',
title: 'Generative Intelligence (LLMs)',
desc: 'The Transformer architecture allowed AI to understand context and generate new content. AI moved from "classifying" (is this a cat?) to "creating" (draw a cat).',
icon: '✨',
appTitle: 'ChatGPT & Midjourney',
label: '生成式 AI',
title: '从“分类”到“生成”(大模型)',
desc: ' Transformer 建模上下文关系,学习“下一 token”分布,从而能生成文本/代码/图像等新内容。',
core: [
'统一接口:给提示词(prompt)→ 生成输出',
'能力来源:规模化预训练 + 指令微调/对齐',
'把很多任务“变成一个生成问题”'
],
pros: ['通用性强(多任务)', '交互友好(自然语言接口)'],
cons: [
'可能幻觉',
'安全与权限边界复杂',
'需要系统化评测与约束(格式/工具/检索)'
],
examples: ['ChatGPT', 'GPT-4', 'Midjourney'],
appDesc:
'AI that can write code, poetry, paint images, and reason across multiple domains. A step towards AGI (General Intelligence).'
'更像“通用助手”:能写、能改、能解释、能生成;但要通过提示词、上下文与工具链把它约束到可验收、可控。'
}
]
</script>
<style scoped>
.ai-evolution-demo {
border-radius: 8px;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
overflow: hidden;
margin: 1rem 0;
}
/* Reusing Timeline Styles from FrontendEvolutionDemo for consistency */
.timeline-container {
padding: 2rem 1rem 1rem;
background: var(--vp-c-bg-soft);
display: flex;
justify-content: space-between;
position: relative;
border-bottom: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1.5rem;
margin: 1rem 0;
color: var(--vp-c-text-1);
}
.timeline-track {
position: absolute;
top: 2.5rem;
left: 3rem;
right: 3rem;
height: 2px;
background: var(--vp-c-divider);
z-index: 0;
.header {
margin-bottom: 1rem;
}
.timeline-node {
position: relative;
z-index: 1;
background: transparent;
border: none;
display: flex;
flex-direction: column;
align-items: center;
cursor: pointer;
padding: 0;
width: 25%;
transition: all 0.3s ease;
opacity: 0.6;
.title {
font-weight: 800;
color: var(--vp-c-text-1);
}
.timeline-node:hover {
opacity: 0.9;
}
.timeline-node.active,
.timeline-node.passed {
opacity: 1;
.subtitle {
margin-top: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.node-dot {
width: 16px;
height: 16px;
border-radius: 50%;
.tabs {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
margin: 0.75rem 0 1rem;
}
.tab {
text-align: left;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-text-3);
margin-bottom: 0.8rem;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
color: var(--vp-c-text-1);
border-radius: 8px;
padding: 0.6rem 0.75rem;
cursor: pointer;
transition:
border-color 0.2s ease,
box-shadow 0.2s ease;
}
.inner-dot {
width: 0;
height: 0;
border-radius: 50%;
background: var(--vp-c-brand);
transition: all 0.3s;
.tab:hover {
border-color: rgba(var(--vp-c-brand-rgb), 0.55);
}
.timeline-node.active .node-dot {
.tab.active {
border-color: var(--vp-c-brand);
transform: scale(1.3);
box-shadow: 0 0 0 4px var(--vp-c-bg-soft);
}
.timeline-node.active .inner-dot {
width: 8px;
height: 8px;
}
.timeline-node.passed .node-dot {
border-color: var(--vp-c-brand);
background: var(--vp-c-brand);
box-shadow: 0 0 0 3px rgba(var(--vp-c-brand-rgb), 0.12);
}
.node-content {
text-align: center;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.2rem;
}
.year-badge {
.tab-year {
font-size: 0.75rem;
color: var(--vp-c-text-2);
font-family: var(--vp-font-family-mono);
background: var(--vp-c-bg-alt);
padding: 2px 6px;
border-radius: 4px;
color: var(--vp-c-text-2);
}
.node-label {
font-size: 0.85rem;
font-weight: 600;
.tab-label {
margin-top: 0.15rem;
font-weight: 800;
}
.stage-head {
margin-bottom: 0.75rem;
}
.stage-title {
font-weight: 900;
color: var(--vp-c-text-1);
}
/* Content Area */
.content-wrapper {
padding: 2rem;
min-height: 400px;
}
.header-section {
text-align: center;
margin-bottom: 2rem;
max-width: 600px;
margin: 0 auto 2rem;
}
.header-section h3 {
font-size: 1.5rem;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.stage-index {
color: var(--vp-c-text-3);
-webkit-text-fill-color: var(--vp-c-text-3);
margin-right: 0.5rem;
font-weight: normal;
}
.header-section p {
font-size: 1rem;
.stage-desc {
margin-top: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.95rem;
line-height: 1.6;
}
/* Visualization */
.visualization-grid {
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2rem;
align-items: stretch;
gap: 0.75rem;
}
@media (max-width: 768px) {
.visualization-grid {
@media (max-width: 720px) {
.tabs {
grid-template-columns: 1fr;
}
.grid {
grid-template-columns: 1fr;
}
}
.mac-window {
border-radius: 12px;
border: 1px solid var(--vp-c-divider);
overflow: hidden;
display: flex;
flex-direction: column;
background: var(--vp-c-bg);
}
.concept-window {
background: var(--vp-c-bg);
}
.app-window {
background: var(--vp-c-bg);
}
.window-bar {
padding: 0.8rem 1rem;
background: var(--vp-c-bg-soft);
border-bottom: 1px solid var(--vp-c-divider);
display: flex;
align-items: center;
position: relative;
}
.traffic-lights {
display: flex;
gap: 6px;
}
.light {
width: 10px;
height: 10px;
border-radius: 50%;
}
.light.red {
background: var(--vp-c-red-1, #ef4444);
}
.light.yellow {
background: var(--vp-c-yellow-1, #f59e0b);
}
.light.green {
background: var(--vp-c-green-1, #22c55e);
}
.window-title {
position: absolute;
left: 0;
right: 0;
text-align: center;
font-size: 0.8rem;
color: var(--vp-c-text-2);
font-weight: 600;
}
.concept-canvas,
.app-canvas {
padding: 2rem;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
min-height: 250px;
}
/* Visualizations */
/* Symbolism */
.logic-gate {
border: 2px solid var(--vp-c-divider);
padding: 1rem;
border-radius: 8px;
text-align: center;
background: var(--vp-c-bg);
}
.input-group {
display: flex;
gap: 1rem;
justify-content: center;
margin-bottom: 0.5rem;
font-family: var(--vp-font-family-mono);
}
.gate-box {
background: var(--vp-c-brand);
color: var(--vp-c-bg);
padding: 4px 10px;
margin: 0.5rem 0;
border-radius: 4px;
}
.math-note {
margin-top: 1rem;
font-family: var(--vp-font-family-mono);
color: var(--vp-c-text-2);
font-size: 0.8rem;
}
/* Expert Systems */
.decision-tree {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
.tree-node {
border: 1px solid var(--vp-c-divider);
padding: 6px 12px;
border-radius: 20px;
background: var(--vp-c-bg);
font-size: 0.8rem;
color: var(--vp-c-text-1);
}
.tree-node.root {
border-color: var(--vp-c-brand);
color: var(--vp-c-brand);
font-weight: bold;
}
.branches {
display: flex;
gap: 2rem;
}
.branch {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
}
.condition {
font-size: 0.7rem;
color: var(--vp-c-text-2);
background: var(--vp-c-bg-alt);
padding: 2px 6px;
border-radius: 4px;
}
.kb-note {
margin-top: 1rem;
font-size: 0.8rem;
color: var(--vp-c-text-2);
font-style: italic;
}
/* Deep Learning */
.neural-net {
display: flex;
gap: 2rem;
align-items: center;
position: relative;
}
.layer {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.neuron {
width: 12px;
height: 12px;
border-radius: 50%;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
}
.layer.input .neuron {
background: rgba(var(--vp-c-brand-rgb), 0.25);
}
.layer.hidden .neuron {
background: rgba(var(--vp-c-brand-rgb), 0.18);
}
.layer.output .neuron {
background: rgba(var(--vp-c-brand-rgb), 0.12);
}
.connections {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
opacity: 0.2;
}
.connections line {
stroke: var(--vp-c-text-2);
stroke-width: 1;
}
.dl-note {
margin-top: 2rem;
font-size: 0.8rem;
color: var(--vp-c-text-2);
}
/* GenAI */
.vis-genai {
display: flex;
flex-direction: column;
gap: 1rem;
align-items: center;
width: 100%;
}
.transformer-block {
border: 2px solid var(--vp-c-brand);
border-radius: 8px;
padding: 0.5rem;
width: 120px;
text-align: center;
background: rgba(var(--vp-c-brand-rgb), 0.08);
}
.block-layer {
.card {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
margin: 4px 0;
padding: 4px;
font-size: 0.7rem;
border-radius: 4px;
}
.chat-sim {
width: 100%;
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
background: var(--vp-c-bg);
font-size: 0.8rem;
}
.msg {
padding: 6px 10px;
border-radius: 12px;
margin-bottom: 0.5rem;
max-width: 80%;
.card.full {
grid-column: 1 / -1;
}
.msg.user {
background: rgba(var(--vp-c-brand-rgb), 0.1);
margin-left: auto;
.card-title {
font-weight: 900;
color: var(--vp-c-text-1);
margin-bottom: 0.5rem;
}
.msg.ai {
background: var(--vp-c-bg-soft);
margin-right: auto;
.list {
margin: 0;
padding-left: 1.15rem;
color: var(--vp-c-text-1);
}
/* Impact Card */
.impact-card {
text-align: center;
}
.impact-icon {
font-size: 4rem;
margin-bottom: 1rem;
}
.impact-title {
font-size: 1.2rem;
font-weight: bold;
.pill-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-bottom: 0.5rem;
color: var(--vp-c-text-1);
}
.impact-desc {
font-size: 0.9rem;
.pill {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-alt);
color: var(--vp-c-text-2);
line-height: 1.5;
padding: 0.2rem 0.6rem;
border-radius: 999px;
font-size: 0.8rem;
font-weight: 700;
}
/* Transitions */
.fade-slide-enter-active,
.fade-slide-leave-active {
transition: all 0.4s ease;
.note {
color: var(--vp-c-text-2);
line-height: 1.6;
}
.fade-slide-enter-from {
opacity: 0;
transform: translateY(20px);
.two-col {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
}
.fade-slide-leave-to {
opacity: 0;
transform: translateY(-20px);
@media (max-width: 720px) {
.two-col {
grid-template-columns: 1fr;
}
}
.col-title {
font-weight: 900;
color: var(--vp-c-text-1);
margin-bottom: 0.35rem;
}
</style>
@@ -46,15 +46,14 @@
</div>
<div class="action-buttons">
<button @click="addRule" class="add-rule-btn" :disabled="ruleCount >= maxRules">
<button
@click="addRule"
class="add-rule-btn"
:disabled="ruleCount >= maxRules"
>
添加规则 ({{ ruleCount }}/{{ maxRules }})
</button>
<button @click="autoGenerate" class="auto-btn" :disabled="autoGenerating">
自动生成
</button>
<button @click="resetRules" class="reset-btn">
🔄 重置
</button>
<button @click="resetRules" class="reset-btn">🔄 重置</button>
</div>
</div>
@@ -62,7 +61,9 @@
<div class="counter-display">
<div class="counter-label">需要的规则总数</div>
<transition name="count-update" mode="out-in">
<div :key="totalRules" class="counter-value">{{ formatNumber(totalRules) }}</div>
<div :key="totalRules" class="counter-value">
{{ formatNumber(totalRules) }}
</div>
</transition>
<div class="counter-formula">
= {{ valuesPerFeature }}<sup>{{ featureCount }}</sup> =
@@ -85,7 +86,10 @@
<div class="rule-content">
<code>{{ rule.text }}</code>
</div>
<div class="rule-visual" :style="{ background: rule.gradient }"></div>
<div
class="rule-visual"
:style="{ background: rule.gradient }"
></div>
</div>
</transition-group>
</div>
@@ -146,7 +150,8 @@
<li>...</li>
</ul>
<p class="conclusion">
<strong>结论</strong>规则永远写不完这就是为什么我们需要<strong>机器学习</strong>
<strong>结论</strong
>规则永远写不完这就是为什么我们需要<strong>机器学习</strong>
</p>
</div>
</div>
@@ -159,7 +164,6 @@ const featureCount = ref(3)
const valuesPerFeature = ref(3)
const ruleCount = ref(0)
const ruleIdCounter = ref(0)
const autoGenerating = ref(false)
const displayedRules = ref([])
const maxRules = 20
@@ -201,7 +205,8 @@ const valueOptions = computed(() => {
const generateRuleText = () => {
const conditions = features.value.map((feature, index) => {
const value = valueOptions.value[Math.floor(Math.random() * valuesPerFeature.value)]
const value =
valueOptions.value[Math.floor(Math.random() * valuesPerFeature.value)]
return `${feature}=${value}`
})
return `IF ${conditions.join(' AND ')} THEN ...`
@@ -217,31 +222,18 @@ const addRule = () => {
displayedRules.value.push({
id: ruleIdCounter.value++,
text: generateRuleText(),
color: getFeatureColor(Math.floor(Math.random() * featureCount.value) + 1),
color: getFeatureColor(
Math.floor(Math.random() * featureCount.value) + 1
),
gradient: generateColor()
})
ruleCount.value++
}
}
const autoGenerate = async () => {
if (autoGenerating.value) return
autoGenerating.value = true
const interval = setInterval(() => {
if (ruleCount.value >= maxRules) {
clearInterval(interval)
autoGenerating.value = false
} else {
addRule()
}
}, 100)
}
const resetRules = () => {
displayedRules.value = []
ruleCount.value = 0
autoGenerating.value = false
}
const formatNumber = (num) => {
@@ -448,8 +440,15 @@ watch([featureCount, valuesPerFeature], () => {
}
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 0.5; }
50% { transform: scale(1.1); opacity: 0.8; }
0%,
100% {
transform: scale(1);
opacity: 0.5;
}
50% {
transform: scale(1.1);
opacity: 0.8;
}
}
.counter-label {
@@ -512,9 +511,16 @@ watch([featureCount, valuesPerFeature], () => {
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
0%,
100% {
transform: translateX(0);
}
25% {
transform: translateX(-5px);
}
75% {
transform: translateX(5px);
}
}
.rules-container {
@@ -590,8 +596,13 @@ watch([featureCount, valuesPerFeature], () => {
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
.warning-content h5 {
@@ -1,232 +1,252 @@
<template>
<div class="neural-network-viz-demo">
<div class="demo-header">
<h4>🧠 神经网络可视化</h4>
<p>观察数据如何在神经网络中流动</p>
</div>
<div class="network-container">
<svg ref="svgRef" class="network-svg" :width="svgWidth" :height="svgHeight">
<defs>
<linearGradient id="connectionGradient" x1="0%" y1="0%" x2="100%" y2="0%">
<stop
offset="0%"
style="stop-color: var(--vp-c-brand); stop-opacity: 0.15"
/>
<stop
offset="100%"
style="stop-color: var(--vp-c-brand); stop-opacity: 0.45"
/>
</linearGradient>
</defs>
<!-- Connections -->
<g class="connections">
<line
v-for="conn in connections"
:key="conn.id"
:x1="conn.x1"
:y1="conn.y1"
:x2="conn.x2"
:y2="conn.y2"
:stroke-width="conn.width"
:opacity="conn.opacity"
stroke="url(#connectionGradient)"
class="connection-line"
/>
</g>
<!-- Neurons -->
<g class="neurons">
<g
v-for="neuron in neurons"
:key="neuron.id"
:transform="`translate(${neuron.x}, ${neuron.y})`"
class="neuron-group"
:class="{ active: neuron.active, input: neuron.layer === 0, output: neuron.layer === layers.length - 1 }"
>
<circle
:r="neuron.radius"
class="neuron-circle"
@click="activateNeuron(neuron)"
/>
<text
v-if="neuron.label"
y="30"
text-anchor="middle"
class="neuron-label"
>
{{ neuron.label }}
</text>
</g>
</g>
</svg>
</div>
<div class="layer-info">
<div
v-for="(layer, index) in layerConfigs"
:key="index"
class="layer-card"
:class="{ active: currentLayer === index }"
@click="currentLayer = index"
>
<div class="layer-badge">{{ layer.name }}</div>
<div class="layer-neurons">{{ layer.neurons }} 个神经元</div>
<div class="layer-desc">{{ layer.desc }}</div>
<div class="nn-viz-demo">
<div class="header">
<div class="title">神经网络手动前向传播可控演示</div>
<div class="subtitle">
开始 / 上一步 /
下一步逐层推进不自动播放避免误把动画当成真实训练过程
</div>
</div>
<div class="controls">
<button @click="startForwardPropagation" class="action-btn">
前向传播
<button class="btn primary" @click="start" :disabled="step !== 0">
开始
</button>
<button @click="resetNetwork" class="action-btn secondary">
🔄 重置
<button class="btn" @click="prev" :disabled="step <= 1">上一步</button>
<button
class="btn primary"
@click="next"
:disabled="step === 0 || step >= maxStep"
>
下一步
</button>
<button class="btn" @click="reset">重置</button>
</div>
<div v-if="step > 0" class="progress">
Step {{ step }} / {{ maxStep }} · {{ stepTitle }}
</div>
<div class="grid">
<div class="card">
<div class="card-title">网络结构</div>
<div class="network-container">
<svg class="network-svg" :viewBox="`0 0 ${svgWidth} ${svgHeight}`">
<defs>
<linearGradient id="conn" x1="0%" y1="0%" x2="100%" y2="0%">
<stop
offset="0%"
:style="{
stopColor: 'var(--vp-c-brand)',
stopOpacity: 0.18
}"
/>
<stop
offset="100%"
:style="{
stopColor: 'var(--vp-c-brand)',
stopOpacity: 0.45
}"
/>
</linearGradient>
</defs>
<g class="connections">
<line
v-for="c in connections"
:key="c.id"
:x1="c.x1"
:y1="c.y1"
:x2="c.x2"
:y2="c.y2"
:class="{
active: isConnectionActive(c),
focus: isConnectionFocus(c)
}"
/>
</g>
<g class="neurons">
<g
v-for="n in neurons"
:key="n.id"
:transform="`translate(${n.x}, ${n.y})`"
:class="{
neuron: true,
active: isNeuronActive(n),
focus: focusLayer === n.layer
}"
@click="focusLayer = n.layer"
>
<circle :r="n.r" />
<text v-if="n.label" y="32" text-anchor="middle">
{{ n.label }}
</text>
</g>
</g>
</svg>
</div>
<div class="hint">
提示点击某一层的神经元可以聚焦该层仅用于查看不会触发自动流程
</div>
</div>
<div class="card">
<div class="card-title">每一层在做什么</div>
<div class="layers">
<button
v-for="(l, idx) in layerConfigs"
:key="l.name"
class="layer"
:class="{ active: focusLayer === idx }"
@click="focusLayer = idx"
>
<div class="layer-name">{{ l.name }}</div>
<div class="layer-desc">{{ l.desc }}</div>
</button>
</div>
<div class="explain">
<div class="explain-title">当前推进到</div>
<div class="explain-text">{{ stepExplain }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { computed, onMounted, ref } from 'vue'
const svgWidth = 800
const svgHeight = 400
const currentLayer = ref(0)
const animationId = ref(null)
const svgWidth = 820
const svgHeight = 360
const layers = ref([4, 6, 6, 3]) // 输入层、2个隐藏层、输出层
const layerConfigs = ref([
{ name: '输入层', neurons: 4, desc: '接收原始数据(如图片像素)' },
{ name: '隐藏层 1', neurons: 6, desc: '识别边缘和简单特征' },
{ name: '隐藏层 2', neurons: 6, desc: '识别形状和复杂特征' },
{ name: '输出层', neurons: 3, desc: '输出分类结果' }
])
const layers = ref([4, 6, 6, 3])
const layerConfigs = [
{ name: '输入层', desc: '接收原始输入(例如像素、特征、词向量等)。' },
{ name: '隐藏层 1', desc: '学习更基础的组合特征(例如边缘、局部模式)。' },
{ name: '隐藏层 2', desc: '学习更复杂的抽象表示(例如形状、语义组合)。' },
{ name: '输出层', desc: '输出任务结果(分类概率、回归值等)。' }
]
const neurons = ref([])
const connections = ref([])
// 计算神经元位置
const calculateNeurons = () => {
neurons.value = []
const maxStep = computed(() => layers.value.length)
const step = ref(0)
const focusLayer = ref(0)
const activeToLayer = computed(() => (step.value === 0 ? -1 : step.value - 1))
const stepTitle = computed(() => {
if (step.value === 1) return '激活输入层'
if (step.value === 2) return '传递到隐藏层 1'
if (step.value === 3) return '传递到隐藏层 2'
if (step.value === 4) return '得到输出'
return '未开始'
})
const stepExplain = computed(() => {
if (step.value === 0)
return '点击“开始”,先把输入层视为已有数据。之后每次“下一步”只推进一层,便于你观察。'
if (step.value === 1) return '输入层被激活:表示我们把输入数据“喂”进网络。'
if (step.value === 2)
return '从输入层到隐藏层 1:连接把输入做加权求和,得到第一层的激活。'
if (step.value === 3) return '从隐藏层 1 到隐藏层 2:更高层的表示通常更抽象。'
if (step.value === 4)
return '输出层激活:拿到最终输出(例如“猫/狗/鸟”的概率)。'
return ''
})
const start = () => {
step.value = 1
focusLayer.value = 0
}
const next = () => {
step.value = Math.min(maxStep.value, step.value + 1)
focusLayer.value = Math.min(activeToLayer.value, layers.value.length - 1)
}
const prev = () => {
step.value = Math.max(1, step.value - 1)
focusLayer.value = Math.min(activeToLayer.value, layers.value.length - 1)
}
const reset = () => {
step.value = 0
focusLayer.value = 0
}
const isNeuronActive = (n) => n.layer >= 0 && n.layer <= activeToLayer.value
const isConnectionActive = (c) => {
if (activeToLayer.value < 1) return false
return c.toLayer <= activeToLayer.value
}
const isConnectionFocus = (c) => {
if (activeToLayer.value < 1) return false
return c.toLayer === activeToLayer.value
}
const layout = () => {
const ns = []
const cs = []
const layerSpacing = svgWidth / (layers.value.length + 1)
layers.value.forEach((neuronCount, layerIndex) => {
layers.value.forEach((count, layerIndex) => {
const x = layerSpacing * (layerIndex + 1)
const neuronSpacing = svgHeight / (neuronCount + 1)
const ySpacing = svgHeight / (count + 1)
for (let i = 0; i < neuronCount; i++) {
const y = neuronSpacing * (i + 1)
neurons.value.push({
for (let i = 0; i < count; i++) {
const y = ySpacing * (i + 1)
ns.push({
id: `${layerIndex}-${i}`,
layer: layerIndex,
x,
y,
radius: 20,
active: false,
r: 18,
label:
layerIndex === 0
? ['像素1', '像素2', '像素3', '像素4'][i]
: layerIndex === layers.value.length - 1
? ['猫', '狗', '鸟'][i]
: ''
? ['猫', '狗', '鸟'][i]
: ''
})
}
})
}
// 计算连接
const calculateConnections = () => {
connections.value = []
let connId = 0
let id = 0
for (let l = 0; l < layers.value.length - 1; l++) {
const currentLayerNeurons = neurons.value.filter((n) => n.layer === l)
const nextLayerNeurons = neurons.value.filter((n) => n.layer === l + 1)
currentLayerNeurons.forEach((fromNeuron) => {
nextLayerNeurons.forEach((toNeuron) => {
connections.value.push({
id: connId++,
x1: fromNeuron.x,
y1: fromNeuron.y,
x2: toNeuron.x,
y2: toNeuron.y,
width: Math.random() * 2 + 0.5,
opacity: 0.3,
active: false
const from = ns.filter((n) => n.layer === l)
const to = ns.filter((n) => n.layer === l + 1)
from.forEach((a) => {
to.forEach((b) => {
cs.push({
id: id++,
x1: a.x,
y1: a.y,
x2: b.x,
y2: b.y,
toLayer: l + 1
})
})
})
}
neurons.value = ns
connections.value = cs
}
const activateNeuron = (neuron) => {
neuron.active = !neuron.active
currentLayer.value = neuron.layer
}
const startForwardPropagation = async () => {
resetNetwork()
// 激活输入层
const inputNeurons = neurons.value.filter((n) => n.layer === 0)
inputNeurons.forEach((n) => {
n.active = true
n.radius = 25
})
currentLayer.value = 0
await sleep(500)
// 逐层激活
for (let l = 1; l < layers.value.length; l++) {
currentLayer.value = l
const layerNeurons = neurons.value.filter((n) => n.layer === l)
layerNeurons.forEach((neuron) => {
neuron.active = true
neuron.radius = 25
})
// 高亮连接
connections.value.forEach((conn) => {
const fromNeuron = neurons.value.find(
(n) => Math.abs(n.x - conn.x1) < 1 && Math.abs(n.y - conn.y1) < 1
)
if (fromNeuron && fromNeuron.layer === l - 1 && fromNeuron.active) {
conn.opacity = 0.8
conn.width = 3
}
})
await sleep(600)
}
}
const resetNetwork = () => {
neurons.value.forEach((n) => {
n.active = false
n.radius = 20
})
connections.value.forEach((conn) => {
conn.opacity = 0.3
conn.width = Math.random() * 2 + 0.5
})
currentLayer.value = 0
}
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms))
onMounted(() => {
calculateNeurons()
calculateConnections()
layout()
})
</script>
<style scoped>
.neural-network-viz-demo {
.nn-viz-demo {
margin: 1rem 0;
padding: 1.5rem;
background: var(--vp-c-bg-soft);
@@ -235,156 +255,194 @@ onMounted(() => {
color: var(--vp-c-text-1);
}
.demo-header {
text-align: center;
margin-bottom: 1.5rem;
.header {
margin-bottom: 1rem;
}
.demo-header h4 {
margin: 0 0 0.5rem 0;
color: var(--vp-c-text-1);
font-size: 1.5rem;
.title {
font-weight: 800;
}
.demo-header p {
margin: 0;
.subtitle {
margin-top: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.875rem;
}
.network-container {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 1rem;
margin-bottom: 1.5rem;
overflow-x: auto;
}
.network-svg {
display: block;
margin: 0 auto;
min-width: 600px;
}
.connection-line {
transition: all 0.3s ease;
}
.neuron-group {
cursor: pointer;
transition: all 0.3s ease;
}
.neuron-group:hover .neuron-circle {
opacity: 0.9;
}
.neuron-circle {
fill: rgba(var(--vp-c-brand-rgb), 0.35);
stroke: var(--vp-c-brand);
stroke-width: 2;
transition: all 0.3s ease;
}
.neuron-group.input .neuron-circle {
fill: rgba(var(--vp-c-brand-rgb), 0.2);
stroke: var(--vp-c-brand);
}
.neuron-group.output .neuron-circle {
fill: rgba(var(--vp-c-brand-rgb), 0.12);
stroke: var(--vp-c-brand);
}
.neuron-group.active .neuron-circle {
stroke-width: 3;
}
.neuron-label {
font-size: 10px;
fill: var(--vp-c-text-1);
font-weight: 600;
}
.layer-info {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
margin-bottom: 1.5rem;
}
.layer-card {
padding: 1rem;
background: var(--vp-c-bg);
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
border: 1px solid var(--vp-c-divider);
}
.layer-card:hover {
border-color: rgba(var(--vp-c-brand-rgb), 0.35);
}
.layer-card.active {
border-color: var(--vp-c-brand);
box-shadow: 0 0 0 3px rgba(var(--vp-c-brand-rgb), 0.12);
}
.layer-badge {
font-weight: 700;
color: var(--vp-c-brand);
margin-bottom: 0.5rem;
}
.layer-neurons {
font-size: 0.875rem;
color: var(--vp-c-text-1);
margin-bottom: 0.5rem;
}
.layer-desc {
font-size: 0.75rem;
color: var(--vp-c-text-2);
line-height: 1.4;
font-size: 0.9rem;
}
.controls {
display: flex;
gap: 1rem;
justify-content: center;
gap: 0.5rem;
flex-wrap: wrap;
margin-bottom: 0.75rem;
}
.action-btn {
padding: 0.75rem 2rem;
background: var(--vp-c-brand);
color: var(--vp-c-bg);
border: 1px solid var(--vp-c-brand);
.btn {
padding: 0.5rem 0.75rem;
border-radius: 6px;
font-weight: 600;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
}
.action-btn:hover {
opacity: 0.95;
}
.action-btn.secondary {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-color: var(--vp-c-divider);
color: var(--vp-c-text-1);
cursor: pointer;
font-weight: 700;
font-size: 0.875rem;
}
.action-btn.secondary:hover {
.btn.primary {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: var(--vp-c-bg);
}
@media (max-width: 768px) {
.layer-info {
grid-template-columns: repeat(2, 1fr);
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.progress {
margin-bottom: 0.75rem;
color: var(--vp-c-text-2);
font-family: var(--vp-font-family-mono);
font-size: 0.85rem;
}
.grid {
display: grid;
grid-template-columns: 1.3fr 1fr;
gap: 0.75rem;
}
@media (max-width: 720px) {
.grid {
grid-template-columns: 1fr;
}
}
.card {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
border-radius: 8px;
padding: 1rem;
}
.card-title {
font-weight: 900;
margin-bottom: 0.5rem;
}
.network-container {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
border-radius: 8px;
padding: 0.75rem;
overflow-x: auto;
}
.network-svg {
width: 100%;
min-width: 640px;
height: auto;
display: block;
}
.connections line {
stroke: var(--vp-c-divider);
stroke-width: 1;
opacity: 0.35;
transition:
opacity 0.15s ease,
stroke-width 0.15s ease;
}
.connections line.active {
stroke: url(#conn);
opacity: 0.75;
}
.connections line.focus {
opacity: 0.95;
stroke-width: 2;
}
.neuron {
cursor: pointer;
}
.neuron circle {
fill: rgba(var(--vp-c-brand-rgb), 0.1);
stroke: var(--vp-c-divider);
stroke-width: 2;
transition:
transform 0.15s ease,
fill 0.15s ease,
stroke 0.15s ease;
}
.neuron.active circle {
fill: rgba(var(--vp-c-brand-rgb), 0.25);
stroke: var(--vp-c-brand);
}
.neuron.focus circle {
transform: scale(1.06);
}
.neuron text {
font-size: 10px;
fill: var(--vp-c-text-2);
font-weight: 700;
}
.hint {
margin-top: 0.6rem;
color: var(--vp-c-text-2);
font-size: 0.85rem;
}
.layers {
display: grid;
grid-template-columns: 1fr;
gap: 0.5rem;
}
.layer {
text-align: left;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
border-radius: 8px;
padding: 0.75rem;
cursor: pointer;
}
.layer.active {
border-color: var(--vp-c-brand);
box-shadow: 0 0 0 3px rgba(var(--vp-c-brand-rgb), 0.12);
}
.layer-name {
font-weight: 900;
}
.layer-desc {
margin-top: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.9rem;
line-height: 1.5;
}
.explain {
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--vp-c-divider);
}
.explain-title {
font-weight: 900;
}
.explain-text {
margin-top: 0.25rem;
color: var(--vp-c-text-2);
line-height: 1.6;
}
</style>
@@ -1,115 +1,225 @@
<template>
<div class="rule-learning-demo">
<div class="demo-grid">
<!-- Rule Based System -->
<div class="panel rule-based">
<div class="panel-header">
<span class="icon">📜</span> Rule-Based System
<div class="header">
<div class="title">
规则 vs 学习你写阈值还是让模型从数据里推断阈值
</div>
<div class="subtitle">
右侧允许你自己添加样本点击训练只做一次计算不会自动连着做下一步
</div>
</div>
<div class="grid">
<div class="card">
<div class="card-title">规则系统手写 If/Else</div>
<div class="row">
<label class="label">阈值 size &gt;</label>
<input
v-model.number="ruleThreshold"
type="number"
min="1"
max="10"
class="input"
/>
<span class="muted">你必须明确写出来</span>
</div>
<div class="panel-body">
<div class="code-block">
if (size >
<input v-model="ruleThreshold" type="number" class="mini-input" />)
{<br />
&nbsp;&nbsp;return "Big 🍎"<br />
} else {<br />
&nbsp;&nbsp;return "Small 🍒"<br />
}
<div class="row">
<label class="label">测试输入 size</label>
<input
v-model.number="testInput"
type="range"
min="1"
max="10"
class="range"
/>
<code class="mono">{{ testInput }}</code>
</div>
<div
class="result"
:class="{
good: ruleResult.label === '🍎',
bad: ruleResult.label === '🍒'
}"
>
<div class="result-title">输出</div>
<div class="result-value">{{ ruleResult.text }}</div>
<div class="result-note mono">
if (size &gt; {{ ruleThreshold }}) return 🍎 else return 🍒
</div>
<div class="test-area">
Test Input:
<input
v-model="testInput"
type="range"
min="1"
max="10"
class="slider"
/>
{{ testInput }}
<div
class="result-box"
:class="ruleResult === 'Big 🍎' ? 'big' : 'small'"
>
Result: {{ ruleResult }}
</div>
</div>
<div class="note">You must explicitly program the rule.</div>
</div>
<div class="hint">
当环境变化比如苹果平均变小了你需要手动改规则规则越多维护成本越高
</div>
</div>
<!-- Machine Learning System -->
<div class="panel learning">
<div class="panel-header">
<span class="icon">🧠</span> Machine Learning
</div>
<div class="panel-body">
<div class="training-data">
<div class="data-point" v-for="(p, i) in trainingData" :key="i">
{{ p.size }}={{ p.label }}
</div>
<button class="train-btn" @click="trainModel" :disabled="isTrained">
{{ isTrained ? 'Model Trained ✅' : '⚡ Train Model' }}
</button>
</div>
<div class="card">
<div class="card-title">机器学习从样本推断边界</div>
<div class="test-area">
Test Input:
<input
v-model="testInput"
type="range"
min="1"
max="10"
class="slider"
/>
{{ testInput }}
<div
class="result-box"
:class="mlResult === 'Big 🍎' ? 'big' : 'small'"
>
Result: {{ mlResult }}
<div class="row">
<label class="label">添加训练样本</label>
<input
v-model.number="newSize"
type="number"
min="1"
max="10"
class="input"
/>
<select v-model="newLabel" class="select">
<option value="🍒">🍒 樱桃</option>
<option value="🍎">🍎 苹果</option>
</select>
<button class="btn" @click="addSample">添加</button>
</div>
<div class="samples">
<div v-if="trainingData.length === 0" class="empty muted">
还没有样本先添加 2-4 个样本再训练
</div>
<div v-else class="chips">
<div v-for="(p, i) in trainingData" :key="p.id" class="chip">
<span class="mono">{{ p.size }}</span>
<span class="sep"></span>
<span class="chip-label">{{ p.label }}</span>
<button class="chip-x" @click="removeSample(i)">×</button>
</div>
</div>
<div class="note">
Model "learned" threshold is ~{{ learnedThreshold }}. <br />
(Derived from data, not coded)
</div>
<div class="controls">
<button
class="btn primary"
@click="train"
:disabled="trainingData.length < 2"
>
训练推断阈值
</button>
<button class="btn" @click="resetLearning">重置样本</button>
</div>
<div class="row">
<label class="label">测试输入 size</label>
<input
v-model.number="testInput"
type="range"
min="1"
max="10"
class="range"
/>
<code class="mono">{{ testInput }}</code>
</div>
<div
class="result"
:class="{
good: mlResult.label === '🍎',
bad: mlResult.label === '🍒'
}"
>
<div class="result-title">输出</div>
<div class="result-value">{{ mlResult.text }}</div>
<div class="result-note">
<span class="muted">学习到的阈值</span>
<code class="mono">{{ learnedThresholdDisplay }}</code>
</div>
</div>
<div class="hint">
这里的训练是极简示意用样本推断一个分界点阈值真实模型会用更复杂的损失函数与优化算法
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { computed, ref } from 'vue'
const testInput = ref(5)
// Rule Based Logic
// Rule based
const ruleThreshold = ref(6)
const ruleResult = computed(() => {
return testInput.value > ruleThreshold.value ? 'Big 🍎' : 'Small 🍒'
const isApple = testInput.value > ruleThreshold.value
return {
label: isApple ? '🍎' : '🍒',
text: isApple ? 'Big 🍎' : 'Small 🍒'
}
})
// ML Logic
const trainingData = [
{ size: 2, label: '🍒' },
{ size: 3, label: '🍒' },
{ size: 8, label: '🍎' },
{ size: 9, label: '🍎' }
]
const isTrained = ref(false)
const learnedThreshold = ref(5.5) // Simplified mock learning
// Learning (toy)
let idCounter = 0
const trainingData = ref([
{ id: idCounter++, size: 2, label: '🍒' },
{ id: idCounter++, size: 3, label: '🍒' },
{ id: idCounter++, size: 8, label: '🍎' },
{ id: idCounter++, size: 9, label: '🍎' }
])
const trainModel = () => {
// Simulate training delay
setTimeout(() => {
isTrained.value = true
}, 500)
const newSize = ref(5)
const newLabel = ref('🍒')
const isTrained = ref(false)
const learnedThreshold = ref(5.5)
const addSample = () => {
const size = Math.max(1, Math.min(10, Number(newSize.value)))
trainingData.value.push({ id: idCounter++, size, label: newLabel.value })
isTrained.value = false
}
const removeSample = (index) => {
trainingData.value.splice(index, 1)
isTrained.value = false
}
const inferThreshold = () => {
const cherries = trainingData.value
.filter((p) => p.label === '🍒')
.map((p) => p.size)
const apples = trainingData.value
.filter((p) => p.label === '🍎')
.map((p) => p.size)
if (cherries.length === 0 || apples.length === 0) return null
const maxCherry = Math.max(...cherries)
const minApple = Math.min(...apples)
return (maxCherry + minApple) / 2
}
const train = () => {
const t = inferThreshold()
if (t === null) {
isTrained.value = false
return
}
learnedThreshold.value = t
isTrained.value = true
}
const resetLearning = () => {
trainingData.value = []
isTrained.value = false
learnedThreshold.value = 5.5
}
const learnedThresholdDisplay = computed(() => {
if (!isTrained.value) return '未训练'
return learnedThreshold.value.toFixed(2)
})
const mlResult = computed(() => {
if (!isTrained.value) return '❓ Untrained'
return testInput.value > learnedThreshold.value ? 'Big 🍎' : 'Small 🍒'
if (!isTrained.value) {
return { label: '❓', text: 'Untrained / 未训练' }
}
const isApple = testInput.value > learnedThreshold.value
return {
label: isApple ? '🍎' : '🍒',
text: isApple ? 'Big 🍎' : 'Small 🍒'
}
})
</script>
@@ -118,135 +228,194 @@ const mlResult = computed(() => {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem;
padding: 1.5rem;
margin: 1rem 0;
color: var(--vp-c-text-1);
}
.demo-grid {
.header {
margin-bottom: 1rem;
}
.title {
font-weight: 800;
}
.subtitle {
margin-top: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.9rem;
}
.grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
gap: 0.75rem;
}
@media (max-width: 640px) {
.demo-grid {
@media (max-width: 720px) {
.grid {
grid-template-columns: 1fr;
}
}
.panel {
background: var(--vp-c-bg);
.card {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
overflow: hidden;
background: var(--vp-c-bg);
padding: 1rem;
}
.panel-header {
padding: 0.8rem;
background: var(--vp-c-bg-alt);
font-weight: bold;
border-bottom: 1px solid var(--vp-c-divider);
.card-title {
font-weight: 900;
margin-bottom: 0.75rem;
}
.row {
display: flex;
gap: 0.5rem;
align-items: center;
flex-wrap: wrap;
margin-bottom: 0.6rem;
}
.label {
font-weight: 800;
color: var(--vp-c-text-1);
}
.input,
.select {
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
border-radius: 6px;
padding: 0.4rem 0.5rem;
font-weight: 700;
}
.input {
width: 84px;
}
.select {
min-width: 140px;
}
.range {
width: 220px;
max-width: 100%;
}
.mono {
font-family: var(--vp-font-family-mono);
}
.muted {
color: var(--vp-c-text-2);
}
.btn {
padding: 0.45rem 0.7rem;
border-radius: 6px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
cursor: pointer;
font-weight: 800;
font-size: 0.875rem;
}
.btn.primary {
background: var(--vp-c-brand);
border-color: var(--vp-c-brand);
color: var(--vp-c-bg);
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.samples {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 0.75rem;
background: var(--vp-c-bg-soft);
margin-bottom: 0.75rem;
}
.chips {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.panel-body {
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.code-block {
background: var(--vp-c-bg-alt);
color: var(--vp-c-text-1);
padding: 0.8rem;
border-radius: 4px;
font-family: var(--vp-font-family-mono);
font-size: 0.8rem;
.chip {
display: inline-flex;
align-items: center;
gap: 0.35rem;
padding: 0.2rem 0.55rem;
border-radius: 999px;
border: 1px solid var(--vp-c-divider);
}
.mini-input {
width: 40px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-1);
border-radius: 2px;
text-align: center;
font-weight: 800;
}
.test-area {
background: var(--vp-c-bg-soft);
padding: 0.8rem;
border-radius: 6px;
text-align: center;
.sep {
color: var(--vp-c-text-2);
}
.slider {
width: 100%;
margin: 0.5rem 0;
.chip-x {
margin-left: 0.2rem;
border: none;
background: transparent;
cursor: pointer;
color: var(--vp-c-text-2);
font-size: 1rem;
line-height: 1;
}
.result-box {
margin-top: 0.5rem;
font-weight: bold;
font-size: 1.1rem;
padding: 0.5rem;
border-radius: 4px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
}
.result-box.big {
color: var(--vp-c-red-1, #ef4444);
border-color: rgba(var(--vp-c-brand-rgb), 0.18);
background: var(--vp-c-bg);
}
.result-box.small {
color: var(--vp-c-text-1);
border-color: rgba(var(--vp-c-brand-rgb), 0.18);
background: var(--vp-c-bg);
}
.note {
font-size: 0.75rem;
color: var(--vp-c-text-3);
font-style: italic;
text-align: center;
}
.training-data {
.controls {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
justify-content: center;
margin-bottom: 0.5rem;
margin: 0.25rem 0 0.75rem;
}
.data-point {
background: var(--vp-c-bg-alt);
.result {
border: 1px solid var(--vp-c-divider);
padding: 2px 6px;
border-radius: 4px;
font-size: 0.7rem;
color: var(--vp-c-text-2);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 0.75rem;
margin: 0.5rem 0;
}
.train-btn {
width: 100%;
padding: 0.5rem;
background: var(--vp-c-brand);
color: var(--vp-c-bg);
border: 1px solid var(--vp-c-brand);
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
.result.good {
border-color: rgba(var(--vp-c-brand-rgb), 0.35);
}
.train-btn:disabled {
background: var(--vp-c-bg);
border-color: var(--vp-c-divider);
.result-title {
font-weight: 900;
color: var(--vp-c-text-1);
}
.result-value {
margin-top: 0.25rem;
font-weight: 900;
font-size: 1.1rem;
}
.result-note {
margin-top: 0.35rem;
color: var(--vp-c-text-2);
cursor: not-allowed;
font-size: 0.85rem;
}
.hint {
margin-top: 0.5rem;
color: var(--vp-c-text-2);
font-size: 0.85rem;
line-height: 1.6;
}
</style>
@@ -0,0 +1,320 @@
<template>
<div class="prompt-templates-demo">
<div class="header">
<div class="title">
<div class="h">常见场景模板标签切换可直接复制</div>
<div class="sub">选一个场景 复制 把占位符替换成你的内容</div>
</div>
<div class="actions">
<input
v-model="q"
class="search"
placeholder="搜索模板(如:会议 / debug / 翻译)"
/>
<button class="btn" @click="copy(active.template)" :disabled="!active">
{{ copied ? '已复制' : '复制模板' }}
</button>
</div>
</div>
<div class="tabs">
<button
v-for="t in filtered"
:key="t.id"
class="tab"
:class="{ active: activeId === t.id }"
@click="select(t.id)"
>
{{ t.title }}
<span class="tag">{{ t.category }}</span>
</button>
<div v-if="filtered.length === 0" class="empty">
没搜到匹配模板{{ q }}
</div>
</div>
<div v-if="active" class="content">
<div class="meta">
<div class="desc">{{ active.desc }}</div>
<div v-if="active.note" class="note">{{ active.note }}</div>
</div>
<pre class="code"><code>{{ active.template }}</code></pre>
</div>
</div>
</template>
<script setup>
import { computed, ref } from 'vue'
const q = ref('')
const copied = ref(false)
const templates = [
{
id: 'summary-boss',
category: '总结',
title: '总结给老板',
desc: '适合把长文压缩成“结论 + 要点 + 下一步”。',
template: `任务:把下面文本总结给“忙碌的老板”。\n要求:\n- 3 个要点\n- 1 句结论\n- 1 个下一步建议\n输出:Markdown\n文本:\n\`\`\`text\n[粘贴原文]\n\`\`\`\n`
},
{
id: 'extract-json',
category: '抽取',
title: '抽取成 JSON',
desc: '适合把非结构化文本转成可直接给程序用的数据。',
template: `任务:从文本中抽取信息。\n输出:只输出 JSON(不要解释)。\nJSON 结构:\n\`\`\`json\n{\n \"title\": \"\",\n \"date\": \"\",\n \"people\": [],\n \"actions\": []\n}\n\`\`\`\n文本:\n\`\`\`text\n[粘贴原文]\n\`\`\`\n`
},
{
id: 'rewrite-clear',
category: '改写',
title: '润色改写',
desc: '适合把口语/混乱的内容变得更清晰、更像“正式输出”。',
template: `任务:把下面文字改写得更清晰、更有条理,但不要改变事实含义。\n要求:\n- 保留关键信息与数字\n- 语气:专业但不生硬\n- 每段不超过 2 句\n输出:Markdown\n原文:\n\`\`\`text\n[粘贴原文]\n\`\`\`\n`
},
{
id: 'translate-deliver',
category: '翻译',
title: '翻译可交付',
desc: '适合跨语言交付,强调术语一致与结构保留。',
template: `任务:把下面内容翻译成英文(或你指定的语言)。\n要求:\n- 术语保持一致(不确定就给 2 个备选译法并说明差异)\n- 保留标题层级与列表结构\n输出:Markdown\n原文:\n\`\`\`text\n[粘贴原文]\n\`\`\`\n`
},
{
id: 'brainstorm-12',
category: '脑暴',
title: '12 个不同想法',
desc: '适合需要“多样性”,而不是唯一正确答案。',
template: `任务:为下面的问题给出 12 个不同方向的想法。\n要求:\n- 每条 <= 20 字\n- 覆盖不同角度(用户/技术/商业/运营/风险)\n输出:Markdown 列表\n问题:\n\`\`\`text\n[描述你的问题/目标/限制条件]\n\`\`\`\n`
},
{
id: 'design-solution',
category: '方案',
title: '方案设计(先澄清)',
desc: '适合复杂问题:先补信息,再给架构与任务拆分。',
template: `你是资深架构师。\n任务:为下面需求给出一个可落地的技术方案。\n要求:\n1) 先列 5 个澄清问题(缺信息就问)\n2) 再给方案(架构图用文字描述也行)\n3) 列出关键权衡(至少 3 条)\n4) 给一份 1-2 周可执行的任务拆分(按天/按模块)\n输出:Markdown\n需求:\n\`\`\`text\n[粘贴需求]\n\`\`\`\n`
},
{
id: 'meeting-minutes',
category: '会议',
title: '会议纪要(行动化)',
desc: '适合把“记录”整理成能执行的清单。',
template: `任务:把下面会议记录整理成可执行的纪要。\n要求:\n- 结论(1-3 条)\n- 决策(谁决定了什么)\n- Action Items(负责人 / 截止时间 / 交付物)\n- 风险与待确认项\n输出:Markdown\n会议记录:\n\`\`\`text\n[粘贴原文]\n\`\`\`\n`
},
{
id: 'support-reply',
category: '沟通',
title: '客服回复',
desc: '适合稳定语气 + 降低误解 + 引导用户补信息。',
template: `你是专业客服/技术支持。\n任务:给用户回复下面这条消息。\n要求:\n- 先共情一句(不要道歉过度)\n- 用 3 步指导用户排查(每步 1 句)\n- 如需更多信息,列出你需要用户提供的 3 个信息\n- 语气:友好、清晰、少术语\n输出:Markdown\n用户消息:\n\`\`\`text\n[粘贴原文]\n\`\`\`\n`
},
{
id: 'debug-fix',
category: 'Debug',
title: '定位并修复',
desc: '适合线上/本地问题:先按概率列原因,再给验证与最终修复。',
template: `你是资深工程师。\n任务:根据下面信息定位问题并给出修复方案。\n要求:\n1) 先列最可能的 3 个原因(按概率排序)\n2) 每个原因给一个最小验证步骤\n3) 给出最终修复(包含代码片段/配置)\n输出:Markdown\n上下文:\n\`\`\`text\n[项目/环境/版本信息]\n\`\`\`\n报错与日志:\n\`\`\`text\n[粘贴错误信息/日志]\n\`\`\`\n相关代码:\n\`\`\`text\n[粘贴代码]\n\`\`\`\n`
},
{
id: 'table-track',
category: '结构化',
title: '整理成表格追踪',
desc: '适合把大段内容变成可执行/可追踪事项。',
template: `任务:把下面内容整理成表格,方便执行与追踪。\n要求:\n- 输出一个 Markdown 表格\n- 列:事项 / 负责人 / 截止时间 / 当前状态 / 备注\n- 如无负责人/截止时间,用“待定”\n原文:\n\`\`\`text\n[粘贴原文]\n\`\`\`\n`
},
{
id: 'self-check',
category: '验收',
title: '自检清单',
desc: '适合让输出“可验收”:最后强制自检,减少跑偏。',
template: `任务:完成下面任务,并在最后做自检。\n要求:\n- 输出最后加一段“自检清单”:逐条回答是否满足(是/否/不适用)\n- 如果不满足,说明原因并给出改进版本\n任务:\n\`\`\`text\n[描述你的任务]\n\`\`\`\n约束(可选):\n\`\`\`text\n[长度/格式/必须包含/必须避免]\n\`\`\`\n`
},
{
id: 'code-review',
category: '工程',
title: '代码审查(先清单)',
desc: '适合做结构化 Review:先给检查清单,再提问题与修复片段。',
template: `你是资深工程师。\n任务:审查下面代码。\n要求:\n1) 先列检查清单(3-5条)\n2) 再列问题(现象/原因/修复)\n3) 最后给修复片段\n代码:\n\`\`\`text\n[粘贴代码]\n\`\`\`\n`
}
]
const filtered = computed(() => {
const s = q.value.trim().toLowerCase()
if (!s) return templates
return templates.filter((t) => {
const hay = `${t.category} ${t.title} ${t.desc}`.toLowerCase()
return hay.includes(s)
})
})
const activeId = ref(templates[0].id)
const active = computed(
() => templates.find((t) => t.id === activeId.value) || templates[0]
)
const select = (id) => {
activeId.value = id
copied.value = false
}
const copy = async (text) => {
try {
await navigator.clipboard.writeText(text)
copied.value = true
setTimeout(() => {
copied.value = false
}, 800)
} catch {
copied.value = false
}
}
</script>
<style scoped>
.prompt-templates-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1.25rem;
margin: 1rem 0;
}
.header {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 1rem;
margin-bottom: 0.75rem;
}
.title .h {
font-weight: 800;
color: var(--vp-c-text-1);
font-size: 1rem;
}
.title .sub {
margin-top: 0.25rem;
color: var(--vp-c-text-2);
font-size: 0.875rem;
}
.actions {
display: flex;
gap: 0.5rem;
align-items: center;
flex-wrap: wrap;
justify-content: flex-end;
}
.search {
min-width: 220px;
padding: 0.45rem 0.6rem;
border-radius: 6px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
}
.btn {
padding: 0.45rem 0.75rem;
border-radius: 6px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
cursor: pointer;
font-weight: 700;
}
.btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.tabs {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin: 0.75rem 0 1rem;
}
.tab {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.45rem 0.75rem;
border-radius: 999px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg);
color: var(--vp-c-text-1);
cursor: pointer;
font-weight: 700;
font-size: 0.875rem;
}
.tab.active {
border-color: rgba(var(--vp-c-brand-rgb), 0.35);
box-shadow: 0 0 0 3px rgba(var(--vp-c-brand-rgb), 0.12);
}
.tag {
font-size: 0.75rem;
padding: 0.15rem 0.5rem;
border-radius: 999px;
border: 1px solid var(--vp-c-divider);
background: var(--vp-c-bg-soft);
color: var(--vp-c-text-2);
font-weight: 600;
}
.empty {
color: var(--vp-c-text-2);
font-size: 0.875rem;
padding: 0.5rem 0.25rem;
}
.meta {
margin-bottom: 0.75rem;
}
.desc {
color: var(--vp-c-text-1);
line-height: 1.6;
}
.note {
margin-top: 0.5rem;
padding: 0.75rem;
border-radius: 6px;
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
color: var(--vp-c-text-2);
}
.code {
margin: 0;
padding: 0.75rem;
border-radius: 8px;
background: var(--vp-c-bg-alt);
border: 1px solid var(--vp-c-divider);
overflow-x: auto;
color: var(--vp-c-text-1);
font-family: var(--vp-font-family-mono);
font-size: 0.875rem;
line-height: 1.6;
white-space: pre-wrap;
}
@media (max-width: 720px) {
.header {
flex-direction: column;
align-items: stretch;
}
.actions {
justify-content: flex-start;
}
.search {
min-width: 0;
width: 100%;
}
}
</style>
+2
View File
@@ -194,6 +194,7 @@ import PromptQuickStartDemo from './components/appendix/prompt-engineering/Promp
import PromptComparisonDemo from './components/appendix/prompt-engineering/PromptComparisonDemo.vue'
import FewShotDemo from './components/appendix/prompt-engineering/FewShotDemo.vue'
import ChainOfThoughtDemo from './components/appendix/prompt-engineering/ChainOfThoughtDemo.vue'
import PromptTemplatesDemo from './components/appendix/prompt-engineering/PromptTemplatesDemo.vue'
// Context Engineering Components
import AgentContextFlow from './components/appendix/context-engineering/AgentContextFlow.vue'
@@ -449,6 +450,7 @@ export default {
app.component('PromptComparisonDemo', PromptComparisonDemo)
app.component('FewShotDemo', FewShotDemo)
app.component('ChainOfThoughtDemo', ChainOfThoughtDemo)
app.component('PromptTemplatesDemo', PromptTemplatesDemo)
// Context Engineering Components Registration
app.component('AgentContextFlow', AgentContextFlow)