feat: update prompt engineering docs and AI history demos
This commit is contained in:
@@ -1,142 +1,78 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ai-evolution-demo">
|
<div class="ai-evolution-demo">
|
||||||
<!-- Timeline -->
|
<div class="header">
|
||||||
<div class="timeline-container">
|
<div class="title">AI 进化:规则 → 学习 → 生成</div>
|
||||||
<div class="timeline-track"></div>
|
<div class="subtitle">
|
||||||
|
点击切换阶段;不自动推进,避免“点一下就连续发生很多事”的误解。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tabs" role="tablist" aria-label="AI Evolution Stages">
|
||||||
<button
|
<button
|
||||||
v-for="(stage, index) in stages"
|
v-for="(stage, index) in stages"
|
||||||
:key="index"
|
:key="stage.key"
|
||||||
class="timeline-node"
|
class="tab"
|
||||||
:class="{
|
:class="{ active: currentStage === index }"
|
||||||
active: currentStage === index,
|
role="tab"
|
||||||
passed: currentStage > index
|
:aria-selected="currentStage === index"
|
||||||
}"
|
|
||||||
@click="currentStage = index"
|
@click="currentStage = index"
|
||||||
>
|
>
|
||||||
<div class="node-dot">
|
<div class="tab-year">{{ stage.year }}</div>
|
||||||
<div class="inner-dot"></div>
|
<div class="tab-label">{{ stage.label }}</div>
|
||||||
</div>
|
|
||||||
<div class="node-content">
|
|
||||||
<span class="year-badge">{{ stage.year }}</span>
|
|
||||||
<span class="node-label">{{ stage.label }}</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Content -->
|
<div class="stage">
|
||||||
<div class="content-wrapper">
|
<div class="stage-head">
|
||||||
<transition name="fade-slide" mode="out-in">
|
<div class="stage-title">{{ stages[currentStage].title }}</div>
|
||||||
<div :key="currentStage" class="stage-content">
|
<div class="stage-desc">{{ stages[currentStage].desc }}</div>
|
||||||
<div class="header-section">
|
</div>
|
||||||
<h3>
|
|
||||||
<span class="stage-index"
|
<div class="grid">
|
||||||
>{{ indexToRoman(currentStage + 1) }}.</span
|
<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
|
||||||
>
|
>
|
||||||
{{ stages[currentStage].title }}
|
</div>
|
||||||
</h3>
|
<div class="note">{{ stages[currentStage].appDesc }}</div>
|
||||||
<p>{{ stages[currentStage].desc }}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="visualization-grid">
|
<div class="card full">
|
||||||
<!-- Concept/Logic View -->
|
<div class="card-title">优势 / 局限</div>
|
||||||
<div class="mac-window concept-window">
|
<div class="two-col">
|
||||||
<div class="window-bar">
|
<div class="col">
|
||||||
<div class="traffic-lights">
|
<div class="col-title">优势</div>
|
||||||
<span class="light red"></span>
|
<ul class="list">
|
||||||
<span class="light yellow"></span>
|
<li v-for="(item, i) in stages[currentStage].pros" :key="i">
|
||||||
<span class="light green"></span>
|
{{ item }}
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="window-title">Core Logic</div>
|
<div class="col">
|
||||||
</div>
|
<div class="col-title">局限</div>
|
||||||
<div class="concept-canvas">
|
<ul class="list">
|
||||||
<!-- Stage 0: Symbolism -->
|
<li v-for="(item, i) in stages[currentStage].cons" :key="i">
|
||||||
<div v-if="currentStage === 0" class="vis-symbolism">
|
{{ item }}
|
||||||
<div class="logic-gate">
|
</li>
|
||||||
<div class="input-group">
|
</ul>
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</transition>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -146,475 +82,226 @@ import { ref } from 'vue'
|
|||||||
|
|
||||||
const currentStage = ref(0)
|
const currentStage = ref(0)
|
||||||
|
|
||||||
const indexToRoman = (num) => {
|
|
||||||
const map = { 1: 'I', 2: 'II', 3: 'III', 4: 'IV' }
|
|
||||||
return map[num] || num
|
|
||||||
}
|
|
||||||
|
|
||||||
const stages = [
|
const stages = [
|
||||||
{
|
{
|
||||||
year: '1950s-1970s',
|
key: 'symbolic',
|
||||||
label: 'Symbolism',
|
year: '1950s–1980s',
|
||||||
title: 'The Dawn: Logic & Rules',
|
label: '符号主义',
|
||||||
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.',
|
title: '规则与逻辑推理(专家系统)',
|
||||||
icon: '♟️',
|
desc: '相信“智能 = 规则 + 推理”。把专家经验写成 If/Then 规则与知识库。',
|
||||||
appTitle: 'Chess & Logic',
|
core: [
|
||||||
|
'知识用“符号/规则”表达:If 条件 Then 结论',
|
||||||
|
'推理引擎按规则匹配、触发、推导',
|
||||||
|
'可解释:能指出用了哪条规则'
|
||||||
|
],
|
||||||
|
pros: ['可解释性强', '在边界明确的垂直领域有效'],
|
||||||
|
cons: [
|
||||||
|
'规则写不完(组合爆炸)',
|
||||||
|
'脆弱:世界稍变就失效',
|
||||||
|
'难处理不确定性与常识'
|
||||||
|
],
|
||||||
|
examples: ['专家系统', 'MYCIN', '逻辑推理'],
|
||||||
appDesc:
|
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',
|
year: '2010s',
|
||||||
label: 'Deep Learning',
|
label: '深度学习',
|
||||||
title: 'Connectionism & Big Data',
|
title: '从数据中学习(连接主义)',
|
||||||
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.',
|
desc: '相信“智能 = 表示学习 + 统计优化”。用神经网络从大量数据里自动学特征与决策边界。',
|
||||||
icon: '🧠',
|
core: [
|
||||||
appTitle: 'AlphaGo & FaceID',
|
'用参数(权重)表示知识;通过优化让参数拟合数据',
|
||||||
|
'特征提取从“手写规则”变成“自动学习”',
|
||||||
|
'数据、算力、算法(GPU + 大数据 + 网络结构)共同推动'
|
||||||
|
],
|
||||||
|
pros: ['强大的模式识别能力', '同一范式覆盖多任务(视觉/语音/推荐等)'],
|
||||||
|
cons: ['数据需求大', '可解释性较弱', '对分布外/对抗样本敏感'],
|
||||||
|
examples: ['AlexNet', 'ImageNet', 'AlphaGo'],
|
||||||
appDesc:
|
appDesc:
|
||||||
'AI learned to see (ImageNet), hear (Siri), and play Go (AlphaGo). It surpassed humans in specific perceptual tasks.'
|
'擅长“感知类”任务(图像、语音、推荐);但对“为何这么判”解释不够直观,且对数据分布较敏感。'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
key: 'genai',
|
||||||
year: '2020s+',
|
year: '2020s+',
|
||||||
label: 'Generative AI',
|
label: '生成式 AI',
|
||||||
title: 'Generative Intelligence (LLMs)',
|
title: '从“分类”到“生成”(大模型)',
|
||||||
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).',
|
desc: '用 Transformer 建模上下文关系,学习“下一 token”分布,从而能生成文本/代码/图像等新内容。',
|
||||||
icon: '✨',
|
core: [
|
||||||
appTitle: 'ChatGPT & Midjourney',
|
'统一接口:给提示词(prompt)→ 生成输出',
|
||||||
|
'能力来源:规模化预训练 + 指令微调/对齐',
|
||||||
|
'把很多任务“变成一个生成问题”'
|
||||||
|
],
|
||||||
|
pros: ['通用性强(多任务)', '交互友好(自然语言接口)'],
|
||||||
|
cons: [
|
||||||
|
'可能幻觉',
|
||||||
|
'安全与权限边界复杂',
|
||||||
|
'需要系统化评测与约束(格式/工具/检索)'
|
||||||
|
],
|
||||||
|
examples: ['ChatGPT', 'GPT-4', 'Midjourney'],
|
||||||
appDesc:
|
appDesc:
|
||||||
'AI that can write code, poetry, paint images, and reason across multiple domains. A step towards AGI (General Intelligence).'
|
'更像“通用助手”:能写、能改、能解释、能生成;但要通过提示词、上下文与工具链把它约束到可验收、可控。'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.ai-evolution-demo {
|
.ai-evolution-demo {
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--vp-c-bg-soft);
|
|
||||||
border: 1px solid var(--vp-c-divider);
|
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);
|
background: var(--vp-c-bg-soft);
|
||||||
display: flex;
|
border-radius: 8px;
|
||||||
justify-content: space-between;
|
padding: 1.5rem;
|
||||||
position: relative;
|
margin: 1rem 0;
|
||||||
border-bottom: 1px solid var(--vp-c-divider);
|
color: var(--vp-c-text-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-track {
|
.header {
|
||||||
position: absolute;
|
margin-bottom: 1rem;
|
||||||
top: 2.5rem;
|
|
||||||
left: 3rem;
|
|
||||||
right: 3rem;
|
|
||||||
height: 2px;
|
|
||||||
background: var(--vp-c-divider);
|
|
||||||
z-index: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-node {
|
.title {
|
||||||
position: relative;
|
font-weight: 800;
|
||||||
z-index: 1;
|
color: var(--vp-c-text-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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-node:hover {
|
.subtitle {
|
||||||
opacity: 0.9;
|
margin-top: 0.25rem;
|
||||||
}
|
color: var(--vp-c-text-2);
|
||||||
.timeline-node.active,
|
font-size: 0.9rem;
|
||||||
.timeline-node.passed {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-dot {
|
.tabs {
|
||||||
width: 16px;
|
display: grid;
|
||||||
height: 16px;
|
grid-template-columns: repeat(3, 1fr);
|
||||||
border-radius: 50%;
|
gap: 0.5rem;
|
||||||
|
margin: 0.75rem 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
text-align: left;
|
||||||
|
border: 1px solid var(--vp-c-divider);
|
||||||
background: var(--vp-c-bg);
|
background: var(--vp-c-bg);
|
||||||
border: 2px solid var(--vp-c-text-3);
|
color: var(--vp-c-text-1);
|
||||||
margin-bottom: 0.8rem;
|
border-radius: 8px;
|
||||||
display: flex;
|
padding: 0.6rem 0.75rem;
|
||||||
align-items: center;
|
cursor: pointer;
|
||||||
justify-content: center;
|
transition:
|
||||||
transition: all 0.3s;
|
border-color 0.2s ease,
|
||||||
|
box-shadow 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inner-dot {
|
.tab:hover {
|
||||||
width: 0;
|
border-color: rgba(var(--vp-c-brand-rgb), 0.55);
|
||||||
height: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: var(--vp-c-brand);
|
|
||||||
transition: all 0.3s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.timeline-node.active .node-dot {
|
.tab.active {
|
||||||
border-color: var(--vp-c-brand);
|
border-color: var(--vp-c-brand);
|
||||||
transform: scale(1.3);
|
box-shadow: 0 0 0 3px rgba(var(--vp-c-brand-rgb), 0.12);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.node-content {
|
.tab-year {
|
||||||
text-align: center;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.year-badge {
|
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
font-family: var(--vp-font-family-mono);
|
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 {
|
.tab-label {
|
||||||
font-size: 0.85rem;
|
margin-top: 0.15rem;
|
||||||
font-weight: 600;
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage-head {
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stage-title {
|
||||||
|
font-weight: 900;
|
||||||
color: var(--vp-c-text-1);
|
color: var(--vp-c-text-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Content Area */
|
.stage-desc {
|
||||||
.content-wrapper {
|
margin-top: 0.25rem;
|
||||||
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;
|
|
||||||
color: var(--vp-c-text-2);
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 0.95rem;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Visualization */
|
.grid {
|
||||||
.visualization-grid {
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 2rem;
|
gap: 0.75rem;
|
||||||
align-items: stretch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 720px) {
|
||||||
.visualization-grid {
|
.tabs {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mac-window {
|
.card {
|
||||||
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 {
|
|
||||||
border: 1px solid var(--vp-c-divider);
|
border: 1px solid var(--vp-c-divider);
|
||||||
background: var(--vp-c-bg);
|
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;
|
border-radius: 8px;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: var(--vp-c-bg);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
}
|
||||||
.msg {
|
|
||||||
padding: 6px 10px;
|
.card.full {
|
||||||
border-radius: 12px;
|
grid-column: 1 / -1;
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
max-width: 80%;
|
|
||||||
}
|
}
|
||||||
.msg.user {
|
|
||||||
background: rgba(var(--vp-c-brand-rgb), 0.1);
|
.card-title {
|
||||||
margin-left: auto;
|
font-weight: 900;
|
||||||
color: var(--vp-c-text-1);
|
color: var(--vp-c-text-1);
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
.msg.ai {
|
|
||||||
background: var(--vp-c-bg-soft);
|
.list {
|
||||||
margin-right: auto;
|
margin: 0;
|
||||||
|
padding-left: 1.15rem;
|
||||||
color: var(--vp-c-text-1);
|
color: var(--vp-c-text-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Impact Card */
|
.pill-row {
|
||||||
.impact-card {
|
display: flex;
|
||||||
text-align: center;
|
flex-wrap: wrap;
|
||||||
}
|
gap: 0.5rem;
|
||||||
.impact-icon {
|
|
||||||
font-size: 4rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
.impact-title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 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);
|
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 */
|
.note {
|
||||||
.fade-slide-enter-active,
|
color: var(--vp-c-text-2);
|
||||||
.fade-slide-leave-active {
|
line-height: 1.6;
|
||||||
transition: all 0.4s ease;
|
|
||||||
}
|
}
|
||||||
.fade-slide-enter-from {
|
|
||||||
opacity: 0;
|
.two-col {
|
||||||
transform: translateY(20px);
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
.fade-slide-leave-to {
|
|
||||||
opacity: 0;
|
@media (max-width: 720px) {
|
||||||
transform: translateY(-20px);
|
.two-col {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-title {
|
||||||
|
font-weight: 900;
|
||||||
|
color: var(--vp-c-text-1);
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
+46
-35
@@ -46,15 +46,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="action-buttons">
|
<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 }})
|
✨ 添加规则 ({{ ruleCount }}/{{ maxRules }})
|
||||||
</button>
|
</button>
|
||||||
<button @click="autoGenerate" class="auto-btn" :disabled="autoGenerating">
|
<button @click="resetRules" class="reset-btn">🔄 重置</button>
|
||||||
⚡ 自动生成
|
|
||||||
</button>
|
|
||||||
<button @click="resetRules" class="reset-btn">
|
|
||||||
🔄 重置
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -62,7 +61,9 @@
|
|||||||
<div class="counter-display">
|
<div class="counter-display">
|
||||||
<div class="counter-label">需要的规则总数</div>
|
<div class="counter-label">需要的规则总数</div>
|
||||||
<transition name="count-update" mode="out-in">
|
<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>
|
</transition>
|
||||||
<div class="counter-formula">
|
<div class="counter-formula">
|
||||||
= {{ valuesPerFeature }}<sup>{{ featureCount }}</sup> =
|
= {{ valuesPerFeature }}<sup>{{ featureCount }}</sup> =
|
||||||
@@ -85,7 +86,10 @@
|
|||||||
<div class="rule-content">
|
<div class="rule-content">
|
||||||
<code>{{ rule.text }}</code>
|
<code>{{ rule.text }}</code>
|
||||||
</div>
|
</div>
|
||||||
<div class="rule-visual" :style="{ background: rule.gradient }"></div>
|
<div
|
||||||
|
class="rule-visual"
|
||||||
|
:style="{ background: rule.gradient }"
|
||||||
|
></div>
|
||||||
</div>
|
</div>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,7 +150,8 @@
|
|||||||
<li>...</li>
|
<li>...</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="conclusion">
|
<p class="conclusion">
|
||||||
<strong>结论</strong>:规则永远写不完,这就是为什么我们需要<strong>机器学习</strong>!
|
<strong>结论</strong
|
||||||
|
>:规则永远写不完,这就是为什么我们需要<strong>机器学习</strong>!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -159,7 +164,6 @@ const featureCount = ref(3)
|
|||||||
const valuesPerFeature = ref(3)
|
const valuesPerFeature = ref(3)
|
||||||
const ruleCount = ref(0)
|
const ruleCount = ref(0)
|
||||||
const ruleIdCounter = ref(0)
|
const ruleIdCounter = ref(0)
|
||||||
const autoGenerating = ref(false)
|
|
||||||
const displayedRules = ref([])
|
const displayedRules = ref([])
|
||||||
const maxRules = 20
|
const maxRules = 20
|
||||||
|
|
||||||
@@ -201,7 +205,8 @@ const valueOptions = computed(() => {
|
|||||||
|
|
||||||
const generateRuleText = () => {
|
const generateRuleText = () => {
|
||||||
const conditions = features.value.map((feature, index) => {
|
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 `${feature}=${value}`
|
||||||
})
|
})
|
||||||
return `IF ${conditions.join(' AND ')} THEN ...`
|
return `IF ${conditions.join(' AND ')} THEN ...`
|
||||||
@@ -217,31 +222,18 @@ const addRule = () => {
|
|||||||
displayedRules.value.push({
|
displayedRules.value.push({
|
||||||
id: ruleIdCounter.value++,
|
id: ruleIdCounter.value++,
|
||||||
text: generateRuleText(),
|
text: generateRuleText(),
|
||||||
color: getFeatureColor(Math.floor(Math.random() * featureCount.value) + 1),
|
color: getFeatureColor(
|
||||||
|
Math.floor(Math.random() * featureCount.value) + 1
|
||||||
|
),
|
||||||
gradient: generateColor()
|
gradient: generateColor()
|
||||||
})
|
})
|
||||||
ruleCount.value++
|
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 = () => {
|
const resetRules = () => {
|
||||||
displayedRules.value = []
|
displayedRules.value = []
|
||||||
ruleCount.value = 0
|
ruleCount.value = 0
|
||||||
autoGenerating.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatNumber = (num) => {
|
const formatNumber = (num) => {
|
||||||
@@ -448,8 +440,15 @@ watch([featureCount, valuesPerFeature], () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% { transform: scale(1); opacity: 0.5; }
|
0%,
|
||||||
50% { transform: scale(1.1); opacity: 0.8; }
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.1);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.counter-label {
|
.counter-label {
|
||||||
@@ -512,9 +511,16 @@ watch([featureCount, valuesPerFeature], () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shake {
|
@keyframes shake {
|
||||||
0%, 100% { transform: translateX(0); }
|
0%,
|
||||||
25% { transform: translateX(-5px); }
|
100% {
|
||||||
75% { transform: translateX(5px); }
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateX(-5px);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateX(5px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.rules-container {
|
.rules-container {
|
||||||
@@ -590,8 +596,13 @@ watch([featureCount, valuesPerFeature], () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes bounce {
|
@keyframes bounce {
|
||||||
0%, 100% { transform: translateY(0); }
|
0%,
|
||||||
50% { transform: translateY(-10px); }
|
100% {
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.warning-content h5 {
|
.warning-content h5 {
|
||||||
|
|||||||
+345
-287
@@ -1,130 +1,213 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="neural-network-viz-demo">
|
<div class="nn-viz-demo">
|
||||||
<div class="demo-header">
|
<div class="header">
|
||||||
<h4>🧠 神经网络可视化</h4>
|
<div class="title">神经网络:手动前向传播(可控演示)</div>
|
||||||
<p>观察数据如何在神经网络中流动</p>
|
<div class="subtitle">
|
||||||
|
用“开始 / 上一步 /
|
||||||
|
下一步”逐层推进,不自动播放,避免误把动画当成真实训练过程。
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<button class="btn primary" @click="start" :disabled="step !== 0">
|
||||||
|
开始
|
||||||
|
</button>
|
||||||
|
<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">
|
<div class="network-container">
|
||||||
<svg ref="svgRef" class="network-svg" :width="svgWidth" :height="svgHeight">
|
<svg class="network-svg" :viewBox="`0 0 ${svgWidth} ${svgHeight}`">
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id="connectionGradient" x1="0%" y1="0%" x2="100%" y2="0%">
|
<linearGradient id="conn" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||||
<stop
|
<stop
|
||||||
offset="0%"
|
offset="0%"
|
||||||
style="stop-color: var(--vp-c-brand); stop-opacity: 0.15"
|
:style="{
|
||||||
|
stopColor: 'var(--vp-c-brand)',
|
||||||
|
stopOpacity: 0.18
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
<stop
|
<stop
|
||||||
offset="100%"
|
offset="100%"
|
||||||
style="stop-color: var(--vp-c-brand); stop-opacity: 0.45"
|
:style="{
|
||||||
|
stopColor: 'var(--vp-c-brand)',
|
||||||
|
stopOpacity: 0.45
|
||||||
|
}"
|
||||||
/>
|
/>
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
|
|
||||||
<!-- Connections -->
|
|
||||||
<g class="connections">
|
<g class="connections">
|
||||||
<line
|
<line
|
||||||
v-for="conn in connections"
|
v-for="c in connections"
|
||||||
:key="conn.id"
|
:key="c.id"
|
||||||
:x1="conn.x1"
|
:x1="c.x1"
|
||||||
:y1="conn.y1"
|
:y1="c.y1"
|
||||||
:x2="conn.x2"
|
:x2="c.x2"
|
||||||
:y2="conn.y2"
|
:y2="c.y2"
|
||||||
:stroke-width="conn.width"
|
:class="{
|
||||||
:opacity="conn.opacity"
|
active: isConnectionActive(c),
|
||||||
stroke="url(#connectionGradient)"
|
focus: isConnectionFocus(c)
|
||||||
class="connection-line"
|
}"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
|
|
||||||
<!-- Neurons -->
|
|
||||||
<g class="neurons">
|
<g class="neurons">
|
||||||
<g
|
<g
|
||||||
v-for="neuron in neurons"
|
v-for="n in neurons"
|
||||||
:key="neuron.id"
|
:key="n.id"
|
||||||
:transform="`translate(${neuron.x}, ${neuron.y})`"
|
:transform="`translate(${n.x}, ${n.y})`"
|
||||||
class="neuron-group"
|
:class="{
|
||||||
:class="{ active: neuron.active, input: neuron.layer === 0, output: neuron.layer === layers.length - 1 }"
|
neuron: true,
|
||||||
|
active: isNeuronActive(n),
|
||||||
|
focus: focusLayer === n.layer
|
||||||
|
}"
|
||||||
|
@click="focusLayer = n.layer"
|
||||||
>
|
>
|
||||||
<circle
|
<circle :r="n.r" />
|
||||||
:r="neuron.radius"
|
<text v-if="n.label" y="32" text-anchor="middle">
|
||||||
class="neuron-circle"
|
{{ n.label }}
|
||||||
@click="activateNeuron(neuron)"
|
|
||||||
/>
|
|
||||||
<text
|
|
||||||
v-if="neuron.label"
|
|
||||||
y="30"
|
|
||||||
text-anchor="middle"
|
|
||||||
class="neuron-label"
|
|
||||||
>
|
|
||||||
{{ neuron.label }}
|
|
||||||
</text>
|
</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="layer-info">
|
<div class="hint">
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="controls">
|
<div class="card">
|
||||||
<button @click="startForwardPropagation" class="action-btn">
|
<div class="card-title">每一层在做什么</div>
|
||||||
▶️ 前向传播
|
<div class="layers">
|
||||||
</button>
|
<button
|
||||||
<button @click="resetNetwork" class="action-btn secondary">
|
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>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="explain">
|
||||||
|
<div class="explain-title">当前推进到:</div>
|
||||||
|
<div class="explain-text">{{ stepExplain }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
const svgWidth = 800
|
const svgWidth = 820
|
||||||
const svgHeight = 400
|
const svgHeight = 360
|
||||||
const currentLayer = ref(0)
|
|
||||||
const animationId = ref(null)
|
|
||||||
|
|
||||||
const layers = ref([4, 6, 6, 3]) // 输入层、2个隐藏层、输出层
|
const layers = ref([4, 6, 6, 3])
|
||||||
const layerConfigs = ref([
|
const layerConfigs = [
|
||||||
{ name: '输入层', neurons: 4, desc: '接收原始数据(如图片像素)' },
|
{ name: '输入层', desc: '接收原始输入(例如像素、特征、词向量等)。' },
|
||||||
{ name: '隐藏层 1', neurons: 6, desc: '识别边缘和简单特征' },
|
{ name: '隐藏层 1', desc: '学习更基础的组合特征(例如边缘、局部模式)。' },
|
||||||
{ name: '隐藏层 2', neurons: 6, desc: '识别形状和复杂特征' },
|
{ name: '隐藏层 2', desc: '学习更复杂的抽象表示(例如形状、语义组合)。' },
|
||||||
{ name: '输出层', neurons: 3, desc: '输出分类结果' }
|
{ name: '输出层', desc: '输出任务结果(分类概率、回归值等)。' }
|
||||||
])
|
]
|
||||||
|
|
||||||
const neurons = ref([])
|
const neurons = ref([])
|
||||||
const connections = ref([])
|
const connections = ref([])
|
||||||
|
|
||||||
// 计算神经元位置
|
const maxStep = computed(() => layers.value.length)
|
||||||
const calculateNeurons = () => {
|
const step = ref(0)
|
||||||
neurons.value = []
|
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)
|
const layerSpacing = svgWidth / (layers.value.length + 1)
|
||||||
|
|
||||||
layers.value.forEach((neuronCount, layerIndex) => {
|
layers.value.forEach((count, layerIndex) => {
|
||||||
const x = layerSpacing * (layerIndex + 1)
|
const x = layerSpacing * (layerIndex + 1)
|
||||||
const neuronSpacing = svgHeight / (neuronCount + 1)
|
const ySpacing = svgHeight / (count + 1)
|
||||||
|
|
||||||
for (let i = 0; i < neuronCount; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
const y = neuronSpacing * (i + 1)
|
const y = ySpacing * (i + 1)
|
||||||
neurons.value.push({
|
ns.push({
|
||||||
id: `${layerIndex}-${i}`,
|
id: `${layerIndex}-${i}`,
|
||||||
layer: layerIndex,
|
layer: layerIndex,
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
radius: 20,
|
r: 18,
|
||||||
active: false,
|
|
||||||
label:
|
label:
|
||||||
layerIndex === 0
|
layerIndex === 0
|
||||||
? ['像素1', '像素2', '像素3', '像素4'][i]
|
? ['像素1', '像素2', '像素3', '像素4'][i]
|
||||||
@@ -134,99 +217,36 @@ const calculateNeurons = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
// 计算连接
|
|
||||||
const calculateConnections = () => {
|
|
||||||
connections.value = []
|
|
||||||
let connId = 0
|
|
||||||
|
|
||||||
|
let id = 0
|
||||||
for (let l = 0; l < layers.value.length - 1; l++) {
|
for (let l = 0; l < layers.value.length - 1; l++) {
|
||||||
const currentLayerNeurons = neurons.value.filter((n) => n.layer === l)
|
const from = ns.filter((n) => n.layer === l)
|
||||||
const nextLayerNeurons = neurons.value.filter((n) => n.layer === l + 1)
|
const to = ns.filter((n) => n.layer === l + 1)
|
||||||
|
from.forEach((a) => {
|
||||||
currentLayerNeurons.forEach((fromNeuron) => {
|
to.forEach((b) => {
|
||||||
nextLayerNeurons.forEach((toNeuron) => {
|
cs.push({
|
||||||
connections.value.push({
|
id: id++,
|
||||||
id: connId++,
|
x1: a.x,
|
||||||
x1: fromNeuron.x,
|
y1: a.y,
|
||||||
y1: fromNeuron.y,
|
x2: b.x,
|
||||||
x2: toNeuron.x,
|
y2: b.y,
|
||||||
y2: toNeuron.y,
|
toLayer: l + 1
|
||||||
width: Math.random() * 2 + 0.5,
|
|
||||||
opacity: 0.3,
|
|
||||||
active: false
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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(() => {
|
onMounted(() => {
|
||||||
calculateNeurons()
|
layout()
|
||||||
calculateConnections()
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.neural-network-viz-demo {
|
.nn-viz-demo {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
background: var(--vp-c-bg-soft);
|
background: var(--vp-c-bg-soft);
|
||||||
@@ -235,156 +255,194 @@ onMounted(() => {
|
|||||||
color: var(--vp-c-text-1);
|
color: var(--vp-c-text-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-header {
|
.header {
|
||||||
text-align: center;
|
margin-bottom: 1rem;
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-header h4 {
|
.title {
|
||||||
margin: 0 0 0.5rem 0;
|
font-weight: 800;
|
||||||
color: var(--vp-c-text-1);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.demo-header p {
|
.subtitle {
|
||||||
margin: 0;
|
margin-top: 0.25rem;
|
||||||
color: var(--vp-c-text-2);
|
color: var(--vp-c-text-2);
|
||||||
font-size: 0.875rem;
|
font-size: 0.9rem;
|
||||||
}
|
|
||||||
|
|
||||||
.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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 0.5rem;
|
||||||
justify-content: center;
|
flex-wrap: wrap;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-btn {
|
.btn {
|
||||||
padding: 0.75rem 2rem;
|
padding: 0.5rem 0.75rem;
|
||||||
background: var(--vp-c-brand);
|
|
||||||
color: var(--vp-c-bg);
|
|
||||||
border: 1px solid var(--vp-c-brand);
|
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
font-weight: 600;
|
border: 1px solid var(--vp-c-divider);
|
||||||
font-size: 1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn:hover {
|
|
||||||
opacity: 0.95;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-btn.secondary {
|
|
||||||
background: var(--vp-c-bg);
|
background: var(--vp-c-bg);
|
||||||
border-color: var(--vp-c-divider);
|
|
||||||
color: var(--vp-c-text-1);
|
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);
|
border-color: var(--vp-c-brand);
|
||||||
|
color: var(--vp-c-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
.btn:disabled {
|
||||||
.layer-info {
|
opacity: 0.5;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
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>
|
</style>
|
||||||
|
|||||||
+335
-166
@@ -1,78 +1,134 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="rule-learning-demo">
|
<div class="rule-learning-demo">
|
||||||
<div class="demo-grid">
|
<div class="header">
|
||||||
<!-- Rule Based System -->
|
<div class="title">
|
||||||
<div class="panel rule-based">
|
规则 vs 学习:你写阈值,还是让模型从数据里“推断”阈值?
|
||||||
<div class="panel-header">
|
|
||||||
<span class="icon">📜</span> Rule-Based System
|
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="subtitle">
|
||||||
<div class="code-block">
|
右侧允许你自己添加样本;点击“训练”只做一次计算,不会自动连着做下一步。
|
||||||
if (size >
|
|
||||||
<input v-model="ruleThreshold" type="number" class="mini-input" />)
|
|
||||||
{<br />
|
|
||||||
return "Big 🍎"<br />
|
|
||||||
} else {<br />
|
|
||||||
return "Small 🍒"<br />
|
|
||||||
}
|
|
||||||
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Machine Learning System -->
|
<div class="grid">
|
||||||
<div class="panel learning">
|
<div class="card">
|
||||||
<div class="panel-header">
|
<div class="card-title">规则系统(手写 If/Else)</div>
|
||||||
<span class="icon">🧠</span> Machine Learning
|
|
||||||
|
<div class="row">
|
||||||
|
<label class="label">阈值 size ></label>
|
||||||
|
<input
|
||||||
|
v-model.number="ruleThreshold"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="10"
|
||||||
|
class="input"
|
||||||
|
/>
|
||||||
|
<span class="muted">(你必须明确写出来)</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
|
||||||
<div class="training-data">
|
<div class="row">
|
||||||
<div class="data-point" v-for="(p, i) in trainingData" :key="i">
|
<label class="label">测试输入 size</label>
|
||||||
{{ p.size }}={{ p.label }}
|
<input
|
||||||
|
v-model.number="testInput"
|
||||||
|
type="range"
|
||||||
|
min="1"
|
||||||
|
max="10"
|
||||||
|
class="range"
|
||||||
|
/>
|
||||||
|
<code class="mono">{{ testInput }}</code>
|
||||||
</div>
|
</div>
|
||||||
<button class="train-btn" @click="trainModel" :disabled="isTrained">
|
|
||||||
{{ isTrained ? 'Model Trained ✅' : '⚡ Train Model' }}
|
<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 > {{ ruleThreshold }}) return 🍎 else return 🍒
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="hint">
|
||||||
|
当环境变化(比如“苹果平均变小了”),你需要手动改规则;规则越多,维护成本越高。
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title">机器学习(从样本推断边界)</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="controls">
|
||||||
|
<button
|
||||||
|
class="btn primary"
|
||||||
|
@click="train"
|
||||||
|
:disabled="trainingData.length < 2"
|
||||||
|
>
|
||||||
|
训练(推断阈值)
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn" @click="resetLearning">重置样本</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="test-area">
|
<div class="row">
|
||||||
Test Input:
|
<label class="label">测试输入 size</label>
|
||||||
<input
|
<input
|
||||||
v-model="testInput"
|
v-model.number="testInput"
|
||||||
type="range"
|
type="range"
|
||||||
min="1"
|
min="1"
|
||||||
max="10"
|
max="10"
|
||||||
class="slider"
|
class="range"
|
||||||
/>
|
/>
|
||||||
{{ testInput }}
|
<code class="mono">{{ testInput }}</code>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="result-box"
|
class="result"
|
||||||
:class="mlResult === 'Big 🍎' ? 'big' : 'small'"
|
:class="{
|
||||||
|
good: mlResult.label === '🍎',
|
||||||
|
bad: mlResult.label === '🍒'
|
||||||
|
}"
|
||||||
>
|
>
|
||||||
Result: {{ mlResult }}
|
<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>
|
</div>
|
||||||
<div class="note">
|
|
||||||
Model "learned" threshold is ~{{ learnedThreshold }}. <br />
|
<div class="hint">
|
||||||
(Derived from data, not coded)
|
这里的“训练”是极简示意:用样本推断一个分界点(阈值)。真实模型会用更复杂的损失函数与优化算法。
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,36 +136,90 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
|
||||||
const testInput = ref(5)
|
const testInput = ref(5)
|
||||||
|
|
||||||
// Rule Based Logic
|
// Rule based
|
||||||
const ruleThreshold = ref(6)
|
const ruleThreshold = ref(6)
|
||||||
const ruleResult = computed(() => {
|
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
|
// Learning (toy)
|
||||||
const trainingData = [
|
let idCounter = 0
|
||||||
{ size: 2, label: '🍒' },
|
const trainingData = ref([
|
||||||
{ size: 3, label: '🍒' },
|
{ id: idCounter++, size: 2, label: '🍒' },
|
||||||
{ size: 8, label: '🍎' },
|
{ id: idCounter++, size: 3, label: '🍒' },
|
||||||
{ size: 9, label: '🍎' }
|
{ id: idCounter++, size: 8, label: '🍎' },
|
||||||
]
|
{ id: idCounter++, size: 9, label: '🍎' }
|
||||||
const isTrained = ref(false)
|
])
|
||||||
const learnedThreshold = ref(5.5) // Simplified mock learning
|
|
||||||
|
|
||||||
const trainModel = () => {
|
const newSize = ref(5)
|
||||||
// Simulate training delay
|
const newLabel = ref('🍒')
|
||||||
setTimeout(() => {
|
const isTrained = ref(false)
|
||||||
isTrained.value = true
|
const learnedThreshold = ref(5.5)
|
||||||
}, 500)
|
|
||||||
|
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(() => {
|
const mlResult = computed(() => {
|
||||||
if (!isTrained.value) return '❓ Untrained'
|
if (!isTrained.value) {
|
||||||
return testInput.value > learnedThreshold.value ? 'Big 🍎' : 'Small 🍒'
|
return { label: '❓', text: 'Untrained / 未训练' }
|
||||||
|
}
|
||||||
|
const isApple = testInput.value > learnedThreshold.value
|
||||||
|
return {
|
||||||
|
label: isApple ? '🍎' : '🍒',
|
||||||
|
text: isApple ? 'Big 🍎' : 'Small 🍒'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -118,135 +228,194 @@ const mlResult = computed(() => {
|
|||||||
border: 1px solid var(--vp-c-divider);
|
border: 1px solid var(--vp-c-divider);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: var(--vp-c-bg-soft);
|
background: var(--vp-c-bg-soft);
|
||||||
padding: 1rem;
|
padding: 1.5rem;
|
||||||
margin: 1rem 0;
|
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;
|
display: grid;
|
||||||
grid-template-columns: 1fr 1fr;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 1.5rem;
|
gap: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 720px) {
|
||||||
.demo-grid {
|
.grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel {
|
.card {
|
||||||
background: var(--vp-c-bg);
|
|
||||||
border: 1px solid var(--vp-c-divider);
|
border: 1px solid var(--vp-c-divider);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow: hidden;
|
background: var(--vp-c-bg);
|
||||||
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-header {
|
.card-title {
|
||||||
padding: 0.8rem;
|
font-weight: 900;
|
||||||
background: var(--vp-c-bg-alt);
|
margin-bottom: 0.75rem;
|
||||||
font-weight: bold;
|
}
|
||||||
border-bottom: 1px solid var(--vp-c-divider);
|
|
||||||
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
align-items: center;
|
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;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-body {
|
.chip {
|
||||||
padding: 1rem;
|
display: inline-flex;
|
||||||
display: flex;
|
align-items: center;
|
||||||
flex-direction: column;
|
gap: 0.35rem;
|
||||||
gap: 1rem;
|
padding: 0.2rem 0.55rem;
|
||||||
}
|
border-radius: 999px;
|
||||||
|
|
||||||
.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;
|
|
||||||
border: 1px solid var(--vp-c-divider);
|
border: 1px solid var(--vp-c-divider);
|
||||||
}
|
|
||||||
|
|
||||||
.mini-input {
|
|
||||||
width: 40px;
|
|
||||||
background: var(--vp-c-bg);
|
background: var(--vp-c-bg);
|
||||||
border: 1px solid var(--vp-c-divider);
|
font-weight: 800;
|
||||||
color: var(--vp-c-text-1);
|
|
||||||
border-radius: 2px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-area {
|
.sep {
|
||||||
background: var(--vp-c-bg-soft);
|
color: var(--vp-c-text-2);
|
||||||
padding: 0.8rem;
|
|
||||||
border-radius: 6px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.slider {
|
.chip-x {
|
||||||
width: 100%;
|
margin-left: 0.2rem;
|
||||||
margin: 0.5rem 0;
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--vp-c-text-2);
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.result-box {
|
.controls {
|
||||||
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 {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
justify-content: center;
|
margin: 0.25rem 0 0.75rem;
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-point {
|
.result {
|
||||||
background: var(--vp-c-bg-alt);
|
|
||||||
border: 1px solid var(--vp-c-divider);
|
border: 1px solid var(--vp-c-divider);
|
||||||
padding: 2px 6px;
|
border-radius: 8px;
|
||||||
border-radius: 4px;
|
background: var(--vp-c-bg-soft);
|
||||||
font-size: 0.7rem;
|
padding: 0.75rem;
|
||||||
color: var(--vp-c-text-2);
|
margin: 0.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.train-btn {
|
.result.good {
|
||||||
width: 100%;
|
border-color: rgba(var(--vp-c-brand-rgb), 0.35);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
.train-btn:disabled {
|
|
||||||
background: var(--vp-c-bg);
|
.result-title {
|
||||||
border-color: var(--vp-c-divider);
|
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);
|
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>
|
</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>
|
||||||
@@ -194,6 +194,7 @@ import PromptQuickStartDemo from './components/appendix/prompt-engineering/Promp
|
|||||||
import PromptComparisonDemo from './components/appendix/prompt-engineering/PromptComparisonDemo.vue'
|
import PromptComparisonDemo from './components/appendix/prompt-engineering/PromptComparisonDemo.vue'
|
||||||
import FewShotDemo from './components/appendix/prompt-engineering/FewShotDemo.vue'
|
import FewShotDemo from './components/appendix/prompt-engineering/FewShotDemo.vue'
|
||||||
import ChainOfThoughtDemo from './components/appendix/prompt-engineering/ChainOfThoughtDemo.vue'
|
import ChainOfThoughtDemo from './components/appendix/prompt-engineering/ChainOfThoughtDemo.vue'
|
||||||
|
import PromptTemplatesDemo from './components/appendix/prompt-engineering/PromptTemplatesDemo.vue'
|
||||||
|
|
||||||
// Context Engineering Components
|
// Context Engineering Components
|
||||||
import AgentContextFlow from './components/appendix/context-engineering/AgentContextFlow.vue'
|
import AgentContextFlow from './components/appendix/context-engineering/AgentContextFlow.vue'
|
||||||
@@ -449,6 +450,7 @@ export default {
|
|||||||
app.component('PromptComparisonDemo', PromptComparisonDemo)
|
app.component('PromptComparisonDemo', PromptComparisonDemo)
|
||||||
app.component('FewShotDemo', FewShotDemo)
|
app.component('FewShotDemo', FewShotDemo)
|
||||||
app.component('ChainOfThoughtDemo', ChainOfThoughtDemo)
|
app.component('ChainOfThoughtDemo', ChainOfThoughtDemo)
|
||||||
|
app.component('PromptTemplatesDemo', PromptTemplatesDemo)
|
||||||
|
|
||||||
// Context Engineering Components Registration
|
// Context Engineering Components Registration
|
||||||
app.component('AgentContextFlow', AgentContextFlow)
|
app.component('AgentContextFlow', AgentContextFlow)
|
||||||
|
|||||||
@@ -75,13 +75,11 @@ AI 不知道你要:写给谁、写多长、用什么风格、怎么验收。
|
|||||||
````markdown
|
````markdown
|
||||||
任务:总结下面的文本,输出 3 个要点。
|
任务:总结下面的文本,输出 3 个要点。
|
||||||
文本如下(用 ``` 包起来):
|
文本如下(用 ``` 包起来):
|
||||||
````
|
|
||||||
|
|
||||||
|
```text
|
||||||
[这里粘贴原文]
|
[这里粘贴原文]
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
````
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -251,36 +249,9 @@ AI 不知道你要:写给谁、写多长、用什么风格、怎么验收。
|
|||||||
|
|
||||||
## 9. 常见场景模板(可直接复制)
|
## 9. 常见场景模板(可直接复制)
|
||||||
|
|
||||||
### 9.1 总结类(给人看)
|
下面这些模板做成了可切换组件(带搜索 + 一键复制),避免你往下翻一大段:
|
||||||
|
|
||||||
```markdown
|
<PromptTemplatesDemo />
|
||||||
任务:把下面文本总结给“忙碌的老板”。
|
|
||||||
要求:
|
|
||||||
|
|
||||||
- 3 个要点
|
|
||||||
- 1 句结论
|
|
||||||
- 1 个下一步建议
|
|
||||||
输出:Markdown
|
|
||||||
文本:
|
|
||||||
```
|
|
||||||
|
|
||||||
[粘贴原文]
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### 9.2 抽取类(给程序用)
|
|
||||||
|
|
||||||
`````markdown
|
|
||||||
任务:从文本中抽取信息。
|
|
||||||
输出:只输出 JSON(不要解释)。
|
|
||||||
JSON 结构:\n{\n \"title\": \"\",\n \"date\": \"\",\n \"people\": [],\n \"actions\": []\n}\n文本:\n`\n[粘贴原文]\n`\n```
|
|
||||||
|
|
||||||
### 9.3 代码审查类(先计划再输出)
|
|
||||||
|
|
||||||
````markdown
|
|
||||||
你是资深工程师。\n任务:审查下面代码。\n要求:\n1) 先列检查清单(3-5条)\n2) 再列问题(现象/原因/修复)\n3) 最后给修复片段\n代码:\n`\n[粘贴代码]\n`\n```
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -307,9 +278,3 @@ JSON 结构:\n{\n \"title\": \"\",\n \"date\": \"\",\n \"people\": [],\n \"act
|
|||||||
| Plan-first(先计划) | 先输出计划/清单,再生成最终结果,减少跑偏。 |
|
| Plan-first(先计划) | 先输出计划/清单,再生成最终结果,减少跑偏。 |
|
||||||
| Prompt Injection(注入) | 把外部材料伪装成“指令”,试图让模型越权执行。 |
|
| Prompt Injection(注入) | 把外部材料伪装成“指令”,试图让模型越权执行。 |
|
||||||
| Self-check(自检) | 让输出附带核对项,方便你验收。 |
|
| Self-check(自检) | 让输出附带核对项,方便你验收。 |
|
||||||
````
|
|
||||||
`````
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|||||||
Reference in New Issue
Block a user