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,569 @@
<!--
ImageGenQuickStartDemo.vue
AI 绘画快速体验组件
用途
让用户在文章开头就能体验 AI 绘画的魅力通过交互式演示理解文生图的基本概念
交互功能
- 预设提示词选择快速体验不同风格的图像生成
- 模拟生成过程展示从文本到图像的渐进过程
- 参数调节调整生成步数CFG Scale 等参数
- 对比展示对比不同参数下的生成效果
-->
<template>
<div class="image-gen-quickstart">
<el-card shadow="never">
<template #header>
<div class="header-title">
<el-icon><Picture /></el-icon>
<span>🎨 AI 绘画体验室</span>
</div>
</template>
<div class="demo-layout">
<!-- 左侧控制面板 -->
<div class="control-panel">
<div class="input-section">
<label>提示词 (Prompt)</label>
<el-input
v-model="prompt"
type="textarea"
:rows="3"
placeholder="描述你想生成的图像..."
/>
<div class="prompt-tags">
<el-tag
v-for="tag in presetPrompts"
:key="tag.label"
size="small"
class="prompt-tag"
@click="prompt = tag.prompt"
>
{{ tag.label }}
</el-tag>
</div>
</div>
<div class="params-section">
<div class="param-row">
<label>生成步数</label>
<el-slider v-model="steps" :min="10" :max="50" :step="5" show-stops />
</div>
<div class="param-row">
<label>CFG Scale (提示词遵循度)</label>
<el-slider v-model="cfgScale" :min="1" :max="15" :step="0.5" />
</div>
<div class="param-row">
<label>采样器</label>
<el-select v-model="sampler" size="small">
<el-option label="Euler" value="euler" />
<el-option label="DPM++" value="dpm" />
<el-option label="DDIM" value="ddim" />
</el-select>
</div>
</div>
<el-button
type="primary"
:loading="isGenerating"
@click="startGeneration"
class="generate-btn"
>
<el-icon><MagicStick /></el-icon>
{{ isGenerating ? '生成中...' : '开始生成' }}
</el-button>
</div>
<!-- 右侧生成展示 -->
<div class="display-panel">
<div class="canvas-container">
<canvas
ref="canvasRef"
width="400"
height="400"
class="gen-canvas"
/>
<div v-if="isGenerating" class="progress-overlay">
<el-progress
type="circle"
:percentage="progress"
:status="progress === 100 ? 'success' : ''"
/>
<p class="step-info">Step {{ currentStep }} / {{ steps }}</p>
</div>
</div>
<div class="image-info" v-if="!isGenerating && hasGenerated">
<el-descriptions :column="2" size="small" border>
<el-descriptions-item label="分辨率">512 × 512</el-descriptions-item>
<el-descriptions-item label="生成步数">{{ steps }}</el-descriptions-item>
<el-descriptions-item label="CFG Scale">{{ cfgScale }}</el-descriptions-item>
<el-descriptions-item label="采样器">{{ sampler }}</el-descriptions-item>
</el-descriptions>
</div>
</div>
</div>
<div class="info-box">
<p>
<span class="icon">💡</span>
<strong>小提示</strong>
提示词越详细生成效果越好尝试使用 "风格词" "赛博朋克""水彩画"来控制图像风格
</p>
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { Picture, MagicStick } from '@element-plus/icons-vue'
const canvasRef = ref(null)
const prompt = ref('一只戴着墨镜的猫,赛博朋克风格,霓虹灯光')
const steps = ref(20)
const cfgScale = ref(7.5)
const sampler = ref('euler')
const isGenerating = ref(false)
const progress = ref(0)
const currentStep = ref(0)
const hasGenerated = ref(false)
const presetPrompts = [
{ label: '🐱 赛博朋克猫', prompt: '一只戴着墨镜的猫,赛博朋克风格,霓虹灯光' },
{ label: '🏔️ 山水画', prompt: '中国山水画,云雾缭绕,水墨风格' },
{ label: '🚀 太空', prompt: '宇航员在火星表面,日落时分,科幻风格' },
{ label: '🌸 樱花', prompt: '樱花树下,日本传统建筑,春天,柔和光线' },
]
// 模拟生成过程
const startGeneration = async () => {
isGenerating.value = true
progress.value = 0
currentStep.value = 0
hasGenerated.value = false
const canvas = canvasRef.value
const ctx = canvas.getContext('2d')
// 生成噪声图像作为起点
const generateNoise = () => {
const imageData = ctx.createImageData(400, 400)
for (let i = 0; i < imageData.data.length; i += 4) {
const val = Math.random() * 255
imageData.data[i] = val
imageData.data[i + 1] = val
imageData.data[i + 2] = val
imageData.data[i + 3] = 255
}
return imageData
}
// 模拟渐进生成
for (let i = 0; i <= steps.value; i++) {
await new Promise(resolve => setTimeout(resolve, 100))
currentStep.value = i
progress.value = Math.round((i / steps.value) * 100)
// 绘制渐进图像(模拟)
const noiseRatio = 1 - (i / steps.value)
drawSimulatedImage(ctx, noiseRatio)
}
isGenerating.value = false
hasGenerated.value = true
}
// 根据提示词绘制模拟图像
const drawSimulatedImage = (ctx, noiseRatio) => {
const width = 400
const height = 400
// 清空画布
ctx.fillStyle = '#1a1a2e'
ctx.fillRect(0, 0, width, height)
// 根据提示词关键词绘制不同内容
const promptLower = prompt.value.toLowerCase()
// 赛博朋克风格
if (promptLower.includes('赛博') || promptLower.includes('cyber')) {
drawCyberpunkScene(ctx, width, height, noiseRatio)
}
// 山水画
else if (promptLower.includes('山水') || promptLower.includes('水墨')) {
drawLandscape(ctx, width, height, noiseRatio)
}
// 太空
else if (promptLower.includes('太空') || promptLower.includes('宇航员')) {
drawSpaceScene(ctx, width, height, noiseRatio)
}
// 樱花
else if (promptLower.includes('樱花') || promptLower.includes('日本')) {
drawSakuraScene(ctx, width, height, noiseRatio)
}
// 默认:抽象艺术
else {
drawAbstractArt(ctx, width, height, noiseRatio)
}
// 添加噪声
if (noiseRatio > 0) {
addNoise(ctx, width, height, noiseRatio)
}
}
const drawCyberpunkScene = (ctx, w, h, noise) => {
// 霓虹背景
const gradient = ctx.createLinearGradient(0, 0, 0, h)
gradient.addColorStop(0, '#0a0a1a')
gradient.addColorStop(1, '#1a0a2e')
ctx.fillStyle = gradient
ctx.fillRect(0, 0, w, h)
// 霓虹灯条
ctx.shadowBlur = 20 * (1 - noise)
ctx.shadowColor = '#ff00ff'
ctx.fillStyle = `rgba(255, 0, 255, ${0.8 * (1 - noise)})`
ctx.fillRect(50, 100, 300, 5)
ctx.shadowColor = '#00ffff'
ctx.fillStyle = `rgba(0, 255, 255, ${0.8 * (1 - noise)})`
ctx.fillRect(100, 200, 200, 5)
// 猫的形状(简化)
ctx.shadowBlur = 0
ctx.fillStyle = `rgba(100, 100, 150, ${0.9 * (1 - noise)})`
ctx.beginPath()
ctx.ellipse(200, 280, 60, 50, 0, 0, Math.PI * 2)
ctx.fill()
// 耳朵
ctx.beginPath()
ctx.moveTo(160, 250)
ctx.lineTo(150, 200)
ctx.lineTo(180, 240)
ctx.fill()
ctx.beginPath()
ctx.moveTo(240, 250)
ctx.lineTo(250, 200)
ctx.lineTo(220, 240)
ctx.fill()
// 墨镜
ctx.fillStyle = `rgba(0, 0, 0, ${0.9 * (1 - noise)})`
ctx.beginPath()
ctx.ellipse(180, 270, 20, 12, 0, 0, Math.PI * 2)
ctx.fill()
ctx.beginPath()
ctx.ellipse(220, 270, 20, 12, 0, 0, Math.PI * 2)
ctx.fill()
// 镜片反光
ctx.fillStyle = `rgba(255, 0, 255, ${0.6 * (1 - noise)})`
ctx.beginPath()
ctx.ellipse(175, 268, 5, 3, 0, 0, Math.PI * 2)
ctx.fill()
ctx.beginPath()
ctx.ellipse(215, 268, 5, 3, 0, 0, Math.PI * 2)
ctx.fill()
}
const drawLandscape = (ctx, w, h, noise) => {
// 天空渐变
const gradient = ctx.createLinearGradient(0, 0, 0, h)
gradient.addColorStop(0, '#e8f4f8')
gradient.addColorStop(0.5, '#c8e0e8')
gradient.addColorStop(1, '#a8c8d8')
ctx.fillStyle = gradient
ctx.fillRect(0, 0, w, h)
// 远山
ctx.fillStyle = `rgba(100, 120, 140, ${0.5 * (1 - noise)})`
ctx.beginPath()
ctx.moveTo(0, 250)
ctx.lineTo(100, 150)
ctx.lineTo(200, 200)
ctx.lineTo(300, 120)
ctx.lineTo(400, 180)
ctx.lineTo(400, 400)
ctx.lineTo(0, 400)
ctx.fill()
// 近山
ctx.fillStyle = `rgba(60, 80, 100, ${0.7 * (1 - noise)})`
ctx.beginPath()
ctx.moveTo(0, 300)
ctx.lineTo(150, 220)
ctx.lineTo(300, 280)
ctx.lineTo(400, 240)
ctx.lineTo(400, 400)
ctx.lineTo(0, 400)
ctx.fill()
// 云雾
ctx.fillStyle = `rgba(255, 255, 255, ${0.4 * (1 - noise)})`
for (let i = 0; i < 5; i++) {
ctx.beginPath()
ctx.ellipse(80 + i * 70, 180 + i * 20, 40, 20, 0, 0, Math.PI * 2)
ctx.fill()
}
}
const drawSpaceScene = (ctx, w, h, noise) => {
// 太空背景
ctx.fillStyle = '#0a0a15'
ctx.fillRect(0, 0, w, h)
// 星星
ctx.fillStyle = `rgba(255, 255, 255, ${1 - noise})`
for (let i = 0; i < 100; i++) {
const x = (i * 37) % w
const y = (i * 73) % h
ctx.beginPath()
ctx.arc(x, y, Math.random() * 2, 0, Math.PI * 2)
ctx.fill()
}
// 火星地面
ctx.fillStyle = `rgba(180, 80, 40, ${0.8 * (1 - noise)})`
ctx.beginPath()
ctx.moveTo(0, 350)
ctx.lineTo(100, 320)
ctx.lineTo(200, 340)
ctx.lineTo(300, 310)
ctx.lineTo(400, 330)
ctx.lineTo(400, 400)
ctx.lineTo(0, 400)
ctx.fill()
// 宇航员(简化)
ctx.fillStyle = `rgba(220, 220, 230, ${0.9 * (1 - noise)})`
ctx.beginPath()
ctx.arc(200, 220, 40, 0, Math.PI * 2)
ctx.fill()
// 头盔反光
ctx.fillStyle = `rgba(255, 200, 100, ${0.5 * (1 - noise)})`
ctx.beginPath()
ctx.arc(185, 210, 15, 0, Math.PI * 2)
ctx.fill()
// 日落
const sunGradient = ctx.createRadialGradient(300, 100, 0, 300, 100, 60)
sunGradient.addColorStop(0, `rgba(255, 200, 100, ${1 - noise})`)
sunGradient.addColorStop(1, `rgba(255, 100, 50, ${0.5 * (1 - noise)})`)
ctx.fillStyle = sunGradient
ctx.beginPath()
ctx.arc(300, 100, 50, 0, Math.PI * 2)
ctx.fill()
}
const drawSakuraScene = (ctx, w, h, noise) => {
// 天空
const gradient = ctx.createLinearGradient(0, 0, 0, h)
gradient.addColorStop(0, '#ffe4e1')
gradient.addColorStop(1, '#ffb6c1')
ctx.fillStyle = gradient
ctx.fillRect(0, 0, w, h)
// 传统建筑屋顶
ctx.fillStyle = `rgba(80, 60, 60, ${0.9 * (1 - noise)})`
ctx.beginPath()
ctx.moveTo(100, 300)
ctx.lineTo(150, 200)
ctx.lineTo(250, 200)
ctx.lineTo(300, 300)
ctx.fill()
// 樱花树
ctx.fillStyle = `rgba(139, 90, 43, ${0.9 * (1 - noise)})`
ctx.fillRect(50, 200, 20, 200)
// 樱花
ctx.fillStyle = `rgba(255, 183, 197, ${0.8 * (1 - noise)})`
for (let i = 0; i < 30; i++) {
const x = 30 + (i * 13) % 80
const y = 150 + (i * 17) % 100
ctx.beginPath()
ctx.arc(x, y, 8 + Math.random() * 5, 0, Math.PI * 2)
ctx.fill()
}
// 飘落的樱花
ctx.fillStyle = `rgba(255, 192, 203, ${0.6 * (1 - noise)})`
for (let i = 0; i < 20; i++) {
const x = (i * 23) % w
const y = 250 + (i * 31) % 150
ctx.beginPath()
ctx.ellipse(x, y, 4, 2, i, 0, Math.PI * 2)
ctx.fill()
}
}
const drawAbstractArt = (ctx, w, h, noise) => {
// 渐变背景
const gradient = ctx.createLinearGradient(0, 0, w, h)
gradient.addColorStop(0, '#667eea')
gradient.addColorStop(1, '#764ba2')
ctx.fillStyle = gradient
ctx.fillRect(0, 0, w, h)
// 抽象形状
const colors = ['#f093fb', '#f5576c', '#4facfe', '#00f2fe']
for (let i = 0; i < 8; i++) {
ctx.fillStyle = colors[i % colors.length] + Math.floor((1 - noise) * 255).toString(16).padStart(2, '0')
ctx.beginPath()
const x = 100 + (i * 50) % 300
const y = 100 + (i * 70) % 250
const r = 30 + i * 5
ctx.arc(x, y, r, 0, Math.PI * 2)
ctx.fill()
}
}
const addNoise = (ctx, w, h, ratio) => {
const imageData = ctx.getImageData(0, 0, w, h)
for (let i = 0; i < imageData.data.length; i += 4) {
const noise = (Math.random() - 0.5) * 50 * ratio
imageData.data[i] = Math.max(0, Math.min(255, imageData.data[i] + noise))
imageData.data[i + 1] = Math.max(0, Math.min(255, imageData.data[i + 1] + noise))
imageData.data[i + 2] = Math.max(0, Math.min(255, imageData.data[i + 2] + noise))
}
ctx.putImageData(imageData, 0, 0)
}
onMounted(() => {
// 初始绘制
const canvas = canvasRef.value
if (canvas) {
const ctx = canvas.getContext('2d')
drawSimulatedImage(ctx, 1)
}
})
</script>
<style scoped>
.image-gen-quickstart {
margin: 1rem 0;
}
.header-title {
display: flex;
align-items: center;
gap: 8px;
font-weight: 600;
}
.demo-layout {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24px;
}
@media (max-width: 768px) {
.demo-layout {
grid-template-columns: 1fr;
}
}
.control-panel {
display: flex;
flex-direction: column;
gap: 16px;
}
.input-section label,
.params-section label {
display: block;
font-size: 0.875rem;
font-weight: 500;
margin-bottom: 8px;
color: var(--vp-c-text-2);
}
.prompt-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 8px;
}
.prompt-tag {
cursor: pointer;
transition: all 0.2s;
}
.prompt-tag:hover {
transform: translateY(-2px);
}
.param-row {
margin-bottom: 12px;
}
.param-row label {
font-size: 0.8rem;
margin-bottom: 4px;
}
.generate-btn {
width: 100%;
margin-top: auto;
}
.display-panel {
display: flex;
flex-direction: column;
gap: 16px;
}
.canvas-container {
position: relative;
width: 100%;
aspect-ratio: 1;
background: var(--vp-c-bg-mute);
border-radius: 8px;
overflow: hidden;
}
.gen-canvas {
width: 100%;
height: 100%;
object-fit: contain;
}
.progress-overlay {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
gap: 16px;
}
.step-info {
color: white;
font-size: 0.9rem;
}
.image-info {
font-size: 0.8rem;
}
.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;
}
</style>