Files
test-repo/docs/.vitepress/theme/components/appendix/image-gen-intro/LatentSpaceViz.vue
T
sanbuphy 0eba9e87e9 fix(eslint): reduce warnings in GitHub Actions deployment
- Disable formatting rules (handled by Prettier)
- Relaxed strict Vue/JS rules for demo code compatibility
- Fix syntax errors in ApiPlayground and VoiceCloningDemo
- Fix duplicate else-if condition in ApiPlayground
- Fix Promise executor async pattern in AutoregressiveAudioDemo
- Add TypeScript file support to ESLint config

Warnings reduced from 295 to 251 problems.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-18 17:38:10 +08:00

331 lines
7.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="latent-space-viz">
<div class="viz-card">
<!-- Left: The Output (Pixel Space) -->
<div class="preview-section">
<div
class="emoji-display"
:style="{ transform: `scale(${1 + zoomLevel})` }"
>
{{ currentEmoji }}
</div>
<div class="label">
像素空间 (Pixel Space)
</div>
<div class="sub-label">
最终看到的图像
</div>
</div>
<!-- Center: The Mechanism -->
<div class="connection">
<div class="arrow">
映射
</div>
<div class="vae-tag">
VAE Decoder
</div>
</div>
<!-- Right: The Input (Latent Space) -->
<div class="control-section">
<div
ref="gridRef"
class="latent-grid"
@mousedown="startDrag"
@touchstart="startDrag"
>
<div class="grid-lines" />
<div class="axis-label x-axis">
开心值 (Happiness)
</div>
<div class="axis-label y-axis">
惊讶值 (Surprise)
</div>
<!-- The Latent Point -->
<div
class="latent-point"
:style="{ left: `${point.x}%`, top: `${point.y}%` }"
>
<div class="tooltip">
Latent Vector: [{{ ((point.x-50)/50).toFixed(1) }}, {{ ((50-point.y)/50).toFixed(1) }}]
</div>
</div>
</div>
<div class="label">
潜空间 (Latent Space)
</div>
<div class="sub-label">
拖动红点改变特征
</div>
</div>
</div>
<div class="info-bar">
<span class="icon">💡</span>
<span>
<strong>核心原理</strong>
在像素空间里修改图片很难要改几千个像素但在潜空间里我们只需要修改两个坐标开心值惊讶值就能生成完全不同的表情这就是 AI "画画" 的本质在数学空间里找坐标
</span>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
const gridRef = ref(null)
const isDragging = ref(false)
const point = ref({ x: 50, y: 50 }) // Percentage 0-100
const zoomLevel = ref(0)
// Emoji map based on quadrants
// X: Unhappy -> Happy
// Y: Calm -> Surprised (Top is 0 in CSS, so small Y is high surprise?)
// Let's map:
// X (0-100): Sad -> Happy
// Y (0-100): Surprised -> Sleepy (Top is 0, so 0 is Surprised, 100 is Sleepy)
const currentEmoji = computed(() => {
const x = point.value.x // 0 (Sad) to 100 (Happy)
const y = point.value.y // 0 (Surprised) to 100 (Sleepy)
if (x < 33) { // Sad Zone
if (y < 33) return '😨' // Sad + Surprised = Fear
if (y > 66) return '😪' // Sad + Sleepy = Tired
return '😢' // Just Sad
} else if (x > 66) { // Happy Zone
if (y < 33) return '🤩' // Happy + Surprised = Starstruck
if (y > 66) return '😌' // Happy + Sleepy = Relieved
return '😃' // Just Happy
} else { // Neutral Zone
if (y < 33) return '😮' // Neutral + Surprised
if (y > 66) return '😴' // Neutral + Sleepy
return '😐' // Just Neutral
}
})
const handleMove = (event) => {
if (!isDragging.value) return
const grid = gridRef.value.getBoundingClientRect()
const clientX = event.touches ? event.touches[0].clientX : event.clientX
const clientY = event.touches ? event.touches[0].clientY : event.clientY
let newX = ((clientX - grid.left) / grid.width) * 100
let newY = ((clientY - grid.top) / grid.height) * 100
// Clamp
newX = Math.max(0, Math.min(100, newX))
newY = Math.max(0, Math.min(100, newY))
point.value = { x: newX, y: newY }
}
const startDrag = (event) => {
isDragging.value = true
handleMove(event)
// Prevent default to stop scrolling on mobile
if (event.type === 'touchstart') event.preventDefault()
}
const stopDrag = () => {
isDragging.value = false
}
onMounted(() => {
window.addEventListener('mousemove', handleMove)
window.addEventListener('mouseup', stopDrag)
window.addEventListener('touchmove', handleMove)
window.addEventListener('touchend', stopDrag)
})
onUnmounted(() => {
window.removeEventListener('mousemove', handleMove)
window.removeEventListener('mouseup', stopDrag)
window.removeEventListener('touchmove', handleMove)
window.removeEventListener('touchend', stopDrag)
})
</script>
<style scoped>
.latent-space-viz {
margin: 20px 0;
font-family: var(--vp-font-family-base);
}
.viz-card {
display: flex;
align-items: center;
justify-content: space-between;
background: var(--vp-c-bg-soft);
border: 1px solid var(--vp-c-divider);
border-radius: 12px;
padding: 24px;
gap: 20px;
flex-wrap: wrap;
}
.preview-section, .control-section {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
min-width: 200px;
}
.emoji-display {
font-size: 80px;
line-height: 1;
height: 100px;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
cursor: default;
filter: drop-shadow(0 4px 12px rgba(0,0,0,0.1));
}
.latent-grid {
width: 200px;
height: 200px;
background: var(--vp-c-bg);
border: 2px solid var(--vp-c-brand);
border-radius: 12px;
position: relative;
cursor: crosshair;
overflow: hidden;
box-shadow: inset 0 2px 8px rgba(0,0,0,0.05);
}
.grid-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(var(--vp-c-divider) 1px, transparent 1px),
linear-gradient(90deg, var(--vp-c-divider) 1px, transparent 1px);
background-size: 20px 20px;
opacity: 0.3;
}
.latent-point {
width: 20px;
height: 20px;
background: var(--vp-c-brand);
border: 3px solid #fff;
border-radius: 50%;
position: absolute;
transform: translate(-50%, -50%);
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
transition: transform 0.1s;
}
.latent-point:hover {
transform: translate(-50%, -50%) scale(1.2);
}
.tooltip {
position: absolute;
bottom: 25px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.8);
color: #fff;
padding: 4px 8px;
border-radius: 4px;
font-size: 10px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
}
.latent-point:hover .tooltip,
.latent-point:active .tooltip {
opacity: 1;
}
.axis-label {
position: absolute;
font-size: 10px;
color: var(--vp-c-text-2);
pointer-events: none;
}
.x-axis {
bottom: 4px;
right: 8px;
}
.y-axis {
top: 8px;
left: 8px;
writing-mode: vertical-rl;
transform: rotate(180deg);
}
.connection {
display: flex;
flex-direction: column;
align-items: center;
color: var(--vp-c-text-2);
font-size: 12px;
}
.arrow {
margin-bottom: 4px;
font-weight: bold;
}
.vae-tag {
background: var(--vp-c-brand-dimm);
color: var(--vp-c-brand-dark);
padding: 2px 8px;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
}
.label {
margin-top: 12px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.sub-label {
font-size: 12px;
color: var(--vp-c-text-2);
}
.info-bar {
margin-top: 16px;
background: var(--vp-c-bg-alt);
padding: 12px 16px;
border-radius: 6px;
font-size: 13px;
line-height: 1.5;
color: var(--vp-c-text-2);
display: flex;
gap: 8px;
}
.icon {
font-size: 16px;
}
@media (max-width: 600px) {
.viz-card {
flex-direction: column-reverse;
}
.connection {
transform: rotate(90deg);
margin: 10px 0;
}
}
</style>