docs: update content and components across multiple files

- Refine chapter introductions in zh-cn docs for clarity and conciseness
- Update navigation links to include '/easy-vibe' prefix
- Simplify UI components (ChapterIntroduction, ContextWindowVisualizer)
- Add new agent-related demo components (AgentMemoryDemo, AgentToolUseDemo)
- Improve context compression demo with better visuals and metrics
- Adjust styling and layout across various components
This commit is contained in:
sanbuphy
2026-02-03 01:46:03 +08:00
parent ad95658a11
commit e5b1c6cc88
31 changed files with 11651 additions and 2156 deletions
@@ -0,0 +1,535 @@
<!--
UNetDenoiseDemo.vue
UNet 去噪过程演示组件
用途
展示 UNet/DiT 如何从噪声中逐步恢复图像理解扩散模型的核心去噪机制
交互功能
- 单步/自动播放去噪过程
- 可视化噪声预测
- 展示不同时间步的预测结果
- 对比有/无文本引导的生成
-->
<template>
<div class="unet-demo">
<el-card shadow="never">
<template #header>
<div class="header-controls">
<span class="title">🧠 UNet 去噪模型</span>
<div class="controls">
<el-button-group>
<el-button @click="stepBackward" :disabled="currentStep <= 0">
<el-icon><ArrowLeft /></el-icon>
</el-button>
<el-button @click="togglePlay">
<el-icon v-if="isPlaying"><VideoPause /></el-icon>
<el-icon v-else><VideoPlay /></el-icon>
</el-button>
<el-button @click="stepForward" :disabled="currentStep >= totalSteps">
<el-icon><ArrowRight /></el-icon>
</el-button>
</el-button-group>
<el-button @click="reset">重置</el-button>
</div>
</div>
</template>
<div class="demo-content">
<!-- 主展示区 -->
<div class="main-display">
<div class="display-section">
<div class="section-label">当前噪声图像 (Noisy Image)</div>
<canvas
ref="noisyCanvas"
width="256"
height="256"
class="display-canvas"
/>
<div class="timestep-info">
<el-tag type="info">Timestep: {{ currentStep }} / {{ totalSteps }}</el-tag>
<el-tag :type="getNoiseLevelType()">噪声强度: {{ getNoiseLevel() }}%</el-tag>
</div>
</div>
<div class="arrow-section">
<el-icon :size="24"><ArrowRight /></el-icon>
<div class="model-box">
<div class="model-name">UNet / DiT</div>
<div class="model-desc">预测噪声</div>
</div>
<el-icon :size="24"><ArrowRight /></el-icon>
</div>
<div class="display-section">
<div class="section-label">预测的噪声 (Predicted Noise)</div>
<canvas
ref="noiseCanvas"
width="256"
height="256"
class="display-canvas noise-preview"
/>
<div class="noise-stats">
<el-tag size="small" type="warning">噪声估计</el-tag>
</div>
</div>
<div class="arrow-section">
<el-icon :size="24"><ArrowRight /></el-icon>
<div class="operation-box">
<div class="op-name">减法</div>
<div class="op-formula">x - ε</div>
</div>
<el-icon :size="24"><ArrowRight /></el-icon>
</div>
<div class="display-section">
<div class="section-label">去噪结果 (Denoised)</div>
<canvas
ref="denoisedCanvas"
width="256"
height="256"
class="display-canvas"
/>
<div class="progress-info">
<el-progress
:percentage="(currentStep / totalSteps) * 100"
:status="currentStep === totalSteps ? 'success' : ''"
/>
</div>
</div>
</div>
<!-- 时间轴 -->
<div class="timeline-section">
<div class="timeline-label">去噪时间轴</div>
<el-slider
v-model="currentStep"
:min="0"
:max="totalSteps"
:step="1"
show-stops
:marks="marks"
@input="updateDisplay"
/>
</div>
<!-- 对比模式 -->
<div class="compare-section">
<el-switch
v-model="showComparison"
active-text="显示对比 (/无文本引导)"
/>
<div v-if="showComparison" class="compare-display">
<div class="compare-item">
<div class="compare-label">无引导 (Unconditional)</div>
<canvas
ref="uncondCanvas"
width="200"
height="200"
class="compare-canvas"
/>
</div>
<div class="compare-item">
<div class="compare-label">有引导 (CFG Scale=7.5)</div>
<canvas
ref="condCanvas"
width="200"
height="200"
class="compare-canvas"
/>
</div>
</div>
</div>
</div>
<div class="info-box">
<p>
<span class="icon">💡</span>
<strong>去噪原理</strong>
UNet 学习预测图像中的噪声然后用原图减去预测的噪声得到更清晰的结果重复这个过程直到从纯噪声恢复出清晰图像
</p>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { ArrowRight, ArrowLeft, VideoPlay, VideoPause } from '@element-plus/icons-vue'
const noisyCanvas = ref(null)
const noiseCanvas = ref(null)
const denoisedCanvas = ref(null)
const uncondCanvas = ref(null)
const condCanvas = ref(null)
const currentStep = ref(0)
const totalSteps = 20
const isPlaying = ref(false)
const showComparison = ref(false)
const marks = {
0: '纯噪声',
10: '中期',
20: '清晰图'
}
let animationId = null
// 生成目标图像(简化版)
const generateTargetImage = () => {
const canvas = document.createElement('canvas')
canvas.width = 256
canvas.height = 256
const ctx = canvas.getContext('2d')
// 绘制简单的目标图案
const gradient = ctx.createLinearGradient(0, 0, 256, 256)
gradient.addColorStop(0, '#667eea')
gradient.addColorStop(1, '#764ba2')
ctx.fillStyle = gradient
ctx.fillRect(0, 0, 256, 256)
// 添加一些形状
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'
for (let i = 0; i < 5; i++) {
ctx.beginPath()
ctx.arc(50 + i * 40, 100 + (i % 2) * 50, 30, 0, Math.PI * 2)
ctx.fill()
}
return ctx.getImageData(0, 0, 256, 256)
}
const targetImage = generateTargetImage()
// 生成噪声
const generateNoise = (width, height, intensity) => {
const data = new Uint8ClampedArray(width * height * 4)
for (let i = 0; i < data.length; i += 4) {
const noise = (Math.random() - 0.5) * intensity * 255
data[i] = 128 + noise
data[i + 1] = 128 + noise
data[i + 2] = 128 + noise
data[i + 3] = 255
}
return new ImageData(data, width, height)
}
// 混合图像和噪声
const blendWithNoise = (imageData, noiseRatio) => {
const result = new Uint8ClampedArray(imageData.data)
for (let i = 0; i < result.length; i += 4) {
const noise = (Math.random() - 0.5) * noiseRatio * 255
result[i] = Math.max(0, Math.min(255, imageData.data[i] * (1 - noiseRatio) + 128 * noiseRatio + noise))
result[i + 1] = Math.max(0, Math.min(255, imageData.data[i + 1] * (1 - noiseRatio) + 128 * noiseRatio + noise))
result[i + 2] = Math.max(0, Math.min(255, imageData.data[i + 2] * (1 - noiseRatio) + 128 * noiseRatio + noise))
}
return new ImageData(result, imageData.width, imageData.height)
}
// 预测噪声(简化模拟)
const predictNoise = (width, height, step) => {
const noiseRatio = 1 - (step / totalSteps)
return generateNoise(width, height, noiseRatio * 0.5)
}
// 去噪
const denoise = (noisyData, noiseData, step) => {
const result = new Uint8ClampedArray(noisyData.data)
const denoiseStrength = 0.1 + (step / totalSteps) * 0.4
for (let i = 0; i < result.length; i += 4) {
// 模拟:从噪声图像中减去预测的噪声
const targetR = targetImage.data[i]
const targetG = targetImage.data[i + 1]
const targetB = targetImage.data[i + 2]
const currentR = noisyData.data[i]
const currentG = noisyData.data[i + 1]
const currentB = noisyData.data[i + 2]
result[i] = currentR + (targetR - currentR) * denoiseStrength
result[i + 1] = currentG + (targetG - currentG) * denoiseStrength
result[i + 2] = currentB + (targetB - currentB) * denoiseStrength
}
return new ImageData(result, noisyData.width, noisyData.height)
}
// 更新显示
const updateDisplay = () => {
const step = currentStep.value
const noiseRatio = 1 - (step / totalSteps)
// 绘制噪声图像
const noisyCtx = noisyCanvas.value.getContext('2d')
const noisyData = blendWithNoise(targetImage, noiseRatio)
noisyCtx.putImageData(noisyData, 0, 0)
// 绘制预测的噪声
const noiseCtx = noiseCanvas.value.getContext('2d')
const noiseData = predictNoise(256, 256, step)
noiseCtx.putImageData(noiseData, 0, 0)
// 绘制去噪结果
const denoisedCtx = denoisedCanvas.value.getContext('2d')
const denoisedData = denoise(noisyData, noiseData, step)
denoisedCtx.putImageData(denoisedData, 0, 0)
// 更新对比图
if (showComparison.value && uncondCanvas.value && condCanvas.value) {
// 无条件生成(更多噪声残留)
const uncondCtx = uncondCanvas.value.getContext('2d')
const uncondData = blendWithNoise(targetImage, noiseRatio * 0.3)
uncondCtx.putImageData(uncondData, 0, 0)
// 有条件生成(更清晰)
const condCtx = condCanvas.value.getContext('2d')
condCtx.putImageData(denoisedData, 0, 0)
}
}
const getNoiseLevel = () => {
return Math.round((1 - currentStep.value / totalSteps) * 100)
}
const getNoiseLevelType = () => {
const level = getNoiseLevel()
if (level > 70) return 'danger'
if (level > 30) return 'warning'
return 'success'
}
const stepForward = () => {
if (currentStep.value < totalSteps) {
currentStep.value++
updateDisplay()
}
}
const stepBackward = () => {
if (currentStep.value > 0) {
currentStep.value--
updateDisplay()
}
}
const togglePlay = () => {
if (isPlaying.value) {
stopAnimation()
} else {
startAnimation()
}
}
const startAnimation = () => {
isPlaying.value = true
const animate = () => {
if (!isPlaying.value) return
if (currentStep.value >= totalSteps) {
currentStep.value = 0
} else {
currentStep.value++
}
updateDisplay()
animationId = setTimeout(() => {
requestAnimationFrame(animate)
}, 200)
}
animate()
}
const stopAnimation = () => {
isPlaying.value = false
if (animationId) {
clearTimeout(animationId)
animationId = null
}
}
const reset = () => {
stopAnimation()
currentStep.value = 0
updateDisplay()
}
onMounted(updateDisplay)
onUnmounted(stopAnimation)
</script>
<style scoped>
.unet-demo {
margin: 1rem 0;
}
.header-controls {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.title {
font-weight: 600;
}
.controls {
display: flex;
gap: 8px;
}
.demo-content {
display: flex;
flex-direction: column;
gap: 24px;
}
.main-display {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
flex-wrap: wrap;
padding: 16px 0;
}
.display-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.section-label {
font-size: 0.875rem;
font-weight: 500;
color: var(--vp-c-text-2);
}
.display-canvas {
width: 200px;
height: 200px;
background: var(--vp-c-bg-mute);
border-radius: 8px;
border: 2px solid var(--vp-c-divider);
}
.noise-preview {
filter: grayscale(100%);
}
.timestep-info {
display: flex;
gap: 8px;
}
.arrow-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
color: var(--vp-c-text-3);
}
.model-box,
.operation-box {
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
padding: 12px 16px;
text-align: center;
min-width: 80px;
}
.model-name,
.op-name {
font-weight: 600;
font-size: 0.875rem;
}
.model-desc,
.op-formula {
font-size: 0.75rem;
color: var(--vp-c-text-3);
margin-top: 4px;
}
.progress-info {
width: 100%;
max-width: 200px;
}
.timeline-section {
padding: 16px;
background: var(--vp-c-bg-soft);
border-radius: 8px;
}
.timeline-label {
font-weight: 500;
margin-bottom: 12px;
}
.compare-section {
padding: 16px;
background: var(--vp-c-bg-soft);
border-radius: 8px;
}
.compare-display {
display: flex;
justify-content: center;
gap: 24px;
margin-top: 16px;
flex-wrap: wrap;
}
.compare-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.compare-label {
font-size: 0.875rem;
color: var(--vp-c-text-2);
}
.compare-canvas {
width: 150px;
height: 150px;
background: var(--vp-c-bg-mute);
border-radius: 8px;
border: 2px solid var(--vp-c-divider);
}
.info-box {
margin-top: 16px;
padding: 12px;
background: var(--vp-c-bg-mute);
border-radius: 6px;
font-size: 0.9rem;
line-height: 1.6;
}
.icon {
font-size: 1.2em;
}
@media (max-width: 768px) {
.main-display {
flex-direction: column;
}
.arrow-section {
transform: rotate(90deg);
margin: 8px 0;
}
}
</style>