Files
test-repo/docs/.vitepress/theme/components/appendix/audio-intro/AutoregressiveAudioDemo.vue
T

301 lines
7.1 KiB
Vue
Raw Normal View History

2026-01-15 20:10:19 +08:00
<template>
<div class="ar-comparison">
<el-card shadow="never">
<div class="controls">
<el-button
type="primary"
:loading="isPlaying"
icon="VideoPlay"
@click="playDemo"
>
2026-01-15 20:10:19 +08:00
开始对比演示
</el-button>
</div>
<div class="comparison-container">
<!-- Left: Autoregressive -->
<el-card
shadow="hover"
class="method-card"
>
2026-01-15 20:10:19 +08:00
<template #header>
<div class="method-header">
<el-icon
:size="20"
color="#F56C6C"
>
<Timer />
</el-icon>
2026-01-15 20:10:19 +08:00
<span class="method-title">自回归 (Autoregressive)</span>
</div>
</template>
<div class="method-body">
<div class="visual-area">
<div class="token-stream">
<transition-group name="list">
<el-tag
v-for="(token, i) in displayedArTokens"
:key="i"
type="danger"
2026-01-15 20:10:19 +08:00
class="token-item"
effect="plain"
>
{{ token }}
</el-tag>
</transition-group>
</div>
</div>
<div class="stats">
<el-descriptions
:column="1"
size="small"
border
>
<el-descriptions-item label="生成方式">
串行 (Serial)
</el-descriptions-item>
2026-01-15 20:10:19 +08:00
<el-descriptions-item label="速度">
<el-tag
type="danger"
size="small"
>
(Slow)
</el-tag>
2026-01-15 20:10:19 +08:00
</el-descriptions-item>
</el-descriptions>
</div>
</div>
</el-card>
<!-- Right: Flow Matching -->
<el-card
shadow="hover"
class="method-card"
>
2026-01-15 20:10:19 +08:00
<template #header>
<div class="method-header">
<el-icon
:size="20"
color="#67C23A"
>
<Lightning />
</el-icon>
2026-01-15 20:10:19 +08:00
<span class="method-title">流匹配 (Flow Matching)</span>
</div>
</template>
<div class="method-body">
<div class="visual-area">
<div
class="flow-field"
:style="{ opacity: flowProgress }"
>
<div
v-for="n in 20"
:key="n"
class="flow-bar"
:style="{
height: flowProgress * (30 + Math.random() * 70) + '%',
transitionDelay: n * 0.02 + 's'
}"
/>
2026-01-15 20:10:19 +08:00
</div>
<div
v-if="flowProgress < 1 && flowProgress > 0"
class="flow-overlay"
>
<el-icon class="is-loading">
<Loading />
</el-icon>
2026-01-15 20:10:19 +08:00
<span>Denoising...</span>
</div>
</div>
<div class="stats">
<el-descriptions
:column="1"
size="small"
border
>
<el-descriptions-item label="生成方式">
并行 (Parallel)
</el-descriptions-item>
2026-01-15 20:10:19 +08:00
<el-descriptions-item label="速度">
<el-tag
type="success"
size="small"
>
极快 (Fast)
</el-tag>
2026-01-15 20:10:19 +08:00
</el-descriptions-item>
</el-descriptions>
</div>
</div>
</el-card>
</div>
<el-divider />
<el-alert
title="技术演进"
type="success"
:closable="false"
show-icon
>
2026-01-15 20:10:19 +08:00
<template #default>
<p>
<strong>自回归</strong> ( VALL-E)
像人说话一样必须说完上一个字才能说下一个字所以很慢
<br>
<strong>流匹配</strong> ( F5-TTS)
像画画一样可以同时在画布的所有角落开始上色效率提升了 10-20
2026-01-15 20:10:19 +08:00
</p>
</template>
</el-alert>
</el-card>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
import { Timer, Lightning, VideoPlay, Loading } from '@element-plus/icons-vue'
const arTokensSource = [1024, 2048, 3072, 4096, 5120, 6144, 7168, 8192]
const displayedArTokens = ref([])
const flowProgress = ref(0)
const isPlaying = ref(false)
const playDemo = async () => {
if (isPlaying.value) return
isPlaying.value = true
displayedArTokens.value = []
flowProgress.value = 0
2026-01-15 20:10:19 +08:00
// Start Flow Matching (Fast)
const flowPromise = new Promise((resolve) => {
2026-01-15 20:10:19 +08:00
let p = 0
const interval = setInterval(() => {
p += 0.05
flowProgress.value = p
if (p >= 1) {
clearInterval(interval)
resolve()
}
}, 50) // Total ~1s
})
2026-01-15 20:10:19 +08:00
// Start AR (Slow)
const arPromise = (async () => {
2026-01-15 20:10:19 +08:00
for (const token of arTokensSource) {
await new Promise((r) => setTimeout(r, 400)) // 400ms per token
2026-01-15 20:10:19 +08:00
displayedArTokens.value.push(token)
}
})()
2026-01-15 20:10:19 +08:00
await Promise.all([flowPromise, arPromise])
isPlaying.value = false
}
</script>
<style scoped>
.ar-comparison {
margin: 20px 0;
}
.controls {
text-align: center;
margin-bottom: 20px;
}
.comparison-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
@media (max-width: 768px) {
.comparison-container {
grid-template-columns: 1fr;
}
}
.method-card {
height: 100%;
}
.method-header {
display: flex;
align-items: center;
gap: 10px;
font-weight: bold;
}
.visual-area {
height: 120px;
background: var(--el-fill-color-light);
border-radius: 4px;
margin-bottom: 15px;
padding: 10px;
overflow: hidden;
position: relative;
display: flex;
align-items: center;
justify-content: center;
}
/* AR Styles */
.token-stream {
display: flex;
flex-wrap: wrap;
gap: 5px;
justify-content: flex-start;
align-content: flex-start;
width: 100%;
height: 100%;
}
.token-item {
font-family: monospace;
}
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateY(10px);
}
/* Flow Styles */
.flow-field {
width: 100%;
height: 100%;
display: flex;
align-items: flex-end;
justify-content: space-around;
gap: 2px;
}
.flow-bar {
flex: 1;
background: linear-gradient(to top, #67c23a, #95d475);
2026-01-15 20:10:19 +08:00
border-radius: 2px 2px 0 0;
transition: height 0.5s ease;
}
.flow-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.5);
2026-01-15 20:10:19 +08:00
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
color: var(--el-text-color-secondary);
}
</style>