feat(ai-protocols): add MCP and A2A protocol demos and documentation

docs(ai-protocols): update AI protocols page with visual demos and detailed explanations
style(git-demos): improve responsive design and layout for git visualization components
refactor(ai-history): simplify and clean up demo components
chore: update config to register new AI protocol components
This commit is contained in:
sanbuphy
2026-02-22 18:26:19 +08:00
parent 4b83a4c23e
commit e5a5b9df5b
31 changed files with 5093 additions and 5333 deletions
@@ -1,239 +1,3 @@
<template>
<div class="ai-evolution-timeline-demo">
<el-card
shadow="hover"
class="main-card"
>
<template #header>
<div class="card-header">
<h3>AI 进化时间轴</h3>
<p class="subtitle">
点击不同时期查看 AI 是如何一步步进化的
</p>
</div>
</template>
<div class="demo-content">
<el-tabs
v-model="activeEraName"
type="border-card"
class="timeline-tabs"
>
<el-tab-pane
v-for="(era, index) in eras"
:key="index"
:label="era.title"
:name="era.title"
>
<div class="era-content">
<div class="era-header">
<el-tag
effect="dark"
size="large"
class="year-tag"
>
{{ era.year }}
</el-tag>
<span class="era-desc-short">{{ era.desc }}</span>
</div>
<div class="era-body">
<p class="full-desc">
{{ era.fullDesc }}
</p>
<div class="info-grid">
<div class="info-column">
<span class="column-title">💡 核心特点</span>
<ul class="key-points-list">
<li
v-for="(point, i) in era.keyPoints"
:key="i"
>
<el-icon class="point-icon">
<CaretRight />
</el-icon>
{{ point }}
</li>
</ul>
</div>
<div class="info-column">
<span class="column-title">🌟 代表成就</span>
<div class="examples-container">
<el-tag
v-for="(example, i) in era.examples"
:key="i"
class="example-tag"
effect="plain"
>
{{ example }}
</el-tag>
</div>
</div>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { CaretRight } from '@element-plus/icons-vue'
const activeEraName = ref('符号主义时代')
const eras = [
{
year: '20世纪50-80年代',
title: '符号主义时代',
desc: '规则与逻辑推理',
fullDesc:
'早期人工智能研究认为,智能可以通过符号和逻辑规则来表达。科学家们尝试编写大量规则来让机器模拟人类专家的决策过程。',
examples: ['专家系统', '深蓝 (Deep Blue)', 'MYCIN'],
keyPoints: [
'人工编写 If-Then 规则',
'逻辑推理能力强大',
'可解释性强',
'难以处理模糊/复杂问题'
]
},
{
year: '21世纪10年代',
title: '连接主义时代',
desc: '神经网络与深度学习',
fullDesc:
'随着大数据和 GPU 算力的突破,深度学习迎来了春天。神经网络通过多层结构自动学习特征,在图像识别、语音识别等领域取得巨大成功。',
examples: ['AlexNet', 'AlphaGo', '人脸识别'],
keyPoints: [
'模仿人脑神经元结构',
'从数据中自动学习特征',
'强大的模式识别能力',
'模型是"黑盒",缺乏可解释性'
]
},
{
year: '21世纪20年代至今',
title: '生成式 AI 时代',
desc: '大模型与创造力',
fullDesc:
'Transformer 架构的诞生让机器理解了上下文关系。GPT 等大语言模型不仅能生成文本、图像,还展现出了惊人的推理和创造能力。',
examples: ['ChatGPT', 'Midjourney', 'Sora'],
keyPoints: [
'基于 Transformer 架构',
'通用的理解与生成能力',
'涌现出推理、规划等高级智能',
'通过提示词 (Prompt) 交互'
]
}
]
</script>
<style scoped>
.ai-evolution-timeline-demo {
margin: 10px 0;
}
.main-card {
/* Compact card style */
}
.card-header h3 {
margin: 0;
font-size: 16px;
font-weight: bold;
}
.subtitle {
font-size: 12px;
color: #909399;
margin: 5px 0 0 0;
}
.timeline-tabs {
margin-top: 10px;
}
.era-content {
padding: 10px;
}
.era-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.era-desc-short {
font-weight: bold;
color: #606266;
font-size: 14px;
}
.full-desc {
font-size: 14px;
color: #303133;
line-height: 1.6;
margin-bottom: 20px;
background: #f5f7fa;
padding: 10px;
border-radius: 4px;
}
.info-grid {
display: flex;
gap: 20px;
}
.info-column {
flex: 1;
}
.column-title {
display: block;
font-size: 13px;
font-weight: bold;
color: #909399;
margin-bottom: 10px;
border-bottom: 1px solid #ebeef5;
padding-bottom: 5px;
}
.key-points-list {
list-style: none;
padding: 0;
margin: 0;
}
.key-points-list li {
display: flex;
align-items: center;
font-size: 13px;
color: #606266;
margin-bottom: 6px;
}
.point-icon {
margin-right: 5px;
color: #409eff;
}
.examples-container {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
@media (max-width: 600px) {
.info-grid {
flex-direction: column;
gap: 15px;
}
}
</style>
<div></div>
</template>
@@ -1,697 +1,41 @@
<template>
<div class="evolution-demo">
<el-card
class="main-card"
shadow="hover"
>
<template #header>
<div class="header-container">
<div class="title-area">
<span class="main-title">AI 进化模拟器</span>
</div>
<el-steps
:active="currentStage"
finish-status="success"
align-center
class="compact-steps"
simple
>
<el-step
v-for="stage in stages"
:key="stage.id"
:title="stage.label"
/>
</el-steps>
</div>
</template>
<!-- Stage 1: Rule Based (Traffic Light Example) -->
<div
v-if="currentStage === 0"
class="stage-pane"
>
<el-alert
type="info"
:closable="false"
show-icon
class="compact-alert mb-2"
>
<template #title>
<span class="alert-title">阶段一规则时代 (Rule-Based)</span>
</template>
<template #default>
<span class="alert-desc">就像教小孩如果看到红灯就停下</span>
</template>
</el-alert>
<div class="game-area-grid">
<div class="panel left-panel">
<div class="panel-header">
规则库 (Code)
</div>
<div class="code-block">
<div class="code-line">
<span class="keyword">function</span> <span class="function">decideTrafficLight</span>(color) {
</div>
<div class="code-line indent">
<span class="keyword">if</span> (color === <span class="string">'red'</span>) <span class="keyword">return</span> <span class="string">'stop'</span>
</div>
<div class="code-line indent">
<span class="keyword">else if</span> (color === <span class="string">'yellow'</span>) <span class="keyword">return</span> <span class="string">'caution'</span>
</div>
<div class="code-line indent">
<span class="keyword">else if</span> (color === <span class="string">'green'</span>) <span class="keyword">return</span> <span class="string">'go'</span>
</div>
<div class="code-line">
}
</div>
</div>
</div>
<div class="panel right-panel">
<div class="panel-header">
测试输入
</div>
<div class="input-controls">
<el-select
v-model="ruleColor"
size="small"
style="width: 120px;"
>
<el-option
value="red"
label="🔴 红灯"
/>
<el-option
value="yellow"
label="🟡 黄灯"
/>
<el-option
value="green"
label="🟢 绿灯"
/>
<el-option
value="blue"
label="🔵 蓝灯"
/>
</el-select>
<div class="arrow">
</div>
<el-tag :type="ruleResult === 'stop' ? 'danger' : ruleResult === 'caution' ? 'warning' : ruleResult === 'go' ? 'success' : 'info'">
{{ ruleResult }}
</el-tag>
</div>
<div
v-if="ruleResult === 'Unknown'"
class="hint-text"
>
规则库中没有定义"蓝灯"所以系统不知道该做什么这就是规则系统的局限性无法处理未定义的规则
</div>
<div
v-else
class="hint-text"
>
系统严格按照预定义的规则执行指令
</div>
</div>
</div>
<div class="demo-card">
<div class="timeline-visual">
<div class="era" v-for="era in eras" :key="era.label" :style="{ flex: era.flex, background: era.bg }">
<div class="era-label">{{ era.label }}</div>
<div class="era-years">{{ era.years }}</div>
</div>
<!-- Stage 2: Machine Learning (Interactive 2D Plot) -->
<div
v-else-if="currentStage === 1"
class="stage-pane"
>
<el-alert
type="info"
:closable="false"
show-icon
class="compact-alert mb-2"
>
<template #title>
<span class="alert-title">阶段二机器学习 (Machine Learning)</span>
</template>
<template #default>
<span class="alert-desc">点击画布添加数据点训练模型自动寻找分类边界 (Decision Boundary)</span>
</template>
</el-alert>
<div class="game-area-grid">
<div
class="panel left-panel canvas-container"
@click="addPoint"
>
<!-- Simple SVG Plot -->
<svg
width="100%"
height="200"
class="ml-plot"
>
<!-- Background Regions (Visible after training) -->
<rect
v-if="modelTrained"
x="0"
y="0"
width="100%"
height="100%"
:fill="boundaryColor"
/>
<!-- Decision Line -->
<line
v-if="modelTrained"
:x1="line.x1"
:y1="line.y1"
:x2="line.x2"
:y2="line.y2"
stroke="#333"
stroke-width="2"
stroke-dasharray="4"
/>
<!-- Points -->
<circle
v-for="(p, i) in points"
:key="i"
:cx="p.x"
:cy="p.y"
r="6"
:fill="p.type === 'A' ? '#409eff' : '#e6a23c'"
stroke="white"
stroke-width="2"
/>
</svg>
<div
v-if="points.length === 0"
class="canvas-hint"
>
👆 点击此处添加数据点
</div>
</div>
<div class="panel right-panel">
<div class="panel-header">
控制面板
</div>
<div class="control-group">
<span class="label">当前类别:</span>
<el-radio-group
v-model="currentClass"
size="small"
>
<el-radio-button label="A">
<span style="color: #409eff"> 蓝类</span>
</el-radio-button>
<el-radio-button label="B">
<span style="color: #e6a23c"> 橙类</span>
</el-radio-button>
</el-radio-group>
</div>
<div class="control-group mt-2">
<el-button
type="primary"
size="small"
:disabled="points.length < 2"
@click="trainLinearModel"
>
开始训练 (Fit)
</el-button>
<el-button
size="small"
:icon="Delete"
circle
@click="clearPoints"
/>
</div>
<div class="stats-info mt-2">
<p
v-if="!modelTrained"
class="text-desc"
>
机器学习不再依赖硬编码规则而是通过统计学方法如寻找中心点或线性回归在数据之间划出一条"界线"试试在不同位置添加点看看界线如何变化
</p>
<p
v-else
class="text-desc"
>
模型已训练它找到了一条最佳分割线新进来的数据将根据它在红区还是蓝区被自动分类
</p>
</div>
</div>
</div>
</div>
<!-- Stage 3: Deep Learning (3x3 Grid Feature Extraction) -->
<div
v-else
class="stage-pane"
>
<el-alert
type="info"
:closable="false"
show-icon
class="compact-alert mb-2"
>
<template #title>
<span class="alert-title">阶段三深度学习 (Deep Learning)</span>
</template>
<template #default>
<span class="alert-desc">神经网络通过多层结构自动提取特征Feature Extraction点击格子绘制图案</span>
</template>
</el-alert>
<div class="game-area-grid">
<div class="panel left-panel grid-container">
<div class="pixel-grid">
<div
v-for="(pixel, i) in pixels"
:key="i"
class="pixel"
:class="{ active: pixel }"
@click="togglePixel(i)"
/>
</div>
<div class="grid-actions">
<el-button
size="small"
link
@click="preset('x')"
>
X型
</el-button>
<el-button
size="small"
link
@click="preset('plus')"
>
十字
</el-button>
<el-button
size="small"
link
@click="clearPixels"
>
清空
</el-button>
</div>
</div>
<div class="panel right-panel">
<div class="panel-header">
神经网络层级透视
</div>
<!-- Visualization of Layers -->
<div class="network-viz">
<div class="layer input-layer">
<div class="layer-label">
输入层 (Pixels)
</div>
<div class="nodes">
<span
v-for="n in 9"
:key="n"
class="node mini"
:class="{active: pixels[n-1]}"
/>
</div>
</div>
<div class="arrow-down">
卷积/提取特征
</div>
<div class="layer hidden-layer">
<div class="layer-label">
隐藏层 (Features)
</div>
<div class="feature-detectors">
<div
class="feature"
:class="{detected: features.center}"
>
<span class="f-icon"></span> 中心点
</div>
<div
class="feature"
:class="{detected: features.corners}"
>
<span class="f-icon">Corners</span> 四角
</div>
<div
class="feature"
:class="{detected: features.cross}"
>
<span class="f-icon"></span> 交叉
</div>
</div>
</div>
<div class="arrow-down">
输出层
</div>
<div class="layer output-layer">
<div class="prediction-box">
识别结果: <span class="result-text">{{ prediction }}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Footer Navigation -->
<div class="footer-nav mt-2 flex justify-end">
<el-button-group>
<el-button
size="small"
:disabled="currentStage === 0"
@click="currentStage--"
>
上一步
</el-button>
<el-button
size="small"
type="primary"
:disabled="currentStage === 2"
@click="currentStage++"
>
下一步
</el-button>
</el-button-group>
</div>
</el-card>
</div>
<div class="legend">
<span class="legend-item"><span class="dot" style="background:#059669"></span>技术浪潮</span>
<span class="legend-item"><span class="dot" style="background:#94a3b8"></span> AI 寒冬</span>
<span class="legend-item"><span class="dot" style="background:#7c3aed"></span>大模型时代</span>
</div>
</div>
</template>
<script setup>
import { ref, reactive, computed } from 'vue'
import { Delete } from '@element-plus/icons-vue'
const currentStage = ref(0)
const stages = [
{ id: 0, label: '规则', desc: '人工规则' },
{ id: 1, label: '机器学习', desc: '统计特征' },
{ id: 2, label: '深度学习', desc: '自动特征' }
const eras = [
{ label: '理论奠基', years: '1940s-50s', flex: 1.5, bg: 'linear-gradient(135deg, #dbeafe, #bfdbfe)' },
{ label: '第一次浪潮', years: '1960s-70s', flex: 1.5, bg: 'linear-gradient(135deg, #d1fae5, #a7f3d0)' },
{ label: '❄️ 寒冬 I', years: '1974-80', flex: 0.7, bg: 'linear-gradient(135deg, #e2e8f0, #cbd5e1)' },
{ label: '第二次浪潮', years: '1980s', flex: 1, bg: 'linear-gradient(135deg, #d1fae5, #a7f3d0)' },
{ label: '❄️ 寒冬 II', years: '1987-93', flex: 0.7, bg: 'linear-gradient(135deg, #e2e8f0, #cbd5e1)' },
{ label: 'ML 崛起', years: '1990s-2000s', flex: 1.5, bg: 'linear-gradient(135deg, #d1fae5, #6ee7b7)' },
{ label: '深度学习', years: '2010s', flex: 1.2, bg: 'linear-gradient(135deg, #a7f3d0, #34d399)' },
{ label: '大模型时代', years: '2018+', flex: 1.2, bg: 'linear-gradient(135deg, #c4b5fd, #a78bfa)' },
]
// --- Stage 1: Rule Based ---
const ruleColor = ref('red')
const ruleResult = computed(() => {
if (ruleColor.value === 'red') return 'stop'
if (ruleColor.value === 'yellow') return 'caution'
if (ruleColor.value === 'green') return 'go'
return 'Unknown'
})
// --- Stage 2: Machine Learning ---
const points = ref([])
const currentClass = ref('A')
const modelTrained = ref(false)
const line = reactive({ x1: 0, y1: 0, x2: 0, y2: 0 })
// SVG click coordinates are relative to the SVG element
// We'll use a simple approximation for the demo
// x, y are percentages (0-100)
const addPoint = (e) => {
const rect = e.target.getBoundingClientRect()
// Ensure we are clicking on the SVG or its children
// Best to put event on wrapper
// But event target might be circle.
// Use currentTarget
const x = e.offsetX
const y = e.offsetY
// Convert to % for responsiveness if needed, but pixel is easier for calc
// Let's stick to pixel for this simple demo, assuming fixed height 200
// width varies.
points.value.push({
x, y,
type: currentClass.value
})
modelTrained.value = false
}
const clearPoints = () => {
points.value = []
modelTrained.value = false
}
const trainLinearModel = () => {
// Simple Nearest Centroid Classifier
const groupA = points.value.filter(p => p.type === 'A')
const groupB = points.value.filter(p => p.type === 'B')
if (groupA.length === 0 || groupB.length === 0) return
const avgA = {
x: groupA.reduce((sum, p) => sum + p.x, 0) / groupA.length,
y: groupA.reduce((sum, p) => sum + p.y, 0) / groupA.length
}
const avgB = {
x: groupB.reduce((sum, p) => sum + p.x, 0) / groupB.length,
y: groupB.reduce((sum, p) => sum + p.y, 0) / groupB.length
}
// Midpoint
const midX = (avgA.x + avgB.x) / 2
const midY = (avgA.y + avgB.y) / 2
// Normal vector (from A to B)
const dx = avgB.x - avgA.x
const dy = avgB.y - avgA.y
// Perpendicular line: dx*x + dy*y = C
// Slope of normal is dy/dx. Slope of perp line is -dx/dy
// Let's just draw a line perpendicular to the segment AB passing through Midpoint
// Slope m = -dx/dy
// Calculate line coordinates for visualization
// y - midY = m * (x - midX)
// if dy is close to 0, vertical line x = midX
const width = 1000 // ample width
if (Math.abs(dy) < 0.001) {
// Vertical line
line.x1 = midX
line.x2 = midX
line.y1 = 0
line.y2 = 200
} else {
const m = -dx / dy
// At x=0
const y0 = midY + m * (0 - midX)
// At x=width
const y1 = midY + m * (width - midX)
line.x1 = 0
line.y1 = y0
line.x2 = width
line.y2 = y1
}
modelTrained.value = true
}
// Simple visual background
// If A is left/top, background is blue-ish
// SVG doesn't support "half plane fill" easily without path math
// For this demo, we won't fill the background perfectly, just draw the line.
const boundaryColor = computed(() => 'transparent')
// --- Stage 3: Deep Learning ---
const pixels = ref(Array(9).fill(false))
const togglePixel = (index) => {
pixels.value[index] = !pixels.value[index]
}
const clearPixels = () => {
pixels.value = pixels.value.map(() => false)
}
const preset = (type) => {
clearPixels()
if (type === 'x') {
[0, 2, 4, 6, 8].forEach(i => pixels.value[i] = true)
} else if (type === 'plus') {
[1, 3, 4, 5, 7].forEach(i => pixels.value[i] = true)
}
}
const features = computed(() => {
// Simple heuristics to simulate feature detection
const p = pixels.value
const center = p[4]
const corners = p[0] && p[2] && p[6] && p[8]
const cross = p[1] && p[3] && p[5] && p[7]
return { center, corners, cross }
})
const prediction = computed(() => {
const f = features.value
if (f.corners && f.center) return 'X 型图案 (X-Shape)'
if (f.cross && f.center) return '十字型 (Plus-Shape)'
if (f.corners && !f.center) return '四角 (Corners)'
if (pixels.value.filter(Boolean).length === 0) return '无输入'
return '未知图案'
})
</script>
<style scoped>
.evolution-demo { margin: 10px 0; }
.header-container { margin-bottom: 5px; }
.main-title { font-weight: bold; font-size: 16px; }
.compact-steps { padding: 5px 0; margin-bottom: 10px; }
.compact-alert { padding: 5px 10px; }
.alert-title { font-weight: bold; font-size: 13px; }
.alert-desc { font-size: 12px; }
.game-area-grid {
display: flex;
gap: 15px;
margin-top: 10px;
}
.panel {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px;
}
.left-panel { flex: 1; }
.right-panel { flex: 1; background-color: #fcfcfc; }
.panel-header {
font-size: 13px;
font-weight: bold;
color: #606266;
margin-bottom: 10px;
border-bottom: 1px solid #ebeef5;
padding-bottom: 5px;
}
/* Stage 1 */
.code-block {
font-family: monospace;
font-size: 12px;
background: #282c34;
color: #abb2bf;
padding: 10px;
border-radius: 4px;
}
.keyword { color: #c678dd; }
.string { color: #98c379; }
.function { color: #61afef; }
.indent { padding-left: 15px; }
.input-controls {
display: flex;
align-items: center;
gap: 10px;
}
.hint-text {
margin-top: 10px;
font-size: 12px;
color: #909399;
}
/* Stage 2 */
.canvas-container {
height: 220px;
background-color: #f5f7fa;
position: relative;
cursor: crosshair;
padding: 0;
overflow: hidden;
}
.ml-plot {
display: block;
}
.canvas-hint {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #909399;
font-size: 12px;
pointer-events: none;
}
.text-desc {
font-size: 12px;
color: #606266;
line-height: 1.5;
}
/* Stage 3 */
.grid-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.pixel-grid {
display: grid;
grid-template-columns: repeat(3, 40px);
gap: 4px;
margin-bottom: 10px;
}
.pixel {
width: 40px;
height: 40px;
background-color: #eee;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s;
}
.pixel:hover { background-color: #d9d9d9; }
.pixel.active { background-color: #333; }
.network-viz {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.layer {
width: 100%;
padding: 5px;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
text-align: center;
}
.layer-label { font-size: 11px; color: #909399; margin-bottom: 4px; }
.nodes { display: flex; gap: 2px; justify-content: center; flex-wrap: wrap; width: 60px; margin: 0 auto; }
.node.mini { width: 6px; height: 6px; border-radius: 50%; background: #ddd; }
.node.mini.active { background: #333; }
.arrow-down { font-size: 10px; color: #ccc; }
.feature-detectors {
display: flex;
justify-content: space-around;
font-size: 11px;
}
.feature {
display: flex;
flex-direction: column;
align-items: center;
opacity: 0.3;
transition: opacity 0.3s;
}
.feature.detected { opacity: 1; color: #409eff; font-weight: bold; }
.f-icon { font-size: 14px; margin-bottom: 2px; }
.prediction-box { font-weight: bold; font-size: 13px; color: #303133; }
.result-text { color: #67c23a; }
@media (max-width: 600px) {
.game-area-grid { flex-direction: column; }
}
.flex { display: flex; }
.justify-end { justify-content: flex-end; }
.mt-2 { margin-top: 8px; }
.mb-2 { margin-bottom: 8px; }
.demo-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); padding: 1rem; margin: 1rem 0; }
.timeline-visual { display: flex; border-radius: 6px; overflow: hidden; border: 1px solid var(--vp-c-divider); min-height: 60px; }
.era { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 0.4rem 0.2rem; text-align: center; border-right: 1px solid rgba(255,255,255,0.4); }
.era:last-child { border-right: none; }
.era-label { font-size: 0.65rem; font-weight: bold; color: #1e293b; line-height: 1.2; }
.era-years { font-size: 0.55rem; color: #475569; margin-top: 0.15rem; }
.legend { display: flex; gap: 1rem; margin-top: 0.6rem; flex-wrap: wrap; }
.legend-item { display: flex; align-items: center; gap: 0.3rem; font-size: 0.72rem; color: var(--vp-c-text-2); }
.dot { width: 8px; height: 8px; border-radius: 2px; }
@media (max-width: 640px) { .era-label { font-size: 0.58rem; } .era-years { display: none; } }
</style>
@@ -1,199 +1,50 @@
<template>
<div class="attention-mechanism-demo">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<h4>👁 注意力机制演示</h4>
<p class="subtitle">
点击词语观察它如何"关注"句子中的其他词
</p>
<div class="demo-card">
<div class="attention-layout">
<div class="sentence-col">
<div class="col-label">处理<strong></strong>时的注意力分配</div>
<div class="sentence-box">
<span v-for="(word, i) in sentence" :key="i" class="word-token" :class="{ focus: i === focusIdx }">{{ word }}</span>
</div>
</template>
<div class="sentence-container">
<div class="sentence">
<el-tag
v-for="(word, index) in sentence"
:key="index"
:type="activeIndex === index ? 'primary' : 'info'"
:effect="activeIndex === index ? 'dark' : 'plain'"
class="word-token"
@click="selectWord(index)"
>
{{ word }}
</el-tag>
</div>
<div
v-if="activeIndex !== null"
class="attention-bars"
>
<div
v-for="(attention, index) in attentionWeights"
:key="index"
class="attention-item"
>
<div class="word-label">
{{ attention.word }}
</div>
<el-progress
:percentage="Math.round(attention.weight * 100)"
:status="attention.weight > 0.5 ? 'exception' : ''"
:color="customColors"
class="attention-progress"
/>
</div>
</div>
<el-empty
v-else
description="👆 点击句子中的任意词语开始"
:image-size="60"
/>
</div>
<el-collapse-transition>
<div
v-if="activeIndex !== null"
class="explanation-panel"
>
<el-alert
type="success"
:closable="false"
show-icon
class="insight-alert"
>
<template #title>
<span class="insight-title">关键洞察</span>
</template>
<p>{{ getInsight(activeIndex) }}</p>
</el-alert>
<div class="bars-col">
<div class="attention-item" v-for="(item, i) in weights" :key="i">
<span class="bar-word" :class="{ focus: i === focusIdx }">{{ item.word }}</span>
<div class="bar-bg">
<div class="bar-fill" :style="{ width: item.w * 100 + '%', background: barColor(item.w) }"></div>
</div>
<span class="bar-pct">{{ Math.round(item.w * 100) }}%</span>
</div>
</el-collapse-transition>
</el-card>
</div>
</div>
<div class="caption">
虽在句中间模型却把 65% 注意力精准投向句首的小明跨越距离识别代词指代
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const sentence = ref(['小明', '把', '苹果', '给了', '他', '的', '母亲'])
const activeIndex = ref(null)
const customColors = [
{ color: '#909399', percentage: 20 },
{ color: '#e6a23c', percentage: 40 },
{ color: '#f56c6c', percentage: 80 },
{ color: '#67c23a', percentage: 100 }
]
// 注意力权重矩阵(模拟)
const attentionMatrix = {
0: [0.15, 0.05, 0.6, 0.05, 0.05, 0.05, 0.05], // 小明 主要关注 苹果、他
1: [0.1, 0.1, 0.4, 0.3, 0.05, 0.03, 0.02], // 把 主要关注 苹果、给了
2: [0.5, 0.1, 0.15, 0.15, 0.05, 0.03, 0.02], // 苹果 主要关注 小明
3: [0.1, 0.1, 0.35, 0.15, 0.2, 0.05, 0.05], // 给了 主要关注 苹果、他
4: [0.65, 0.05, 0.1, 0.1, 0.05, 0.03, 0.02], // 他 主要关注 小明
5: [0.08, 0.05, 0.07, 0.08, 0.62, 0.05, 0.05], // 的 主要关注 他
6: [0.25, 0.1, 0.15, 0.15, 0.2, 0.1, 0.05] // 母亲 关注多个词
}
const insights = {
0: '当模型处理"小明"时,它最关注"苹果"60%),因为这表明是"谁"拥有苹果。',
1: '"把"是介词,模型关注"苹果"和"给了",理解动作的对象和方向。',
2: '"苹果"作为宾语,主要关注主语"小明",确定归属关系。',
3: '"给了"关注"苹果"和"他",理解传递动作的对象。',
4: '"他"最关注"小明"65%),因为"他"指代的就是"小明"',
5: '"的"连接"他"和"母亲",主要关注"他"62%)。',
6: '"母亲"作为句末宾语,关注前面的多个词语来理解完整语境。'
}
const attentionWeights = computed(() => {
if (activeIndex.value === null) return []
return sentence.value.map((word, index) => ({
word,
weight: attentionMatrix[activeIndex.value][index]
}))
})
const selectWord = (index) => {
activeIndex.value = index
}
const getInsight = (index) => {
return insights[index]
}
const sentence = ['小明', '把', '苹果', '给了', '他', '的', '母亲']
const focusIdx = 4
const attn = [0.65, 0.05, 0.10, 0.10, 0.05, 0.03, 0.02]
const weights = sentence.map((word, i) => ({ word, w: attn[i] }))
const barColor = (v) => v > 0.5 ? '#dc2626' : v > 0.15 ? '#d97706' : v > 0.06 ? '#059669' : 'var(--vp-c-divider)'
</script>
<style scoped>
.attention-mechanism-demo {
margin: 20px 0;
}
.card-header h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.subtitle {
font-size: 13px;
color: var(--vp-c-text-2);
margin: 4px 0 0;
}
.sentence {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
margin-bottom: 24px;
padding: 16px;
background-color: var(--vp-c-bg-alt);
border-radius: 6px;
}
.word-token {
cursor: pointer;
font-size: 16px;
padding: 8px 16px;
transition: all 0.2s;
}
.word-token:hover {
transform: translateY(-2px);
}
.attention-bars {
display: flex;
flex-direction: column;
gap: 12px;
margin-bottom: 20px;
}
.attention-item {
display: flex;
align-items: center;
gap: 12px;
}
.word-label {
width: 40px;
text-align: right;
font-weight: bold;
font-size: 14px;
}
.attention-progress {
flex: 1;
}
.explanation-panel {
margin-top: 16px;
}
.insight-title {
font-weight: bold;
font-size: 14px;
}
.demo-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); padding: 1.25rem; margin: 1rem 0; }
.attention-layout { display: grid; grid-template-columns: 1fr 1.3fr; gap: 1rem; background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 6px; padding: 0.9rem; margin-bottom: 0.5rem; }
@media (max-width: 560px) { .attention-layout { grid-template-columns: 1fr; } }
.col-label { font-size: 0.76rem; color: var(--vp-c-text-2); margin-bottom: 0.5rem; font-weight: bold; }
.sentence-box { display: flex; flex-wrap: wrap; gap: 0.35rem; background: var(--vp-c-bg-alt); padding: 0.6rem; border-radius: 5px; border: 1px dashed var(--vp-c-divider); }
.word-token { font-size: 0.88rem; font-weight: bold; padding: 0.2rem 0.5rem; background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 4px; }
.word-token.focus { background: var(--vp-c-brand); color: white; border-color: var(--vp-c-brand); }
.bars-col { display: flex; flex-direction: column; gap: 0.3rem; justify-content: center; }
.attention-item { display: flex; align-items: center; gap: 0.4rem; }
.bar-word { width: 30px; text-align: right; font-size: 0.8rem; font-weight: bold; color: var(--vp-c-text-2); flex-shrink: 0; }
.bar-word.focus { color: var(--vp-c-brand); }
.bar-bg { flex: 1; height: 12px; background: var(--vp-c-bg-alt); border-radius: 6px; overflow: hidden; border: 1px solid var(--vp-c-divider); }
.bar-fill { height: 100%; border-radius: 6px; }
.bar-pct { font-size: 0.7rem; font-weight: bold; color: var(--vp-c-text-2); width: 30px; flex-shrink: 0; }
.caption { font-size: 0.75rem; color: var(--vp-c-text-3); text-align: center; }
</style>
@@ -1,332 +1,54 @@
<template>
<div class="backpropagation-demo">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<h4>🔄 反向传播演示</h4>
<p class="subtitle">
观察神经网络如何通过误差反向调整权重
</p>
</div>
</template>
<div class="demo-content">
<div class="network-view">
<svg
class="network-svg"
viewBox="0 0 600 300"
>
<!-- Layers visualization -->
<g
v-for="(layer, lIndex) in 3"
:key="lIndex"
>
<text
:x="100 + lIndex * 200"
y="20"
text-anchor="middle"
class="layer-label"
fill="currentColor"
>
{{
lIndex === 0 ? '输入层' : lIndex === 1 ? '隐藏层' : '输出层'
}}
</text>
<circle
v-for="n in 3"
:key="`${lIndex}-${n}`"
:cx="100 + lIndex * 200"
:cy="60 + n * 70"
:r="25"
:class="['neuron', getNeuronClass(lIndex, n)]"
/>
</g>
<!-- Connections with error flow -->
<line
v-for="conn in connections"
:key="conn.id"
:x1="conn.x1"
:y1="conn.y1"
:x2="conn.x2"
:y2="conn.y2"
:stroke="conn.color"
:stroke-width="conn.width"
:opacity="conn.opacity"
class="connection"
/>
</svg>
</div>
<el-divider />
<div class="controls-panel">
<el-steps
:active="currentStep"
align-center
finish-status="success"
>
<el-step
v-for="(step, index) in steps"
:key="index"
:title="step"
/>
</el-steps>
<div class="error-display mt-4">
<div class="flex justify-between mb-2">
<span class="text-sm">误差 (Loss)</span>
<span class="text-sm font-bold">{{ errorValue.toFixed(4) }}</span>
</div>
<el-progress
:percentage="Math.round(errorValue * 100)"
:color="customColors"
:striped="currentStep === 2"
:striped-flow="currentStep === 2"
/>
</div>
<el-alert
:title="explanations[currentStep]"
type="info"
show-icon
:closable="false"
class="mt-4"
/>
<div class="action-buttons mt-4 flex justify-center gap-4">
<el-button
:disabled="currentStep === 0"
@click="resetDemo"
>
重置
</el-button>
<el-button
type="primary"
:disabled="currentStep >= 4"
@click="nextStep"
>
{{ currentStep < 4 ? '下一步' : '完成' }}
</el-button>
</div>
</div>
<div class="demo-card">
<div class="bp-flow">
<div class="step-block" v-for="(step, i) in steps" :key="i" :style="{ borderTopColor: step.color }">
<div class="step-num" :style="{ background: step.color }">{{ i + 1 }}</div>
<div class="step-icon">{{ step.icon }}</div>
<div class="step-name">{{ step.name }}</div>
<div class="step-desc">{{ step.desc }}</div>
</div>
</el-card>
</div>
<div class="loss-visual">
<div class="loss-label">Loss误差随训练轮次下降</div>
<svg viewBox="0 0 300 60" class="loss-svg">
<polyline :points="lossPoints" fill="none" stroke="var(--vp-c-brand)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
<text x="5" y="10" class="ax"></text>
<text x="5" y="56" class="ax"></text>
<text x="220" y="56" class="ax">训练轮次 </text>
</svg>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const currentStep = ref(0)
const errorValue = ref(0.95)
const steps = ['前向传播', '计算误差', '反向传播', '更新权重']
const explanations = [
'输入数据通过各层传递,得到预测输出。就像学生做完了一套试卷。',
'对比预测值和真实值,计算误差。就像老师批改试卷,算出得了多少分(错得有多离谱)。',
'将误差从输出层反向传递到各层。就像老师把错题反馈给学生,告诉他是哪一步思路错了。',
'根据误差梯度调整每个神经元的权重。学生根据反馈修正自己的理解(权重),下次就能做对了。',
'演示完成!通过不断重复这个过程,网络就学会了任务。'
const steps = [
{ icon: '➡️', name: '前向传播', desc: '数据流过网络,得出预测', color: '#3b82f6' },
{ icon: '📐', name: '计算误差', desc: '预测值 vs 正确答案,算 Loss', color: '#d97706' },
{ icon: '⬅️', name: '反向传播', desc: '逐层追溯每个权重的"责任"', color: '#dc2626' },
{ icon: '⚙️', name: '更新权重', desc: '按责任微调,减少下次误差', color: '#059669' },
]
const customColors = [
{ color: '#67c23a', percentage: 20 },
{ color: '#e6a23c', percentage: 50 },
{ color: '#f56c6c', percentage: 100 }
]
const connections = ref([])
// Generate initial connections
const initConnections = () => {
const conns = []
// Input -> Hidden
for (let i = 1; i <= 3; i++) {
for (let j = 1; j <= 3; j++) {
conns.push({
id: `i${i}-h${j}`,
x1: 100,
y1: 60 + i * 70,
x2: 300,
y2: 60 + j * 70,
width: 2,
color: '#dcdfe6',
opacity: 0.5
})
}
const lossPoints = (() => {
const pts = []
for (let i = 0; i <= 50; i++) {
const x = 20 + i * 5.2
const y = 52 - 44 * Math.exp(-i * 0.09) + Math.sin(i * 0.7) * 1
pts.push(`${x},${y}`)
}
// Hidden -> Output
for (let i = 1; i <= 3; i++) {
for (let j = 1; j <= 3; j++) {
conns.push({
id: `h${i}-o${j}`,
x1: 300,
y1: 60 + i * 70,
x2: 500,
y2: 60 + j * 70,
width: 2,
color: '#dcdfe6',
opacity: 0.5
})
}
}
connections.value = conns
}
const getNeuronClass = (layerIndex, neuronIndex) => {
if (currentStep.value === 0) return 'active' // Forward
if (currentStep.value === 2) {
// Backward
if (layerIndex === 2) return 'error-source'
if (layerIndex === 1) return 'error-passing'
}
if (currentStep.value === 3) return 'updating' // Update
return ''
}
const nextStep = () => {
if (currentStep.value >= 4) return
currentStep.value++
if (currentStep.value === 1) {
// Calculate Error
// Visual effect only
} else if (currentStep.value === 2) {
// Backprop: highlight connections red
connections.value.forEach((c) => {
c.color = '#f56c6c'
c.width = 4
c.opacity = 1
})
} else if (currentStep.value === 3) {
// Update weights: error drops
const reduceError = setInterval(() => {
if (errorValue.value > 0.1) {
errorValue.value -= 0.05
} else {
clearInterval(reduceError)
}
}, 50)
connections.value.forEach((c) => {
c.color = '#67c23a'
c.width = 2
c.opacity = 0.8
})
}
}
const resetDemo = () => {
currentStep.value = 0
errorValue.value = 0.95
initConnections()
}
onMounted(() => {
initConnections()
})
return pts.join(' ')
})()
</script>
<style scoped>
.backpropagation-demo {
margin: 20px 0;
}
.card-header h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.subtitle {
font-size: 13px;
color: var(--vp-c-text-2);
margin: 4px 0 0;
}
.network-view {
display: flex;
justify-content: center;
background-color: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 10px;
}
.network-svg {
width: 100%;
max-width: 600px;
height: auto;
}
.layer-label {
font-size: 14px;
font-weight: bold;
fill: var(--vp-c-text-2);
}
.neuron {
fill: var(--vp-c-bg);
stroke: var(--vp-c-text-2);
stroke-width: 2;
transition: all 0.5s;
}
.neuron.active {
fill: var(--el-color-primary-light-9);
stroke: var(--el-color-primary);
}
.neuron.error-source {
fill: var(--el-color-danger-light-9);
stroke: var(--el-color-danger);
filter: drop-shadow(0 0 5px var(--el-color-danger));
}
.neuron.error-passing {
fill: var(--el-color-warning-light-9);
stroke: var(--el-color-warning);
}
.neuron.updating {
fill: var(--el-color-success-light-9);
stroke: var(--el-color-success);
r: 28; /* Pulse effect */
}
.connection {
transition: all 0.5s;
}
.mt-4 {
margin-top: 16px;
}
.mb-2 {
margin-bottom: 8px;
}
.flex {
display: flex;
}
.justify-between {
justify-content: space-between;
}
.justify-center {
justify-content: center;
}
.gap-4 {
gap: 16px;
}
.text-sm {
font-size: 14px;
}
.font-bold {
font-weight: bold;
}
.demo-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); padding: 1.25rem; margin: 1rem 0; }
.bp-flow { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; margin-bottom: 0.8rem; }
@media (max-width: 600px) { .bp-flow { grid-template-columns: repeat(2, 1fr); } }
.step-block { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-top: 3px solid; border-radius: 6px; padding: 0.7rem 0.5rem; display: flex; flex-direction: column; align-items: center; gap: 0.25rem; text-align: center; }
.step-num { width: 16px; height: 16px; border-radius: 50%; color: white; font-size: 0.6rem; font-weight: bold; display: flex; align-items: center; justify-content: center; }
.step-icon { font-size: 1.2rem; }
.step-name { font-weight: bold; font-size: 0.78rem; }
.step-desc { font-size: 0.68rem; color: var(--vp-c-text-2); line-height: 1.3; }
.loss-visual { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 6px; padding: 0.7rem; }
.loss-label { font-size: 0.75rem; color: var(--vp-c-text-2); margin-bottom: 0.3rem; }
.loss-svg { width: 100%; max-width: 380px; height: auto; display: block; margin: 0 auto; }
.ax { font-size: 6px; fill: var(--vp-c-text-3); }
</style>
@@ -1,278 +1,3 @@
<template>
<div class="combinatorial-explosion-demo">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<h4>🎯 组合爆炸模拟器</h4>
<p class="subtitle">
亲手体验"规则指数增长"的恐怖
</p>
</div>
</template>
<div class="demo-content">
<div class="controls-grid">
<div class="control-item">
<div class="label-row">
<span class="label-icon">🎨</span>
<span class="label-text">物体特征数量: {{ featureCount }}</span>
</div>
<el-slider
v-model="featureCount"
:min="2"
:max="6"
show-stops
:marks="{ 2: '2', 4: '4', 6: '6' }"
/>
<div class="preview-tags">
<el-tag
v-for="i in featureCount"
:key="i"
size="small"
:type="getFeatureTagType(i)"
effect="plain"
>
特征{{ i }}
</el-tag>
</div>
</div>
<div class="control-item">
<div class="label-row">
<span class="label-icon">🔢</span>
<span class="label-text">每个特征的可能值: {{ valuesPerFeature }}</span>
</div>
<el-slider
v-model="valuesPerFeature"
:min="2"
:max="4"
show-stops
:marks="{ 2: '2', 3: '3', 4: '4' }"
/>
</div>
</div>
<el-divider />
<div class="visualization-panel">
<div class="counter-display">
<el-statistic
title="需要的规则总数"
:value="totalRules"
value-style="font-weight: bold; color: var(--el-color-primary)"
>
<template #suffix>
<span class="formula-suffix">= {{ valuesPerFeature }}<sup>{{ featureCount }}</sup></span>
</template>
</el-statistic>
<el-tag
:type="complexityInfo.type"
effect="dark"
class="mt-2"
>
{{ complexityInfo.label }}
</el-tag>
</div>
<div class="action-buttons mt-4">
<el-button
type="primary"
:disabled="ruleCount >= maxRules"
@click="addRule"
>
添加规则 ({{ ruleCount }}/{{ maxRules }})
</el-button>
<el-button @click="resetRules">
🔄 重置
</el-button>
</div>
<div class="rules-container mt-4">
<transition-group
name="el-zoom-in-center"
tag="div"
class="rules-grid"
>
<div
v-for="(rule, index) in displayedRules"
:key="rule.id"
class="rule-card-mini"
:style="{ borderColor: rule.color }"
>
<div class="rule-idx">
#{{ index + 1 }}
</div>
<div class="rule-dots">
<span
v-for="d in 3"
:key="d"
class="dot"
:style="{ backgroundColor: rule.color }"
/>
</div>
</div>
</transition-group>
</div>
<el-alert
v-if="showWarning"
title="规则太多了!"
description="这就是'组合爆炸'。仅仅增加一点点复杂度,规则数量就会爆炸式增长,人类根本写不完。"
type="error"
show-icon
class="mt-4"
/>
</div>
</div>
</el-card>
</div>
<div></div>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
const featureCount = ref(3)
const valuesPerFeature = ref(2)
const displayedRules = ref([])
const maxRules = 20 // Visual limit
const totalRules = computed(() =>
Math.pow(valuesPerFeature.value, featureCount.value)
)
const ruleCount = computed(() => displayedRules.value.length)
const showWarning = computed(() => totalRules.value > 50)
const complexityInfo = computed(() => {
if (totalRules.value <= 10)
return { label: '简单 (可人工处理)', type: 'success' }
if (totalRules.value <= 50)
return { label: '中等 (有点累了)', type: 'warning' }
return { label: '极难 (组合爆炸!)', type: 'danger' }
})
const getFeatureTagType = (i) => {
const types = ['', 'success', 'warning', 'danger', 'info']
return types[i % types.length]
}
const addRule = () => {
if (ruleCount.value >= maxRules) return
const colors = ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#909399']
displayedRules.value.push({
id: Date.now(),
color: colors[Math.floor(Math.random() * colors.length)]
})
}
const resetRules = () => {
displayedRules.value = []
}
// Reset rules when parameters change
watch([featureCount, valuesPerFeature], () => {
resetRules()
})
</script>
<style scoped>
.combinatorial-explosion-demo {
margin: 20px 0;
}
.card-header h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.subtitle {
font-size: 13px;
color: var(--vp-c-text-2);
margin: 4px 0 0;
}
.controls-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 640px) {
.controls-grid {
grid-template-columns: 1fr;
}
}
.label-row {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
font-weight: 500;
}
.preview-tags {
display: flex;
gap: 4px;
flex-wrap: wrap;
margin-top: 8px;
}
.counter-display {
text-align: center;
background-color: var(--vp-c-bg-alt);
padding: 16px;
border-radius: 6px;
}
.formula-suffix {
font-size: 0.6em;
color: var(--vp-c-text-2);
margin-left: 8px;
}
.rules-grid {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
min-height: 60px;
}
.rule-card-mini {
width: 50px;
height: 50px;
border: 2px solid;
border-radius: 6px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--vp-c-bg);
}
.rule-idx {
font-size: 10px;
color: var(--vp-c-text-2);
}
.rule-dots {
display: flex;
gap: 2px;
margin-top: 2px;
}
.dot {
width: 6px;
height: 6px;
border-radius: 50%;
}
.mt-2 {
margin-top: 8px;
}
.mt-4 {
margin-top: 16px;
}
</style>
@@ -1,327 +1,36 @@
<template>
<div class="discriminative-vs-generative-demo">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<h4>🎯 判别式 vs 生成式 AI</h4>
<p class="subtitle">
理解两种不同的 AI 范式
</p>
<div class="demo-card">
<div class="schools-grid">
<div class="school-card" v-for="s in schools" :key="s.name" :style="{ borderTopColor: s.color }">
<div class="card-head">
<span class="school-icon">{{ s.icon }}</span>
<span class="school-name" :style="{ color: s.color }">{{ s.name }}</span>
</div>
</template>
<div class="comparison-container">
<el-row :gutter="20">
<!-- Discriminative AI -->
<el-col
:xs="24"
:sm="12"
>
<el-card
shadow="always"
class="ai-panel discriminative"
:class="{ active: mode === 'discriminative' }"
@click="mode = 'discriminative'"
>
<div class="panel-header">
<div class="icon">
🔍
</div>
<h5>判别式 AI</h5>
<el-tag
size="small"
type="success"
>
分类/识别
</el-tag>
</div>
<div class="panel-content">
<div class="input-output">
<div class="io-box input">
<div class="io-label">
输入
</div>
<div class="io-content">
<div class="svg-placeholder green">
<span class="svg-text">猫图</span>
</div>
</div>
</div>
<div class="arrow">
<el-icon><Bottom /></el-icon>
</div>
<div class="io-box output">
<div class="io-label">
输出
</div>
<div class="io-content">
<el-tag
effect="dark"
type="success"
>
这是猫
</el-tag>
<div class="probability">
置信度: 98%
</div>
</div>
</div>
</div>
<div class="examples">
<h6>典型应用:</h6>
<div class="example-tags">
<el-tag
v-for="tag in [
'图像分类',
'垃圾邮件过滤',
'疾病诊断',
'人脸识别'
]"
:key="tag"
size="small"
effect="plain"
>
{{ tag }}
</el-tag>
</div>
</div>
</div>
</el-card>
</el-col>
<!-- Generative AI -->
<el-col
:xs="24"
:sm="12"
>
<el-card
shadow="always"
class="ai-panel generative"
:class="{ active: mode === 'generative' }"
@click="mode = 'generative'"
>
<div class="panel-header">
<div class="icon">
</div>
<h5>生成式 AI</h5>
<el-tag
size="small"
type="primary"
>
创造/生成
</el-tag>
</div>
<div class="panel-content">
<div class="input-output">
<div class="io-box input">
<div class="io-label">
输入
</div>
<div class="io-content">
<div class="prompt-text">
"一只戴墨镜的猫"
</div>
</div>
</div>
<div class="arrow">
<el-icon><Bottom /></el-icon>
</div>
<div class="io-box output">
<div class="io-label">
输出
</div>
<div class="io-content">
<div class="svg-placeholder blue">
<span class="svg-text">生成图像 ✓</span>
</div>
</div>
</div>
</div>
<div class="examples">
<h6>典型应用:</h6>
<div class="example-tags">
<el-tag
v-for="tag in [
'ChatGPT',
'Midjourney',
'代码生成',
'音乐创作'
]"
:key="tag"
size="small"
effect="plain"
type="primary"
>
{{ tag }}
</el-tag>
</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<div class="school-idea">{{ s.idea }}</div>
<div class="school-rep">代表{{ s.rep }}</div>
<div class="school-status">{{ s.status }}</div>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { Bottom } from '@element-plus/icons-vue'
const mode = ref('discriminative')
const schools = [
{ name: '符号主义', icon: '📜', color: '#059669', idea: '智能 = 符号推理 / If-Then 规则', rep: '专家系统、深蓝', status: '→ 与连接主义融合(神经符号 AI)' },
{ name: '连接主义', icon: '🧠', color: '#7c3aed', idea: '智能 = 神经元网络 + 海量数据', rep: 'AlphaGo、GPT 系列', status: '→ 主导大模型时代,当前主流' },
{ name: '行为主义', icon: '🎮', color: '#d97706', idea: '智能 = 与环境互动 / 强化学习', rep: 'AlphaGoRL 部分)', status: '→ 与连接主义融合(深度强化学习)' },
]
</script>
<style scoped>
.discriminative-vs-generative-demo {
margin: 20px 0;
}
.card-header h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.subtitle {
font-size: 13px;
color: var(--vp-c-text-2);
margin: 4px 0 0;
}
.comparison-container {
padding: 10px;
}
.ai-panel {
cursor: pointer;
transition: all 0.3s;
border: 2px solid transparent;
height: 100%;
}
.ai-panel:hover {
transform: translateY(-2px);
}
.ai-panel.active {
border-color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9);
}
.panel-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 16px;
}
.icon {
font-size: 24px;
}
.panel-header h5 {
margin: 0;
flex: 1;
font-size: 16px;
font-weight: 600;
}
.panel-content {
display: flex;
flex-direction: column;
gap: 20px;
}
.input-output {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
background-color: var(--vp-c-bg);
padding: 16px;
border-radius: 6px;
}
.io-box {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.io-label {
font-size: 12px;
color: var(--vp-c-text-2);
text-transform: uppercase;
}
.io-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
}
.svg-placeholder {
width: 60px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 4px;
}
.svg-placeholder.green {
background-color: #48bb78;
}
.svg-placeholder.blue {
background-color: #667eea;
}
.svg-text {
color: white;
font-size: 12px;
font-weight: bold;
}
.prompt-text {
background-color: var(--vp-c-bg-alt);
padding: 8px;
border-radius: 4px;
font-family: monospace;
font-size: 12px;
}
.arrow {
color: var(--vp-c-text-2);
}
.probability {
font-size: 12px;
color: var(--vp-c-text-2);
}
.examples h6 {
margin: 0 0 8px 0;
font-size: 13px;
color: var(--vp-c-text-2);
}
.example-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.demo-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); padding: 1.25rem; margin: 1rem 0; }
.schools-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.7rem; }
@media (max-width: 640px) { .schools-grid { grid-template-columns: 1fr; } }
.school-card { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-top: 3px solid; border-radius: 6px; padding: 0.9rem; display: flex; flex-direction: column; gap: 0.4rem; }
.card-head { display: flex; align-items: center; gap: 0.5rem; }
.school-icon { font-size: 1.3rem; }
.school-name { font-weight: bold; font-size: 0.9rem; }
.school-idea { font-size: 0.78rem; color: var(--vp-c-text-1); background: var(--vp-c-bg-alt); padding: 0.35rem 0.5rem; border-radius: 4px; }
.school-rep { font-size: 0.72rem; color: var(--vp-c-text-3); }
.school-status { font-size: 0.72rem; color: var(--vp-c-text-2); border-top: 1px dashed var(--vp-c-divider); padding-top: 0.35rem; }
</style>
@@ -0,0 +1,44 @@
<template>
<div class="demo-card">
<div class="events">
<div class="event" v-for="e in events" :key="e.year">
<div class="year-col">
<span class="year-badge">{{ e.year }}</span>
</div>
<div class="dot-col">
<div class="dot" :style="{ background: e.color }"></div>
<div class="line" v-if="e !== events[events.length - 1]"></div>
</div>
<div class="content-col">
<div class="event-title">{{ e.title }}</div>
<div class="event-note">{{ e.note }}</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
const events = [
{ year: '1943', title: 'MP 神经元模型', note: '麦卡洛克 & 皮茨:首次用数学公式模拟神经元,证明"神经元可被计算"。', color: '#3b82f6' },
{ year: '1950', title: '图灵测试', note: '图灵:如果机器的回答让人无法分辨是人还是机器,则认为它具备智能。', color: '#7c3aed' },
{ year: '1956', title: '达特茅斯会议 — AI 学科诞生', note: '麦卡锡等人首次提出"人工智能"概念,AI 正式成为一门学科。', color: '#059669' },
{ year: '1956', title: '逻辑理论家(Logic Theorist', note: '纽厄尔 & 西蒙:第一个用规则自动证明数学定理的 AI 程序。', color: '#059669' },
{ year: '1958', title: 'LISP 语言诞生', note: '麦卡锡发明,成为此后数十年 AI 研究的核心编程语言。', color: '#d97706' },
{ year: '1959', title: '首台工业机器人', note: '德沃尔 & 恩格尔伯格:AI 从实验室走向工厂,开始改变工业生产。', color: '#dc2626' },
]
</script>
<style scoped>
.demo-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); padding: 1.25rem; margin: 1rem 0; }
.events { display: flex; flex-direction: column; }
.event { display: grid; grid-template-columns: 52px 24px 1fr; gap: 0 0.6rem; }
.year-col { display: flex; align-items: flex-start; padding-top: 0.15rem; justify-content: flex-end; }
.year-badge { font-size: 0.7rem; font-weight: bold; color: var(--vp-c-text-3); white-space: nowrap; }
.dot-col { display: flex; flex-direction: column; align-items: center; }
.dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; margin-top: 0.2rem; }
.line { width: 2px; flex: 1; background: var(--vp-c-divider); margin: 3px 0; min-height: 16px; }
.content-col { padding-bottom: 0.9rem; }
.event-title { font-weight: bold; font-size: 0.85rem; color: var(--vp-c-text-1); margin-bottom: 0.15rem; }
.event-note { font-size: 0.78rem; color: var(--vp-c-text-2); line-height: 1.5; }
</style>
@@ -1,293 +1,40 @@
<template>
<div class="gpt-evolution-demo">
<el-card
shadow="hover"
class="main-card"
>
<template #header>
<div class="card-header">
<span class="title">🚀 GPT 进化历程</span>
<span class="subtitle"> GPT-1 GPT-4 的演进之路</span>
<div class="demo-card">
<div class="gpt-grid">
<div class="gpt-card" v-for="m in models" :key="m.name" :style="{ borderTopColor: m.color }">
<div class="card-top">
<span class="gpt-name" :style="{ color: m.color }">{{ m.name }}</span>
<span class="gpt-year">{{ m.year }}</span>
</div>
</template>
<div class="demo-content">
<!-- Replace Vertical Timeline with Horizontal Tabs -->
<el-tabs
v-model="activeModelName"
type="card"
class="evolution-tabs"
@tab-click="handleTabClick"
>
<el-tab-pane
v-for="(model, index) in gptModels"
:key="index"
:label="model.name"
:name="model.name"
>
<div class="model-view">
<div class="model-info-header">
<el-tag
effect="dark"
size="large"
>
{{ model.year }}
</el-tag>
<div class="model-stats">
<div class="stat-item">
<span class="label">参数量</span>
<span class="value">{{ model.parameters }}</span>
</div>
<div class="stat-item">
<span class="label">上下文</span>
<span class="value">{{ model.context }}</span>
</div>
</div>
</div>
<div class="model-description">
<p>{{ model.description }}</p>
</div>
<div class="model-milestones">
<span class="section-label">🎯 关键能力:</span>
<div class="tags-container">
<el-tag
v-for="(milestone, i) in model.milestones"
:key="i"
size="small"
class="milestone-tag"
>
{{ milestone }}
</el-tag>
</div>
</div>
</div>
</el-tab-pane>
</el-tabs>
<el-divider class="compact-divider">
进化趋势
</el-divider>
<div class="evolution-insight">
<div class="insight-row">
<div class="insight-item">
<el-icon><TrendCharts /></el-icon>
<div class="insight-text">
<span class="label">参数量增长</span>
<span class="value">10000+</span>
</div>
</div>
<div class="insight-item">
<el-icon><ChatDotSquare /></el-icon>
<div class="insight-text">
<span class="label">对话能力</span>
<span class="value">单轮 &rarr; 多轮</span>
</div>
</div>
<div class="insight-item">
<el-icon><Cpu /></el-icon>
<div class="insight-text">
<span class="label">逻辑推理</span>
<span class="value"> &rarr; </span>
</div>
</div>
</div>
<div class="param-val">{{ m.params }}</div>
<div class="param-bar-bg">
<div class="param-bar" :style="{ width: m.barWidth, background: m.color }"></div>
</div>
<div class="gpt-key">{{ m.key }}</div>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { TrendCharts, ChatDotSquare, Cpu } from '@element-plus/icons-vue'
const activeModelName = ref('GPT-1')
const gptModels = [
{
name: 'GPT-1',
year: '2018',
parameters: '1.17 亿',
paramDetail: '117 Million',
context: '512 tokens',
contextDetail: '约 1 页文本',
capability: '无监督预训练',
description: '开创性地使用了 Transformer 解码器进行预训练,证明了无监督学习在 NLP 中的潜力。',
milestones: ['预训练+微调范式', 'BookCorpus 数据集', '单向 Transformer']
},
{
name: 'GPT-2',
year: '2019',
parameters: '15 亿',
paramDetail: '1.5 Billion',
context: '1024 tokens',
contextDetail: '约 2 页文本',
capability: '零样本任务',
description: '参数量扩大 10 倍,展示了惊人的零样本(Zero-shot)能力,能生成连贯的文本。',
milestones: ['WebText 数据集', 'Zero-shot Learning', '生成长文本']
},
{
name: 'GPT-3',
year: '2020',
parameters: '1750 亿',
paramDetail: '175 Billion',
context: '2048 tokens',
contextDetail: '约 4 页文本',
capability: '上下文学习 (ICL)',
description: '参数量爆炸式增长,涌现出上下文学习能力(In-Context Learning),无需微调即可完成任务。',
milestones: ['Few-shot Learning', 'Common Crawl', '能力涌现']
},
{
name: 'GPT-4',
year: '2023',
parameters: '1.8 万亿 (推测)',
paramDetail: '1.8 Trillion (Est.)',
context: '128k tokens',
contextDetail: '约 300 页书',
capability: '多模态 & 推理',
description: '引入多模态能力(识图),逻辑推理和代码能力大幅提升,支持超长上下文。',
milestones: ['多模态输入', 'MoE 架构', 'RLHF 对齐', '考试高手']
}
const models = [
{ name: 'GPT-1', year: '2018', params: '1.17 亿', barWidth: '2%', color: '#94a3b8', key: '预训练+微调范式确立' },
{ name: 'GPT-2', year: '2019', params: '15 亿', barWidth: '6%', color: '#3b82f6', key: 'Zero-shot 零样本泛化' },
{ name: 'GPT-3', year: '2020', params: '1750 亿', barWidth: '45%', color: '#7c3aed', key: '⚡ 涌现!上下文学习' },
{ name: 'GPT-4', year: '2023', params: '~1.8 万亿', barWidth: '100%', color: '#dc2626', key: '多模态 + 复杂推理' },
]
const handleTabClick = (tab) => {
// activeModelName updated automatically
}
</script>
<style scoped>
.gpt-evolution-demo {
margin: 10px 0;
}
.main-card {
border-radius: 6px;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.title {
font-weight: bold;
font-size: 16px;
}
.subtitle {
font-size: 12px;
color: #909399;
}
/* Compact Tabs */
.evolution-tabs :deep(.el-tabs__header) {
margin-bottom: 15px;
}
.model-view {
padding: 0 10px;
}
.model-info-header {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 12px;
}
.model-stats {
display: flex;
gap: 20px;
}
.stat-item {
display: flex;
flex-direction: column;
}
.stat-item .label {
font-size: 12px;
color: #909399;
}
.stat-item .value {
font-weight: bold;
color: #409eff;
font-size: 14px;
}
.model-description {
background-color: #f5f7fa;
padding: 10px;
border-radius: 4px;
margin-bottom: 12px;
font-size: 14px;
color: #606266;
line-height: 1.5;
}
.model-milestones {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
}
.section-label {
font-size: 13px;
font-weight: bold;
color: #303133;
}
.milestone-tag {
margin-right: 4px;
}
.compact-divider {
margin: 15px 0;
}
.evolution-insight {
background-color: #f0f9eb;
padding: 10px;
border-radius: 4px;
}
.insight-row {
display: flex;
justify-content: space-around;
}
.insight-item {
display: flex;
align-items: center;
gap: 8px;
}
.insight-text {
display: flex;
flex-direction: column;
}
.insight-text .label {
font-size: 12px;
color: #67c23a;
}
.insight-text .value {
font-weight: bold;
font-size: 13px;
color: #303133;
}
@media (max-width: 600px) {
.insight-row {
flex-direction: column;
gap: 10px;
}
}
.demo-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); padding: 1.25rem; margin: 1rem 0; }
.gpt-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.5rem; }
@media (max-width: 640px) { .gpt-grid { grid-template-columns: repeat(2, 1fr); } }
.gpt-card { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-top: 3px solid; border-radius: 6px; padding: 0.7rem; display: flex; flex-direction: column; gap: 0.35rem; }
.card-top { display: flex; justify-content: space-between; }
.gpt-name { font-weight: bold; font-size: 0.88rem; }
.gpt-year { font-size: 0.68rem; color: var(--vp-c-text-3); }
.param-val { font-size: 0.78rem; font-weight: bold; font-family: monospace; color: var(--vp-c-text-1); }
.param-bar-bg { height: 6px; background: var(--vp-c-bg-alt); border-radius: 3px; overflow: hidden; }
.param-bar { height: 100%; border-radius: 3px; min-width: 3px; }
.gpt-key { font-size: 0.7rem; color: var(--vp-c-brand-1); background: var(--vp-c-brand-soft); padding: 0.15rem 0.4rem; border-radius: 3px; }
</style>
@@ -1,424 +1,58 @@
<template>
<div class="nn-viz-demo">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<h4>神经网络手动前向传播可控演示</h4>
<p class="subtitle">
"开始 / 上一步 / 下一步"逐层推进避免误把动画当成真实训练过程
</p>
</div>
</template>
<div class="controls mb-4 flex gap-2">
<el-button-group>
<el-button
type="primary"
:disabled="step !== 0"
@click="start"
>
开始
</el-button>
<el-button
:disabled="step <= 1"
@click="prev"
>
上一步
</el-button>
<el-button
type="primary"
:disabled="step === 0 || step >= maxStep"
@click="next"
>
下一步
</el-button>
<el-button @click="reset">
重置
</el-button>
</el-button-group>
<div class="demo-card">
<div class="net-layout">
<div class="svg-wrap">
<svg viewBox="0 0 380 200" class="net-svg">
<line v-for="c in connections" :key="c.id" :x1="c.x1" :y1="c.y1" :x2="c.x2" :y2="c.y2" stroke="#94a3b8" stroke-width="1.2" opacity="0.35" />
<g v-for="layer in layers" :key="layer.idx">
<circle v-for="n in layer.nodes" :key="n.id" :cx="n.x" :cy="n.y" r="15" :fill="layer.fill" :stroke="layer.stroke" stroke-width="2" />
</g>
<text v-for="layer in layers" :key="'l-'+layer.idx" :x="layer.x" y="194" text-anchor="middle" :fill="layer.stroke" class="lbl">{{ layer.name }}</text>
</svg>
</div>
<div
v-if="step > 0"
class="progress mb-4"
>
<el-steps
:active="step"
align-center
finish-status="success"
>
<el-step title="输入层" />
<el-step title="隐藏层" />
<el-step title="输出层" />
</el-steps>
<div class="step-info text-center mt-2 text-sm text-gray-500">
Step {{ step }} / {{ maxStep }} · {{ stepTitle }}
<div class="layer-cards">
<div class="layer-card" v-for="info in layerInfo" :key="info.name" :style="{ borderLeftColor: info.color }">
<div class="lc-title" :style="{ color: info.color }">{{ info.name }}</div>
<div class="lc-desc">{{ info.desc }}</div>
</div>
</div>
<div class="grid-layout">
<el-card
shadow="never"
class="viz-card"
>
<template #header>
<div class="card-title">
网络结构
</div>
</template>
<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(--el-color-primary)',
stopOpacity: 0.18
}"
/>
<stop
offset="100%"
:style="{
stopColor: 'var(--el-color-primary)',
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)
}"
class="connection-line"
/>
</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"
class="neuron-label"
>
{{ n.label }}
</text>
</g>
</g>
</svg>
</div>
<el-alert
title="提示:点击某一层的神经元可以“聚焦”该层(仅用于查看,不会触发自动流程)。"
type="info"
show-icon
:closable="false"
class="mt-2"
/>
</el-card>
<el-card
shadow="never"
class="info-card"
>
<template #header>
<div class="card-title">
每一层在做什么
</div>
</template>
<div class="layers-info">
<el-collapse v-model="activeCollapse">
<el-collapse-item
v-for="(l, idx) in layerConfigs"
:key="l.name"
:title="l.name"
:name="idx"
>
<div class="layer-detail">
<p>{{ l.desc }}</p>
<div class="math-box">
<code>{{ l.math }}</code>
</div>
</div>
</el-collapse-item>
</el-collapse>
</div>
</el-card>
</div>
</el-card>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
const svgWidth = 600
const svgHeight = 300
const step = ref(0)
const maxStep = 3
const focusLayer = ref(null)
const activeCollapse = ref([0])
// Mock logic for demo
const layerConfigs = [
{
name: '输入层 (Input)',
desc: '接收原始数据(如图片的像素值)。',
math: 'x = [x1, x2, x3]'
},
{
name: '隐藏层 (Hidden)',
desc: '提取特征(如边缘、形状)。每个神经元计算加权和并激活。',
math: 'h = ReLU(W1·x + b1)'
},
{
name: '输出层 (Output)',
desc: '给出最终结果(如分类概率)。',
math: 'y = Softmax(W2·h + b2)'
}
const W = 380, H = 185
const layerDef = [
{ name: '输入层', count: 3, xFrac: 0.13, color: '#3b82f6', fill: '#dbeafe' },
{ name: '隐藏层', count: 4, xFrac: 0.5, color: '#7c3aed', fill: '#ede9fe' },
{ name: '输出层', count: 2, xFrac: 0.87, color: '#059669', fill: '#d1fae5' },
]
const neurons = ref([])
const connections = ref([])
const stepTitle = computed(() => {
if (step.value === 0) return '准备就绪'
if (step.value === 1) return '输入数据进入网络'
if (step.value === 2) return '隐藏层提取特征'
if (step.value === 3) return '输出层得出结果'
return ''
const layers = layerDef.map((l, idx) => {
const x = l.xFrac * W
const gap = Math.min(46, (H - 36) / (l.count - 1 || 1))
const startY = (H - gap * (l.count - 1)) / 2
return { idx, x, name: l.name, fill: l.fill, stroke: l.color, nodes: Array.from({ length: l.count }, (_, i) => ({ id: `${idx}-${i}`, x, y: startY + i * gap })) }
})
const initNetwork = () => {
// Simple layout logic
const layers = [3, 4, 2] // Neuron counts per layer
const layerX = [100, 300, 500]
const ns = []
const cs = []
layers.forEach((count, layerIdx) => {
const startY = (svgHeight - (count - 1) * 60) / 2
for (let i = 0; i < count; i++) {
ns.push({
id: `n-${layerIdx}-${i}`,
layer: layerIdx,
x: layerX[layerIdx],
y: startY + i * 60,
r: 20,
label: layerIdx === 0 ? `x${i + 1}` : layerIdx === 2 ? `y${i + 1}` : ''
})
}
})
// Create connections
ns.forEach((src) => {
ns.forEach((tgt) => {
if (tgt.layer === src.layer + 1) {
cs.push({
id: `c-${src.id}-${tgt.id}`,
srcId: src.id,
tgtId: tgt.id,
srcLayer: src.layer,
x1: src.x,
y1: src.y,
x2: tgt.x,
y2: tgt.y
})
}
})
})
neurons.value = ns
connections.value = cs
}
onMounted(() => {
initNetwork()
})
const start = () => {
step.value = 1
focusLayer.value = 0
activeCollapse.value = [0]
}
const next = () => {
if (step.value < maxStep) {
step.value++
focusLayer.value = step.value - 1
activeCollapse.value = [step.value - 1]
}
}
const prev = () => {
if (step.value > 1) {
step.value--
focusLayer.value = step.value - 1
activeCollapse.value = [step.value - 1]
}
}
const reset = () => {
step.value = 0
focusLayer.value = null
activeCollapse.value = [0]
}
const isNeuronActive = (n) => {
if (step.value === 0) return false
return n.layer < step.value
}
const isConnectionActive = (c) => {
if (step.value === 0) return false
return c.srcLayer < step.value - 1
}
const isConnectionFocus = (c) => {
// Optional: highlight connections related to focused layer
return false
const connections = []
for (let li = 0; li < layers.length - 1; li++) {
layers[li].nodes.forEach(a => { layers[li + 1].nodes.forEach(b => { connections.push({ id: `${a.id}-${b.id}`, x1: a.x, y1: a.y, x2: b.x, y2: b.y }) }) })
}
const layerInfo = [
{ name: '输入层', desc: '原始像素 / 数值信号', color: '#3b82f6' },
{ name: '隐藏层(可叠加多层)', desc: '底层识别边缘 → 中层识别形状 → 高层识别语义概念', color: '#7c3aed' },
{ name: '输出层', desc: '最终分类或预测结果', color: '#059669' },
]
</script>
<style scoped>
.nn-viz-demo {
margin: 20px 0;
}
.card-header h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.subtitle {
font-size: 13px;
color: var(--vp-c-text-2);
margin: 4px 0 0;
}
.mb-4 {
margin-bottom: 16px;
}
.mt-2 {
margin-top: 8px;
}
.flex {
display: flex;
}
.gap-2 {
gap: 8px;
}
.text-center {
text-align: center;
}
.text-sm {
font-size: 12px;
}
.text-gray-500 {
color: var(--vp-c-text-2);
}
.grid-layout {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 20px;
}
@media (max-width: 768px) {
.grid-layout {
grid-template-columns: 1fr;
}
}
.network-container {
display: flex;
justify-content: center;
background-color: var(--vp-c-bg-alt);
border-radius: 6px;
padding: 10px;
}
.network-svg {
width: 100%;
height: auto;
}
.connection-line {
stroke: var(--vp-c-divider);
stroke-width: 2;
transition: all 0.5s;
}
.connection-line.active {
stroke: var(--el-color-primary);
opacity: 0.5;
}
.neuron circle {
fill: var(--vp-c-bg);
stroke: var(--vp-c-text-2);
stroke-width: 2;
transition: all 0.5s;
cursor: pointer;
}
.neuron.active circle {
fill: var(--el-color-primary-light-9);
stroke: var(--el-color-primary);
}
.neuron.focus circle {
stroke-width: 4;
}
.neuron-label {
font-size: 12px;
fill: var(--vp-c-text-1);
}
.math-box {
background-color: var(--vp-c-bg-alt);
padding: 8px;
border-radius: 4px;
margin-top: 8px;
font-family: monospace;
font-size: 12px;
}
.demo-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); padding: 1.25rem; margin: 1rem 0; }
.net-layout { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 6px; padding: 0.9rem; }
@media (max-width: 600px) { .net-layout { grid-template-columns: 1fr; } }
.svg-wrap { display: flex; align-items: center; justify-content: center; background: var(--vp-c-bg-alt); border-radius: 6px; }
.net-svg { width: 100%; height: auto; }
.lbl { font-size: 9px; font-weight: bold; }
.layer-cards { display: flex; flex-direction: column; gap: 0.4rem; justify-content: center; }
.layer-card { border-left: 3px solid; padding: 0.5rem 0.7rem; background: var(--vp-c-bg-alt); border-radius: 0 5px 5px 0; }
.lc-title { font-weight: bold; font-size: 0.78rem; margin-bottom: 0.15rem; }
.lc-desc { font-size: 0.73rem; color: var(--vp-c-text-2); line-height: 1.4; }
</style>
@@ -1,288 +1,71 @@
<template>
<div class="perceptron-demo">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<h4>感知机 (Perceptron) 演示</h4>
<p class="subtitle">
最简单的神经元输入 x 权重 + 偏置 = 输出
</p>
</div>
</template>
<div class="neuron-viz-container">
<!-- Inputs -->
<div class="col inputs-col">
<div class="node-wrapper">
<el-tag effect="dark">
输入 A
</el-tag>
<el-input-number
v-model="x1"
size="small"
:step="1"
/>
</div>
<div class="node-wrapper">
<el-tag effect="dark">
输入 B
</el-tag>
<el-input-number
v-model="x2"
size="small"
:step="1"
/>
</div>
</div>
<!-- Weights Visual -->
<div class="col weights-col">
<div class="weight-group">
<div
class="weight-line"
:style="{
height: Math.abs(w1) * 2 + 2 + 'px',
opacity: Math.abs(w1) / 5 + 0.2
}"
/>
<div class="weight-control">
<span class="label">权重 A: {{ w1 }}</span>
<el-slider
v-model="w1"
:min="-5"
:max="5"
:step="0.1"
size="small"
/>
</div>
</div>
<div class="weight-group">
<div
class="weight-line"
:style="{
height: Math.abs(w2) * 2 + 2 + 'px',
opacity: Math.abs(w2) / 5 + 0.2
}"
/>
<div class="weight-control">
<span class="label">权重 B: {{ w2 }}</span>
<el-slider
v-model="w2"
:min="-5"
:max="5"
:step="0.1"
size="small"
/>
</div>
</div>
</div>
<!-- Neuron Body -->
<div class="col neuron-col">
<div class="neuron-circle">
<div class="sum-symbol">
总分
</div>
<div class="sum-value">
{{ weightedSum.toFixed(1) }}
</div>
</div>
<div class="bias-control mt-2">
<span class="label">基础分 (Bias):</span>
<el-input-number
v-model="bias"
size="small"
:step="1"
/>
</div>
</div>
<!-- Output -->
<div class="col output-col">
<el-icon class="arrow-icon">
<Right />
</el-icon>
<div class="node-wrapper">
<el-tag
:type="output > 0 ? 'success' : 'info'"
effect="dark"
>
结果 (Output)
</el-tag>
<div
class="output-value"
:class="{ active: output > 0 }"
>
{{ output }}
</div>
</div>
<div class="demo-card">
<div class="perceptron-layout">
<div class="inputs-col">
<div class="input-node" v-for="inp in inputs" :key="inp.label">
<span class="node-circle">{{ inp.val }}</span>
<span class="node-label">{{ inp.label }}</span>
</div>
</div>
<el-divider />
<div class="formula-bar">
<el-alert
type="info"
:closable="false"
>
<template #title>
<div class="formula-content">
<div>
<strong>总分计算: </strong>
<span class="calc-step">
(输入A {{ x1 }} × 权重 {{ w1 }}) + (输入B {{ x2 }} × 权重 {{ w2 }}) + 基础分 {{ bias }} =
{{ weightedSum.toFixed(1) }}
</span>
</div>
<div class="mt-1">
<strong>判断结果: </strong>
<span class="calc-step">
{{ weightedSum.toFixed(1) }} {{ weightedSum > 0 ? '>' : '≤' }} 0
输出 {{ output }} ({{ output > 0 ? '激活' : '静默' }})
</span>
</div>
</div>
</template>
</el-alert>
<div class="weights-col">
<div class="weight-arrow" v-for="inp in inputs" :key="inp.label">
<span class="arrow"></span>
<span class="w-tag">×{{ inp.weight }}</span>
</div>
</div>
</el-card>
<div class="neuron-col">
<div class="neuron-circle">
<div class="n-sym">Σ</div>
<div class="n-val">{{ sum }}</div>
</div>
<span class="bias-tag">偏置 {{ bias }}</span>
</div>
<div class="act-col">
<span class="arrow big"></span>
<div class="act-box">sum &gt; 0 ?</div>
<span class="arrow big"></span>
</div>
<div class="output-col">
<div class="output-node" :class="{ on: output === 1 }">
<span class="out-val">{{ output }}</span>
<span class="out-lbl">{{ output ? '激活' : '静默' }}</span>
</div>
</div>
</div>
<div class="caption">
输入特征&emsp; 乘以权重重要性&emsp; 求和 + 偏置&emsp; 超过阈值就激活输出 1否则输出 0
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { Right } from '@element-plus/icons-vue'
const x1 = ref(1)
const x2 = ref(0)
const w1 = ref(2.0)
const w2 = ref(-1.0)
const bias = ref(0)
const weightedSum = computed(() => {
return x1.value * w1.value + x2.value * w2.value + bias.value
})
const output = computed(() => {
return weightedSum.value > 0 ? 1 : 0
})
import { computed } from 'vue'
const inputs = [{ label: '特征 x₁', val: 1, weight: 0.6 }, { label: '特征 x₂', val: 0, weight: 0.4 }]
const bias = -0.3
const sum = computed(() => Number((inputs.reduce((s, i) => s + i.val * i.weight, 0) + bias).toFixed(2)))
const output = computed(() => sum.value > 0 ? 1 : 0)
</script>
<style scoped>
.perceptron-demo {
margin: 20px 0;
}
.card-header h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.subtitle {
font-size: 13px;
color: var(--vp-c-text-2);
margin: 4px 0 0;
}
.neuron-viz-container {
display: flex;
align-items: center;
justify-content: space-between;
gap: 20px;
padding: 20px 0;
overflow-x: auto;
}
.col {
display: flex;
flex-direction: column;
align-items: center;
gap: 20px;
}
.node-wrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.weight-group {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
width: 120px;
}
.weight-line {
width: 100%;
background-color: var(--el-color-primary);
height: 2px;
min-height: 2px;
}
.weight-control {
width: 100%;
text-align: center;
}
.label {
font-size: 12px;
color: var(--vp-c-text-2);
}
.neuron-circle {
width: 80px;
height: 80px;
border-radius: 50%;
border: 2px solid var(--el-color-primary);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: var(--vp-c-bg);
}
.sum-symbol {
font-size: 24px;
font-weight: bold;
}
.sum-value {
font-family: monospace;
}
.arrow-icon {
font-size: 24px;
color: var(--vp-c-text-2);
}
.output-value {
font-size: 24px;
font-weight: bold;
color: var(--vp-c-text-2);
}
.output-value.active {
color: var(--el-color-success);
}
.mt-1 {
margin-top: 4px;
}
.mt-2 {
margin-top: 8px;
}
.formula-content code {
background-color: var(--vp-c-bg-alt);
padding: 2px 4px;
border-radius: 4px;
font-family: monospace;
}
.demo-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); padding: 1.25rem; margin: 1rem 0; }
.perceptron-layout { display: flex; align-items: center; justify-content: center; gap: 0.5rem; background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-radius: 6px; padding: 1.2rem 0.8rem; flex-wrap: wrap; }
.inputs-col, .weights-col, .neuron-col, .act-col, .output-col { display: flex; flex-direction: column; align-items: center; gap: 1rem; }
.input-node { display: flex; flex-direction: column; align-items: center; gap: 0.2rem; }
.node-circle { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; background: var(--vp-c-brand-soft); border: 2px solid var(--vp-c-brand); color: var(--vp-c-brand-1); }
.node-label { font-size: 0.62rem; color: var(--vp-c-text-2); }
.weight-arrow { display: flex; align-items: center; gap: 0.3rem; }
.arrow { color: var(--vp-c-text-3); font-size: 1.1rem; }
.arrow.big { font-size: 1.4rem; }
.w-tag { font-size: 0.72rem; font-weight: bold; font-family: monospace; background: var(--vp-c-bg-alt); border: 1px solid var(--vp-c-divider); padding: 0.1rem 0.4rem; border-radius: 4px; color: var(--vp-c-brand-1); }
.neuron-circle { width: 64px; height: 64px; border-radius: 50%; border: 3px solid var(--vp-c-brand); background: var(--vp-c-bg-alt); display: flex; flex-direction: column; align-items: center; justify-content: center; }
.n-sym { font-size: 1.2rem; font-weight: bold; color: var(--vp-c-brand); }
.n-val { font-size: 0.8rem; font-weight: bold; font-family: monospace; }
.bias-tag { font-size: 0.62rem; color: var(--vp-c-text-3); padding: 0.1rem 0.4rem; border: 1px dashed var(--vp-c-divider); border-radius: 4px; }
.act-col { flex-direction: row; }
.act-box { font-size: 0.72rem; font-family: monospace; background: var(--vp-c-bg-alt); border: 1px solid var(--vp-c-divider); border-radius: 6px; padding: 0.4rem 0.6rem; }
.output-node { width: 54px; height: 54px; border-radius: 50%; border: 2px solid var(--vp-c-divider); background: var(--vp-c-bg-alt); display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.1rem; }
.output-node.on { border-color: #059669; background: rgba(5,150,105,0.08); }
.out-val { font-size: 1.3rem; font-weight: bold; }
.out-lbl { font-size: 0.58rem; color: var(--vp-c-text-2); }
.caption { font-size: 0.75rem; color: var(--vp-c-text-3); text-align: center; margin-top: 0.6rem; }
</style>
@@ -1,361 +1,49 @@
<template>
<div class="rule-learning-demo">
<el-card shadow="hover">
<template #header>
<div class="card-header">
<h4>规则 vs 学习</h4>
<p class="subtitle">
对比你写阈值 (规则) vs 让模型从数据里"推断"阈值 (学习)
</p>
<div class="demo-card">
<div class="demo-header">
<span class="title">关键发展路径总结</span>
</div>
<div class="path-flow">
<div class="path-item" v-for="(item, i) in path" :key="i">
<div class="path-card" :style="{ borderLeftColor: item.color }">
<div class="path-top">
<span class="path-icon" :style="{ background: item.color }">{{ i + 1 }}</span>
<div>
<div class="path-name">{{ item.name }}</div>
<div class="path-years">{{ item.years }}</div>
</div>
</div>
<div class="path-desc">{{ item.desc }}</div>
</div>
</template>
<el-row :gutter="20">
<!-- Rule Based -->
<el-col
:xs="24"
:md="12"
class="mb-4-xs"
>
<el-card
shadow="never"
class="panel-card"
>
<template #header>
<div class="panel-title">
规则系统手写 If/Else
</div>
</template>
<div class="panel-content">
<div class="control-row">
<span class="label">阈值 size &gt;</span>
<el-input-number
v-model="ruleThreshold"
:min="1"
:max="10"
size="small"
/>
<span class="text-xs text-gray">必须明确写出</span>
</div>
<div class="control-row mt-4">
<span class="label">测试输入 size</span>
<el-slider
v-model="testInput"
:min="1"
:max="10"
show-input
input-size="small"
class="flex-1"
/>
</div>
<div
class="result-box mt-4"
:class="{
good: ruleResult.label === '🍎',
bad: ruleResult.label === '🍒'
}"
>
<div class="result-title">
输出
</div>
<div class="result-value">
{{ ruleResult.text }}
</div>
<div class="result-code">
if (size &gt; {{ ruleThreshold }}) return 🍎 else return 🍒
</div>
</div>
<el-alert
title="当环境变化(比如'苹果平均变小了'),你需要手动改规则;规则越多,维护成本越高。"
type="warning"
:closable="false"
class="mt-4"
/>
</div>
</el-card>
</el-col>
<!-- Machine Learning -->
<el-col
:xs="24"
:md="12"
>
<el-card
shadow="never"
class="panel-card"
>
<template #header>
<div class="panel-title">
机器学习从样本推断边界
</div>
</template>
<div class="panel-content">
<div class="control-row">
<el-input-number
v-model="newSize"
:min="1"
:max="10"
size="small"
placeholder="Size"
/>
<el-select
v-model="newLabel"
size="small"
placeholder="Label"
style="width: 120px"
>
<el-option
label="🍒 樱桃"
value="🍒"
/>
<el-option
label="🍎 苹果"
value="🍎"
/>
</el-select>
<el-button
type="primary"
size="small"
@click="addSample"
>
添加样本
</el-button>
</div>
<div class="samples-area mt-4">
<el-empty
v-if="trainingData.length === 0"
description="还没有样本:先添加 2-4 个样本再训练"
:image-size="40"
/>
<div
v-else
class="sample-chips"
>
<el-tag
v-for="(p, i) in trainingData"
:key="p.id"
closable
:type="p.label === '🍎' ? 'danger' : 'info'"
effect="plain"
@close="removeSample(i)"
>
{{ p.size }} {{ p.label }}
</el-tag>
</div>
</div>
<div class="actions mt-4 flex gap-2">
<el-button
type="success"
:disabled="trainingData.length < 2"
@click="train"
>
训练推断阈值
</el-button>
<el-button @click="resetLearning">
重置
</el-button>
</div>
<div
v-if="learnedThreshold !== null"
class="learned-result mt-4"
>
<el-alert
type="success"
:closable="false"
show-icon
title="学习完成!"
>
<p>
模型推断出阈值应为: <strong>{{ learnedThreshold }}</strong>
</p>
<p class="text-xs">
(大于 {{ learnedThreshold }} 是苹果否则是樱桃)
</p>
</el-alert>
</div>
</div>
</el-card>
</el-col>
</el-row>
</el-card>
<div v-if="i < path.length - 1" class="path-connector">
<svg width="20" height="24" viewBox="0 0 20 24"><path d="M10 0 L10 18 L5 13 M10 18 L15 13" fill="none" stroke="var(--vp-c-text-3)" stroke-width="1.5" stroke-linecap="round" /></svg>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
// Rule Based Logic
const ruleThreshold = ref(5)
const testInput = ref(6)
const ruleResult = computed(() => {
if (testInput.value > ruleThreshold.value) {
return { label: '🍎', text: '🍎 苹果' }
} else {
return { label: '🍒', text: '🍒 樱桃' }
}
})
// ML Logic
const newSize = ref(5)
const newLabel = ref('🍎')
const trainingData = ref([
{ id: 1, size: 2, label: '🍒' },
{ id: 2, size: 8, label: '🍎' }
])
const learnedThreshold = ref(null)
const addSample = () => {
trainingData.value.push({
id: Date.now(),
size: newSize.value,
label: newLabel.value
})
}
const removeSample = (index) => {
trainingData.value.splice(index, 1)
}
const resetLearning = () => {
trainingData.value = []
learnedThreshold.value = null
}
const train = () => {
// Simple "training": find the boundary between cherry and apple
// Sort data by size
const sorted = [...trainingData.value].sort((a, b) => a.size - b.size)
// Find the first Apple
const firstAppleIndex = sorted.findIndex((item) => item.label === '🍎')
if (firstAppleIndex === -1) {
// All cherries
learnedThreshold.value = 10
} else if (firstAppleIndex === 0) {
// All apples
learnedThreshold.value = 0
} else {
// Boundary is between last cherry and first apple
const lastCherry = sorted[firstAppleIndex - 1]
const firstApple = sorted[firstAppleIndex]
learnedThreshold.value = (lastCherry.size + firstApple.size) / 2
}
}
const path = [
{ name: '理论奠基', years: '1940s-1950s', desc: '图灵测试、达特茅斯会议,符号主义诞生', color: '#3b82f6' },
{ name: '符号主义主导', years: '1960s-1980s', desc: '专家系统兴起与两次 AI 寒冬', color: '#059669' },
{ name: '机器学习转型', years: '1990s-2000s', desc: '统计方法取代规则,连接主义复苏', color: '#d97706' },
{ name: '深度学习革命', years: '2010s', desc: 'AlexNet、AlphaGo、Transformer 架构,连接主义成为主流', color: '#dc2626' },
{ name: '大模型时代', years: '2018 至今', desc: 'GPT 系列、多模态融合,通用智能曙光初现', color: '#7c3aed' },
]
</script>
<style scoped>
.rule-learning-demo {
margin: 20px 0;
}
.card-header h4 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
.subtitle {
font-size: 13px;
color: var(--vp-c-text-2);
margin: 4px 0 0;
}
.panel-title {
font-weight: bold;
font-size: 14px;
}
.control-row {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.label {
font-size: 14px;
}
.text-xs {
font-size: 12px;
}
.text-gray {
color: var(--vp-c-text-2);
}
.flex-1 {
flex: 1;
}
.mt-4 {
margin-top: 16px;
}
.mb-4-xs {
margin-bottom: 20px;
}
@media (min-width: 992px) {
.mb-4-xs {
margin-bottom: 0;
}
}
.result-box {
background-color: var(--vp-c-bg-alt);
padding: 12px;
border-radius: 6px;
border: 1px solid var(--vp-c-divider);
text-align: center;
}
.result-box.good {
border-color: var(--el-color-danger);
background-color: var(--el-color-danger-light-9);
}
.result-box.bad {
border-color: var(--el-color-primary);
background-color: var(--el-color-primary-light-9);
}
.result-title {
font-size: 12px;
color: var(--vp-c-text-2);
text-transform: uppercase;
}
.result-value {
font-size: 24px;
font-weight: bold;
margin: 8px 0;
}
.result-code {
font-family: monospace;
font-size: 12px;
background-color: rgba(0, 0, 0, 0.05);
padding: 4px;
border-radius: 4px;
}
.sample-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
min-height: 40px;
}
.gap-2 {
gap: 8px;
}
.demo-card { border: 1px solid var(--vp-c-divider); border-radius: 8px; background: var(--vp-c-bg-soft); padding: 1.25rem; margin: 1rem 0; }
.demo-header { margin-bottom: 1rem; }
.demo-header .title { font-weight: bold; font-size: 1rem; }
.path-flow { display: flex; flex-direction: column; align-items: stretch; }
.path-item { display: flex; flex-direction: column; align-items: center; }
.path-card { background: var(--vp-c-bg); border: 1px solid var(--vp-c-divider); border-left: 4px solid; border-radius: 0 8px 8px 0; padding: 0.8rem 1rem; width: 100%; }
.path-top { display: flex; align-items: center; gap: 0.7rem; margin-bottom: 0.35rem; }
.path-icon { width: 24px; height: 24px; border-radius: 50%; color: white; font-size: 0.72rem; font-weight: bold; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.path-name { font-weight: bold; font-size: 0.9rem; color: var(--vp-c-text-1); }
.path-years { font-size: 0.72rem; color: var(--vp-c-text-3); font-weight: bold; }
.path-desc { font-size: 0.8rem; color: var(--vp-c-text-2); line-height: 1.4; padding-left: 2.2rem; }
.path-connector { display: flex; justify-content: center; padding: 0.15rem 0; }
</style>