7c70c37072
Add placeholder Vue components for visualizing technical concepts across multiple domains including frontend routing, browser rendering, cache design, queue design, database principles, API design, cloud services, and backend evolution. These components provide interactive educational content for the documentation. Update documentation structure to include new appendix sections and enhance existing content with visual components. Remove unused 'codex' dependency from package.json.
307 lines
7.2 KiB
Vue
307 lines
7.2 KiB
Vue
<template>
|
||
<div class="flow-matching-demo">
|
||
<div class="demo-card">
|
||
<div class="controls">
|
||
<button class="play-btn" @click="startRace" :disabled="isPlaying">
|
||
<span class="icon">{{ isPlaying ? 'Running...' : '🚀 开始比赛 (Start Race)' }}</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="track-container">
|
||
<!-- Track 1: Diffusion -->
|
||
<div class="track">
|
||
<div class="track-info">
|
||
<span class="track-name">Diffusion (迷宫模式)</span>
|
||
<span class="step-count">{{ diffSteps }} Steps</span>
|
||
</div>
|
||
<div class="canvas-wrapper">
|
||
<canvas ref="diffCanvasRef" width="400" height="100"></canvas>
|
||
<div class="marker start">噪声</div>
|
||
<div class="marker end">图像</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Track 2: Flow Matching -->
|
||
<div class="track">
|
||
<div class="track-info">
|
||
<span class="track-name">Flow Matching (直通模式)</span>
|
||
<span class="step-count highlight">{{ flowSteps }} Steps</span>
|
||
</div>
|
||
<div class="canvas-wrapper">
|
||
<canvas ref="flowCanvasRef" width="400" height="100"></canvas>
|
||
<div class="marker start">噪声</div>
|
||
<div class="marker end">图像</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="info-bar">
|
||
<span class="icon">💡</span>
|
||
<span>
|
||
<strong>核心区别:</strong>
|
||
Diffusion 就像在走迷宫,虽然也能到终点,但绕了很多弯路(步数多)。Flow Matching 则是直接修了一条直线高速公路,所以 8 步就能走完别人 50 步的路。
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, onUnmounted } from 'vue'
|
||
|
||
const diffCanvasRef = ref(null)
|
||
const flowCanvasRef = ref(null)
|
||
const isPlaying = ref(false)
|
||
const diffSteps = ref(0)
|
||
const flowSteps = ref(0)
|
||
let animationId = null
|
||
|
||
// Constants
|
||
const TARGET_STEPS_DIFF = 50
|
||
const TARGET_STEPS_FLOW = 8
|
||
const DURATION = 3000 // 3 seconds for the whole race
|
||
|
||
// Particles state
|
||
let particles = []
|
||
const NUM_PARTICLES = 5
|
||
|
||
class Particle {
|
||
constructor(type) {
|
||
this.type = type // 'diff' or 'flow'
|
||
this.progress = 0
|
||
this.path = []
|
||
this.noiseOffset = Math.random() * 1000
|
||
this.yOffset = (Math.random() - 0.5) * 60 // Spread vertically
|
||
}
|
||
|
||
update(dt) {
|
||
// Speed varies: Flow is faster because it covers distance in fewer steps?
|
||
// Actually, let's make them finish at the same TIME, but show the path difference.
|
||
// Or make Flow finish faster. Let's make Flow finish faster.
|
||
|
||
const speed = this.type === 'flow' ? 0.8 : 0.3
|
||
this.progress += speed * dt
|
||
|
||
if (this.progress > 1) this.progress = 1
|
||
|
||
// Calculate Position
|
||
const startX = 20
|
||
const endX = 380
|
||
const startY = 50 + this.yOffset
|
||
const endY = 50
|
||
|
||
// Linear interpolation base
|
||
let x = startX + (endX - startX) * this.progress
|
||
let y = startY + (endY - startY) * this.progress
|
||
|
||
if (this.type === 'diff') {
|
||
// Add noise to path
|
||
if (this.progress < 1) {
|
||
const noise = Math.sin(this.progress * 20 + this.noiseOffset) * 30 * (1 - this.progress)
|
||
y += noise
|
||
}
|
||
}
|
||
|
||
this.path.push({x, y})
|
||
return {x, y}
|
||
}
|
||
|
||
draw(ctx) {
|
||
ctx.beginPath()
|
||
ctx.moveTo(this.path[0].x, this.path[0].y)
|
||
for (let p of this.path) {
|
||
ctx.lineTo(p.x, p.y)
|
||
}
|
||
ctx.strokeStyle = this.type === 'flow' ? '#10b981' : '#f43f5e'
|
||
ctx.lineWidth = 2
|
||
ctx.stroke()
|
||
|
||
const current = this.path[this.path.length - 1]
|
||
ctx.beginPath()
|
||
ctx.arc(current.x, current.y, 4, 0, Math.PI * 2)
|
||
ctx.fillStyle = this.type === 'flow' ? '#10b981' : '#f43f5e'
|
||
ctx.fill()
|
||
}
|
||
}
|
||
|
||
const startRace = () => {
|
||
if (isPlaying.value) return
|
||
isPlaying.value = true
|
||
diffSteps.value = 0
|
||
flowSteps.value = 0
|
||
particles = []
|
||
|
||
// Create particles
|
||
for(let i=0; i<NUM_PARTICLES; i++) {
|
||
particles.push(new Particle('diff'))
|
||
particles.push(new Particle('flow'))
|
||
}
|
||
|
||
let lastTime = performance.now()
|
||
|
||
const animate = (time) => {
|
||
const dt = (time - lastTime) / 1000
|
||
lastTime = time
|
||
|
||
const dCtx = diffCanvasRef.value.getContext('2d')
|
||
const fCtx = flowCanvasRef.value.getContext('2d')
|
||
|
||
// Clear
|
||
dCtx.clearRect(0, 0, 400, 100)
|
||
fCtx.clearRect(0, 0, 400, 100)
|
||
|
||
// Draw Guidelines
|
||
drawGuide(dCtx)
|
||
drawGuide(fCtx)
|
||
|
||
let allFinished = true
|
||
|
||
particles.forEach(p => {
|
||
p.update(dt)
|
||
if (p.progress < 1) allFinished = false
|
||
|
||
if (p.type === 'diff') p.draw(dCtx)
|
||
else p.draw(fCtx)
|
||
})
|
||
|
||
// Update steps counter simulation
|
||
// Flow finishes in 8 steps, Diff in 50
|
||
// Map progress to steps
|
||
const flowP = particles.find(p => p.type === 'flow')
|
||
const diffP = particles.find(p => p.type === 'diff')
|
||
|
||
if (flowP) flowSteps.value = Math.floor(flowP.progress * TARGET_STEPS_FLOW)
|
||
if (diffP) diffSteps.value = Math.floor(diffP.progress * TARGET_STEPS_DIFF)
|
||
|
||
if (!allFinished) {
|
||
animationId = requestAnimationFrame(animate)
|
||
} else {
|
||
isPlaying.value = false
|
||
flowSteps.value = TARGET_STEPS_FLOW
|
||
diffSteps.value = TARGET_STEPS_DIFF
|
||
}
|
||
}
|
||
|
||
requestAnimationFrame(animate)
|
||
}
|
||
|
||
const drawGuide = (ctx) => {
|
||
ctx.strokeStyle = 'rgba(128,128,128,0.1)'
|
||
ctx.lineWidth = 1
|
||
ctx.setLineDash([5, 5])
|
||
ctx.beginPath()
|
||
ctx.moveTo(20, 50)
|
||
ctx.lineTo(380, 50)
|
||
ctx.stroke()
|
||
ctx.setLineDash([])
|
||
}
|
||
|
||
onMounted(() => {
|
||
// Initial draw
|
||
const dCtx = diffCanvasRef.value.getContext('2d')
|
||
const fCtx = flowCanvasRef.value.getContext('2d')
|
||
drawGuide(dCtx)
|
||
drawGuide(fCtx)
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
if (animationId) cancelAnimationFrame(animationId)
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.flow-matching-demo {
|
||
margin: 20px 0;
|
||
font-family: var(--vp-font-family-base);
|
||
}
|
||
|
||
.demo-card {
|
||
background: var(--vp-c-bg-soft);
|
||
border: 1px solid var(--vp-c-divider);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
}
|
||
|
||
.controls {
|
||
margin-bottom: 20px;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.play-btn {
|
||
background: var(--vp-c-brand);
|
||
color: white;
|
||
border: none;
|
||
padding: 8px 24px;
|
||
border-radius: 20px;
|
||
font-weight: 600;
|
||
cursor: pointer;
|
||
transition: transform 0.1s;
|
||
}
|
||
|
||
.play-btn:hover:not(:disabled) {
|
||
transform: scale(1.05);
|
||
}
|
||
|
||
.play-btn:disabled {
|
||
opacity: 0.7;
|
||
cursor: default;
|
||
}
|
||
|
||
.track {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
.track-info {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.step-count {
|
||
font-family: monospace;
|
||
background: var(--vp-c-bg-alt);
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
}
|
||
|
||
.step-count.highlight {
|
||
color: #10b981;
|
||
}
|
||
|
||
.canvas-wrapper {
|
||
position: relative;
|
||
background: var(--vp-c-bg);
|
||
border-radius: 8px;
|
||
border: 1px solid var(--vp-c-divider);
|
||
height: 100px;
|
||
}
|
||
|
||
.marker {
|
||
position: absolute;
|
||
bottom: 4px;
|
||
font-size: 10px;
|
||
color: var(--vp-c-text-3);
|
||
}
|
||
|
||
.marker.start { left: 10px; }
|
||
.marker.end { right: 10px; }
|
||
|
||
canvas {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
.info-bar {
|
||
margin-top: 16px;
|
||
font-size: 13px;
|
||
color: var(--vp-c-text-2);
|
||
display: flex;
|
||
gap: 8px;
|
||
line-height: 1.4;
|
||
padding: 0 8px;
|
||
}
|
||
</style>
|